diff --git a/go.mod b/go.mod index 224eab98..00fc9216 100644 --- a/go.mod +++ b/go.mod @@ -15,13 +15,12 @@ require ( github.com/garyburd/redigo v1.6.0 // indirect github.com/gocraft/health v0.0.0-20170925182251-8675af27fef0 github.com/gocraft/web v0.0.0-20190207150652-9707327fb69b - github.com/gocraft/work v0.5.1 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/gomodule/redigo v2.0.0+incompatible github.com/jrallison/go-workers v0.0.0-20180112190529-dbf81d0b75bb + github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect github.com/kr/pretty v0.2.0 // indirect github.com/orfjackal/nanospec.go v0.0.0-20120727230329-de4694c1d701 // indirect - github.com/robfig/cron v1.2.0 // indirect github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.5.1 github.com/youtube/vitess v2.1.1+incompatible // indirect diff --git a/go.sum b/go.sum index 63aa5f0a..002d8b48 100644 --- a/go.sum +++ b/go.sum @@ -24,14 +24,14 @@ github.com/gocraft/health v0.0.0-20170925182251-8675af27fef0 h1:pKjeDsx7HGGbjr7V github.com/gocraft/health v0.0.0-20170925182251-8675af27fef0/go.mod h1:rWibcVfwbUxi/QXW84U7vNTcIcZFd6miwbt8ritxh/Y= github.com/gocraft/web v0.0.0-20190207150652-9707327fb69b h1:g2Qcs0B+vOQE1L3a7WQ/JUUSzJnHbTz14qkJSqEWcF4= github.com/gocraft/web v0.0.0-20190207150652-9707327fb69b/go.mod h1:Ag7UMbZNGrnHwaXPJOUKJIVgx4QOWMOWZngrvsN6qak= -github.com/gocraft/work v0.5.1 h1:3bRjMiOo6N4zcRgZWV3Y7uX7R22SF+A9bPTk4xRXr34= -github.com/gocraft/work v0.5.1/go.mod h1:pc3n9Pb5FAESPPGfM0nL+7Q1xtgtRnF8rr/azzhQVlM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/jrallison/go-workers v0.0.0-20180112190529-dbf81d0b75bb h1:y9LFhCM3gwK94Xz9/h7GcSVLteky9pFHEkP04AqQupA= github.com/jrallison/go-workers v0.0.0-20180112190529-dbf81d0b75bb/go.mod h1:ziQRRNHCWZe0wVNzF8y8kCWpso0VMpqHJjB19DSenbE= +github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts= +github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -41,8 +41,6 @@ github.com/orfjackal/nanospec.go v0.0.0-20120727230329-de4694c1d701 h1:yOXfzNV7q github.com/orfjackal/nanospec.go v0.0.0-20120727230329-de4694c1d701/go.mod h1:VtBIF1XX0c1nKkeAPk8i4aXkYopqQgfDqolHUIHPwNI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/vendor/github.com/albrow/jobs/CONTRIBUTING.md b/vendor/github.com/albrow/jobs/CONTRIBUTING.md new file mode 100644 index 00000000..9adfc0cf --- /dev/null +++ b/vendor/github.com/albrow/jobs/CONTRIBUTING.md @@ -0,0 +1,29 @@ +Contributing +------------ + +Feedback, bug reports, and pull requests are greatly appreciated :) + +### Issues + +The following are all great reasons to submit an issue: + +1. You found a bug in the code. +2. Something is missing from the documentation or the existing documentation is unclear. +3. You have an idea for a new feature. + +If you are thinking about submitting an issue please remember to: + +1. Describe the issue in detail. +2. If applicable, describe the steps to reproduce the error, which probably should include some example code. +3. Mention details about your platform: OS, version of Go and Redis, etc. + +### Pull Requests + +Jobs uses semantic versioning and the [git branching model described here](http://nvie.com/posts/a-successful-git-branching-model/). +If you plan on submitting a pull request, you should: + +1. Fork the repository. +2. Create a new "feature branch" off of **develop** (not master) with a descriptive name (e.g. fix-database-error). +3. Make your changes in the feature branch. +4. Run the tests to make sure that they still pass. Updated the tests if needed. +5. Submit a pull request to merge your feature branch into the **develop** branch. Please do not request to merge directly into master. \ No newline at end of file diff --git a/vendor/github.com/albrow/jobs/LICENSE b/vendor/github.com/albrow/jobs/LICENSE new file mode 100644 index 00000000..e21ef6cf --- /dev/null +++ b/vendor/github.com/albrow/jobs/LICENSE @@ -0,0 +1,9 @@ +(The MIT License) + +Copyright (C) 2015, Alex Browne + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/albrow/jobs/README.md b/vendor/github.com/albrow/jobs/README.md new file mode 100644 index 00000000..308a011f --- /dev/null +++ b/vendor/github.com/albrow/jobs/README.md @@ -0,0 +1,276 @@ +Jobs +==== + +A persistent and flexible background jobs library for go. + +[![Version](https://img.shields.io/badge/version-0.4.2-5272B4.svg)](https://github.com/albrow/jobs/releases) +[![Circle CI](https://img.shields.io/circleci/project/albrow/jobs.svg)](https://circleci.com/gh/albrow/jobs) +[![GoDoc](https://godoc.org/github.com/albrow/jobs?status.svg)](https://godoc.org/github.com/albrow/jobs) + +Jobs is powered by Redis and supports the following features: + + - A job can encapsulate any arbitrary functionality. A job can do anything + which can be done in a go function. + - A job can be one-off (only executed once) or recurring (scheduled to + execute at a specific interval). + - A job can be retried a specified number of times if it fails. + - A job is persistent, with protections against power loss and other worst + case scenarios. (See the [Guarantees](#guarantees) section below) + - Work on jobs can be spread amongst any number of concurrent workers across any + number of machines. + - Provided it is persisted to disk, every job will be executed *at least* once, + and in ideal network conditions will be executed *exactly* once. (See the + [Guarantees](#guarantees) section below) + - You can query the database to find out e.g. the number of jobs that are + currently executing or how long a particular job took to execute. + - Any job that permanently fails will have its error captured and stored. + + +Why is it Useful? +----------------- + +Jobs is intended to be used in web applications. It is useful for cases where you need +to execute some long-running code, but you don't want your users to wait for the code to +execute before rendering a response. A good example is sending a welcome email to your users +after they sign up. You can use Jobs to schedule the email to be sent asynchronously, and +render a response to your user without waiting for the email to be sent. You could use a +goroutine to accomplish the same thing, but in the event of a server restart or power loss, +the email might never be sent. Jobs guarantees that the email will be sent at some time +and allows you to spread the work between different machines. + + +Development Status +------------------ + +Jobs is being actively developed and is well-tested. However, since it is still a young library, +it is not recommended for use in mission-critical production applications at this time. It is probably +fine to use for low-traffic hobby sites, and in fact we encourage its use in those settings to help +polish the API and find missing features and hidden bugs. Pull requests and issue reports are much +appreciated :) + +Jobs follows semantic versioning but offers no guarantees of backwards compatibility until version +1.0. Keep in mind that breaking changes might occur. We will do our best to make the community aware +of any non-trivial breaking changes beforehand. + +Installation +------------ + +Jobs requires Go version >= 1.2. If you do not already have it, follow these instructions: + +- [Install Go](http://golang.org/doc/install) +- Follow the instructions for [setting up your go workspace](https://golang.org/doc/code.html) + +Jobs requires access to a Redis database. If you plan to have multiple worker pools spread +out across different machines, they should all connect to the same Redis database. If you +only want to run one worker pool, it is safe to install Redis locally and run it on the same +machine. In either case, if you need to install Redis, follow these instructions: + +- [Install Redis](http://redis.io/download). +- Follow the instructions in the section called + [Installing Redis more properly](http://redis.io/topics/quickstart#installing-redis-more-properly). +- Make sure you understand how [Redis Persistence](http://redis.io/topics/persistence) works and have + edited your config file to get your desired persistence. We recommend using both RDB and AOF and setting + fsync to either "always" or "everysec". + +After that, you can install Jobs like you would any other go package: `go get github.com/albrow/jobs`. +If you want to update the package later, use `go get -u github.com/albrow/jobs`. Then you can import +Jobs like you would any other go package by adding `import github.com/albrow/jobs` to your go source +file. + + +Quickstart Guide +---------------- + +### Connecting to Redis + +You can configure the connection to Redis by editing Config.Db. Here are the options: + +- Address is the address of the redis database to connect to. Default is + "localhost:6379". +- Network is the type of network to use to connect to the redis database +Default is "tcp". +- Database is the redis database number to use for storing all data. Default + is 0. +- Password is a password to use for connecting to a redis database via the + AUTH command. If empty, Jobs will not attempt to authenticate. Default is + "" (an empty string). + +You should edit Config.Db during program initialization, before running Pool.Start +or scheduling any jobs. Here's an example of how to configure Jobs to use databse #10 +and authenticate with the password "foobar": + +``` go +func main() { + // Configure database options at the start of your application + jobs.Config.Db.Database = 10 + jobs.Config.Db.Password = "foobar" +} +``` + +### Registering Job Types + +Jobs must be organized into discrete types. Here's an example of how to register a job +which sends a welcome email to users: + +``` go +// We'll specify that we want the job to be retried 3 times before finally failing +welcomeEmailJobs, err := jobs.RegisterType("welcomeEmail", 3, func(user *User) error { + msg := fmt.Sprintf("Hello, %s! Thanks for signing up for foo.com.", user.Name) + if err := emails.Send(user.EmailAddress, msg); err != nil { + // The returned error will be captured by a worker, which will then log the error + // in the database and trigger up to 3 retries. + return err + } +}) +``` + +The final argument to the RegisterType function is a [HandlerFunc](http://godoc.org/github.com/albrow/jobs#HandlerFunc) +which will be executed when the job runs. HandlerFunc must be a function which accepts either +zero or one arguments and returns an error. + +### Scheduling a Job + +After registering a job type, you can schedule a job using the Schedule or ScheduleRecurring +methods like so: + +``` go +// The priority argument lets you choose how important the job is. Higher +// priority jobs will be executed first. +job, err := welcomeEmailJobs.Schedule(100, time.Now(), &User{EmailAddress: "foo@example.com"}) +if err != nil { + // Handle err +} +``` + +You can use the [Job object](http://godoc.org/github.com/albrow/jobs#Job) returned by Schedule +or ScheduleRecurring to check on the status of the job or cancel it manually. + +### Starting and Configuring Worker Pools + +You can schedule any number of worker pools across any number of machines, provided every machine +agrees on the definition of the job types. If you want, you can start a worker pool on the same +machines that are scheduling jobs, or you can have each worker pool running on a designated machine. +Since each pool is assigned an id based on a unique hardware identifier, you must only run one +worker pool per machine. + +To create a new pool with the [default configuration](http://godoc.org/github.com/albrow/jobs#pkg-variables), +just pass in nil: + +``` go +pool, err := jobs.NewPool(nil) +if err != nil { + // Handle err +} +``` + +You can also specify a different configuration by passing in +[*PoolConfig](http://godoc.org/github.com/albrow/jobs#PoolConfig). Any zero values in the config you pass +in will fallback to the default values. So here's how you could start a pool with 10 workers and a batch +size of 10, while letting the other options remain the default. + +``` go +pool, err := jobs.NewPool(&jobs.PoolConfig{ + NumWorkers: 10, + BatchSize: 10, +}) +if err != nil { + // Handle err +} +``` + +After you have created a pool, you can start it with the Start method. Once started, the pool will +continuously query the database for new jobs and delegate those jobs to workers. Any program that calls +Pool.Start() should also wait for the workers to finish before exiting. You can do so by wrapping Close and +Wait in a defer statement. Typical usage looks something like this: + +``` go +func main() { + pool, err := jobs.NewPool(nil) + if err != nil { + // Handle err + } + defer func() { + pool.Close() + if err := pool.Wait(); err != nil { + // Handle err + } + }() + if err := pool.Start(); err != nil { + // Handle err + } +} +``` + +You can also call Close and Wait at any time to manually stop the pool from executing new jobs. In this +case, any jobs that are currently being executed will still finish. + + +Testing +------- + +To run the tests, make sure you have Redis running and accepting unix socket connections on the address +/tmp/redis.sock. The tests will use database #14. **WARNING:** After each test is run, database #14 will be completely +erased, so make sure you do not have any important data stored there. + +To run the tests just run `go test .` If anything fails, please report an issue and describe what happened. + + +Contributing +------------ + +See [Contributing.md](https://github.com/albrow/jobs/blob/master/CONTRIBUTING.md) + + +Guarantees +----------- + +### Persistence + +Since jobs is powered by Redis, there is a chance that you can lose data with the default Redis configuration. +To get the best persistence guarantees, you should set Redis to use both AOF and RDB persistence modes and set +fsync to "always". With these settings, Redis is more or less +[as persistent as a database like postgres](http://redis.io/topics/persistence#ok-so-what-should-i-use). If want +better performance and are okay with a slightly greater chance of losing data (i.e. jobs not executing), you can +set fsync to "everysec". + +[Read more about Redis persistence](http://redis.io/topics/persistence). + +### Atomicity + +Jobs is carefully written using Redis transactions and lua scripting so that all database changes are atomic. +If Redis crashes in the middle of a transaction or script execution, it is possible that your AOF file can become +corrupted. If this happens, Redis will refuse to start until the AOF file is fixed. It is relatively easy to fix +the problem with the redis-check-aof tool, which will remove the partial transaction from the AOF file. In effect, +this guarantees that modifications of the database are atomic, even in the event of a power loss or hard reset, +with the caveat that you may need to use the redis-check-aof tool in the worst case scenario. + +Read more about [Redis transactions](http://redis.io/topics/transactions) and +[scripts](http://redis.io/commands#scripting). + +### Job Execution + +Jobs guarantees that a job will be executed *at least* once, provided it has been persisted on disk. (See the section +on [Persistence](#persistence) directly above). A job can only picked up by one pool at a time because a pool +atomically pops (gets and immediately moves) the next available jobs from the database. A job can only be executed +by one worker at a time because the jobs are delegated to workers via a shared channel. Each worker pool checks on +the health of all the other pools when it starts. If a pool crashes or is otherwise disconnected, any jobs it had +grabbed from the database that did not yet finish will be re-queued and picked up by a different pool. + +This is in no way an exhaustive list, but here are some known examples of scenarios that may cause a job to be +executed more than once: + +1. If there is a power failure or hard reset while a worker is in the middle of executing a job, the job may be + stuck in a half-executed state. Since there is no way to know how much of the job was successfully completed, + the job will be re-queued and picked up by a different pool, where it may be partially or fully executed + more than once. +2. If a pool becomes disconnected, it will be considered stale and its jobs will be re-queued and reclaimed + by a different pool. However, if the stale pool is able to partly or fully execute jobs without a reliable + internet connection, any jobs belonging to the stale pool might be executed more than once. You can increase + the [StaleTimeout](https://godoc.org/github.com/albrow/jobs#PoolConfig) parameter for a pool to make this + scenario less likely. + + +License +------- + +Jobs is licensed under the MIT License. See the LICENSE file for more information. diff --git a/vendor/github.com/albrow/jobs/config.go b/vendor/github.com/albrow/jobs/config.go new file mode 100644 index 00000000..6176d868 --- /dev/null +++ b/vendor/github.com/albrow/jobs/config.go @@ -0,0 +1,41 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import () + +// configType holds different config variables +type configType struct { + Db databaseConfig +} + +// databaseConfig holds config variables specific to the database +type databaseConfig struct { + Address string + Network string + Database int + Password string +} + +// Config is where all configuration variables are stored. You may modify Config +// directly in order to change config variables, and should only do so at the start +// of your program. +var Config = configType{ + Db: databaseConfig{ + // Address is the address of the redis database to connect to. Default is + // "localhost:6379". + Address: "localhost:6379", + // Network is the type of network to use to connect to the redis database + // Default is "tcp". + Network: "tcp", + // Database is the redis database number to use for storing all data. Default + // is 0. + Database: 0, + // Password is a password to use for connecting to a redis database via the + // AUTH command. If empty, Jobs will not attempt to authenticate. Default is + // "" (an empty string). + Password: "", + }, +} diff --git a/vendor/github.com/albrow/jobs/doc.go b/vendor/github.com/albrow/jobs/doc.go new file mode 100644 index 00000000..14d7e4e8 --- /dev/null +++ b/vendor/github.com/albrow/jobs/doc.go @@ -0,0 +1,43 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +// Package jobs is a persistent and flexible background jobs library. +// +// Version: 0.4.2 +// +// Jobs is powered by redis and supports the following features: +// +// - A job can encapsulate any arbitrary functionality. A job can do anything +// which can be done in a go function. +// - A job can be one-off (only executed once) or recurring (scheduled to +// execute at a specific interval). +// - A job can be retried a specified number of times if it fails. +// - A job is persistent, with protections against power loss and other worst +// case scenarios. +// - Jobs can be executed by any number of concurrent workers accross any +// number of machines. +// - Provided it is persisted to disk, every job will be executed *at least* once, +// and in ideal network conditions will be executed *exactly* once. +// - You can query the database to find out e.g. the number of jobs that are +// currently executing or how long a particular job took to execute. +// - Any job that permanently fails will have its error captured and stored. +// +// Why is it Useful +// +// Jobs is intended to be used in web applications. It is useful for cases where you need +// to execute some long-running code, but you don't want your users to wait for the code to +// execute before rendering a response. A good example is sending a welcome email to your users +// after they sign up. You can use Jobs to schedule the email to be sent asynchronously, and +// render a response to your user without waiting for the email to be sent. You could use a +// goroutine to accomplish the same thing, but in the event of a server restart or power loss, +// the email might never be sent. Jobs guarantees that the email will be sent at some time, +// and allows you to spread the work between different machines. +// +// +// More Information +// +// Visit https://github.com/albrow/jobs for a Quickstart Guide, code examples, and more +// information. +// +package jobs diff --git a/vendor/github.com/albrow/jobs/encode.go b/vendor/github.com/albrow/jobs/encode.go new file mode 100644 index 00000000..ad3a488e --- /dev/null +++ b/vendor/github.com/albrow/jobs/encode.go @@ -0,0 +1,46 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import ( + "bytes" + "encoding/gob" + "fmt" + "reflect" +) + +// decode decodes a slice of bytes and scans the value into dest using the gob package. +// All types are supported except recursive data structures and functions. +func decode(reply []byte, dest interface{}) error { + // Check the type of dest and make sure it is a pointer to something, + // otherwise we can't set its value in any meaningful way. + val := reflect.ValueOf(dest) + if val.Kind() != reflect.Ptr { + return fmt.Errorf("jobs: Argument to decode must be pointer. Got %T", dest) + } + + // Use the gob package to decode the reply and write the result into + // dest. + buf := bytes.NewBuffer(reply) + dec := gob.NewDecoder(buf) + if err := dec.DecodeValue(val.Elem()); err != nil { + return err + } + return nil +} + +// encode encodes data into a slice of bytes using the gob package. +// All types are supported except recursive data structures and functions. +func encode(data interface{}) ([]byte, error) { + if data == nil { + return nil, nil + } + buf := bytes.NewBuffer([]byte{}) + enc := gob.NewEncoder(buf) + if err := enc.Encode(data); err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/vendor/github.com/albrow/jobs/job.go b/vendor/github.com/albrow/jobs/job.go new file mode 100644 index 00000000..ede8c39c --- /dev/null +++ b/vendor/github.com/albrow/jobs/job.go @@ -0,0 +1,473 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import ( + "fmt" + "github.com/garyburd/redigo/redis" + "time" +) + +// Job represents a discrete piece of work to be done by a worker. +type Job struct { + id string + data []byte + typ *Type + status Status + time int64 + freq int64 + priority int + err error + retries uint + started int64 + finished int64 + poolId string +} + +// ErrorJobNotFound is returned whenever a specific job is not found, +// e.g. from the FindById function. +type ErrorJobNotFound struct { + id string +} + +func (e ErrorJobNotFound) Error() string { + if e.id == "" { + return fmt.Sprintf("jobs: Could not find job with the given criteria.") + } + return fmt.Sprintf("jobs: Could not find job with id: %s", e.id) +} + +// Id returns the unique identifier used for the job. If the job has not yet +// been saved to the database, it may return an empty string. +func (j *Job) Id() string { + return j.id +} + +// Data returns the gob-encoded data of the job +func (j *Job) Data() []byte { + return j.data +} + +// Status returns the status of the job. +func (j *Job) Status() Status { + return j.status +} + +// Time returns the time at which the job should be executed in UTC UNIX +// format with nanosecond precision. +func (j *Job) Time() int64 { + return j.time +} + +// Freq returns the frequency at which the job should be executed. Specifically +// it returns the number of nanoseconds between each scheduled execution. +func (j *Job) Freq() int64 { + return j.freq +} + +// Priority returns the job's priority. +func (j *Job) Priority() int { + return j.priority +} + +// Error returns the last error that arose during execution of the job. It is +// only non-nil if the job has failed at some point. +func (j *Job) Error() error { + return j.err +} + +// Retries returns the number of remaining retries for the job. +func (j *Job) Retries() uint { + return j.retries +} + +// Started returns the time that the job started executing (in local time +// with nanosecond precision) or the zero time if the job has not started +// executing yet. +func (j *Job) Started() time.Time { + return time.Unix(0, j.started).Local() +} + +// Finished returns the time that the job finished executing (in local +// time with nanosecond precision) or the zero time if the job has not +// finished executing yet. +func (j *Job) Finished() time.Time { + return time.Unix(0, j.finished).Local() +} + +// PoolId returns the pool id of the job if it is currently being executed +// or has been executed and at some point has been assigned to a specific pool. +// Otherwise, it returns an empty string. +func (j *Job) PoolId() string { + return j.poolId +} + +// Duration returns how long the job took to execute with nanosecond +// precision. I.e. the difference between j.Finished() and j.Started(). +// It returns a duration of zero if the job has not finished yet. +func (j *Job) Duration() time.Duration { + if j.Finished().IsZero() { + return 0 * time.Second + } + return j.Finished().Sub(j.Started()) +} + +// Key returns the key used for the hash in redis which stores all the +// fields for this job. +func (j *Job) Key() string { + return "jobs:" + j.id +} + +// IsRecurring returns true iff the job is recurring +func (j *Job) IsRecurring() bool { + return j.freq != 0 +} + +// NextTime returns the time (unix UTC with nanosecond precision) that the +// job should execute next, if it is a recurring job, and 0 if it is not. +func (j *Job) NextTime() int64 { + if !j.IsRecurring() { + return 0 + } + // NOTE: is this the proper way to handle rescheduling? + // What if we schedule jobs faster than they can be executed? + // Should we just let them build up and expect the end user to + // allocate more workers? Or should we schedule for time.Now at + // the earliest to prevent buildup? + return j.time + j.freq +} + +// save writes the job to the database and adds it to the appropriate indexes and status +// sets, but does not enqueue it. If you want to add it to the queue, use the enqueue method +// after save. +func (j *Job) save() error { + t := newTransaction() + t.saveJob(j) + if err := t.exec(); err != nil { + return err + } + return nil +} + +// saveJob adds commands to the transaction to set all the fields for the main hash for the job, +// add the job to the time index, move the job to the appropriate status set. It will +// also mutate the job by 1) generating an id if the id is empty and 2) setting the status to +// StatusSaved if the status is empty. +func (t *transaction) saveJob(job *Job) { + // Generate id if needed + if job.id == "" { + job.id = generateRandomId() + } + // Set status to saved if needed + if job.status == "" { + job.status = StatusSaved + } + // Add the job attributes to a hash + t.command("HMSET", job.mainHashArgs(), nil) + // Add the job to the appropriate status set + t.setStatus(job, job.status) + // Add the job to the time index + t.addJobToTimeIndex(job) +} + +// addJobToTimeIndex adds commands to the transaction which will, when executed, +// add the job id to the time index with a score equal to the job's time field. +// If the job has been destroyed, addJobToTimeIndex will have no effect. +func (t *transaction) addJobToTimeIndex(job *Job) { + t.addJobToSet(job, Keys.JobsTimeIndex, float64(job.time)) +} + +// Refresh mutates the job by setting its fields to the most recent data +// found in the database. It returns an error if there was a problem connecting +// to the database or if the job was destroyed. +func (j *Job) Refresh() error { + t := newTransaction() + t.scanJobById(j.id, j) + if err := t.exec(); err != nil { + return err + } + return nil +} + +// enqueue adds the job to the queue and sets its status to StatusQueued. Queued jobs will +// be completed by workers in order of priority. Attempting to enqueue a destroyed job +// will have no effect. +func (j *Job) enqueue() error { + if err := j.setStatus(StatusQueued); err != nil { + return err + } + return nil +} + +// Reschedule reschedules the job with the given time. It can be used to reschedule +// cancelled jobs. It may also be used to reschedule finished or failed jobs, however, +// in most cases if you want to reschedule finished jobs you should use the ScheduleRecurring +// method and if you want to reschedule failed jobs, you should set the number of retries > 0 +// when registering the job type. Attempting to reschedule a destroyed job will have no effect. +// Reschedule returns an error if there was a problem connecting to the database. +func (j *Job) Reschedule(time time.Time) error { + t := newTransaction() + unixNanoTime := time.UTC().UnixNano() + t.setJobField(j, "time", unixNanoTime) + t.setStatus(j, StatusQueued) + j.time = unixNanoTime + t.addJobToTimeIndex(j) + if err := t.exec(); err != nil { + return err + } + j.status = StatusQueued + return nil +} + +// Cancel cancels the job, but does not remove it from the database. It will be +// added to a list of cancelled jobs. If you wish to remove it from the database, +// use the Destroy method. Attempting to cancel a destroyed job will have no effect. +func (j *Job) Cancel() error { + if err := j.setStatus(StatusCancelled); err != nil { + return err + } + return nil +} + +// setError sets the err property of j and adds it to the set of jobs which had errors. +// If the job has been destroyed, setError will have no effect. +func (j *Job) setError(err error) error { + j.err = err + t := newTransaction() + t.setJobField(j, "error", j.err.Error()) + if err := t.exec(); err != nil { + return err + } + return nil +} + +// Destroy removes all traces of the job from the database. If the job is currently +// being executed by a worker, the worker may still finish the job. Attempting to +// destroy a job that has already been destroyed will have no effect, so it is safe +// to call Destroy multiple times. +func (j *Job) Destroy() error { + if j.id == "" { + return fmt.Errorf("jobs: Cannot destroy job that doesn't have an id.") + } + // Start a new transaction + t := newTransaction() + // Call the script to destroy the job + t.destroyJob(j) + // Execute the transaction + if err := t.exec(); err != nil { + return err + } + j.status = StatusDestroyed + return nil +} + +// setStatus updates the job's status in the database and moves it to the appropriate +// status set. Attempting to set the status of a job which has been destroyed will have +// no effect. +func (j *Job) setStatus(status Status) error { + if j.id == "" { + return fmt.Errorf("jobs: Cannot set status to %s because job doesn't have an id.", status) + } + if j.status == StatusDestroyed { + return fmt.Errorf("jobs: Cannot set job:%s status to %s because it was destroyed.", j.id, status) + } + // Use a transaction to move the job to the appropriate status set and set its status + t := newTransaction() + t.setStatus(j, status) + if err := t.exec(); err != nil { + return err + } + j.status = status + return nil +} + +// mainHashArgs returns the args for the hash which will store the job data +func (j *Job) mainHashArgs() []interface{} { + hashArgs := []interface{}{j.Key(), + "data", string(j.data), + "type", j.typ.name, + "time", j.time, + "freq", j.freq, + "priority", j.priority, + "retries", j.retries, + "status", j.status, + "started", j.started, + "finished", j.finished, + "poolId", j.poolId, + } + if j.err != nil { + hashArgs = append(hashArgs, "error", j.err.Error()) + } + return hashArgs +} + +// scanJob scans the values of reply into job. reply should be the +// response of an HMGET or HGETALL query. +func scanJob(reply interface{}, job *Job) error { + fields, err := redis.Values(reply, nil) + if err != nil { + return err + } else if len(fields) == 0 { + return ErrorJobNotFound{} + } else if len(fields)%2 != 0 { + return fmt.Errorf("jobs: In scanJob: Expected length of fields to be even but got: %d", len(fields)) + } + for i := 0; i < len(fields)-1; i += 2 { + fieldName, err := redis.String(fields[i], nil) + if err != nil { + return fmt.Errorf("jobs: In scanJob: Could not convert fieldName (fields[%d] = %v) of type %T to string.", i, fields[i], fields[i]) + } + fieldValue := fields[i+1] + switch fieldName { + case "id": + if err := scanString(fieldValue, &(job.id)); err != nil { + return err + } + case "data": + if err := scanBytes(fieldValue, &(job.data)); err != nil { + return err + } + case "type": + typeName := "" + if err := scanString(fieldValue, &typeName); err != nil { + return err + } + Type, found := Types[typeName] + if !found { + return fmt.Errorf("jobs: In scanJob: Could not find Type with name = %s", typeName) + } + job.typ = Type + case "time": + if err := scanInt64(fieldValue, &(job.time)); err != nil { + return err + } + case "freq": + if err := scanInt64(fieldValue, &(job.freq)); err != nil { + return err + } + case "priority": + if err := scanInt(fieldValue, &(job.priority)); err != nil { + return err + } + case "retries": + if err := scanUint(fieldValue, &(job.retries)); err != nil { + return err + } + case "status": + status := "" + if err := scanString(fieldValue, &status); err != nil { + return err + } + job.status = Status(status) + case "started": + if err := scanInt64(fieldValue, &(job.started)); err != nil { + return err + } + case "finished": + if err := scanInt64(fieldValue, &(job.finished)); err != nil { + return err + } + case "poolId": + if err := scanString(fieldValue, &(job.poolId)); err != nil { + return err + } + } + } + return nil +} + +// scanInt converts a reply from redis into an int and scans the value into v. +func scanInt(reply interface{}, v *int) error { + if v == nil { + return fmt.Errorf("jobs: In scanInt: argument v was nil") + } + val, err := redis.Int(reply, nil) + if err != nil { + return fmt.Errorf("jobs: In scanInt: Could not convert %v of type %T to int.", reply, reply) + } + (*v) = val + return nil +} + +// scanUint converts a reply from redis into a uint and scans the value into v. +func scanUint(reply interface{}, v *uint) error { + if v == nil { + return fmt.Errorf("jobs: In scanUint: argument v was nil") + } + val, err := redis.Uint64(reply, nil) + if err != nil { + return fmt.Errorf("jobs: In scanUint: Could not convert %v of type %T to uint.", reply, reply) + } + (*v) = uint(val) + return nil +} + +// scanInt64 converts a reply from redis into an int64 and scans the value into v. +func scanInt64(reply interface{}, v *int64) error { + if v == nil { + return fmt.Errorf("jobs: In scanInt64: argument v was nil") + } + val, err := redis.Int64(reply, nil) + if err != nil { + return fmt.Errorf("jobs: In scanInt64: Could not convert %v of type %T to int64.", reply, reply) + } + (*v) = val + return nil +} + +// scanString converts a reply from redis into a string and scans the value into v. +func scanString(reply interface{}, v *string) error { + if v == nil { + return fmt.Errorf("jobs: In String: argument v was nil") + } + val, err := redis.String(reply, nil) + if err != nil { + return fmt.Errorf("jobs: In String: Could not convert %v of type %T to string.", reply, reply) + } + (*v) = val + return nil +} + +// scanBytes converts a reply from redis into a slice of bytes and scans the value into v. +func scanBytes(reply interface{}, v *[]byte) error { + if v == nil { + return fmt.Errorf("jobs: In scanBytes: argument v was nil") + } + val, err := redis.Bytes(reply, nil) + if err != nil { + return fmt.Errorf("jobs: In scanBytes: Could not convert %v of type %T to []byte.", reply, reply) + } + (*v) = val + return nil +} + +// scanJobById adds commands and a reply handler to the transaction which, when run, +// will scan the values of the job corresponding to id into job. It does not execute +// the transaction. +func (t *transaction) scanJobById(id string, job *Job) { + job.id = id + t.command("HGETALL", redis.Args{job.Key()}, newScanJobHandler(job)) +} + +// FindById returns the job with the given id or an error if the job cannot be found +// (in which case the error will have type ErrorJobNotFound) or there was a problem +// connecting to the database. +func FindById(id string) (*Job, error) { + job := &Job{} + t := newTransaction() + t.scanJobById(id, job) + if err := t.exec(); err != nil { + switch e := err.(type) { + case ErrorJobNotFound: + // If the job was not found, add the id to the error + // so that the caller can get a more useful error message. + e.id = id + return nil, e + default: + return nil, err + } + } + return job, nil +} diff --git a/vendor/github.com/albrow/jobs/job_status.go b/vendor/github.com/albrow/jobs/job_status.go new file mode 100644 index 00000000..bfc4bdaa --- /dev/null +++ b/vendor/github.com/albrow/jobs/job_status.go @@ -0,0 +1,76 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import ( + "github.com/garyburd/redigo/redis" +) + +// Status represents the different statuses a job can have. +type Status string + +const ( + // StatusSaved is the status of any job that has been saved into the database but not yet queued + StatusSaved Status = "saved" + // StatusQueued is the status of any job that has been queued for execution but not yet selected + StatusQueued Status = "queued" + // StatusExecuting is the status of any job that has been selected for execution and is being delegated + // to some worker and any job that is currently being executed by some worker. + StatusExecuting Status = "executing" + // StatusFinished is the status of any job that has been successfully executed. + StatusFinished Status = "finished" + // StatusFailed is the status of any job that failed to execute and for which there are no remaining retries. + StatusFailed Status = "failed" + // StatusCancelled is the status of any job that was manually cancelled. + StatusCancelled Status = "cancelled" + // StatusDestroyed is the status of any job that has been destroyed, i.e. completely removed + // from the database. + StatusDestroyed Status = "destroyed" +) + +// key returns the key used for the sorted set in redis which will hold +// all jobs with this status. +func (status Status) Key() string { + return "jobs:" + string(status) +} + +// Count returns the number of jobs that currently have the given status +// or an error if there was a problem connecting to the database. +func (status Status) Count() (int, error) { + conn := redisPool.Get() + defer conn.Close() + return redis.Int(conn.Do("ZCARD", status.Key())) +} + +// JobIds returns the ids of all jobs that have the given status, ordered by +// priority or an error if there was a problem connecting to the database. +func (status Status) JobIds() ([]string, error) { + conn := redisPool.Get() + defer conn.Close() + return redis.Strings(conn.Do("ZREVRANGE", status.Key(), 0, -1)) +} + +// Jobs returns all jobs that have the given status, ordered by priority or +// an error if there was a problem connecting to the database. +func (status Status) Jobs() ([]*Job, error) { + t := newTransaction() + jobs := []*Job{} + t.getJobsByIds(status.Key(), newScanJobsHandler(&jobs)) + if err := t.exec(); err != nil { + return nil, err + } + return jobs, nil +} + +// possibleStatuses is simply an array of all the possible job statuses. +var possibleStatuses = []Status{ + StatusSaved, + StatusQueued, + StatusExecuting, + StatusFinished, + StatusFailed, + StatusCancelled, + StatusDestroyed, +} diff --git a/vendor/github.com/albrow/jobs/job_type.go b/vendor/github.com/albrow/jobs/job_type.go new file mode 100644 index 00000000..d9414989 --- /dev/null +++ b/vendor/github.com/albrow/jobs/job_type.go @@ -0,0 +1,166 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import ( + "fmt" + "reflect" + "time" +) + +// Types is map of job type names to *Type +var Types = map[string]*Type{} + +// Type represents a type of job that can be executed by workers +type Type struct { + name string + handler interface{} + retries uint + dataType reflect.Type +} + +// ErrorNameAlreadyRegistered is returned whenever RegisterType is called +// with a name that has already been registered. +type ErrorNameAlreadyRegistered struct { + name string +} + +// Error satisfies the error interface. +func (e ErrorNameAlreadyRegistered) Error() string { + return fmt.Sprintf("jobs: Cannot register job type because job type with name %s already exists", e.name) +} + +// newErrorNameAlreadyRegistered returns an ErrorNameAlreadyRegistered with the given name. +func newErrorNameAlreadyRegistered(name string) ErrorNameAlreadyRegistered { + return ErrorNameAlreadyRegistered{name: name} +} + +// A HandlerFunc is a function which accepts ether zero or one arguments and returns an error. +// The function will be executed by a worker. If the function returns a non-nil error or causes +// a panic, the worker will capture and log the error, and if applicable the job may be queued +// for retry. +type HandlerFunc interface{} + +// RegisterType registers a new type of job that can be executed by workers. +// name should be a unique string identifier for the job. +// retries is the number of times this type of job should be retried if it fails. +// handler is a function that a worker will call in order to execute the job. +// handler should be a function which accepts either 0 or 1 arguments of any type, +// corresponding to the data for a job of this type. All jobs of this type must have +// data with the same type as the first argument to handler, or nil if the handler +// accepts no arguments. +func RegisterType(name string, retries uint, handler HandlerFunc) (*Type, error) { + // Make sure name is unique + if _, found := Types[name]; found { + return Types[name], newErrorNameAlreadyRegistered(name) + } + // Make sure handler is a function + handlerType := reflect.TypeOf(handler) + if handlerType.Kind() != reflect.Func { + return nil, fmt.Errorf("jobs: in RegisterNewType, handler must be a function. Got %T", handler) + } + if handlerType.NumIn() > 1 { + return nil, fmt.Errorf("jobs: in RegisterNewType, handler must accept 0 or 1 arguments. Got %d.", handlerType.NumIn()) + } + if handlerType.NumOut() != 1 { + return nil, fmt.Errorf("jobs: in RegisterNewType, handler must have exactly one return value. Got %d.", handlerType.NumOut()) + } + if !typeIsError(handlerType.Out(0)) { + return nil, fmt.Errorf("jobs: in RegisterNewType, handler must return an error. Got return value of type %s.", handlerType.Out(0).String()) + } + Type := &Type{ + name: name, + handler: handler, + retries: retries, + } + if handlerType.NumIn() == 1 { + Type.dataType = handlerType.In(0) + } + Types[name] = Type + return Type, nil +} + +var errorType = reflect.TypeOf(make([]error, 1)).Elem() + +func typeIsError(typ reflect.Type) bool { + return typ.Implements(errorType) +} + +// String satisfies the Stringer interface and returns the name of the Type. +func (jt *Type) String() string { + return jt.name +} + +// Schedule schedules a on-off job of the given type with the given parameters. +// Jobs with a higher priority will be executed first. The job will not be +// executed until after time. data is the data associated with this particular +// job and should have the same type as the first argument to the handler for this +// Type. +func (jt *Type) Schedule(priority int, time time.Time, data interface{}) (*Job, error) { + // Encode the data + encodedData, err := jt.encodeData(data) + if err != nil { + return nil, err + } + // Create and save the job + job := &Job{ + data: encodedData, + typ: jt, + time: time.UTC().UnixNano(), + retries: jt.retries, + priority: priority, + } + // Set the job's status to queued and save it in the database + job.status = StatusQueued + if err := job.save(); err != nil { + return nil, err + } + return job, nil +} + +// ScheduleRecurring schedules a recurring job of the given type with the given parameters. +// Jobs with a higher priority will be executed first. The job will not be executed until after +// time. After time, the job will be executed with a frequency specified by freq. data is the +// data associated with this particular job and should have the same type as the first argument +// to the handler for this Type. Every recurring execution of the job will use the +// same data. +func (jt *Type) ScheduleRecurring(priority int, time time.Time, freq time.Duration, data interface{}) (*Job, error) { + // Encode the data + encodedData, err := jt.encodeData(data) + if err != nil { + return nil, err + } + // Create and save the job + job := &Job{ + data: encodedData, + typ: jt, + time: time.UTC().UnixNano(), + retries: jt.retries, + freq: freq.Nanoseconds(), + priority: priority, + } + // Set the job's status to queued and save it in the database + job.status = StatusQueued + if err := job.save(); err != nil { + return nil, err + } + return job, nil +} + +// encodeData checks that the type of data is what we expect based on the handler for the Type. +// If it is, it encodes the data into a slice of bytes. +func (jt *Type) encodeData(data interface{}) ([]byte, error) { + // Check the type of data + dataType := reflect.TypeOf(data) + if dataType != jt.dataType { + return nil, fmt.Errorf("jobs: provided data was not of the correct type.\nExpected %s for Type %s, but got %s", jt.dataType, jt, dataType) + } + // Encode the data + encodedData, err := encode(data) + if err != nil { + return nil, fmt.Errorf("jobs: error encoding data: %s", err.Error()) + } + return encodedData, nil +} diff --git a/vendor/github.com/albrow/jobs/pool.go b/vendor/github.com/albrow/jobs/pool.go new file mode 100644 index 00000000..f99cdb88 --- /dev/null +++ b/vendor/github.com/albrow/jobs/pool.go @@ -0,0 +1,481 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import ( + "fmt" + "net" + "runtime" + "sync" + "time" + + "github.com/garyburd/redigo/redis" +) + +// Pool is a pool of workers. Pool will query the database for queued jobs +// and delegate those jobs to some number of workers. It will do this continuously +// until the main program exits or you call Pool.Close(). +type Pool struct { + // config holds all config options for the pool + config *PoolConfig + // id is a unique identifier for each worker, which is generated whenver + // Start() is called + id string + // workers is a slice of all workers + workers []*worker + // jobs is a channel through which jobs are delegated to workers + jobs chan *Job + // wg can be used after the jobs channel is closed to wait for all + // workers to finish executing their current jobs. + wg *sync.WaitGroup + // exit is used to signal the pool to stop running the query loop + // and close the jobs channel + exit chan bool + // afterFunc is a function that gets called after each job. + afterFunc func(*Job) + // RWMutex is only used during testing when we need to + // change some of the fields for the pool after it was started. + // NOTE: currently only used in one test (TestStalePoolsArePurged) + // and might be removed if we refactor later. + sync.RWMutex +} + +// PoolConfig is a set of configuration options for pools. Setting any value +// to the zero value will be interpretted as the default. +type PoolConfig struct { + // NumWorkers is the number of workers to run + // Each worker will run inside its own goroutine + // and execute jobs asynchronously. Default is + // runtime.GOMAXPROCS. + NumWorkers int + // BatchSize is the number of jobs to send through + // the jobs channel at once. Increasing BatchSize means + // the worker pool will query the database less frequently, + // so you would get higher performance. However this comes + // at the cost that jobs with lower priority may sometimes be + // executed before jobs with higher priority, because the jobs + // with higher priority were not ready yet the last time the pool + // queried the database. Decreasing BatchSize means more + // frequent queries to the database and lower performance, but + // greater likelihood of executing jobs in perfect order with regards + // to priority. Setting BatchSize to 1 gaurantees that higher priority + // jobs are always executed first as soon as they are ready. Default is + // runtime.GOMAXPROCS. + BatchSize int + // MinWait is the minimum amount of time the pool will wait before checking + // the database for queued jobs. The pool may take longer to query the database + // if the jobs channel is blocking (i.e. if no workers are ready to execute new + // jobs). Default is 200ms. + MinWait time.Duration + // StaleTimeout is the amount of time to wait for a pool to reply to a ping request + // before considering it stale. Stale pools will be purged and if they have any + // corresponding jobs in the executing set, those jobs will be requeued. Default + // is 30 seconds. + StaleTimeout time.Duration +} + +// DefaultPoolConfig is the default config for pools. You can override any values +// by passing in a *PoolConfig to NewPool. Any zero values in PoolConfig will be +// interpreted as the default. +var DefaultPoolConfig = &PoolConfig{ + NumWorkers: runtime.GOMAXPROCS(0), + BatchSize: runtime.GOMAXPROCS(0), + MinWait: 200 * time.Millisecond, + StaleTimeout: 30 * time.Second, +} + +// NewPool creates and returns a new pool with the given configuration. You can +// pass in nil to use the default values. Otherwise, any zero values in config will +// be interpreted as the default value. +func NewPool(config *PoolConfig) (*Pool, error) { + finalConfig := getPoolConfig(config) + hardwareId, err := getHardwareId() + if err != nil { + return nil, err + } + return &Pool{ + config: finalConfig, + id: hardwareId, + wg: &sync.WaitGroup{}, + exit: make(chan bool), + workers: make([]*worker, finalConfig.NumWorkers), + jobs: make(chan *Job, finalConfig.BatchSize), + }, nil +} + +// getPoolConfig replaces any zero values in passedConfig with the default values. +// If passedConfig is nil, every value will be set to the default. +func getPoolConfig(passedConfig *PoolConfig) *PoolConfig { + if passedConfig == nil { + return DefaultPoolConfig + } + finalConfig := &PoolConfig{} + (*finalConfig) = (*passedConfig) + if passedConfig.NumWorkers == 0 { + finalConfig.NumWorkers = DefaultPoolConfig.NumWorkers + } + if passedConfig.BatchSize == 0 { + finalConfig.BatchSize = DefaultPoolConfig.BatchSize + } + if passedConfig.MinWait == 0 { + finalConfig.MinWait = DefaultPoolConfig.MinWait + } + if passedConfig.StaleTimeout == 0 { + finalConfig.StaleTimeout = DefaultPoolConfig.StaleTimeout + } + return finalConfig +} + +// addToPoolSet adds the id of the worker pool to a set of active pools +// in the database. +func (p *Pool) addToPoolSet() error { + conn := redisPool.Get() + defer conn.Close() + p.RLock() + thisId := p.id + p.RUnlock() + if _, err := conn.Do("SADD", Keys.ActivePools, thisId); err != nil { + return err + } + return nil +} + +// removeFromPoolSet removes the id of the worker pool from a set of active pools +// in the database. +func (p *Pool) removeFromPoolSet() error { + conn := redisPool.Get() + defer conn.Close() + p.RLock() + thisId := p.id + p.RUnlock() + if _, err := conn.Do("SREM", Keys.ActivePools, thisId); err != nil { + return err + } + return nil +} + +// getHardwareId returns a unique identifier for the current machine. It does this +// by iterating through the network interfaces of the machine and picking the first +// one that has a non-empty hardware (MAC) address. MAC Addresses are guaranteed by +// IEEE to be unique, however, they are also sometimes spoofable. Spoofed MAC addresses +// are fine as long as no two machines in the job pool have the same MAC address. +func getHardwareId() (string, error) { + inters, err := net.Interfaces() + if err != nil { + return "", fmt.Errorf("jobs: Unable to get network interfaces via net.Interfaces(). Does this machine have any network interfaces?\n%s", err.Error()) + } + address := "" + for _, inter := range inters { + if inter.HardwareAddr.String() != "" { + address = inter.HardwareAddr.String() + break + } + } + if address == "" { + return "", fmt.Errorf("jobs: Unable to find a network interface with a non-empty hardware (MAC) address. Does this machine have any valid network interfaces?\n%s", err.Error()) + } + return address, nil +} + +// pingKey is the key for a pub/sub connection which allows a pool to ping, i.e. +// check the status of, another pool. +func (p *Pool) pingKey() string { + p.RLock() + thisId := p.id + p.RUnlock() + return "workers:" + thisId + ":ping" +} + +// pongKey is the key for a pub/sub connection which allows a pool to respond to +// pings with a pong, i.e. acknowledge that it is still alive and working. +func (p *Pool) pongKey() string { + p.RLock() + thisId := p.id + p.RUnlock() + return "workers:" + thisId + ":pong" +} + +// purgeStalePools will first get all the ids of pools from the activePools +// set. All of these should be active, but if Pool.Wait was never called for +// a pool (possibly because of power failure), some of them might not actually +// be active. To find out for sure, purgeStalePools will ping each pool that is +// supposed to be active and wait for a pong response. If it does not receive +// a pong within some amount of time, the pool is considered stale (i.e. whatever +// process that was running it was exited and it is no longer executing jobs). If +// any stale pools are found, purgeStalePools will remove them from the set of +// active pools and then moves any jobs associated with the stale pool from the +// executing set to the queued set to be retried. +func (p *Pool) purgeStalePools() error { + conn := redisPool.Get() + defer conn.Close() + poolIds, err := redis.Strings(conn.Do("SMEMBERS", Keys.ActivePools)) + if err != nil { + return err + } + for _, poolId := range poolIds { + p.RLock() + thisId := p.id + p.RUnlock() + if poolId == thisId { + // Don't ping self + continue + } + pool := &Pool{id: poolId} + go func(pool *Pool) { + if err := p.pingAndPurgeIfNeeded(pool); err != nil { + // TODO: send accross an err channel instead of panicking + panic(err) + } + }(pool) + } + return nil +} + +// pingAndPurgeIfNeeded pings other by publishing to others ping key. If it +// does not receive a pong reply within some amount of time, it will +// assume the pool is stale and purge it. +func (p *Pool) pingAndPurgeIfNeeded(other *Pool) error { + ping := redisPool.Get() + pong := redis.PubSubConn{redisPool.Get()} + // Listen for pongs by subscribing to the other pool's pong key + pong.Subscribe(other.pongKey()) + // Ping the other pool by publishing to its ping key + ping.Do("PUBLISH", other.pingKey(), 1) + // Use a select statement to either receive the pong or timeout + pongChan := make(chan interface{}) + errChan := make(chan error) + go func() { + defer func() { + pong.Close() + ping.Close() + }() + select { + case <-p.exit: + return + default: + } + for { + reply := pong.Receive() + switch reply.(type) { + case redis.Message: + // The pong was received + pongChan <- reply + return + case error: + // There was some unexpected error + err := reply.(error) + errChan <- err + return + } + } + }() + timeout := time.After(p.config.StaleTimeout) + select { + case <-pongChan: + // The other pool responded with a pong + return nil + case err := <-errChan: + // Received an error from the pubsub conn + return err + case <-timeout: + // The pool is considered stale and should be purged + t := newTransaction() + other.RLock() + otherId := other.id + other.RUnlock() + t.purgeStalePool(otherId) + if err := t.exec(); err != nil { + return err + } + } + return nil +} + +// respondToPings continuously listens for pings from other worker pools and +// immediately responds with a pong. It will only return if there is an error. +func (p *Pool) respondToPings() error { + pong := redisPool.Get() + ping := redis.PubSubConn{redisPool.Get()} + defer func() { + pong.Close() + ping.Close() + }() + // Subscribe to the ping key for this pool to receive pings. + if err := ping.Subscribe(p.pingKey()); err != nil { + return err + } + for { + // Whenever we recieve a ping, reply immediately with a pong by + // publishing to the pong key for this pool. + switch reply := ping.Receive().(type) { + case redis.Message: + if _, err := pong.Do("PUBLISH", p.pongKey(), 0); err != nil { + return err + } + case error: + err := reply.(error) + return err + } + time.Sleep(1 * time.Millisecond) + } +} + +// removeStaleSelf will check if the current machine recently failed hard +// (e.g. due to power failuer) by checking if p.id is in the set of active +// pools. If p.id is still "active" according to the database, it means +// there was a hard failure, and so removeStaleSelf then re-queues the +// stale jobs. removeStaleSelf should only be run when the Pool is started. +func (p *Pool) removeStaleSelf() error { + t := newTransaction() + t.purgeStalePool(p.id) + if err := t.exec(); err != nil { + return err + } + return nil +} + +// SetAfterFunc will assign a function that will be executed each time +// a job is finished. +func (p *Pool) SetAfterFunc(f func(*Job)) { + p.afterFunc = f +} + +// Start starts the worker pool. This means the pool will initialize workers, +// continuously query the database for queued jobs, and delegate those jobs +// to the workers. +func (p *Pool) Start() error { + // Purge stale jobs belonging to this pool if there was a recent + // hard failure + if err := p.removeStaleSelf(); err != nil { + return err + } + + // Check on the status of other worker pools by pinging them and + // start the process to repond to pings from other pools + if err := p.addToPoolSet(); err != nil { + return err + } + go func() { + select { + case <-p.exit: + return + default: + } + if err := p.respondToPings(); err != nil { + // TODO: send the err accross a channel instead of panicking + panic(err) + } + }() + if err := p.purgeStalePools(); err != nil { + return err + } + + // Initialize workers + for i := range p.workers { + p.wg.Add(1) + worker := &worker{ + wg: p.wg, + jobs: p.jobs, + afterFunc: p.afterFunc, + } + p.workers[i] = worker + worker.start() + } + go func() { + if err := p.queryLoop(); err != nil { + // TODO: send the err accross a channel instead of panicking + panic(err) + } + }() + return nil +} + +// Close closes the worker pool and prevents it from delegating +// any new jobs. However, any jobs that are currently being executed +// will still be executed. Close returns immediately. If you want to +// wait until all workers are done executing their current jobs, use the +// Wait method. +func (p *Pool) Close() { + close(p.exit) +} + +// Wait will return when all workers are done executing their jobs. +// Wait can only possibly return after you have called Close. To prevent +// errors due to partially-executed jobs, any go program which starts a +// worker pool should call Wait (and Close before that if needed) before +// exiting. +func (p *Pool) Wait() error { + // The shared waitgroup will only return after each worker is finished + p.wg.Wait() + // Remove the pool id from the set of active pools, only after we know + // each worker finished executing. + if err := p.removeFromPoolSet(); err != nil { + return err + } + return nil +} + +// queryLoop continuously queries the database for new jobs and, if +// it finds any, sends them through the jobs channel for execution +// by some worker. +func (p *Pool) queryLoop() error { + if err := p.sendNextJobs(p.config.BatchSize); err != nil { + return err + } + for { + minWait := time.After(p.config.MinWait) + select { + case <-p.exit: + // Close the channel to tell workers to stop executing new jobs + close(p.jobs) + return nil + case <-minWait: + if err := p.sendNextJobs(p.config.BatchSize); err != nil { + return err + } + } + } + return nil +} + +// sendNextJobs queries the database to find the next n ready jobs, then +// sends those jobs to the jobs channel, effectively delegating them to +// a worker. +func (p *Pool) sendNextJobs(n int) error { + jobs, err := p.getNextJobs(p.config.BatchSize) + if err != nil { + return err + } + // Send the jobs across the channel, where they will be picked up + // by exactly one worker + for _, job := range jobs { + p.jobs <- job + } + return nil +} + +// getNextJobs queries the database and returns the next n ready jobs. +func (p *Pool) getNextJobs(n int) ([]*Job, error) { + p.RLock() + thisId := p.id + p.RUnlock() + return getNextJobs(n, thisId) +} + +// getNextJobs queries the database and returns the next n ready jobs. +func getNextJobs(n int, poolId string) ([]*Job, error) { + // Start a new transaction + t := newTransaction() + // Invoke a script to get all the jobs which are ready to execute based on their + // time parameter and whether or not they are in the queued set. + jobs := []*Job{} + t.popNextJobs(n, poolId, newScanJobsHandler(&jobs)) + + // Execute the transaction + if err := t.exec(); err != nil { + return nil, err + } + return jobs, nil +} diff --git a/vendor/github.com/albrow/jobs/redis_keys.go b/vendor/github.com/albrow/jobs/redis_keys.go new file mode 100644 index 00000000..a197ca50 --- /dev/null +++ b/vendor/github.com/albrow/jobs/redis_keys.go @@ -0,0 +1,23 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +// keys stores any constant redis keys. By storing them all here, +// we avoid using string literals which are prone to typos. +var Keys = struct { + // jobsTimeIndex is the key for a sorted set which keeps all outstanding + // jobs sorted by their time field. + JobsTimeIndex string + // jobsTemp is the key for a temporary set which is created and then destroyed + // during the process of getting the next jobs in the queue. + JobsTemp string + // activePools is the key for a set which holds the pool ids for all active + // pools. + ActivePools string +}{ + JobsTimeIndex: "jobs:time", + JobsTemp: "jobs:temp", + ActivePools: "pools:active", +} diff --git a/vendor/github.com/albrow/jobs/redis_pool.go b/vendor/github.com/albrow/jobs/redis_pool.go new file mode 100644 index 00000000..405191e1 --- /dev/null +++ b/vendor/github.com/albrow/jobs/redis_pool.go @@ -0,0 +1,37 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import ( + "github.com/garyburd/redigo/redis" + "time" +) + +// redisPool is a thread-safe pool of redis connections. Use the Get method +// to get a new connection. +var redisPool = &redis.Pool{ + MaxIdle: 10, + MaxActive: 0, + IdleTimeout: 240 * time.Second, + Dial: func() (redis.Conn, error) { + c, err := redis.Dial(Config.Db.Network, Config.Db.Address) + if err != nil { + return nil, err + } + // If a password was provided, use the AUTH command to authenticate + if Config.Db.Password != "" { + _, err = c.Do("AUTH", Config.Db.Password) + if err != nil { + return nil, err + } + } + // Connect to the appropriate database with SELECT + if _, err := c.Do("SELECT", Config.Db.Database); err != nil { + c.Close() + return nil, err + } + return c, nil + }, +} diff --git a/vendor/github.com/albrow/jobs/scripts.go b/vendor/github.com/albrow/jobs/scripts.go new file mode 100644 index 00000000..eb9af846 --- /dev/null +++ b/vendor/github.com/albrow/jobs/scripts.go @@ -0,0 +1,375 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +// File scripts.go contains code related to parsing +// lua scripts in the scripts file. + +// This file has been automatically generated by go generate, +// which calls scripts/generate.go. Do not edit it directly! + +package jobs + +import ( + "github.com/garyburd/redigo/redis" +) + +var ( + + addJobToSetScript = redis.NewScript(0, `-- Copyright 2015 Alex Browne. All rights reserved. +-- Use of this source code is governed by the MIT +-- license, which can be found in the LICENSE file. + +-- add_job_to_set represents a lua script that takes the following arguments: +-- 1) The id of the job +-- 2) The name of a sorted set +-- 3) The score the inserted job should have in the sorted set +-- It first checks if the job exists in the database (has not been destroyed) +-- and then adds it to the sorted set with the given score. + +-- IMPORTANT: If you edit this file, you must run go generate . to rewrite ../scripts.go + +local jobId = ARGV[1] +local setName = ARGV[2] +local score = ARGV[3] +local jobKey = 'jobs:' .. jobId +-- Make sure the job hasn't already been destroyed +local exists = redis.call('EXISTS', jobKey) +if exists ~= 1 then + return +end +redis.call('ZADD', setName, score, jobId)`) + destroyJobScript = redis.NewScript(0, `-- Copyright 2015 Alex Browne. All rights reserved. +-- Use of this source code is governed by the MIT +-- license, which can be found in the LICENSE file. + +-- destroy_job is a lua script that takes the following arguments: +-- 1) The id of the job to destroy +-- It then removes all traces of the job in the database by doing the following: +-- 1) Removes the job from the status set (which it determines with an HGET call) +-- 2) Removes the job from the time index +-- 3) Removes the main hash for the job + +-- IMPORTANT: If you edit this file, you must run go generate . to rewrite ../scripts.go + +-- Assign args to variables for easy reference +local jobId = ARGV[1] +local jobKey = 'jobs:' .. jobId +-- Remove the job from the status set +local status = redis.call('HGET', jobKey, 'status') +if status ~= '' then + local statusSet = 'jobs:' .. status + redis.call('ZREM', statusSet, jobId) +end +-- Remove the job from the time index +redis.call('ZREM', 'jobs:time', jobId) +-- Remove the main hash for the job +redis.call('DEL', jobKey)`) + getJobsByIdsScript = redis.NewScript(0, `-- Copyright 2015 Alex Browne. All rights reserved. +-- Use of this source code is governed by the MIT +-- license, which can be found in the LICENSE file. + +-- get_jobs_by_ids is a lua script that takes the following arguments: +-- 1) The key of a sorted set of some job ids +-- The script then gets all the data for those job ids from their respective +-- hashes in the database. It returns an array of arrays where each element +-- contains the fields for a particular job, and the jobs are sorted by +-- priority. +-- Here's an example response: +-- [ +-- [ +-- "id", "afj9afjpa30", +-- "data", [34, 67, 34, 23, 56, 67, 78, 79], +-- "type", "emailJob", +-- "time", 1234567, +-- "freq", 0, +-- "priority", 100, +-- "retries", 0, +-- "status", "executing", +-- "started", 0, +-- "finished", 0, +-- ], +-- [ +-- "id", "E8v2ovkdaIw", +-- "data", [46, 43, 12, 08, 34, 45, 57, 43], +-- "type", "emailJob", +-- "time", 1234568, +-- "freq", 0, +-- "priority", 95, +-- "retries", 0, +-- "status", "executing", +-- "started", 0, +-- "finished", 0, +-- ] +-- ] + +-- IMPORTANT: If you edit this file, you must run go generate . to rewrite ../scripts.go + +-- Assign keys to variables for easy access +local setKey = ARGV[1] +-- Get all the ids from the set name +local jobIds = redis.call('ZREVRANGE', setKey, 0, -1) +local allJobs = {} +if #jobIds > 0 then + -- Iterate over the ids and find each job + for i, jobId in ipairs(jobIds) do + local jobKey = 'jobs:' .. jobId + local jobFields = redis.call('HGETALL', jobKey) + -- Add the id itself to the fields + jobFields[#jobFields+1] = 'id' + jobFields[#jobFields+1] = jobId + -- Add the field values to allJobs + allJobs[#allJobs+1] = jobFields + end +end +return allJobs`) + popNextJobsScript = redis.NewScript(0, `-- Copyright 2015 Alex Browne. All rights reserved. +-- Use of this source code is governed by the MIT +-- license, which can be found in the LICENSE file. + +-- pop_next_jobs is a lua script that takes the following arguments: +-- 1) The maximum number of jobs to pop and return +-- 2) The current unix time UTC with nanosecond precision +-- The script gets the next available jobs from the queued set which are +-- ready based on their time parameter. Then it adds those jobs to the +-- executing set, sets their status to executing, and removes them from the +-- queued set. It returns an array of arrays where each element contains the +-- fields for a particular job, and the jobs are sorted by priority. +-- Here's an example response: +-- [ +-- [ +-- "id", "afj9afjpa30", +-- "data", [34, 67, 34, 23, 56, 67, 78, 79], +-- "type", "emailJob", +-- "time", 1234567, +-- "freq", 0, +-- "priority", 100, +-- "retries", 0, +-- "status", "executing", +-- "started", 0, +-- "finished", 0, +-- ], +-- [ +-- "id", "E8v2ovkdaIw", +-- "data", [46, 43, 12, 08, 34, 45, 57, 43], +-- "type", "emailJob", +-- "time", 1234568, +-- "freq", 0, +-- "priority", 95, +-- "retries", 0, +-- "status", "executing", +-- "started", 0, +-- "finished", 0, +-- ] +-- ] + +-- IMPORTANT: If you edit this file, you must run go generate . to rewrite ../scripts.go + +-- Assign args to variables for easy reference +local n = ARGV[1] +local currentTime = ARGV[2] +local poolId = ARGV[3] +-- Copy the time index set to a new temporary set +redis.call('ZUNIONSTORE', 'jobs:temp', 1, 'jobs:time') +-- Trim the new temporary set we just created to leave only the jobs which have a time +-- parameter in the past +redis.call('ZREMRANGEBYSCORE', 'jobs:temp', currentTime, '+inf') +-- Intersect the jobs which are ready based on their time with those in the +-- queued set. Use the weights parameter to set the scores entirely based on the +-- queued set, effectively sorting the jobs by priority. Store the results in the +-- temporary set. +redis.call('ZINTERSTORE', 'jobs:temp', 2, 'jobs:queued', 'jobs:temp', 'WEIGHTS', 1, 0) +-- Trim the temp set, so it contains only the first n jobs ordered by +-- priority +redis.call('ZREMRANGEBYRANK', 'jobs:temp', 0, -n - 1) +-- Get all job ids from the temp set +local jobIds = redis.call('ZREVRANGE', 'jobs:temp', 0, -1) +local allJobs = {} +if #jobIds > 0 then + -- Add job ids to the executing set + redis.call('ZUNIONSTORE', 'jobs:executing', 2, 'jobs:executing', 'jobs:temp') + -- Now we are ready to construct our response. + for i, jobId in ipairs(jobIds) do + local jobKey = 'jobs:' .. jobId + -- Remove the job from the queued set + redis.call('ZREM', 'jobs:queued', jobId) + -- Set the poolId field for the job + redis.call('HSET', jobKey, 'poolId', poolId) + -- Set the job status to executing + redis.call('HSET', jobKey, 'status', 'executing') + -- Get the fields from its main hash + local jobFields = redis.call('HGETALL', jobKey) + -- Add the id itself to the fields + jobFields[#jobFields+1] = 'id' + jobFields[#jobFields+1] = jobId + -- Add the field values to allJobs + allJobs[#allJobs+1] = jobFields + end +end +-- Delete the temporary set +redis.call('DEL', 'jobs:temp') +-- Return all the fields for all the jobs +return allJobs`) + purgeStalePoolScript = redis.NewScript(0, `-- Copyright 2015 Alex Browne. All rights reserved. +-- Use of this source code is governed by the MIT +-- license, which can be found in the LICENSE file. + +-- purge_stale_pool is a lua script which takes the following arguments: +-- 1) The id of the stale pool to purge +-- It then does the following: +-- 1) Removes the pool id from the set of active pools +-- 2) Iterates through each job in the executing set and finds any jobs which +-- have a poolId field equal to the id of the stale pool +-- 3) If it finds any such jobs, it removes them from the executing set and +-- adds them to the queued so that they will be retried + +-- IMPORTANT: If you edit this file, you must run go generate . to rewrite ../scripts.go + +-- Assign args to variables for easy reference +local stalePoolId = ARGV[1] +-- Check if the stale pool is in the set of active pools first +local isActive = redis.call('SISMEMBER', 'pools:active', stalePoolId) +if isActive then + -- Remove the stale pool from the set of active pools + redis.call('SREM', 'pools:active', stalePoolId) + -- Get all the jobs in the executing set + local jobIds = redis.call('ZRANGE', 'jobs:executing', 0, -1) + for i, jobId in ipairs(jobIds) do + local jobKey = 'jobs:' .. jobId + -- Check the poolId field + -- If the poolId is equal to the stale id, then this job is stuck + -- in the executing set even though no worker is actually executing it + local poolId = redis.call('HGET', jobKey, 'poolId') + if poolId == stalePoolId then + local jobPriority = redis.call('HGET', jobKey, 'priority') + -- Move the job into the queued set + redis.call('ZADD', 'jobs:queued', jobPriority, jobId) + -- Remove the job from the executing set + redis.call('ZREM', 'jobs:executing', jobId) + -- Set the job status to queued and the pool id to blank + redis.call('HMSET', jobKey, 'status', 'queued', 'poolId', '') + end + end +end +`) + retryOrFailJobScript = redis.NewScript(0, `-- Copyright 2015 Alex Browne. All rights reserved. +-- Use of this source code is governed by the MIT +-- license, which can be found in the LICENSE file. + +-- retry_or_fail_job represents a lua script that takes the following arguments: +-- 1) The id of the job to either retry or fail +-- It first checks if the job has any retries remaining. If it does, +-- then it: +-- 1) Decrements the number of retries for the given job +-- 2) Adds the job to the queued set +-- 3) Removes the job from the executing set +-- 4) Returns true +-- If the job has no retries remaining then it: +-- 1) Adds the job to the failed set +-- 3) Removes the job from the executing set +-- 2) Returns false + +-- IMPORTANT: If you edit this file, you must run go generate . to rewrite ../scripts.go + +-- Assign args to variables for easy reference +local jobId = ARGV[1] +local jobKey = 'jobs:' .. jobId +-- Make sure the job hasn't already been destroyed +local exists = redis.call('EXISTS', jobKey) +if exists ~= 1 then + return 0 +end +-- Check how many retries remain +local retries = redis.call('HGET', jobKey, 'retries') +local newStatus = '' +if retries == '0' then + -- newStatus should be failed because there are no retries left + newStatus = 'failed' +else + -- subtract 1 from the remaining retries + redis.call('HINCRBY', jobKey, 'retries', -1) + -- newStatus should be queued, so the job will be retried + newStatus = 'queued' +end +-- Get the job priority (used as score) +local jobPriority = redis.call('HGET', jobKey, 'priority') +-- Add the job to the appropriate new set +local newStatusSet = 'jobs:' .. newStatus +redis.call('ZADD', newStatusSet, jobPriority, jobId) +-- Remove the job from the old status set +local oldStatus = redis.call('HGET', jobKey, 'status') +if ((oldStatus ~= '') and (oldStatus ~= newStatus)) then + local oldStatusSet = 'jobs:' .. oldStatus + redis.call('ZREM', oldStatusSet, jobId) +end +-- Set the job status in the hash +redis.call('HSET', jobKey, 'status', newStatus) +if retries == '0' then + -- Return false to indicate the job has not been queued for retry + -- NOTE: 0 is used to represent false because apparently + -- false gets converted to nil + return 0 +else + -- Return true to indicate the job has been queued for retry + -- NOTE: 1 is used to represent true (for consistency) + return 1 +end`) + setJobFieldScript = redis.NewScript(0, `-- Copyright 2015 Alex Browne. All rights reserved. +-- Use of this source code is governed by the MIT +-- license, which can be found in the LICENSE file. + +-- set_job_field represents a lua script that takes the following arguments: +-- 1) The id of the job +-- 2) The name of the field +-- 3) The value to set the field to +-- It first checks if the job exists in the database (has not been destroyed) +-- and then sets the given field to the given value. + +-- IMPORTANT: If you edit this file, you must run go generate . to rewrite ../scripts.go + +local jobId = ARGV[1] +local fieldName = ARGV[2] +local fieldVal = ARGV[3] +local jobKey = 'jobs:' .. jobId +-- Make sure the job hasn't already been destroyed +local exists = redis.call('EXISTS', jobKey) +if exists ~= 1 then + return +end +redis.call('HSET', jobKey, fieldName, fieldVal)`) + setJobStatusScript = redis.NewScript(0, `-- Copyright 2015 Alex Browne. All rights reserved. +-- Use of this source code is governed by the MIT +-- license, which can be found in the LICENSE file. + +-- set_job_status is a lua script that takes the following arguments: +-- 1) The id of the job +-- 2) The new status (e.g. "queued") +-- It then does the following: +-- 1) Adds the job to the new status set +-- 2) Removes the job from the old status set (which it gets with an HGET call) +-- 3) Sets the 'status' field in the main hash for the job + +-- IMPORTANT: If you edit this file, you must run go generate . to rewrite ../scripts.go + +-- Assign args to variables for easy reference +local jobId = ARGV[1] +local newStatus = ARGV[2] +local jobKey = 'jobs:' .. jobId +-- Make sure the job hasn't already been destroyed +local exists = redis.call('EXISTS', jobKey) +if exists ~= 1 then + return +end +local newStatusSet = 'jobs:' .. newStatus +-- Add the job to the new status set +local jobPriority = redis.call('HGET', jobKey, 'priority') +redis.call('ZADD', newStatusSet, jobPriority, jobId) +-- Remove the job from the old status set +local oldStatus = redis.call('HGET', jobKey, 'status') +if ((oldStatus ~= '') and (oldStatus ~= newStatus)) then + local oldStatusSet = 'jobs:' .. oldStatus + redis.call('ZREM', oldStatusSet, jobId) +end +-- Set the status field +redis.call('HSET', jobKey, 'status', newStatus)`) +) \ No newline at end of file diff --git a/vendor/github.com/albrow/jobs/test_utils.go b/vendor/github.com/albrow/jobs/test_utils.go new file mode 100644 index 00000000..1e873446 --- /dev/null +++ b/vendor/github.com/albrow/jobs/test_utils.go @@ -0,0 +1,335 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import ( + "fmt" + "reflect" + "sync" + "testing" + "time" + + "github.com/garyburd/redigo/redis" +) + +// setUpOnce enforces that certain pieces of the set up process only occur once, +// even after successive calls to testingSetUp. +var setUpOnce = sync.Once{} + +// testingSetUp should be called at the beginning of any test that touches the database +// or registers new job types. +func testingSetUp() { + setUpOnce.Do(func() { + // Use database 14 and a unix socket connection for testing + // TODO: allow this to be configured via command-line flags + Config.Db.Database = 14 + }) + // Clear out any old job types + Types = map[string]*Type{} +} + +// testingSetUp should be called at the end of any test that touches the database +// or registers new job types, usually via defer. +func testingTeardown() { + // Flush the database + flushdb() +} + +// flushdb removes all keys from the current database. +func flushdb() { + conn := redisPool.Get() + if _, err := conn.Do("FLUSHDB"); err != nil { + panic(err) + } +} + +// noOpHandler is a HandlerFunc that simply does nothing +var noOpHandler = func() error { return nil } + +// createTestJob creates and returns a job that can be used for testing. +func createTestJob() (*Job, error) { + // Register the "testType" + TypeName := "testType" + Type, err := RegisterType(TypeName, 0, noOpHandler) + if err != nil { + if _, ok := err.(ErrorNameAlreadyRegistered); !ok { + // If the name was already registered, that's fine. + // We should return any other type of error + return nil, err + } + } + // Create and return a test job + j := &Job{ + id: "testJob", + data: []byte("testData"), + typ: Type, + time: time.Now().UTC().UnixNano(), + priority: 100, + } + return j, nil +} + +// createTestJobs creates and returns n jobs that can be used for testing. +// Each job has a unique id and priority, and the jobs are returned in order +// of decreasing priority. +func createTestJobs(n int) ([]*Job, error) { + // Register the "testType" + TypeName := "testType" + Type, err := RegisterType(TypeName, 0, noOpHandler) + if err != nil { + if _, ok := err.(ErrorNameAlreadyRegistered); !ok { + // If the name was already registered, that's fine. + // We should return any other type of error + return nil, err + } + } + jobs := make([]*Job, n) + for i := 0; i < n; i++ { + jobs[i] = &Job{ + id: fmt.Sprintf("testJob%d", i), + data: []byte("testData"), + typ: Type, + time: time.Now().UTC().UnixNano(), + priority: (n - i) + 1, + } + } + return jobs, nil +} + +// createAndSaveTestJob creates, saves, and returns a job which can be used +// for testing. Each job has a unique id and priority, and the jobs are +// returned in order of decreasing priority. +func createAndSaveTestJob() (*Job, error) { + j, err := createTestJob() + if err != nil { + return nil, err + } + if err := j.save(); err != nil { + return nil, fmt.Errorf("Unexpected error in j.save(): %s", err.Error()) + } + return j, nil +} + +// createAndSaveTestJobs creates, saves, and returns n jobs which can be used +// for testing. +func createAndSaveTestJobs(n int) ([]*Job, error) { + jobs, err := createTestJobs(n) + if err != nil { + return nil, err + } + // Save all the jobs in a single transaction + t := newTransaction() + for _, job := range jobs { + t.saveJob(job) + } + if err := t.exec(); err != nil { + return nil, err + } + return jobs, nil +} + +// expectJobFieldEquals sets an error via t.Errorf if the the field identified by fieldName does +// not equal expected according to the database. +func expectJobFieldEquals(t *testing.T, job *Job, fieldName string, expected interface{}, converter replyConverter) { + conn := redisPool.Get() + defer conn.Close() + got, err := conn.Do("HGET", job.Key(), fieldName) + if err != nil { + t.Errorf("Unexpected error: %s", err.Error()) + } + if converter != nil { + got, err = converter(got) + if err != nil { + t.Errorf("Unexpected error in converter: %s", err.Error()) + } + } + if !reflect.DeepEqual(expected, got) { + t.Errorf("job.%s was not saved correctly.\n\tExpected: %v\n\tBut got: %v", fieldName, expected, got) + } +} + +// replyConverter represents a function which is capable of converting a redis +// reply to some other type. +type replyConverter func(interface{}) (interface{}, error) + +var ( + int64Converter replyConverter = func(in interface{}) (interface{}, error) { + return redis.Int64(in, nil) + } + intConverter replyConverter = func(in interface{}) (interface{}, error) { + return redis.Int(in, nil) + } + uintConverter replyConverter = func(in interface{}) (interface{}, error) { + v, err := redis.Uint64(in, nil) + if err != nil { + return nil, err + } + return uint(v), nil + } + stringConverter replyConverter = func(in interface{}) (interface{}, error) { + return redis.String(in, nil) + } + bytesConverter replyConverter = func(in interface{}) (interface{}, error) { + return redis.Bytes(in, nil) + } +) + +// expectSetContains sets an error via t.Errorf if member is not in the set +func expectSetContains(t *testing.T, setName string, member string) { + conn := redisPool.Get() + defer conn.Close() + contains, err := redis.Bool(conn.Do("SISMEMBER", setName, member)) + if err != nil { + t.Errorf("Unexpected error: %s", err.Error()) + } + if !contains { + t.Errorf("Expected set %s to contain %s but it did not.", setName, member) + } +} + +// expectSetDoesNotContain sets an error via t.Errorf if member is in the set +func expectSetDoesNotContain(t *testing.T, setName string, member string) { + conn := redisPool.Get() + defer conn.Close() + contains, err := redis.Bool(conn.Do("SISMEMBER", setName, member)) + if err != nil { + t.Errorf("Unexpected error: %s", err.Error()) + } + if contains { + t.Errorf("Expected set %s to not contain %s but it did.", setName, member) + } +} + +// expectJobInStatusSet sets an error via t.Errorf if job is not in the status set +// corresponding to status. +func expectJobInStatusSet(t *testing.T, j *Job, status Status) { + conn := redisPool.Get() + defer conn.Close() + gotIds, err := redis.Strings(conn.Do("ZRANGEBYSCORE", status.Key(), j.priority, j.priority)) + if err != nil { + t.Errorf("Unexpected error: %s", err.Error()) + } + for _, id := range gotIds { + if id == j.id { + // We found the job we were looking for + return + } + } + // If we reached here, we did not find the job we were looking for + t.Errorf("job:%s was not found in set %s", j.id, status.Key()) +} + +// expectJobInTimeIndex sets an error via t.Errorf if job is not in the time index +// set. +func expectJobInTimeIndex(t *testing.T, j *Job) { + conn := redisPool.Get() + defer conn.Close() + gotIds, err := redis.Strings(conn.Do("ZRANGEBYSCORE", Keys.JobsTimeIndex, j.time, j.time)) + if err != nil { + t.Errorf("Unexpected error: %s", err.Error()) + } + for _, id := range gotIds { + if id == j.id { + // We found the job we were looking for + return + } + } + // If we reached here, we did not find the job we were looking for + t.Errorf("job:%s was not found in set %s", j.id, Keys.JobsTimeIndex) +} + +// expectJobNotInStatusSet sets an error via t.Errorf if job is in the status set +// corresponding to status. +func expectJobNotInStatusSet(t *testing.T, j *Job, status Status) { + conn := redisPool.Get() + defer conn.Close() + gotIds, err := redis.Strings(conn.Do("ZRANGEBYSCORE", status.Key(), j.priority, j.priority)) + if err != nil { + t.Errorf("Unexpected error: %s", err.Error()) + } + for _, id := range gotIds { + if id == j.id { + // We found the job, but it wasn't supposed to be here! + t.Errorf("job:%s was found in set %s but expected it to be removed", j.id, status.Key()) + } + } +} + +// expectJobNotInTimeIndex sets an error via t.Errorf if job is in the time index +// set. +func expectJobNotInTimeIndex(t *testing.T, j *Job) { + conn := redisPool.Get() + defer conn.Close() + gotIds, err := redis.Strings(conn.Do("ZRANGEBYSCORE", Keys.JobsTimeIndex, j.time, j.time)) + if err != nil { + t.Errorf("Unexpected error: %s", err.Error()) + } + for _, id := range gotIds { + if id == j.id { + // We found the job, but it wasn't supposed to be here! + t.Errorf("job:%s was found in set %s but expected it to be removed", j.id, Keys.JobsTimeIndex) + } + } +} + +// expectStatusEquals sets an error via t.Errorf if job.status does not equal expected, +// if the status field for the job in the database does not equal expected, if the job is +// not in the status set corresponding to expected, or if the job is in some other status +// set. +func expectStatusEquals(t *testing.T, job *Job, expected Status) { + if job.status != expected { + t.Errorf("Expected jobs:%s status to be %s but got %s", job.id, expected, job.status) + } + if expected == StatusDestroyed { + // If the status is destroyed, we don't expect the job to be in the database + // anymore. + expectJobDestroyed(t, job) + } else { + // For every status other status, we expect the job to be in the database + for _, status := range possibleStatuses { + if status == expected { + // Make sure the job hash has the correct status + expectJobInStatusSet(t, job, status) + // Make sure the job is in the correct set + expectJobFieldEquals(t, job, "status", string(status), stringConverter) + } else { + // Make sure the job is not in any other set + expectJobNotInStatusSet(t, job, status) + } + } + } +} + +// expectJobDestroyed sets an error via t.Errorf if job has not been destroyed. +func expectJobDestroyed(t *testing.T, job *Job) { + // Make sure the main hash is gone + expectKeyNotExists(t, job.Key()) + expectJobNotInTimeIndex(t, job) + for _, status := range possibleStatuses { + expectJobNotInStatusSet(t, job, status) + } +} + +// expectKeyExists sets an error via t.Errorf if key does not exist in the database. +func expectKeyExists(t *testing.T, key string) { + conn := redisPool.Get() + defer conn.Close() + if exists, err := redis.Bool(conn.Do("EXISTS", key)); err != nil { + t.Errorf("Unexpected error in EXISTS: %s", err.Error()) + } else if !exists { + t.Errorf("Expected key %s to exist, but it did not.", key) + } +} + +// expectKeyNotExists sets an error via t.Errorf if key does exist in the database. +func expectKeyNotExists(t *testing.T, key string) { + conn := redisPool.Get() + defer conn.Close() + if exists, err := redis.Bool(conn.Do("EXISTS", key)); err != nil { + t.Errorf("Unexpected error in EXISTS: %s", err.Error()) + } else if exists { + t.Errorf("Expected key %s to not exist, but it did exist.", key) + } +} diff --git a/vendor/github.com/albrow/jobs/transaction.go b/vendor/github.com/albrow/jobs/transaction.go new file mode 100644 index 00000000..1fd8a1d1 --- /dev/null +++ b/vendor/github.com/albrow/jobs/transaction.go @@ -0,0 +1,310 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import ( + "fmt" + "github.com/garyburd/redigo/redis" + "time" +) + +// transaction is an abstraction layer around a redis transaction. +// transactions feature delayed execution, so nothing touches the database +// until exec is called. +type transaction struct { + conn redis.Conn + actions []*action +} + +// action is a single step in a transaction and must be either a command +// or a script with optional arguments. +type action struct { + kind actionKind + name string + script *redis.Script + args redis.Args + handler replyHandler +} + +// actionKind is either a command or a script +type actionKind int + +const ( + actionCommand = iota + actionScript +) + +// replyHandler is a function which does something with the reply from a redis +// command or script. +type replyHandler func(interface{}) error + +// newTransaction instantiates and returns a new transaction. +func newTransaction() *transaction { + t := &transaction{ + conn: redisPool.Get(), + } + return t +} + +// command adds a command action to the transaction with the given args. +// handler will be called with the reply from this specific command when +// the transaction is executed. +func (t *transaction) command(name string, args redis.Args, handler replyHandler) { + t.actions = append(t.actions, &action{ + kind: actionCommand, + name: name, + args: args, + handler: handler, + }) +} + +// command adds a script action to the transaction with the given args. +// handler will be called with the reply from this specific script when +// the transaction is executed. +func (t *transaction) script(script *redis.Script, args redis.Args, handler replyHandler) { + t.actions = append(t.actions, &action{ + kind: actionScript, + script: script, + args: args, + handler: handler, + }) +} + +// sendAction writes a to a connection buffer using conn.Send() +func (t *transaction) sendAction(a *action) error { + switch a.kind { + case actionCommand: + return t.conn.Send(a.name, a.args...) + case actionScript: + return a.script.Send(t.conn, a.args...) + } + return nil +} + +// doAction writes a to the connection buffer and then immediately +// flushes the buffer and reads the reply via conn.Do() +func (t *transaction) doAction(a *action) (interface{}, error) { + switch a.kind { + case actionCommand: + return t.conn.Do(a.name, a.args...) + case actionScript: + return a.script.Do(t.conn, a.args...) + } + return nil, nil +} + +// exec executes the transaction, sequentially sending each action and +// calling all the action handlers with the corresponding replies. +func (t *transaction) exec() error { + // Return the connection to the pool when we are done + defer t.conn.Close() + + if len(t.actions) == 1 { + // If there is only one command, no need to use MULTI/EXEC + a := t.actions[0] + reply, err := t.doAction(a) + if err != nil { + return err + } + if a.handler != nil { + if err := a.handler(reply); err != nil { + return err + } + } + } else { + // Send all the commands and scripts at once using MULTI/EXEC + t.conn.Send("MULTI") + for _, a := range t.actions { + if err := t.sendAction(a); err != nil { + return err + } + } + + // Invoke redis driver to execute the transaction + replies, err := redis.Values(t.conn.Do("EXEC")) + if err != nil { + return err + } + + // Iterate through the replies, calling the corresponding handler functions + for i, reply := range replies { + a := t.actions[i] + if a.handler != nil { + if err := a.handler(reply); err != nil { + return err + } + } + } + } + return nil +} + +// newScanJobHandler returns a replyHandler which, when run, will scan the values +// of reply into job. +func newScanJobHandler(job *Job) replyHandler { + return func(reply interface{}) error { + return scanJob(reply, job) + } +} + +// newScanJobsHandler returns a replyHandler which, when run, will scan the values +// of reply into jobs. +func newScanJobsHandler(jobs *[]*Job) replyHandler { + return func(reply interface{}) error { + values, err := redis.Values(reply, nil) + if err != nil { + return nil + } + for _, fields := range values { + job := &Job{} + if err := scanJob(fields, job); err != nil { + return err + } + (*jobs) = append((*jobs), job) + } + return nil + } +} + +// debugSet simply prints out the value of the given set +func (t *transaction) debugSet(setName string) { + t.command("ZRANGE", redis.Args{setName, 0, -1, "WITHSCORES"}, func(reply interface{}) error { + vals, err := redis.Strings(reply, nil) + if err != nil { + return err + } + fmt.Printf("%s: %v\n", setName, vals) + return nil + }) +} + +// newScanStringsHandler returns a replyHandler which, when run, will scan the values +// of reply into strings. +func newScanStringsHandler(strings *[]string) replyHandler { + return func(reply interface{}) error { + if strings == nil { + return fmt.Errorf("jobs: Error in newScanStringsHandler: expected strings arg to be a pointer to a slice of strings but it was nil") + } + var err error + (*strings), err = redis.Strings(reply, nil) + if err != nil { + return fmt.Errorf("jobs: Error in newScanStringsHandler: %s", err.Error()) + } + return nil + } +} + +// newScanStringHandler returns a replyHandler which, when run, will convert reply to a +// string and scan it into s. +func newScanStringHandler(s *string) replyHandler { + return func(reply interface{}) error { + if s == nil { + return fmt.Errorf("jobs: Error in newScanStringHandler: expected arg s to be a pointer to a string but it was nil") + } + var err error + (*s), err = redis.String(reply, nil) + if err != nil { + return fmt.Errorf("jobs: Error in newScanStringHandler: %s", err.Error()) + } + return nil + } +} + +// newScanIntHandler returns a replyHandler which, when run, will convert reply to a +// int and scan it into i. +func newScanIntHandler(i *int) replyHandler { + return func(reply interface{}) error { + if i == nil { + return fmt.Errorf("jobs: Error in newScanIntHandler: expected arg s to be a pointer to a string but it was nil") + } + var err error + (*i), err = redis.Int(reply, nil) + if err != nil { + return fmt.Errorf("jobs: Error in newScanIntHandler: %s", err.Error()) + } + return nil + } +} + +// newScanBoolHandler returns a replyHandler which, when run, will convert reply to a +// bool and scan it into b. +func newScanBoolHandler(b *bool) replyHandler { + return func(reply interface{}) error { + if b == nil { + return fmt.Errorf("jobs: Error in newScanBoolHandler: expected arg v to be a pointer to a bool but it was nil") + } + var err error + (*b), err = redis.Bool(reply, nil) + if err != nil { + return fmt.Errorf("jobs: Error in newScanBoolHandler: %s", err.Error()) + } + return nil + } +} + +//go:generate go run scripts/generate.go + +// popNextJobs is a small function wrapper around getAndMovesJobToExecutingScript. +// It offers some type safety and helps make sure the arguments you pass through to the are correct. +// The script will get the next n jobs from the queue that are ready based on their time parameter. +func (t *transaction) popNextJobs(n int, poolId string, handler replyHandler) { + currentTime := time.Now().UTC().UnixNano() + t.script(popNextJobsScript, redis.Args{n, currentTime, poolId}, handler) +} + +// retryOrFailJob is a small function wrapper around retryOrFailJobScript. +// It offers some type safety and helps make sure the arguments you pass through to the are correct. +// The script will either mark the job as failed or queue it for retry depending on the number of +// retries left. +func (t *transaction) retryOrFailJob(job *Job, handler replyHandler) { + t.script(retryOrFailJobScript, redis.Args{job.id}, handler) +} + +// setStatus is a small function wrapper around setStatusScript. +// It offers some type safety and helps make sure the arguments you pass through to the are correct. +// The script will atomically update the status of the job, removing it from its old status set and +// adding it to the new one. +func (t *transaction) setStatus(job *Job, status Status) { + t.script(setJobStatusScript, redis.Args{job.id, string(status)}, nil) +} + +// destroyJob is a small function wrapper around destroyJobScript. +// It offers some type safety and helps make sure the arguments you pass through to the are correct. +// The script will remove all records associated with job from the database. +func (t *transaction) destroyJob(job *Job) { + t.script(destroyJobScript, redis.Args{job.id}, nil) +} + +// purgeStalePool is a small function wrapper around purgeStalePoolScript. +// It offers some type safety and helps make sure the arguments you pass through to the are correct. +// The script will remove the stale pool from the active pools set, and then requeue any jobs associated +// with the stale pool that are stuck in the executing set. +func (t *transaction) purgeStalePool(poolId string) { + t.script(purgeStalePoolScript, redis.Args{poolId}, nil) +} + +// getJobsByIds is a small function wrapper around getJobsByIdsScript. +// It offers some type safety and helps make sure the arguments you pass through to the are correct. +// The script will return all the fields for jobs which are identified by ids in the given sorted set. +// You can use the handler to scan the jobs into a slice of jobs. +func (t *transaction) getJobsByIds(setKey string, handler replyHandler) { + t.script(getJobsByIdsScript, redis.Args{setKey}, handler) +} + +// setJobField is a small function wrapper around setJobFieldScript. +// It offers some type safety and helps make sure the arguments you pass through are correct. +// The script will set the given field to the given value iff the job exists and has not been +// destroyed. +func (t *transaction) setJobField(job *Job, fieldName string, fieldValue interface{}) { + t.script(setJobFieldScript, redis.Args{job.id, fieldName, fieldValue}, nil) +} + +// addJobToSet is a small function wrapper around addJobToSetScript. +// It offers some type safety and helps make sure the arguments you pass through are correct. +// The script will add the job to the given set with the given score iff the job exists +// and has not been destroyed. +func (t *transaction) addJobToSet(job *Job, setName string, score float64) { + t.script(addJobToSetScript, redis.Args{job.id, setName, score}, nil) +} diff --git a/vendor/github.com/albrow/jobs/utils.go b/vendor/github.com/albrow/jobs/utils.go new file mode 100644 index 00000000..cd5664eb --- /dev/null +++ b/vendor/github.com/albrow/jobs/utils.go @@ -0,0 +1,20 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import ( + "github.com/dchest/uniuri" + "strconv" + "time" +) + +// generateRandomId generates a random string that is more or less +// garunteed to be unique. +func generateRandomId() string { + timeInt := time.Now().UnixNano() + timeString := strconv.FormatInt(timeInt, 36) + randomString := uniuri.NewLen(16) + return randomString + timeString +} diff --git a/vendor/github.com/albrow/jobs/worker.go b/vendor/github.com/albrow/jobs/worker.go new file mode 100644 index 00000000..344d9bf9 --- /dev/null +++ b/vendor/github.com/albrow/jobs/worker.go @@ -0,0 +1,129 @@ +// Copyright 2015 Alex Browne. All rights reserved. +// Use of this source code is governed by the MIT +// license, which can be found in the LICENSE file. + +package jobs + +import ( + "fmt" + "reflect" + "sync" + "time" +) + +// worker continuously executes jobs within its own goroutine. +// The jobs chan is shared between all jobs. To stop the worker, +// simply close the jobs channel. +type worker struct { + jobs chan *Job + wg *sync.WaitGroup + afterFunc func(*Job) +} + +// start starts a goroutine in which the worker will continuously +// execute jobs until the jobs channel is closed. +func (w *worker) start() { + go func() { + for job := range w.jobs { + w.doJob(job) + } + w.wg.Done() + }() +} + +// doJob executes the given job. It also sets the status and timestamps for +// the job appropriately depending on the outcome of the execution. +func (w *worker) doJob(job *Job) { + if w.afterFunc != nil { + defer w.afterFunc(job) + } + + defer func() { + if r := recover(); r != nil { + // Get a reasonable error message from the panic + msg := "" + if err, ok := r.(error); ok { + msg = err.Error() + } else { + msg = fmt.Sprint(r) + } + if err := setJobError(job, msg); err != nil { + // Nothing left to do but panic + panic(err) + } + } + }() + // Set the started field and save the job + job.started = time.Now().UTC().UnixNano() + t0 := newTransaction() + t0.setJobField(job, "started", job.started) + if err := t0.exec(); err != nil { + if err := setJobError(job, err.Error()); err != nil { + // NOTE: panics will be caught by the recover statment above + panic(err) + } + return + } + // Use reflection to instantiate arguments for the handler + handlerArgs := []reflect.Value{} + if job.typ.dataType != nil { + // Instantiate a new variable to hold this argument + dataVal := reflect.New(job.typ.dataType) + if err := decode(job.data, dataVal.Interface()); err != nil { + if err := setJobError(job, err.Error()); err != nil { + // NOTE: panics will be caught by the recover statment above + panic(err) + } + return + } + handlerArgs = append(handlerArgs, dataVal.Elem()) + } + // Call the handler using the arguments we just instantiated + handlerVal := reflect.ValueOf(job.typ.handler) + returnVals := handlerVal.Call(handlerArgs) + // Set the finished timestamp + job.finished = time.Now().UTC().UnixNano() + + // Check if the error return value was nil + if !returnVals[0].IsNil() { + err := returnVals[0].Interface().(error) + if err := setJobError(job, err.Error()); err != nil { + // NOTE: panics will be caught by the recover statment above + panic(err) + } + return + } + t1 := newTransaction() + t1.setJobField(job, "finished", job.finished) + if job.IsRecurring() { + // If the job is recurring, reschedule and set status to queued + job.time = job.NextTime() + t1.setJobField(job, "time", job.time) + t1.addJobToTimeIndex(job) + t1.setStatus(job, StatusQueued) + } else { + // Otherwise, set status to finished + t1.setStatus(job, StatusFinished) + } + if err := t1.exec(); err != nil { + if err := setJobError(job, err.Error()); err != nil { + // NOTE: panics will be caught by the recover statment above + panic(err) + } + return + } +} + +func setJobError(job *Job, msg string) error { + // Start a new transaction + t := newTransaction() + // Set the job error field + t.setJobField(job, "error", msg) + // Either queue the job for retry or mark it as failed depending + // on how many retries the job has left + t.retryOrFailJob(job, nil) + if err := t.exec(); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/benmanns/goworker/.travis.yml b/vendor/github.com/benmanns/goworker/.travis.yml new file mode 100644 index 00000000..9603e19d --- /dev/null +++ b/vendor/github.com/benmanns/goworker/.travis.yml @@ -0,0 +1,15 @@ +language: go + +go: + - 1.1 + - 1.2 + - 1.5.2 + - tip + +services: + - redis-server + +matrix: + allowed_failures: + go: + - tip diff --git a/vendor/github.com/benmanns/goworker/LICENSE b/vendor/github.com/benmanns/goworker/LICENSE new file mode 100644 index 00000000..4508cd41 --- /dev/null +++ b/vendor/github.com/benmanns/goworker/LICENSE @@ -0,0 +1,28 @@ +The MIT License (MIT) + +Copyright (c) 2013 Benjamin Manns + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +The goworker Logo + +The goworker logo is a work by Rachel Falwell combining the Go mascot by Renée +French (CC-BY) and the Ruby logo by the Ruby Visual Identity Team (CC-BY-SA). +The logo is released under a Creative Commons Attribution-ShareAlike 4.0 +International License in keeping with the restrictions of the works from which +it is derived. diff --git a/vendor/github.com/benmanns/goworker/README.md b/vendor/github.com/benmanns/goworker/README.md new file mode 100644 index 00000000..2f273ef3 --- /dev/null +++ b/vendor/github.com/benmanns/goworker/README.md @@ -0,0 +1,228 @@ +# goworker + +[![Build Status](https://travis-ci.org/benmanns/goworker.png?branch=master)](https://travis-ci.org/benmanns/goworker) +[![GoDoc](https://godoc.org/github.com/benmanns/goworker?status.svg)](https://godoc.org/github.com/benmanns/goworker) + +goworker is a Resque-compatible, Go-based background worker. It allows you to push jobs into a queue using an expressive language like Ruby while harnessing the efficiency and concurrency of Go to minimize job latency and cost. + +goworker workers can run alongside Ruby Resque clients so that you can keep all but your most resource-intensive jobs in Ruby. + +## Installation + +To install goworker, use + +```sh +go get github.com/benmanns/goworker +``` + +to install the package, and then from your worker + +```go +import "github.com/benmanns/goworker" +``` + +## Getting Started + +To create a worker, write a function matching the signature + +```go +func(string, ...interface{}) error +``` + +and register it using + +```go +goworker.Register("MyClass", myFunc) +``` + +Here is a simple worker that prints its arguments: + +```go +package main + +import ( + "fmt" + "github.com/benmanns/goworker" +) + +func myFunc(queue string, args ...interface{}) error { + fmt.Printf("From %s, %v\n", queue, args) + return nil +} + +func init() { + goworker.Register("MyClass", myFunc) +} + +func main() { + if err := goworker.Work(); err != nil { + fmt.Println("Error:", err) + } +} +``` + +To create workers that share a database pool or other resources, use a closure to share variables. + +```go +package main + +import ( + "fmt" + "github.com/benmanns/goworker" +) + +func newMyFunc(uri string) (func(queue string, args ...interface{}) error) { + foo := NewFoo(uri) + return func(queue string, args ...interface{}) error { + foo.Bar(args) + return nil + } +} + +func init() { + goworker.Register("MyClass", newMyFunc("http://www.example.com/")) +} + +func main() { + if err := goworker.Work(); err != nil { + fmt.Println("Error:", err) + } +} +``` + +Here is a simple worker with settings: + +```go +package main + +import ( + "fmt" + "github.com/benmanns/goworker" +) + +func myFunc(queue string, args ...interface{}) error { + fmt.Printf("From %s, %v\n", queue, args) + return nil +} + +func init() { + settings := goworker.WorkerSettings{ + URI: "redis://localhost:6379/", + Connections: 100, + Queues: []string{"myqueue", "delimited", "queues"}, + UseNumber: true, + ExitOnComplete: false, + Concurrency: 2, + Namespace: "resque:", + Interval: 5.0, + } + goworker.SetSettings(settings) + goworker.Register("MyClass", myFunc) +} + +func main() { + if err := goworker.Work(); err != nil { + fmt.Println("Error:", err) + } +} +``` + +goworker worker functions receive the queue they are serving and a slice of interfaces. To use them as parameters to other functions, use Go type assertions to convert them into usable types. + +```go +// Expecting (int, string, float64) +func myFunc(queue, args ...interface{}) error { + idNum, ok := args[0].(json.Number) + if !ok { + return errorInvalidParam + } + id, err := idNum.Int64() + if err != nil { + return errorInvalidParam + } + name, ok := args[1].(string) + if !ok { + return errorInvalidParam + } + weightNum, ok := args[2].(json.Number) + if !ok { + return errorInvalidParam + } + weight, err := weightNum.Float64() + if err != nil { + return errorInvalidParam + } + doSomething(id, name, weight) + return nil +} +``` + +For testing, it is helpful to use the `redis-cli` program to insert jobs onto the Redis queue: + +```sh +redis-cli -r 100 RPUSH resque:queue:myqueue '{"class":"MyClass","args":["hi","there"]}' +``` + +will insert 100 jobs for the `MyClass` worker onto the `myqueue` queue. It is equivalent to: + +```ruby +class MyClass + @queue = :myqueue +end + +100.times do + Resque.enqueue MyClass, ['hi', 'there'] +end +``` + +or + +```golang +goworker.Enqueue(&goworker.Job{ + Queue: "myqueue", + Payload: goworker.Payload{ + Class: "MyClass", + Args: []interface{}{"hi", "there"}, + }, +}) +``` + +## Flags + +There are several flags which control the operation of the goworker client. + +* `-queues="comma,delimited,queues"` — This is the only required flag. The recommended practice is to separate your Resque workers from your goworkers with different queues. Otherwise, Resque worker classes that have no goworker analog will cause the goworker process to fail the jobs. Because of this, there is no default queue, nor is there a way to select all queues (à la Resque's `*` queue). If you have multiple queues you can assign them weights. A queue with a weight of 2 will be checked twice as often as a queue with a weight of 1: `-queues='high=2,low=1'`. +* `-interval=5.0` — Specifies the wait period between polling if no job was in the queue the last time one was requested. +* `-concurrency=25` — Specifies the number of concurrently executing workers. This number can be as low as 1 or rather comfortably as high as 100,000, and should be tuned to your workflow and the availability of outside resources. +* `-connections=2` — Specifies the maximum number of Redis connections that goworker will consume between the poller and all workers. There is not much performance gain over two and a slight penalty when using only one. This is configurable in case you need to keep connection counts low for cloud Redis providers who limit plans on `maxclients`. +* `-uri=redis://localhost:6379/` — Specifies the URI of the Redis database from which goworker polls for jobs. Accepts URIs of the format `redis://user:pass@host:port/db` or `unix:///path/to/redis.sock`. The flag may also be set by the environment variable `$($REDIS_PROVIDER)` or `$REDIS_URL`. E.g. set `$REDIS_PROVIDER` to `REDISTOGO_URL` on Heroku to let the Redis To Go add-on configure the Redis database. +* `-namespace=resque:` — Specifies the namespace from which goworker retrieves jobs and stores stats on workers. +* `-exit-on-complete=false` — Exits goworker when there are no jobs left in the queue. This is helpful in conjunction with the `time` command to benchmark different configurations. + +You can also configure your own flags for use within your workers. Be sure to set them before calling `goworker.Main()`. It is okay to call `flags.Parse()` before calling `goworker.Main()` if you need to do additional processing on your flags. + +## Signal Handling in goworker + +To stop goworker, send a `QUIT`, `TERM`, or `INT` signal to the process. This will immediately stop job polling. There can be up to `$CONCURRENCY` jobs currently running, which will continue to run until they are finished. + +## Failure Modes + +Like Resque, goworker makes no guarantees about the safety of jobs in the event of process shutdown. Workers must be both idempotent and tolerant to loss of the job in the event of failure. + +If the process is killed with a `KILL` or by a system failure, there may be one job that is currently in the poller's buffer that will be lost without any representation in either the queue or the worker variable. + +If you are running goworker on a system like Heroku, which sends a `TERM` to signal a process that it needs to stop, ten seconds later sends a `KILL` to force the process to stop, your jobs must finish within 10 seconds or they may be lost. Jobs will be recoverable from the Redis database under + +``` +resque:worker::-: +``` + +as a JSON object with keys `queue`, `run_at`, and `payload`, but the process is manual. Additionally, there is no guarantee that the job in Redis under the worker key has not finished, if the process is killed before goworker can flush the update to Redis. + +## Contributing + +1. [Fork it](https://github.com/benmanns/goworker/fork) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request diff --git a/vendor/github.com/benmanns/goworker/doc.go b/vendor/github.com/benmanns/goworker/doc.go new file mode 100644 index 00000000..e6588003 --- /dev/null +++ b/vendor/github.com/benmanns/goworker/doc.go @@ -0,0 +1,118 @@ +// Package goworker is a Resque-compatible, Go-based +// background worker. It allows you to push jobs into a +// queue using an expressive language like Ruby while +// harnessing the efficiency and concurrency of Go to +// minimize job latency and cost. +// +// goworker workers can run alongside Ruby Resque clients +// so that you can keep all but your most +// resource-intensive jobs in Ruby. +// +// To create a worker, write a function matching the +// signature +// +// func(string, ...interface{}) error +// +// and register it using +// +// goworker.Register("MyClass", myFunc) +// +// Here is a simple worker that prints its arguments: +// +// package main +// +// import ( +// "fmt" +// "github.com/benmanns/goworker" +// ) +// +// func myFunc(queue string, args ...interface{}) error { +// fmt.Printf("From %s, %v\n", queue, args) +// return nil +// } +// +// func init() { +// goworker.Register("MyClass", myFunc) +// } +// +// func main() { +// if err := goworker.Work(); err != nil { +// fmt.Println("Error:", err) +// } +// } +// +// To create workers that share a database pool or other +// resources, use a closure to share variables. +// +// package main +// +// import ( +// "fmt" +// "github.com/benmanns/goworker" +// ) +// +// func newMyFunc(uri string) (func(queue string, args ...interface{}) error) { +// foo := NewFoo(uri) +// return func(queue string, args ...interface{}) error { +// foo.Bar(args) +// return nil +// } +// } +// +// func init() { +// goworker.Register("MyClass", newMyFunc("http://www.example.com/")) +// } +// +// func main() { +// if err := goworker.Work(); err != nil { +// fmt.Println("Error:", err) +// } +// } +// +// goworker worker functions receive the queue they are +// serving and a slice of interfaces. To use them as +// parameters to other functions, use Go type assertions +// to convert them into usable types. +// +// // Expecting (int, string, float64) +// func myFunc(queue, args ...interface{}) error { +// idNum, ok := args[0].(json.Number) +// if !ok { +// return errorInvalidParam +// } +// id, err := idNum.Int64() +// if err != nil { +// return errorInvalidParam +// } +// name, ok := args[1].(string) +// if !ok { +// return errorInvalidParam +// } +// weightNum, ok := args[2].(json.Number) +// if !ok { +// return errorInvalidParam +// } +// weight, err := weightNum.Float64() +// if err != nil { +// return errorInvalidParam +// } +// doSomething(id, name, weight) +// return nil +// } +// +// For testing, it is helpful to use the redis-cli program +// to insert jobs onto the Redis queue: +// +// redis-cli -r 100 RPUSH resque:queue:myqueue '{"class":"MyClass","args":["hi","there"]}' +// +// will insert 100 jobs for the MyClass worker onto the +// myqueue queue. It is equivalent to: +// +// class MyClass +// @queue = :myqueue +// end +// +// 100.times do +// Resque.enqueue MyClass, ['hi', 'there'] +// end +package goworker diff --git a/vendor/github.com/benmanns/goworker/failure.go b/vendor/github.com/benmanns/goworker/failure.go new file mode 100644 index 00000000..c6349f5b --- /dev/null +++ b/vendor/github.com/benmanns/goworker/failure.go @@ -0,0 +1,15 @@ +package goworker + +import ( + "time" +) + +type failure struct { + FailedAt time.Time `json:"failed_at"` + Payload Payload `json:"payload"` + Exception string `json:"exception"` + Error string `json:"error"` + Backtrace []string `json:"backtrace"` + Worker *worker `json:"worker"` + Queue string `json:"queue"` +} diff --git a/vendor/github.com/benmanns/goworker/flags.go b/vendor/github.com/benmanns/goworker/flags.go new file mode 100644 index 00000000..ba57300e --- /dev/null +++ b/vendor/github.com/benmanns/goworker/flags.go @@ -0,0 +1,149 @@ +// Running goworker +// +// After building your workers, you will have an +// executable that you can run which will +// automatically poll a Redis server and call +// your workers as jobs arrive. +// +// Flags +// +// There are several flags which control the +// operation of the goworker client. +// +// -queues="comma,delimited,queues" +// — This is the only required flag. The +// recommended practice is to separate your +// Resque workers from your goworkers with +// different queues. Otherwise, Resque worker +// classes that have no goworker analog will +// cause the goworker process to fail the jobs. +// Because of this, there is no default queue, +// nor is there a way to select all queues (à la +// Resque's * queue). Queues are processed in +// the order they are specififed. +// If you have multiple queues you can assign +// them weights. A queue with a weight of 2 will +// be checked twice as often as a queue with a +// weight of 1: -queues='high=2,low=1'. +// +// -interval=5.0 +// — Specifies the wait period between polling if +// no job was in the queue the last time one was +// requested. +// +// -concurrency=25 +// — Specifies the number of concurrently +// executing workers. This number can be as low +// as 1 or rather comfortably as high as 100,000, +// and should be tuned to your workflow and the +// availability of outside resources. +// +// -connections=2 +// — Specifies the maximum number of Redis +// connections that goworker will consume between +// the poller and all workers. There is not much +// performance gain over two and a slight penalty +// when using only one. This is configurable in +// case you need to keep connection counts low +// for cloud Redis providers who limit plans on +// maxclients. +// +// -uri=redis://localhost:6379/ +// — Specifies the URI of the Redis database from +// which goworker polls for jobs. Accepts URIs of +// the format redis://user:pass@host:port/db or +// unix:///path/to/redis.sock. The flag may also +// be set by the environment variable +// $($REDIS_PROVIDER) or $REDIS_URL. E.g. set +// $REDIS_PROVIDER to REDISTOGO_URL on Heroku to +// let the Redis To Go add-on configure the Redis +// database. +// +// -namespace=resque: +// — Specifies the namespace from which goworker +// retrieves jobs and stores stats on workers. +// +// -exit-on-complete=false +// — Exits goworker when there are no jobs left +// in the queue. This is helpful in conjunction +// with the time command to benchmark different +// configurations. +// +// -use-number=false +// — Uses json.Number when decoding numbers in the +// job payloads. This will avoid issues that +// occur when goworker and the json package decode +// large numbers as floats, which then get +// encoded in scientific notation, losing +// pecision. This will default to true soon. +// +// You can also configure your own flags for use +// within your workers. Be sure to set them +// before calling goworker.Main(). It is okay to +// call flags.Parse() before calling +// goworker.Main() if you need to do additional +// processing on your flags. +package goworker + +import ( + "flag" + "os" + "strings" +) + +// Namespace returns the namespace flag for goworker. You +// can use this with the GetConn and PutConn functions to +// operate on the same namespace that goworker uses. +func Namespace() string { + return workerSettings.Namespace +} + +func init() { + flag.StringVar(&workerSettings.QueuesString, "queues", "", "a comma-separated list of Resque queues") + + flag.Float64Var(&workerSettings.IntervalFloat, "interval", 5.0, "sleep interval when no jobs are found") + + flag.IntVar(&workerSettings.Concurrency, "concurrency", 25, "the maximum number of concurrently executing jobs") + + flag.IntVar(&workerSettings.Connections, "connections", 2, "the maximum number of connections to the Redis database") + + redisProvider := os.Getenv("REDIS_PROVIDER") + var redisEnvURI string + if redisProvider != "" { + redisEnvURI = os.Getenv(redisProvider) + } else { + redisEnvURI = os.Getenv("REDIS_URL") + } + if redisEnvURI == "" { + redisEnvURI = "redis://localhost:6379/" + } + flag.StringVar(&workerSettings.URI, "uri", redisEnvURI, "the URI of the Redis server") + + flag.StringVar(&workerSettings.Namespace, "namespace", "resque:", "the Redis namespace") + + flag.BoolVar(&workerSettings.ExitOnComplete, "exit-on-complete", false, "exit when the queue is empty") + + flag.BoolVar(&workerSettings.UseNumber, "use-number", false, "use json.Number instead of float64 when decoding numbers in JSON. will default to true soon") +} + +func flags() error { + if !flag.Parsed() { + flag.Parse() + } + if err := workerSettings.Queues.Set(workerSettings.QueuesString); err != nil { + return err + } + if err := workerSettings.Interval.SetFloat(workerSettings.IntervalFloat); err != nil { + return err + } + workerSettings.IsStrict = strings.IndexRune(workerSettings.QueuesString, '=') == -1 + + if !workerSettings.UseNumber { + logger.Warn("== DEPRECATION WARNING ==") + logger.Warn(" Currently, encoding/json decodes numbers as float64.") + logger.Warn(" This can cause numbers to lose precision as they are read from the Resque queue.") + logger.Warn(" Set the -use-number flag to use json.Number when decoding numbers and remove this warning.") + } + + return nil +} diff --git a/vendor/github.com/benmanns/goworker/goworker.go b/vendor/github.com/benmanns/goworker/goworker.go new file mode 100644 index 00000000..a83d4a45 --- /dev/null +++ b/vendor/github.com/benmanns/goworker/goworker.go @@ -0,0 +1,145 @@ +package goworker + +import ( + "os" + "strconv" + "sync" + "time" + + "golang.org/x/net/context" + + "github.com/cihub/seelog" + "github.com/youtube/vitess/go/pools" +) + +var ( + logger seelog.LoggerInterface + pool *pools.ResourcePool + ctx context.Context + initMutex sync.Mutex + initialized bool +) + +var workerSettings WorkerSettings + +type WorkerSettings struct { + QueuesString string + Queues queuesFlag + IntervalFloat float64 + Interval intervalFlag + Concurrency int + Connections int + URI string + Namespace string + ExitOnComplete bool + IsStrict bool + UseNumber bool +} + +func SetSettings(settings WorkerSettings) { + workerSettings = settings +} + +// Init initializes the goworker process. This will be +// called by the Work function, but may be used by programs +// that wish to access goworker functions and configuration +// without actually processing jobs. +func Init() error { + initMutex.Lock() + defer initMutex.Unlock() + if !initialized { + var err error + logger, err = seelog.LoggerFromWriterWithMinLevel(os.Stdout, seelog.InfoLvl) + if err != nil { + return err + } + + if err := flags(); err != nil { + return err + } + ctx = context.Background() + + pool = newRedisPool(workerSettings.URI, workerSettings.Connections, workerSettings.Connections, time.Minute) + + initialized = true + } + return nil +} + +// GetConn returns a connection from the goworker Redis +// connection pool. When using the pool, check in +// connections as quickly as possible, because holding a +// connection will cause concurrent worker functions to lock +// while they wait for an available connection. Expect this +// API to change drastically. +func GetConn() (*RedisConn, error) { + resource, err := pool.Get(ctx) + + if err != nil { + return nil, err + } + return resource.(*RedisConn), nil +} + +// PutConn puts a connection back into the connection pool. +// Run this as soon as you finish using a connection that +// you got from GetConn. Expect this API to change +// drastically. +func PutConn(conn *RedisConn) { + pool.Put(conn) +} + +// Close cleans up resources initialized by goworker. This +// will be called by Work when cleaning up. However, if you +// are using the Init function to access goworker functions +// and configuration without processing jobs by calling +// Work, you should run this function when cleaning up. For +// example, +// +// if err := goworker.Init(); err != nil { +// fmt.Println("Error:", err) +// } +// defer goworker.Close() +func Close() { + initMutex.Lock() + defer initMutex.Unlock() + if initialized { + pool.Close() + initialized = false + } +} + +// Work starts the goworker process. Check for errors in +// the return value. Work will take over the Go executable +// and will run until a QUIT, INT, or TERM signal is +// received, or until the queues are empty if the +// -exit-on-complete flag is set. +func Work() error { + err := Init() + if err != nil { + return err + } + defer Close() + + quit := signals() + + poller, err := newPoller(workerSettings.Queues, workerSettings.IsStrict) + if err != nil { + return err + } + jobs := poller.poll(time.Duration(workerSettings.Interval), quit) + + var monitor sync.WaitGroup + + for id := 0; id < workerSettings.Concurrency; id++ { + worker, err := newWorker(strconv.Itoa(id), workerSettings.Queues) + if err != nil { + return err + } + worker.work(jobs, &monitor) + } + + monitor.Wait() + + return nil +} diff --git a/vendor/github.com/benmanns/goworker/interval_flag.go b/vendor/github.com/benmanns/goworker/interval_flag.go new file mode 100644 index 00000000..a55c99d3 --- /dev/null +++ b/vendor/github.com/benmanns/goworker/interval_flag.go @@ -0,0 +1,27 @@ +package goworker + +import ( + "fmt" + "strconv" + "time" +) + +type intervalFlag time.Duration + +func (i *intervalFlag) Set(value string) error { + f, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + i.SetFloat(f) + return nil +} + +func (i *intervalFlag) SetFloat(value float64) error { + *i = intervalFlag(time.Duration(value * float64(time.Second))) + return nil +} + +func (i *intervalFlag) String() string { + return fmt.Sprint(*i) +} diff --git a/vendor/github.com/benmanns/goworker/job.go b/vendor/github.com/benmanns/goworker/job.go new file mode 100644 index 00000000..b4dbec12 --- /dev/null +++ b/vendor/github.com/benmanns/goworker/job.go @@ -0,0 +1,6 @@ +package goworker + +type Job struct { + Queue string + Payload Payload +} diff --git a/vendor/github.com/benmanns/goworker/payload.go b/vendor/github.com/benmanns/goworker/payload.go new file mode 100644 index 00000000..b13b3f11 --- /dev/null +++ b/vendor/github.com/benmanns/goworker/payload.go @@ -0,0 +1,6 @@ +package goworker + +type Payload struct { + Class string `json:"class"` + Args []interface{} `json:"args"` +} diff --git a/vendor/github.com/benmanns/goworker/poller.go b/vendor/github.com/benmanns/goworker/poller.go new file mode 100644 index 00000000..d47db7bf --- /dev/null +++ b/vendor/github.com/benmanns/goworker/poller.go @@ -0,0 +1,143 @@ +package goworker + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +type poller struct { + process + isStrict bool +} + +func newPoller(queues []string, isStrict bool) (*poller, error) { + process, err := newProcess("poller", queues) + if err != nil { + return nil, err + } + return &poller{ + process: *process, + isStrict: isStrict, + }, nil +} + +func (p *poller) getJob(conn *RedisConn) (*Job, error) { + for _, queue := range p.queues(p.isStrict) { + logger.Debugf("Checking %s", queue) + + reply, err := conn.Do("LPOP", fmt.Sprintf("%squeue:%s", workerSettings.Namespace, queue)) + if err != nil { + return nil, err + } + if reply != nil { + logger.Debugf("Found job on %s", queue) + + job := &Job{Queue: queue} + + decoder := json.NewDecoder(bytes.NewReader(reply.([]byte))) + if workerSettings.UseNumber { + decoder.UseNumber() + } + + if err := decoder.Decode(&job.Payload); err != nil { + return nil, err + } + return job, nil + } + } + + return nil, nil +} + +func (p *poller) poll(interval time.Duration, quit <-chan bool) <-chan *Job { + jobs := make(chan *Job) + + conn, err := GetConn() + if err != nil { + logger.Criticalf("Error on getting connection in poller %s: %v", p, err) + close(jobs) + return jobs + } else { + p.open(conn) + p.start(conn) + PutConn(conn) + } + + go func() { + defer func() { + close(jobs) + + conn, err := GetConn() + if err != nil { + logger.Criticalf("Error on getting connection in poller %s: %v", p, err) + return + } else { + p.finish(conn) + p.close(conn) + PutConn(conn) + } + }() + + for { + select { + case <-quit: + return + default: + conn, err := GetConn() + if err != nil { + logger.Criticalf("Error on getting connection in poller %s: %v", p, err) + return + } + + job, err := p.getJob(conn) + if err != nil { + logger.Criticalf("Error on %v getting job from %v: %v", p, p.Queues, err) + PutConn(conn) + return + } + if job != nil { + conn.Send("INCR", fmt.Sprintf("%sstat:processed:%v", workerSettings.Namespace, p)) + conn.Flush() + PutConn(conn) + select { + case jobs <- job: + case <-quit: + buf, err := json.Marshal(job.Payload) + if err != nil { + logger.Criticalf("Error requeueing %v: %v", job, err) + return + } + conn, err := GetConn() + if err != nil { + logger.Criticalf("Error on getting connection in poller %s: %v", p, err) + return + } + + conn.Send("LPUSH", fmt.Sprintf("%squeue:%s", workerSettings.Namespace, job.Queue), buf) + conn.Flush() + PutConn(conn) + return + } + } else { + PutConn(conn) + if workerSettings.ExitOnComplete { + return + } + logger.Debugf("Sleeping for %v", interval) + logger.Debugf("Waiting for %v", p.Queues) + + timeout := time.After(interval) + select { + case <-quit: + return + case <-timeout: + } + } + } + } + }() + + return jobs +} diff --git a/vendor/github.com/benmanns/goworker/process.go b/vendor/github.com/benmanns/goworker/process.go new file mode 100644 index 00000000..82bcd9eb --- /dev/null +++ b/vendor/github.com/benmanns/goworker/process.go @@ -0,0 +1,90 @@ +package goworker + +import ( + "fmt" + "math/rand" + "os" + "strings" + "time" +) + +type process struct { + Hostname string + Pid int + ID string + Queues []string +} + +func newProcess(id string, queues []string) (*process, error) { + hostname, err := os.Hostname() + if err != nil { + return nil, err + } + + return &process{ + Hostname: hostname, + Pid: os.Getpid(), + ID: id, + Queues: queues, + }, nil +} + +func (p *process) String() string { + return fmt.Sprintf("%s:%d-%s:%s", p.Hostname, p.Pid, p.ID, strings.Join(p.Queues, ",")) +} + +func (p *process) open(conn *RedisConn) error { + conn.Send("SADD", fmt.Sprintf("%sworkers", workerSettings.Namespace), p) + conn.Send("SET", fmt.Sprintf("%sstat:processed:%v", workerSettings.Namespace, p), "0") + conn.Send("SET", fmt.Sprintf("%sstat:failed:%v", workerSettings.Namespace, p), "0") + conn.Flush() + + return nil +} + +func (p *process) close(conn *RedisConn) error { + logger.Infof("%v shutdown", p) + conn.Send("SREM", fmt.Sprintf("%sworkers", workerSettings.Namespace), p) + conn.Send("DEL", fmt.Sprintf("%sstat:processed:%s", workerSettings.Namespace, p)) + conn.Send("DEL", fmt.Sprintf("%sstat:failed:%s", workerSettings.Namespace, p)) + conn.Flush() + + return nil +} + +func (p *process) start(conn *RedisConn) error { + conn.Send("SET", fmt.Sprintf("%sworker:%s:started", workerSettings.Namespace, p), time.Now().String()) + conn.Flush() + + return nil +} + +func (p *process) finish(conn *RedisConn) error { + conn.Send("DEL", fmt.Sprintf("%sworker:%s", workerSettings.Namespace, p)) + conn.Send("DEL", fmt.Sprintf("%sworker:%s:started", workerSettings.Namespace, p)) + conn.Flush() + + return nil +} + +func (p *process) fail(conn *RedisConn) error { + conn.Send("INCR", fmt.Sprintf("%sstat:failed", workerSettings.Namespace)) + conn.Send("INCR", fmt.Sprintf("%sstat:failed:%s", workerSettings.Namespace, p)) + conn.Flush() + + return nil +} + +func (p *process) queues(strict bool) []string { + // If the queues order is strict then just return them. + if strict { + return p.Queues + } + + // If not then we want to to shuffle the queues before returning them. + queues := make([]string, len(p.Queues)) + for i, v := range rand.Perm(len(p.Queues)) { + queues[i] = p.Queues[v] + } + return queues +} diff --git a/vendor/github.com/benmanns/goworker/queues_flag.go b/vendor/github.com/benmanns/goworker/queues_flag.go new file mode 100644 index 00000000..b44484ed --- /dev/null +++ b/vendor/github.com/benmanns/goworker/queues_flag.go @@ -0,0 +1,59 @@ +package goworker + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +var ( + errorEmptyQueues = errors.New("you must specify at least one queue") + errorNonNumericWeight = errors.New("the weight must be a numeric value") +) + +type queuesFlag []string + +func (q *queuesFlag) Set(value string) error { + for _, queueAndWeight := range strings.Split(value, ",") { + if queueAndWeight == "" { + continue + } + + queue, weight, err := parseQueueAndWeight(queueAndWeight) + if err != nil { + return err + } + + for i := 0; i < weight; i++ { + *q = append(*q, queue) + } + } + if len(*q) == 0 { + return errorEmptyQueues + } + return nil +} + +func (q *queuesFlag) String() string { + return fmt.Sprint(*q) +} + +func parseQueueAndWeight(queueAndWeight string) (queue string, weight int, err error) { + parts := strings.SplitN(queueAndWeight, "=", 2) + queue = parts[0] + + if queue == "" { + return + } + + if len(parts) == 1 { + weight = 1 + } else { + weight, err = strconv.Atoi(parts[1]) + if err != nil { + err = errorNonNumericWeight + } + } + return +} diff --git a/vendor/github.com/benmanns/goworker/redis.go b/vendor/github.com/benmanns/goworker/redis.go new file mode 100644 index 00000000..9a8989fe --- /dev/null +++ b/vendor/github.com/benmanns/goworker/redis.go @@ -0,0 +1,84 @@ +package goworker + +import ( + "errors" + "net/url" + "time" + + "github.com/garyburd/redigo/redis" + "github.com/youtube/vitess/go/pools" +) + +var ( + errorInvalidScheme = errors.New("invalid Redis database URI scheme") +) + +type RedisConn struct { + redis.Conn +} + +func (r *RedisConn) Close() { + _ = r.Conn.Close() +} + +func newRedisFactory(uri string) pools.Factory { + return func() (pools.Resource, error) { + return redisConnFromURI(uri) + } +} + +func newRedisPool(uri string, capacity int, maxCapacity int, idleTimout time.Duration) *pools.ResourcePool { + return pools.NewResourcePool(newRedisFactory(uri), capacity, maxCapacity, idleTimout) +} + +func redisConnFromURI(uriString string) (*RedisConn, error) { + uri, err := url.Parse(uriString) + if err != nil { + return nil, err + } + + var network string + var host string + var password string + var db string + + switch uri.Scheme { + case "redis": + network = "tcp" + host = uri.Host + if uri.User != nil { + password, _ = uri.User.Password() + } + if len(uri.Path) > 1 { + db = uri.Path[1:] + } + case "unix": + network = "unix" + host = uri.Path + default: + return nil, errorInvalidScheme + } + + conn, err := redis.Dial(network, host) + if err != nil { + return nil, err + } + + if password != "" { + _, err := conn.Do("AUTH", password) + if err != nil { + conn.Close() + return nil, err + } + } + + if db != "" { + _, err := conn.Do("SELECT", db) + if err != nil { + conn.Close() + return nil, err + } + } + + return &RedisConn{Conn: conn}, nil +} diff --git a/vendor/github.com/benmanns/goworker/signal_stop_1.0.go b/vendor/github.com/benmanns/goworker/signal_stop_1.0.go new file mode 100644 index 00000000..0ea0cd37 --- /dev/null +++ b/vendor/github.com/benmanns/goworker/signal_stop_1.0.go @@ -0,0 +1,12 @@ +// +build !go1.1 + +package goworker + +import ( + "os" +) + +// Stops signals channel. This does not exist in +// Go less than 1.1. +func signalStop(c chan<- os.Signal) { +} diff --git a/vendor/github.com/benmanns/goworker/signal_stop_1.1.go b/vendor/github.com/benmanns/goworker/signal_stop_1.1.go new file mode 100644 index 00000000..11f6b9e4 --- /dev/null +++ b/vendor/github.com/benmanns/goworker/signal_stop_1.1.go @@ -0,0 +1,14 @@ +// +build go1.1 + +package goworker + +import ( + "os" + "os/signal" +) + +// Stops signals channel. This function exists +// in Go greater or equal to 1.1. +func signalStop(c chan<- os.Signal) { + signal.Stop(c) +} diff --git a/vendor/github.com/benmanns/goworker/signals.go b/vendor/github.com/benmanns/goworker/signals.go new file mode 100644 index 00000000..95697cde --- /dev/null +++ b/vendor/github.com/benmanns/goworker/signals.go @@ -0,0 +1,62 @@ +// Signal Handling in goworker +// +// To stop goworker, send a QUIT, TERM, or INT +// signal to the process. This will immediately +// stop job polling. There can be up to +// $CONCURRENCY jobs currently running, which +// will continue to run until they are finished. +// +// Failure Modes +// +// Like Resque, goworker makes no guarantees +// about the safety of jobs in the event of +// process shutdown. Workers must be both +// idempotent and tolerant to loss of the job in +// the event of failure. +// +// If the process is killed with a KILL or by a +// system failure, there may be one job that is +// currently in the poller's buffer that will be +// lost without any representation in either the +// queue or the worker variable. +// +// If you are running Goworker on a system like +// Heroku, which sends a TERM to signal a process +// that it needs to stop, ten seconds later sends +// a KILL to force the process to stop, your jobs +// must finish within 10 seconds or they may be +// lost. Jobs will be recoverable from the Redis +// database under +// +// resque:worker::-: +// +// as a JSON object with keys queue, run_at, and +// payload, but the process is manual. +// Additionally, there is no guarantee that the +// job in Redis under the worker key has not +// finished, if the process is killed before +// goworker can flush the update to Redis. +package goworker + +import ( + "os" + "os/signal" + "syscall" +) + +func signals() <-chan bool { + quit := make(chan bool) + + go func() { + signals := make(chan os.Signal) + defer close(signals) + + signal.Notify(signals, syscall.SIGQUIT, syscall.SIGTERM, os.Interrupt) + defer signalStop(signals) + + <-signals + quit <- true + }() + + return quit +} diff --git a/vendor/github.com/benmanns/goworker/work.go b/vendor/github.com/benmanns/goworker/work.go new file mode 100644 index 00000000..b04aa0af --- /dev/null +++ b/vendor/github.com/benmanns/goworker/work.go @@ -0,0 +1,11 @@ +package goworker + +import ( + "time" +) + +type work struct { + Queue string `json:"queue"` + RunAt time.Time `json:"run_at"` + Payload Payload `json:"payload"` +} diff --git a/vendor/github.com/benmanns/goworker/worker.go b/vendor/github.com/benmanns/goworker/worker.go new file mode 100644 index 00000000..cecd34fd --- /dev/null +++ b/vendor/github.com/benmanns/goworker/worker.go @@ -0,0 +1,155 @@ +package goworker + +import ( + "encoding/json" + "errors" + "fmt" + "sync" + "time" +) + +type worker struct { + process +} + +func newWorker(id string, queues []string) (*worker, error) { + process, err := newProcess(id, queues) + if err != nil { + return nil, err + } + return &worker{ + process: *process, + }, nil +} + +func (w *worker) MarshalJSON() ([]byte, error) { + return json.Marshal(w.String()) +} + +func (w *worker) start(conn *RedisConn, job *Job) error { + work := &work{ + Queue: job.Queue, + RunAt: time.Now(), + Payload: job.Payload, + } + + buffer, err := json.Marshal(work) + if err != nil { + return err + } + + conn.Send("SET", fmt.Sprintf("%sworker:%s", workerSettings.Namespace, w), buffer) + logger.Debugf("Processing %s since %s [%v]", work.Queue, work.RunAt, work.Payload.Class) + + return w.process.start(conn) +} + +func (w *worker) fail(conn *RedisConn, job *Job, err error) error { + failure := &failure{ + FailedAt: time.Now(), + Payload: job.Payload, + Exception: "Error", + Error: err.Error(), + Worker: w, + Queue: job.Queue, + } + buffer, err := json.Marshal(failure) + if err != nil { + return err + } + conn.Send("RPUSH", fmt.Sprintf("%sfailed", workerSettings.Namespace), buffer) + + return w.process.fail(conn) +} + +func (w *worker) succeed(conn *RedisConn, job *Job) error { + conn.Send("INCR", fmt.Sprintf("%sstat:processed", workerSettings.Namespace)) + conn.Send("INCR", fmt.Sprintf("%sstat:processed:%s", workerSettings.Namespace, w)) + + return nil +} + +func (w *worker) finish(conn *RedisConn, job *Job, err error) error { + if err != nil { + w.fail(conn, job, err) + } else { + w.succeed(conn, job) + } + return w.process.finish(conn) +} + +func (w *worker) work(jobs <-chan *Job, monitor *sync.WaitGroup) { + conn, err := GetConn() + if err != nil { + logger.Criticalf("Error on getting connection in worker %v: %v", w, err) + return + } else { + w.open(conn) + PutConn(conn) + } + + monitor.Add(1) + + go func() { + defer func() { + defer monitor.Done() + + conn, err := GetConn() + if err != nil { + logger.Criticalf("Error on getting connection in worker %v: %v", w, err) + return + } else { + w.close(conn) + PutConn(conn) + } + }() + for job := range jobs { + if workerFunc, ok := workers[job.Payload.Class]; ok { + w.run(job, workerFunc) + + logger.Debugf("done: (Job{%s} | %s | %v)", job.Queue, job.Payload.Class, job.Payload.Args) + } else { + errorLog := fmt.Sprintf("No worker for %s in queue %s with args %v", job.Payload.Class, job.Queue, job.Payload.Args) + logger.Critical(errorLog) + + conn, err := GetConn() + if err != nil { + logger.Criticalf("Error on getting connection in worker %v: %v", w, err) + return + } else { + w.finish(conn, job, errors.New(errorLog)) + PutConn(conn) + } + } + } + }() +} + +func (w *worker) run(job *Job, workerFunc workerFunc) { + var err error + defer func() { + conn, errCon := GetConn() + if errCon != nil { + logger.Criticalf("Error on getting connection in worker on finish %v: %v", w, errCon) + return + } else { + w.finish(conn, job, err) + PutConn(conn) + } + }() + defer func() { + if r := recover(); r != nil { + err = errors.New(fmt.Sprint(r)) + } + }() + + conn, err := GetConn() + if err != nil { + logger.Criticalf("Error on getting connection in worker on start %v: %v", w, err) + return + } else { + w.start(conn, job) + PutConn(conn) + } + err = workerFunc(job.Queue, job.Payload.Args...) +} diff --git a/vendor/github.com/benmanns/goworker/worker_func.go b/vendor/github.com/benmanns/goworker/worker_func.go new file mode 100644 index 00000000..3fe43afa --- /dev/null +++ b/vendor/github.com/benmanns/goworker/worker_func.go @@ -0,0 +1,3 @@ +package goworker + +type workerFunc func(string, ...interface{}) error diff --git a/vendor/github.com/benmanns/goworker/workers.go b/vendor/github.com/benmanns/goworker/workers.go new file mode 100644 index 00000000..0a5fb6fd --- /dev/null +++ b/vendor/github.com/benmanns/goworker/workers.go @@ -0,0 +1,56 @@ +package goworker + +import ( + "encoding/json" + "fmt" +) + +var ( + workers map[string]workerFunc +) + +func init() { + workers = make(map[string]workerFunc) +} + +// Register registers a goworker worker function. Class +// refers to the Ruby name of the class which enqueues the +// job. Worker is a function which accepts a queue and an +// arbitrary array of interfaces as arguments. +func Register(class string, worker workerFunc) { + workers[class] = worker +} + +func Enqueue(job *Job) error { + err := Init() + if err != nil { + return err + } + + conn, err := GetConn() + if err != nil { + logger.Criticalf("Error on getting connection on enqueue") + return err + } + defer PutConn(conn) + + buffer, err := json.Marshal(job.Payload) + if err != nil { + logger.Criticalf("Cant marshal payload on enqueue") + return err + } + + err = conn.Send("RPUSH", fmt.Sprintf("%squeue:%s", workerSettings.Namespace, job.Queue), buffer) + if err != nil { + logger.Criticalf("Cant push to queue") + return err + } + + err = conn.Send("SADD", fmt.Sprintf("%squeues", workerSettings.Namespace), job.Queue) + if err != nil { + logger.Criticalf("Cant register queue to list of use queues") + return err + } + + return conn.Flush() +} diff --git a/vendor/github.com/bitly/go-simplejson/.travis.yml b/vendor/github.com/bitly/go-simplejson/.travis.yml new file mode 100644 index 00000000..55accb9b --- /dev/null +++ b/vendor/github.com/bitly/go-simplejson/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - 1.0.3 + - 1.1.2 + - 1.2 + - tip +install: + - go get github.com/bmizerany/assert +notifications: + email: false diff --git a/vendor/github.com/bitly/go-simplejson/LICENSE b/vendor/github.com/bitly/go-simplejson/LICENSE new file mode 100644 index 00000000..89de3547 --- /dev/null +++ b/vendor/github.com/bitly/go-simplejson/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/bitly/go-simplejson/README.md b/vendor/github.com/bitly/go-simplejson/README.md new file mode 100644 index 00000000..8c6c101a --- /dev/null +++ b/vendor/github.com/bitly/go-simplejson/README.md @@ -0,0 +1,13 @@ +### go-simplejson + +a Go package to interact with arbitrary JSON + +[![Build Status](https://secure.travis-ci.org/bitly/go-simplejson.png)](http://travis-ci.org/bitly/go-simplejson) + +### Importing + + import github.com/bitly/go-simplejson + +### Documentation + +Visit the docs on [gopkgdoc](http://godoc.org/github.com/bitly/go-simplejson) diff --git a/vendor/github.com/bitly/go-simplejson/simplejson.go b/vendor/github.com/bitly/go-simplejson/simplejson.go new file mode 100644 index 00000000..27ce9861 --- /dev/null +++ b/vendor/github.com/bitly/go-simplejson/simplejson.go @@ -0,0 +1,446 @@ +package simplejson + +import ( + "encoding/json" + "errors" + "log" +) + +// returns the current implementation version +func Version() string { + return "0.5.0" +} + +type Json struct { + data interface{} +} + +// NewJson returns a pointer to a new `Json` object +// after unmarshaling `body` bytes +func NewJson(body []byte) (*Json, error) { + j := new(Json) + err := j.UnmarshalJSON(body) + if err != nil { + return nil, err + } + return j, nil +} + +// New returns a pointer to a new, empty `Json` object +func New() *Json { + return &Json{ + data: make(map[string]interface{}), + } +} + +// Interface returns the underlying data +func (j *Json) Interface() interface{} { + return j.data +} + +// Encode returns its marshaled data as `[]byte` +func (j *Json) Encode() ([]byte, error) { + return j.MarshalJSON() +} + +// EncodePretty returns its marshaled data as `[]byte` with indentation +func (j *Json) EncodePretty() ([]byte, error) { + return json.MarshalIndent(&j.data, "", " ") +} + +// Implements the json.Marshaler interface. +func (j *Json) MarshalJSON() ([]byte, error) { + return json.Marshal(&j.data) +} + +// Set modifies `Json` map by `key` and `value` +// Useful for changing single key/value in a `Json` object easily. +func (j *Json) Set(key string, val interface{}) { + m, err := j.Map() + if err != nil { + return + } + m[key] = val +} + +// SetPath modifies `Json`, recursively checking/creating map keys for the supplied path, +// and then finally writing in the value +func (j *Json) SetPath(branch []string, val interface{}) { + if len(branch) == 0 { + j.data = val + return + } + + // in order to insert our branch, we need map[string]interface{} + if _, ok := (j.data).(map[string]interface{}); !ok { + // have to replace with something suitable + j.data = make(map[string]interface{}) + } + curr := j.data.(map[string]interface{}) + + for i := 0; i < len(branch)-1; i++ { + b := branch[i] + // key exists? + if _, ok := curr[b]; !ok { + n := make(map[string]interface{}) + curr[b] = n + curr = n + continue + } + + // make sure the value is the right sort of thing + if _, ok := curr[b].(map[string]interface{}); !ok { + // have to replace with something suitable + n := make(map[string]interface{}) + curr[b] = n + } + + curr = curr[b].(map[string]interface{}) + } + + // add remaining k/v + curr[branch[len(branch)-1]] = val +} + +// Del modifies `Json` map by deleting `key` if it is present. +func (j *Json) Del(key string) { + m, err := j.Map() + if err != nil { + return + } + delete(m, key) +} + +// Get returns a pointer to a new `Json` object +// for `key` in its `map` representation +// +// useful for chaining operations (to traverse a nested JSON): +// js.Get("top_level").Get("dict").Get("value").Int() +func (j *Json) Get(key string) *Json { + m, err := j.Map() + if err == nil { + if val, ok := m[key]; ok { + return &Json{val} + } + } + return &Json{nil} +} + +// GetPath searches for the item as specified by the branch +// without the need to deep dive using Get()'s. +// +// js.GetPath("top_level", "dict") +func (j *Json) GetPath(branch ...string) *Json { + jin := j + for _, p := range branch { + jin = jin.Get(p) + } + return jin +} + +// GetIndex returns a pointer to a new `Json` object +// for `index` in its `array` representation +// +// this is the analog to Get when accessing elements of +// a json array instead of a json object: +// js.Get("top_level").Get("array").GetIndex(1).Get("key").Int() +func (j *Json) GetIndex(index int) *Json { + a, err := j.Array() + if err == nil { + if len(a) > index { + return &Json{a[index]} + } + } + return &Json{nil} +} + +// CheckGet returns a pointer to a new `Json` object and +// a `bool` identifying success or failure +// +// useful for chained operations when success is important: +// if data, ok := js.Get("top_level").CheckGet("inner"); ok { +// log.Println(data) +// } +func (j *Json) CheckGet(key string) (*Json, bool) { + m, err := j.Map() + if err == nil { + if val, ok := m[key]; ok { + return &Json{val}, true + } + } + return nil, false +} + +// Map type asserts to `map` +func (j *Json) Map() (map[string]interface{}, error) { + if m, ok := (j.data).(map[string]interface{}); ok { + return m, nil + } + return nil, errors.New("type assertion to map[string]interface{} failed") +} + +// Array type asserts to an `array` +func (j *Json) Array() ([]interface{}, error) { + if a, ok := (j.data).([]interface{}); ok { + return a, nil + } + return nil, errors.New("type assertion to []interface{} failed") +} + +// Bool type asserts to `bool` +func (j *Json) Bool() (bool, error) { + if s, ok := (j.data).(bool); ok { + return s, nil + } + return false, errors.New("type assertion to bool failed") +} + +// String type asserts to `string` +func (j *Json) String() (string, error) { + if s, ok := (j.data).(string); ok { + return s, nil + } + return "", errors.New("type assertion to string failed") +} + +// Bytes type asserts to `[]byte` +func (j *Json) Bytes() ([]byte, error) { + if s, ok := (j.data).(string); ok { + return []byte(s), nil + } + return nil, errors.New("type assertion to []byte failed") +} + +// StringArray type asserts to an `array` of `string` +func (j *Json) StringArray() ([]string, error) { + arr, err := j.Array() + if err != nil { + return nil, err + } + retArr := make([]string, 0, len(arr)) + for _, a := range arr { + if a == nil { + retArr = append(retArr, "") + continue + } + s, ok := a.(string) + if !ok { + return nil, err + } + retArr = append(retArr, s) + } + return retArr, nil +} + +// MustArray guarantees the return of a `[]interface{}` (with optional default) +// +// useful when you want to interate over array values in a succinct manner: +// for i, v := range js.Get("results").MustArray() { +// fmt.Println(i, v) +// } +func (j *Json) MustArray(args ...[]interface{}) []interface{} { + var def []interface{} + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustArray() received too many arguments %d", len(args)) + } + + a, err := j.Array() + if err == nil { + return a + } + + return def +} + +// MustMap guarantees the return of a `map[string]interface{}` (with optional default) +// +// useful when you want to interate over map values in a succinct manner: +// for k, v := range js.Get("dictionary").MustMap() { +// fmt.Println(k, v) +// } +func (j *Json) MustMap(args ...map[string]interface{}) map[string]interface{} { + var def map[string]interface{} + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustMap() received too many arguments %d", len(args)) + } + + a, err := j.Map() + if err == nil { + return a + } + + return def +} + +// MustString guarantees the return of a `string` (with optional default) +// +// useful when you explicitly want a `string` in a single value return context: +// myFunc(js.Get("param1").MustString(), js.Get("optional_param").MustString("my_default")) +func (j *Json) MustString(args ...string) string { + var def string + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustString() received too many arguments %d", len(args)) + } + + s, err := j.String() + if err == nil { + return s + } + + return def +} + +// MustStringArray guarantees the return of a `[]string` (with optional default) +// +// useful when you want to interate over array values in a succinct manner: +// for i, s := range js.Get("results").MustStringArray() { +// fmt.Println(i, s) +// } +func (j *Json) MustStringArray(args ...[]string) []string { + var def []string + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustStringArray() received too many arguments %d", len(args)) + } + + a, err := j.StringArray() + if err == nil { + return a + } + + return def +} + +// MustInt guarantees the return of an `int` (with optional default) +// +// useful when you explicitly want an `int` in a single value return context: +// myFunc(js.Get("param1").MustInt(), js.Get("optional_param").MustInt(5150)) +func (j *Json) MustInt(args ...int) int { + var def int + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustInt() received too many arguments %d", len(args)) + } + + i, err := j.Int() + if err == nil { + return i + } + + return def +} + +// MustFloat64 guarantees the return of a `float64` (with optional default) +// +// useful when you explicitly want a `float64` in a single value return context: +// myFunc(js.Get("param1").MustFloat64(), js.Get("optional_param").MustFloat64(5.150)) +func (j *Json) MustFloat64(args ...float64) float64 { + var def float64 + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustFloat64() received too many arguments %d", len(args)) + } + + f, err := j.Float64() + if err == nil { + return f + } + + return def +} + +// MustBool guarantees the return of a `bool` (with optional default) +// +// useful when you explicitly want a `bool` in a single value return context: +// myFunc(js.Get("param1").MustBool(), js.Get("optional_param").MustBool(true)) +func (j *Json) MustBool(args ...bool) bool { + var def bool + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustBool() received too many arguments %d", len(args)) + } + + b, err := j.Bool() + if err == nil { + return b + } + + return def +} + +// MustInt64 guarantees the return of an `int64` (with optional default) +// +// useful when you explicitly want an `int64` in a single value return context: +// myFunc(js.Get("param1").MustInt64(), js.Get("optional_param").MustInt64(5150)) +func (j *Json) MustInt64(args ...int64) int64 { + var def int64 + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustInt64() received too many arguments %d", len(args)) + } + + i, err := j.Int64() + if err == nil { + return i + } + + return def +} + +// MustUInt64 guarantees the return of an `uint64` (with optional default) +// +// useful when you explicitly want an `uint64` in a single value return context: +// myFunc(js.Get("param1").MustUint64(), js.Get("optional_param").MustUint64(5150)) +func (j *Json) MustUint64(args ...uint64) uint64 { + var def uint64 + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustUint64() received too many arguments %d", len(args)) + } + + i, err := j.Uint64() + if err == nil { + return i + } + + return def +} diff --git a/vendor/github.com/bitly/go-simplejson/simplejson_go10.go b/vendor/github.com/bitly/go-simplejson/simplejson_go10.go new file mode 100644 index 00000000..c9151e9b --- /dev/null +++ b/vendor/github.com/bitly/go-simplejson/simplejson_go10.go @@ -0,0 +1,75 @@ +// +build !go1.1 + +package simplejson + +import ( + "encoding/json" + "errors" + "io" + "reflect" +) + +// NewFromReader returns a *Json by decoding from an io.Reader +func NewFromReader(r io.Reader) (*Json, error) { + j := new(Json) + dec := json.NewDecoder(r) + err := dec.Decode(&j.data) + return j, err +} + +// Implements the json.Unmarshaler interface. +func (j *Json) UnmarshalJSON(p []byte) error { + return json.Unmarshal(p, &j.data) +} + +// Float64 coerces into a float64 +func (j *Json) Float64() (float64, error) { + switch j.data.(type) { + case float32, float64: + return reflect.ValueOf(j.data).Float(), nil + case int, int8, int16, int32, int64: + return float64(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return float64(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Int coerces into an int +func (j *Json) Int() (int, error) { + switch j.data.(type) { + case float32, float64: + return int(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return int(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return int(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Int64 coerces into an int64 +func (j *Json) Int64() (int64, error) { + switch j.data.(type) { + case float32, float64: + return int64(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return reflect.ValueOf(j.data).Int(), nil + case uint, uint8, uint16, uint32, uint64: + return int64(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Uint64 coerces into an uint64 +func (j *Json) Uint64() (uint64, error) { + switch j.data.(type) { + case float32, float64: + return uint64(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return uint64(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return reflect.ValueOf(j.data).Uint(), nil + } + return 0, errors.New("invalid value type") +} diff --git a/vendor/github.com/bitly/go-simplejson/simplejson_go11.go b/vendor/github.com/bitly/go-simplejson/simplejson_go11.go new file mode 100644 index 00000000..1c479532 --- /dev/null +++ b/vendor/github.com/bitly/go-simplejson/simplejson_go11.go @@ -0,0 +1,89 @@ +// +build go1.1 + +package simplejson + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "reflect" + "strconv" +) + +// Implements the json.Unmarshaler interface. +func (j *Json) UnmarshalJSON(p []byte) error { + dec := json.NewDecoder(bytes.NewBuffer(p)) + dec.UseNumber() + return dec.Decode(&j.data) +} + +// NewFromReader returns a *Json by decoding from an io.Reader +func NewFromReader(r io.Reader) (*Json, error) { + j := new(Json) + dec := json.NewDecoder(r) + dec.UseNumber() + err := dec.Decode(&j.data) + return j, err +} + +// Float64 coerces into a float64 +func (j *Json) Float64() (float64, error) { + switch j.data.(type) { + case json.Number: + return j.data.(json.Number).Float64() + case float32, float64: + return reflect.ValueOf(j.data).Float(), nil + case int, int8, int16, int32, int64: + return float64(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return float64(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Int coerces into an int +func (j *Json) Int() (int, error) { + switch j.data.(type) { + case json.Number: + i, err := j.data.(json.Number).Int64() + return int(i), err + case float32, float64: + return int(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return int(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return int(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Int64 coerces into an int64 +func (j *Json) Int64() (int64, error) { + switch j.data.(type) { + case json.Number: + return j.data.(json.Number).Int64() + case float32, float64: + return int64(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return reflect.ValueOf(j.data).Int(), nil + case uint, uint8, uint16, uint32, uint64: + return int64(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Uint64 coerces into an uint64 +func (j *Json) Uint64() (uint64, error) { + switch j.data.(type) { + case json.Number: + return strconv.ParseUint(j.data.(json.Number).String(), 10, 64) + case float32, float64: + return uint64(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return uint64(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return reflect.ValueOf(j.data).Uint(), nil + } + return 0, errors.New("invalid value type") +} diff --git a/vendor/github.com/braintree/manners/LICENSE b/vendor/github.com/braintree/manners/LICENSE new file mode 100644 index 00000000..91ef5bee --- /dev/null +++ b/vendor/github.com/braintree/manners/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Braintree, a division of PayPal, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/braintree/manners/README.md b/vendor/github.com/braintree/manners/README.md new file mode 100644 index 00000000..09f6f969 --- /dev/null +++ b/vendor/github.com/braintree/manners/README.md @@ -0,0 +1,36 @@ +# Manners + +A *polite* webserver for Go. + +Manners allows you to shut your Go webserver down gracefully, without dropping any requests. It can act as a drop-in replacement for the standard library's http.ListenAndServe function: + +```go +func main() { + handler := MyHTTPHandler() + manners.ListenAndServe(":7000", handler) +} +``` + +Then, when you want to shut the server down: + +```go +manners.Close() +``` + +(Note that this does not block until all the requests are finished. Rather, the call to manners.ListenAndServe will stop blocking when all the requests are finished.) + +Manners ensures that all requests are served by incrementing a WaitGroup when a request comes in and decrementing it when the request finishes. + +If your request handler spawns Goroutines that are not guaranteed to finish with the request, you can ensure they are also completed with the `StartRoutine` and `FinishRoutine` functions on the server. + +### Known Issues + +Manners does not correctly shut down long-lived keepalive connections when issued a shutdown command. Clients on an idle keepalive connection may see a connection reset error rather than a close. See https://github.com/braintree/manners/issues/13 for details. + +### Compatability + +Manners 0.3.0 and above uses standard library functionality introduced in Go 1.3. + +### Installation + +`go get github.com/braintree/manners` diff --git a/vendor/github.com/braintree/manners/interfaces.go b/vendor/github.com/braintree/manners/interfaces.go new file mode 100644 index 00000000..fd073285 --- /dev/null +++ b/vendor/github.com/braintree/manners/interfaces.go @@ -0,0 +1,7 @@ +package manners + +type waitGroup interface { + Add(int) + Done() + Wait() +} diff --git a/vendor/github.com/braintree/manners/server.go b/vendor/github.com/braintree/manners/server.go new file mode 100644 index 00000000..dfd3b873 --- /dev/null +++ b/vendor/github.com/braintree/manners/server.go @@ -0,0 +1,292 @@ +/* +Package manners provides a wrapper for a standard net/http server that +ensures all active HTTP client have completed their current request +before the server shuts down. + +It can be used a drop-in replacement for the standard http package, +or can wrap a pre-configured Server. + +eg. + + http.Handle("/hello", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello\n")) + }) + + log.Fatal(manners.ListenAndServe(":8080", nil)) + +or for a customized server: + + s := manners.NewWithServer(&http.Server{ + Addr: ":8080", + Handler: myHandler, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + }) + log.Fatal(s.ListenAndServe()) + +The server will shut down cleanly when the Close() method is called: + + go func() { + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, os.Interrupt, os.Kill) + <-sigchan + log.Info("Shutting down...") + manners.Close() + }() + + http.Handle("/hello", myHandler) + log.Fatal(manners.ListenAndServe(":8080", nil)) +*/ +package manners + +import ( + "crypto/tls" + "net" + "net/http" + "sync" + "sync/atomic" +) + +// A GracefulServer maintains a WaitGroup that counts how many in-flight +// requests the server is handling. When it receives a shutdown signal, +// it stops accepting new requests but does not actually shut down until +// all in-flight requests terminate. +// +// GracefulServer embeds the underlying net/http.Server making its non-override +// methods and properties avaiable. +// +// It must be initialized by calling NewWithServer. +type GracefulServer struct { + *http.Server + + shutdown chan bool + shutdownFinished chan bool + wg waitGroup + routinesCount int + + lcsmu sync.RWMutex + connections map[net.Conn]bool + + up chan net.Listener // Only used by test code. +} + +// NewServer creates a new GracefulServer. +func NewServer() *GracefulServer { + return NewWithServer(new(http.Server)) +} + +// NewWithServer wraps an existing http.Server object and returns a +// GracefulServer that supports all of the original Server operations. +func NewWithServer(s *http.Server) *GracefulServer { + return &GracefulServer{ + Server: s, + shutdown: make(chan bool), + shutdownFinished: make(chan bool, 1), + wg: new(sync.WaitGroup), + routinesCount: 0, + connections: make(map[net.Conn]bool), + } +} + +// Close stops the server from accepting new requets and begins shutting down. +// It returns true if it's the first time Close is called. +func (s *GracefulServer) Close() bool { + return <-s.shutdown +} + +// BlockingClose is similar to Close, except that it blocks until the last +// connection has been closed. +func (s *GracefulServer) BlockingClose() bool { + result := s.Close() + <-s.shutdownFinished + return result +} + +// ListenAndServe provides a graceful equivalent of net/http.Serve.ListenAndServe. +func (s *GracefulServer) ListenAndServe() error { + addr := s.Addr + if addr == "" { + addr = ":http" + } + listener, err := net.Listen("tcp", addr) + if err != nil { + return err + } + + return s.Serve(listener) +} + +// ListenAndServeTLS provides a graceful equivalent of net/http.Serve.ListenAndServeTLS. +func (s *GracefulServer) ListenAndServeTLS(certFile, keyFile string) error { + // direct lift from net/http/server.go + addr := s.Addr + if addr == "" { + addr = ":https" + } + config := &tls.Config{} + if s.TLSConfig != nil { + *config = *s.TLSConfig + } + if config.NextProtos == nil { + config.NextProtos = []string{"http/1.1"} + } + + var err error + config.Certificates = make([]tls.Certificate, 1) + config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + + ln, err := net.Listen("tcp", addr) + if err != nil { + return err + } + + return s.Serve(tls.NewListener(ln, config)) +} + +// Serve provides a graceful equivalent net/http.Server.Serve. +func (s *GracefulServer) Serve(listener net.Listener) error { + // Wrap the server HTTP handler into graceful one, that will close kept + // alive connections if a new request is received after shutdown. + gracefulHandler := newGracefulHandler(s.Server.Handler) + s.Server.Handler = gracefulHandler + + // Start a goroutine that waits for a shutdown signal and will stop the + // listener when it receives the signal. That in turn will result in + // unblocking of the http.Serve call. + go func() { + s.shutdown <- true + close(s.shutdown) + gracefulHandler.Close() + s.Server.SetKeepAlivesEnabled(false) + listener.Close() + }() + + originalConnState := s.Server.ConnState + + // s.ConnState is invoked by the net/http.Server every time a connection + // changes state. It keeps track of each connection's state over time, + // enabling manners to handle persisted connections correctly. + s.ConnState = func(conn net.Conn, newState http.ConnState) { + s.lcsmu.RLock() + protected := s.connections[conn] + s.lcsmu.RUnlock() + + switch newState { + + case http.StateNew: + // New connection -> StateNew + protected = true + s.StartRoutine() + + case http.StateActive: + // (StateNew, StateIdle) -> StateActive + if gracefulHandler.IsClosed() { + conn.Close() + break + } + + if !protected { + protected = true + s.StartRoutine() + } + + default: + // (StateNew, StateActive) -> (StateIdle, StateClosed, StateHiJacked) + if protected { + s.FinishRoutine() + protected = false + } + } + + s.lcsmu.Lock() + if newState == http.StateClosed || newState == http.StateHijacked { + delete(s.connections, conn) + } else { + s.connections[conn] = protected + } + s.lcsmu.Unlock() + + if originalConnState != nil { + originalConnState(conn, newState) + } + } + + // A hook to allow the server to notify others when it is ready to receive + // requests; only used by tests. + if s.up != nil { + s.up <- listener + } + + err := s.Server.Serve(listener) + // An error returned on shutdown is not worth reporting. + if err != nil && gracefulHandler.IsClosed() { + err = nil + } + + // Wait for pending requests to complete regardless the Serve result. + s.wg.Wait() + s.shutdownFinished <- true + return err +} + +// StartRoutine increments the server's WaitGroup. Use this if a web request +// starts more goroutines and these goroutines are not guaranteed to finish +// before the request. +func (s *GracefulServer) StartRoutine() { + s.lcsmu.Lock() + defer s.lcsmu.Unlock() + s.wg.Add(1) + s.routinesCount++ +} + +// FinishRoutine decrements the server's WaitGroup. Use this to complement +// StartRoutine(). +func (s *GracefulServer) FinishRoutine() { + s.lcsmu.Lock() + defer s.lcsmu.Unlock() + s.wg.Done() + s.routinesCount-- +} + +// RoutinesCount returns the number of currently running routines +func (s *GracefulServer) RoutinesCount() int { + s.lcsmu.RLock() + defer s.lcsmu.RUnlock() + return s.routinesCount +} + +// gracefulHandler is used by GracefulServer to prevent calling ServeHTTP on +// to be closed kept-alive connections during the server shutdown. +type gracefulHandler struct { + closed int32 // accessed atomically. + wrapped http.Handler +} + +func newGracefulHandler(wrapped http.Handler) *gracefulHandler { + return &gracefulHandler{ + wrapped: wrapped, + } +} + +func (gh *gracefulHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if atomic.LoadInt32(&gh.closed) == 0 { + gh.wrapped.ServeHTTP(w, r) + return + } + r.Body.Close() + // Server is shutting down at this moment, and the connection that this + // handler is being called on is about to be closed. So we do not need to + // actually execute the handler logic. +} + +func (gh *gracefulHandler) Close() { + atomic.StoreInt32(&gh.closed, 1) +} + +func (gh *gracefulHandler) IsClosed() bool { + return atomic.LoadInt32(&gh.closed) == 1 +} diff --git a/vendor/github.com/braintree/manners/static.go b/vendor/github.com/braintree/manners/static.go new file mode 100644 index 00000000..b5395067 --- /dev/null +++ b/vendor/github.com/braintree/manners/static.go @@ -0,0 +1,47 @@ +package manners + +import ( + "net" + "net/http" + "sync" +) + +var ( + defaultServer *GracefulServer + defaultServerLock = &sync.Mutex{} +) + +func init() { + defaultServerLock.Lock() +} + +// ListenAndServe provides a graceful version of the function provided by the +// net/http package. Call Close() to stop the server. +func ListenAndServe(addr string, handler http.Handler) error { + defaultServer = NewWithServer(&http.Server{Addr: addr, Handler: handler}) + defaultServerLock.Unlock() + return defaultServer.ListenAndServe() +} + +// ListenAndServeTLS provides a graceful version of the function provided by the +// net/http package. Call Close() to stop the server. +func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error { + defaultServer = NewWithServer(&http.Server{Addr: addr, Handler: handler}) + defaultServerLock.Unlock() + return defaultServer.ListenAndServeTLS(certFile, keyFile) +} + +// Serve provides a graceful version of the function provided by the net/http +// package. Call Close() to stop the server. +func Serve(l net.Listener, handler http.Handler) error { + defaultServer = NewWithServer(&http.Server{Handler: handler}) + defaultServerLock.Unlock() + return defaultServer.Serve(l) +} + +// Shuts down the default server used by ListenAndServe, ListenAndServeTLS and +// Serve. It returns true if it's the first time Close is called. +func Close() bool { + defaultServerLock.Lock() + return defaultServer.Close() +} diff --git a/vendor/github.com/cihub/seelog/LICENSE.txt b/vendor/github.com/cihub/seelog/LICENSE.txt new file mode 100644 index 00000000..8c706814 --- /dev/null +++ b/vendor/github.com/cihub/seelog/LICENSE.txt @@ -0,0 +1,24 @@ +Copyright (c) 2012, Cloud Instruments Co., Ltd. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Cloud Instruments Co., Ltd. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/cihub/seelog/README.markdown b/vendor/github.com/cihub/seelog/README.markdown new file mode 100644 index 00000000..7dd1ab35 --- /dev/null +++ b/vendor/github.com/cihub/seelog/README.markdown @@ -0,0 +1,116 @@ +Seelog +======= + +Seelog is a powerful and easy-to-learn logging framework that provides functionality for flexible dispatching, filtering, and formatting log messages. +It is natively written in the [Go](http://golang.org/) programming language. + +[![Build Status](https://drone.io/github.com/cihub/seelog/status.png)](https://drone.io/github.com/cihub/seelog/latest) + +Features +------------------ + +* Xml configuring to be able to change logger parameters without recompilation +* Changing configurations on the fly without app restart +* Possibility to set different log configurations for different project files and functions +* Adjustable message formatting +* Simultaneous log output to multiple streams +* Choosing logger priority strategy to minimize performance hit +* Different output writers + * Console writer + * File writer + * Buffered writer (Chunk writer) + * Rolling log writer (Logging with rotation) + * SMTP writer + * Others... (See [Wiki](https://github.com/cihub/seelog/wiki)) +* Log message wrappers (JSON, XML, etc.) +* Global variables and functions for easy usage in standalone apps +* Functions for flexible usage in libraries + +Quick-start +----------- + +```go +package main + +import log "github.com/cihub/seelog" + +func main() { + defer log.Flush() + log.Info("Hello from Seelog!") +} +``` + +Installation +------------ + +If you don't have the Go development environment installed, visit the +[Getting Started](http://golang.org/doc/install.html) document and follow the instructions. Once you're ready, execute the following command: + +``` +go get -u github.com/cihub/seelog +``` + +*IMPORTANT*: If you are not using the latest release version of Go, check out this [wiki page](https://github.com/cihub/seelog/wiki/Notes-on-'go-get') + +Documentation +--------------- + +Seelog has github wiki pages, which contain detailed how-tos references: https://github.com/cihub/seelog/wiki + +Examples +--------------- + +Seelog examples can be found here: [seelog-examples](https://github.com/cihub/seelog-examples) + +Issues +--------------- + +Feel free to push issues that could make Seelog better: https://github.com/cihub/seelog/issues + +Changelog +--------------- +* **v2.6** : Config using code and custom formatters + * Configuration using code in addition to xml (All internal receiver/dispatcher/logger types are now exported). + * Custom formatters. Check [wiki](https://github.com/cihub/seelog/wiki/Custom-formatters) + * Bugfixes and internal improvements. +* **v2.5** : Interaction with other systems. Part 2: custom receivers + * Finished custom receivers feature. Check [wiki](https://github.com/cihub/seelog/wiki/custom-receivers) + * Added 'LoggerFromCustomReceiver' + * Added 'LoggerFromWriterWithMinLevelAndFormat' + * Added 'LoggerFromCustomReceiver' + * Added 'LoggerFromParamConfigAs...' +* **v2.4** : Interaction with other systems. Part 1: wrapping seelog + * Added configurable caller stack skip logic + * Added 'SetAdditionalStackDepth' to 'LoggerInterface' +* **v2.3** : Rethinking 'rolling' receiver + * Reimplemented 'rolling' receiver + * Added 'Max rolls' feature for 'rolling' receiver with type='date' + * Fixed 'rolling' receiver issue: renaming on Windows +* **v2.2** : go1.0 compatibility point [go1.0 tag] + * Fixed internal bugs + * Added 'ANSI n [;k]' format identifier: %EscN + * Made current release go1 compatible +* **v2.1** : Some new features + * Rolling receiver archiving option. + * Added format identifier: %Line + * Smtp: added paths to PEM files directories + * Added format identifier: %FuncShort + * Warn, Error and Critical methods now return an error +* **v2.0** : Second major release. BREAKING CHANGES. + * Support of binaries with stripped symbols + * Added log strategy: adaptive + * Critical message now forces Flush() + * Added predefined formats: xml-debug, xml-debug-short, xml, xml-short, json-debug, json-debug-short, json, json-short, debug, debug-short, fast + * Added receiver: conn (network connection writer) + * BREAKING CHANGE: added Tracef, Debugf, Infof, etc. to satisfy the print/printf principle + * Bug fixes +* **v1.0** : Initial release. Features: + * Xml config + * Changing configurations on the fly without app restart + * Contraints and exceptions + * Formatting + * Log strategies: sync, async loop, async timer + * Receivers: buffered, console, file, rolling, smtp + + + diff --git a/vendor/github.com/cihub/seelog/archive/archive.go b/vendor/github.com/cihub/seelog/archive/archive.go new file mode 100644 index 00000000..923036f2 --- /dev/null +++ b/vendor/github.com/cihub/seelog/archive/archive.go @@ -0,0 +1,198 @@ +package archive + +import ( + "archive/tar" + "archive/zip" + "fmt" + "io" + "io/ioutil" + "os" + "time" + + "github.com/cihub/seelog/archive/gzip" +) + +// Reader is the interface for reading files from an archive. +type Reader interface { + NextFile() (name string, err error) + io.Reader +} + +// ReadCloser is the interface that groups Reader with the Close method. +type ReadCloser interface { + Reader + io.Closer +} + +// Writer is the interface for writing files to an archived format. +type Writer interface { + NextFile(name string, fi os.FileInfo) error + io.Writer +} + +// WriteCloser is the interface that groups Writer with the Close method. +type WriteCloser interface { + Writer + io.Closer +} + +type nopCloser struct{ Reader } + +func (nopCloser) Close() error { return nil } + +// NopCloser returns a ReadCloser with a no-op Close method wrapping the +// provided Reader r. +func NopCloser(r Reader) ReadCloser { + return nopCloser{r} +} + +// Copy copies from src to dest until either EOF is reached on src or an error +// occurs. +// +// When the archive format of src matches that of dst, Copy streams the files +// directly into dst. Otherwise, copy buffers the contents to disk to compute +// headers before writing to dst. +func Copy(dst Writer, src Reader) error { + switch src := src.(type) { + case tarReader: + if dst, ok := dst.(tarWriter); ok { + return copyTar(dst, src) + } + case zipReader: + if dst, ok := dst.(zipWriter); ok { + return copyZip(dst, src) + } + // Switch on concrete type because gzip has no special methods + case *gzip.Reader: + if dst, ok := dst.(*gzip.Writer); ok { + _, err := io.Copy(dst, src) + return err + } + } + + return copyBuffer(dst, src) +} + +func copyBuffer(dst Writer, src Reader) (err error) { + const defaultFileMode = 0666 + + buf, err := ioutil.TempFile("", "archive_copy_buffer") + if err != nil { + return err + } + defer os.Remove(buf.Name()) // Do not care about failure removing temp + defer buf.Close() // Do not care about failure closing temp + for { + // Handle the next file + name, err := src.NextFile() + switch err { + case io.EOF: // Done copying + return nil + default: // Failed to write: bail out + return err + case nil: // Proceed below + } + + // Buffer the file + if _, err := io.Copy(buf, src); err != nil { + return fmt.Errorf("buffer to disk: %v", err) + } + + // Seek to the start of the file for full file copy + if _, err := buf.Seek(0, os.SEEK_SET); err != nil { + return err + } + + // Set desired file permissions + if err := os.Chmod(buf.Name(), defaultFileMode); err != nil { + return err + } + fi, err := buf.Stat() + if err != nil { + return err + } + + // Write the buffered file + if err := dst.NextFile(name, fi); err != nil { + return err + } + if _, err := io.Copy(dst, buf); err != nil { + return fmt.Errorf("copy to dst: %v", err) + } + if err := buf.Truncate(0); err != nil { + return err + } + if _, err := buf.Seek(0, os.SEEK_SET); err != nil { + return err + } + } +} + +type tarReader interface { + Next() (*tar.Header, error) + io.Reader +} + +type tarWriter interface { + WriteHeader(hdr *tar.Header) error + io.Writer +} + +type zipReader interface { + Files() []*zip.File +} + +type zipWriter interface { + CreateHeader(fh *zip.FileHeader) (io.Writer, error) +} + +func copyTar(w tarWriter, r tarReader) error { + for { + hdr, err := r.Next() + switch err { + case io.EOF: + return nil + default: // Handle error + return err + case nil: // Proceed below + } + + info := hdr.FileInfo() + // Skip directories + if info.IsDir() { + continue + } + if err := w.WriteHeader(hdr); err != nil { + return err + } + if _, err := io.Copy(w, r); err != nil { + return err + } + } +} + +func copyZip(zw zipWriter, r zipReader) error { + for _, f := range r.Files() { + if err := copyZipFile(zw, f); err != nil { + return err + } + } + return nil +} + +func copyZipFile(zw zipWriter, f *zip.File) error { + rc, err := f.Open() + if err != nil { + return err + } + defer rc.Close() // Read-only + + hdr := f.FileHeader + hdr.SetModTime(time.Now()) + w, err := zw.CreateHeader(&hdr) + if err != nil { + return err + } + _, err = io.Copy(w, rc) + return err +} diff --git a/vendor/github.com/cihub/seelog/archive/gzip/gzip.go b/vendor/github.com/cihub/seelog/archive/gzip/gzip.go new file mode 100644 index 00000000..ea121018 --- /dev/null +++ b/vendor/github.com/cihub/seelog/archive/gzip/gzip.go @@ -0,0 +1,64 @@ +// Package gzip implements reading and writing of gzip format compressed files. +// See the compress/gzip package for more details. +package gzip + +import ( + "compress/gzip" + "fmt" + "io" + "os" +) + +// Reader is an io.Reader that can be read to retrieve uncompressed data from a +// gzip-format compressed file. +type Reader struct { + gzip.Reader + name string + isEOF bool +} + +// NewReader creates a new Reader reading the given reader. +func NewReader(r io.Reader, name string) (*Reader, error) { + gr, err := gzip.NewReader(r) + if err != nil { + return nil, err + } + return &Reader{ + Reader: *gr, + name: name, + }, nil +} + +// NextFile returns the file name. Calls subsequent to the first call will +// return EOF. +func (r *Reader) NextFile() (name string, err error) { + if r.isEOF { + return "", io.EOF + } + + r.isEOF = true + return r.name, nil +} + +// Writer is an io.WriteCloser. Writes to a Writer are compressed and written to w. +type Writer struct { + gzip.Writer + name string + noMoreFiles bool +} + +// NextFile never returns a next file, and should not be called more than once. +func (w *Writer) NextFile(name string, _ os.FileInfo) error { + if w.noMoreFiles { + return fmt.Errorf("gzip: only accepts one file: already received %q and now %q", w.name, name) + } + w.noMoreFiles = true + w.name = name + return nil +} + +// NewWriter returns a new Writer. Writes to the returned writer are compressed +// and written to w. +func NewWriter(w io.Writer) *Writer { + return &Writer{Writer: *gzip.NewWriter(w)} +} diff --git a/vendor/github.com/cihub/seelog/archive/tar/tar.go b/vendor/github.com/cihub/seelog/archive/tar/tar.go new file mode 100644 index 00000000..8dd87f57 --- /dev/null +++ b/vendor/github.com/cihub/seelog/archive/tar/tar.go @@ -0,0 +1,72 @@ +package tar + +import ( + "archive/tar" + "io" + "os" +) + +// Reader provides sequential access to the contents of a tar archive. +type Reader struct { + tar.Reader +} + +// NewReader creates a new Reader reading from r. +func NewReader(r io.Reader) *Reader { + return &Reader{Reader: *tar.NewReader(r)} +} + +// NextFile advances to the next file in the tar archive. +func (r *Reader) NextFile() (name string, err error) { + hdr, err := r.Next() + if err != nil { + return "", err + } + return hdr.Name, nil +} + +// Writer provides sequential writing of a tar archive in POSIX.1 format. +type Writer struct { + tar.Writer + closers []io.Closer +} + +// NewWriter creates a new Writer writing to w. +func NewWriter(w io.Writer) *Writer { + return &Writer{Writer: *tar.NewWriter(w)} +} + +// NewWriteMultiCloser creates a new Writer writing to w that also closes all +// closers in order on close. +func NewWriteMultiCloser(w io.WriteCloser, closers ...io.Closer) *Writer { + return &Writer{ + Writer: *tar.NewWriter(w), + closers: closers, + } +} + +// NextFile computes and writes a header and prepares to accept the file's +// contents. +func (w *Writer) NextFile(name string, fi os.FileInfo) error { + if name == "" { + name = fi.Name() + } + hdr, err := tar.FileInfoHeader(fi, name) + if err != nil { + return err + } + hdr.Name = name + return w.WriteHeader(hdr) +} + +// Close closes the tar archive and all other closers, flushing any unwritten +// data to the underlying writer. +func (w *Writer) Close() error { + err := w.Writer.Close() + for _, c := range w.closers { + if cerr := c.Close(); cerr != nil && err == nil { + err = cerr + } + } + return err +} diff --git a/vendor/github.com/cihub/seelog/archive/zip/zip.go b/vendor/github.com/cihub/seelog/archive/zip/zip.go new file mode 100644 index 00000000..4210b03b --- /dev/null +++ b/vendor/github.com/cihub/seelog/archive/zip/zip.go @@ -0,0 +1,89 @@ +package zip + +import ( + "archive/zip" + "io" + "os" +) + +// Reader provides sequential access to the contents of a zip archive. +type Reader struct { + zip.Reader + unread []*zip.File + rc io.ReadCloser +} + +// NewReader returns a new Reader reading from r, which is assumed to have the +// given size in bytes. +func NewReader(r io.ReaderAt, size int64) (*Reader, error) { + zr, err := zip.NewReader(r, size) + if err != nil { + return nil, err + } + return &Reader{Reader: *zr}, nil +} + +// NextFile advances to the next file in the zip archive. +func (r *Reader) NextFile() (name string, err error) { + // Initialize unread + if r.unread == nil { + r.unread = r.Files()[:] + } + + // Close previous file + if r.rc != nil { + r.rc.Close() // Read-only + } + + if len(r.unread) == 0 { + return "", io.EOF + } + + // Open and return next unread + f := r.unread[0] + name, r.unread = f.Name, r.unread[1:] + r.rc, err = f.Open() + if err != nil { + return "", err + } + return name, nil +} + +func (r *Reader) Read(p []byte) (n int, err error) { + return r.rc.Read(p) +} + +// Files returns the full list of files in the zip archive. +func (r *Reader) Files() []*zip.File { + return r.File +} + +// Writer provides sequential writing of a zip archive.1 format. +type Writer struct { + zip.Writer + w io.Writer +} + +// NewWriter returns a new Writer writing to w. +func NewWriter(w io.Writer) *Writer { + return &Writer{Writer: *zip.NewWriter(w)} +} + +// NextFile computes and writes a header and prepares to accept the file's +// contents. +func (w *Writer) NextFile(name string, fi os.FileInfo) error { + if name == "" { + name = fi.Name() + } + hdr, err := zip.FileInfoHeader(fi) + if err != nil { + return err + } + hdr.Name = name + w.w, err = w.CreateHeader(hdr) + return err +} + +func (w *Writer) Write(p []byte) (n int, err error) { + return w.w.Write(p) +} diff --git a/vendor/github.com/cihub/seelog/behavior_adaptivelogger.go b/vendor/github.com/cihub/seelog/behavior_adaptivelogger.go new file mode 100644 index 00000000..0c640cae --- /dev/null +++ b/vendor/github.com/cihub/seelog/behavior_adaptivelogger.go @@ -0,0 +1,129 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" + "fmt" + "math" + "time" +) + +var ( + adaptiveLoggerMaxInterval = time.Minute + adaptiveLoggerMaxCriticalMsgCount = uint32(1000) +) + +// asyncAdaptiveLogger represents asynchronous adaptive logger which acts like +// an async timer logger, but its interval depends on the current message count +// in the queue. +// +// Interval = I, minInterval = m, maxInterval = M, criticalMsgCount = C, msgCount = c: +// I = m + (C - Min(c, C)) / C * (M - m) +type asyncAdaptiveLogger struct { + asyncLogger + minInterval time.Duration + criticalMsgCount uint32 + maxInterval time.Duration +} + +// NewAsyncLoopLogger creates a new asynchronous adaptive logger +func NewAsyncAdaptiveLogger( + config *logConfig, + minInterval time.Duration, + maxInterval time.Duration, + criticalMsgCount uint32) (*asyncAdaptiveLogger, error) { + + if minInterval <= 0 { + return nil, errors.New("async adaptive logger min interval should be > 0") + } + + if maxInterval > adaptiveLoggerMaxInterval { + return nil, fmt.Errorf("async adaptive logger max interval should be <= %s", + adaptiveLoggerMaxInterval) + } + + if criticalMsgCount <= 0 { + return nil, errors.New("async adaptive logger critical msg count should be > 0") + } + + if criticalMsgCount > adaptiveLoggerMaxCriticalMsgCount { + return nil, fmt.Errorf("async adaptive logger critical msg count should be <= %s", + adaptiveLoggerMaxInterval) + } + + asnAdaptiveLogger := new(asyncAdaptiveLogger) + + asnAdaptiveLogger.asyncLogger = *newAsyncLogger(config) + asnAdaptiveLogger.minInterval = minInterval + asnAdaptiveLogger.maxInterval = maxInterval + asnAdaptiveLogger.criticalMsgCount = criticalMsgCount + + go asnAdaptiveLogger.processQueue() + + return asnAdaptiveLogger, nil +} + +func (asnAdaptiveLogger *asyncAdaptiveLogger) processItem() (closed bool, itemCount int) { + asnAdaptiveLogger.queueHasElements.L.Lock() + defer asnAdaptiveLogger.queueHasElements.L.Unlock() + + for asnAdaptiveLogger.msgQueue.Len() == 0 && !asnAdaptiveLogger.Closed() { + asnAdaptiveLogger.queueHasElements.Wait() + } + + if asnAdaptiveLogger.Closed() { + return true, asnAdaptiveLogger.msgQueue.Len() + } + + asnAdaptiveLogger.processQueueElement() + return false, asnAdaptiveLogger.msgQueue.Len() - 1 +} + +// I = m + (C - Min(c, C)) / C * (M - m) => +// I = m + cDiff * mDiff, +// cDiff = (C - Min(c, C)) / C) +// mDiff = (M - m) +func (asnAdaptiveLogger *asyncAdaptiveLogger) calcAdaptiveInterval(msgCount int) time.Duration { + critCountF := float64(asnAdaptiveLogger.criticalMsgCount) + cDiff := (critCountF - math.Min(float64(msgCount), critCountF)) / critCountF + mDiff := float64(asnAdaptiveLogger.maxInterval - asnAdaptiveLogger.minInterval) + + return asnAdaptiveLogger.minInterval + time.Duration(cDiff*mDiff) +} + +func (asnAdaptiveLogger *asyncAdaptiveLogger) processQueue() { + for !asnAdaptiveLogger.Closed() { + closed, itemCount := asnAdaptiveLogger.processItem() + + if closed { + break + } + + interval := asnAdaptiveLogger.calcAdaptiveInterval(itemCount) + + <-time.After(interval) + } +} diff --git a/vendor/github.com/cihub/seelog/behavior_asynclogger.go b/vendor/github.com/cihub/seelog/behavior_asynclogger.go new file mode 100644 index 00000000..75231067 --- /dev/null +++ b/vendor/github.com/cihub/seelog/behavior_asynclogger.go @@ -0,0 +1,142 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "container/list" + "fmt" + "sync" +) + +// MaxQueueSize is the critical number of messages in the queue that result in an immediate flush. +const ( + MaxQueueSize = 10000 +) + +type msgQueueItem struct { + level LogLevel + context LogContextInterface + message fmt.Stringer +} + +// asyncLogger represents common data for all asynchronous loggers +type asyncLogger struct { + commonLogger + msgQueue *list.List + queueHasElements *sync.Cond +} + +// newAsyncLogger creates a new asynchronous logger +func newAsyncLogger(config *logConfig) *asyncLogger { + asnLogger := new(asyncLogger) + + asnLogger.msgQueue = list.New() + asnLogger.queueHasElements = sync.NewCond(new(sync.Mutex)) + + asnLogger.commonLogger = *newCommonLogger(config, asnLogger) + + return asnLogger +} + +func (asnLogger *asyncLogger) innerLog( + level LogLevel, + context LogContextInterface, + message fmt.Stringer) { + + asnLogger.addMsgToQueue(level, context, message) +} + +func (asnLogger *asyncLogger) Close() { + asnLogger.m.Lock() + defer asnLogger.m.Unlock() + + if !asnLogger.Closed() { + asnLogger.flushQueue(true) + asnLogger.config.RootDispatcher.Flush() + + if err := asnLogger.config.RootDispatcher.Close(); err != nil { + reportInternalError(err) + } + + asnLogger.closedM.Lock() + asnLogger.closed = true + asnLogger.closedM.Unlock() + asnLogger.queueHasElements.Broadcast() + } +} + +func (asnLogger *asyncLogger) Flush() { + asnLogger.m.Lock() + defer asnLogger.m.Unlock() + + if !asnLogger.Closed() { + asnLogger.flushQueue(true) + asnLogger.config.RootDispatcher.Flush() + } +} + +func (asnLogger *asyncLogger) flushQueue(lockNeeded bool) { + if lockNeeded { + asnLogger.queueHasElements.L.Lock() + defer asnLogger.queueHasElements.L.Unlock() + } + + for asnLogger.msgQueue.Len() > 0 { + asnLogger.processQueueElement() + } +} + +func (asnLogger *asyncLogger) processQueueElement() { + if asnLogger.msgQueue.Len() > 0 { + backElement := asnLogger.msgQueue.Front() + msg, _ := backElement.Value.(msgQueueItem) + asnLogger.processLogMsg(msg.level, msg.message, msg.context) + asnLogger.msgQueue.Remove(backElement) + } +} + +func (asnLogger *asyncLogger) addMsgToQueue( + level LogLevel, + context LogContextInterface, + message fmt.Stringer) { + + if !asnLogger.Closed() { + asnLogger.queueHasElements.L.Lock() + defer asnLogger.queueHasElements.L.Unlock() + + if asnLogger.msgQueue.Len() >= MaxQueueSize { + fmt.Printf("Seelog queue overflow: more than %v messages in the queue. Flushing.\n", MaxQueueSize) + asnLogger.flushQueue(false) + } + + queueItem := msgQueueItem{level, context, message} + + asnLogger.msgQueue.PushBack(queueItem) + asnLogger.queueHasElements.Broadcast() + } else { + err := fmt.Errorf("queue closed! Cannot process element: %d %#v", level, message) + reportInternalError(err) + } +} diff --git a/vendor/github.com/cihub/seelog/behavior_asynclooplogger.go b/vendor/github.com/cihub/seelog/behavior_asynclooplogger.go new file mode 100644 index 00000000..972467b3 --- /dev/null +++ b/vendor/github.com/cihub/seelog/behavior_asynclooplogger.go @@ -0,0 +1,69 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +// asyncLoopLogger represents asynchronous logger which processes the log queue in +// a 'for' loop +type asyncLoopLogger struct { + asyncLogger +} + +// NewAsyncLoopLogger creates a new asynchronous loop logger +func NewAsyncLoopLogger(config *logConfig) *asyncLoopLogger { + + asnLoopLogger := new(asyncLoopLogger) + + asnLoopLogger.asyncLogger = *newAsyncLogger(config) + + go asnLoopLogger.processQueue() + + return asnLoopLogger +} + +func (asnLoopLogger *asyncLoopLogger) processItem() (closed bool) { + asnLoopLogger.queueHasElements.L.Lock() + defer asnLoopLogger.queueHasElements.L.Unlock() + + for asnLoopLogger.msgQueue.Len() == 0 && !asnLoopLogger.Closed() { + asnLoopLogger.queueHasElements.Wait() + } + + if asnLoopLogger.Closed() { + return true + } + + asnLoopLogger.processQueueElement() + return false +} + +func (asnLoopLogger *asyncLoopLogger) processQueue() { + for !asnLoopLogger.Closed() { + closed := asnLoopLogger.processItem() + + if closed { + break + } + } +} diff --git a/vendor/github.com/cihub/seelog/behavior_asynctimerlogger.go b/vendor/github.com/cihub/seelog/behavior_asynctimerlogger.go new file mode 100644 index 00000000..8118f205 --- /dev/null +++ b/vendor/github.com/cihub/seelog/behavior_asynctimerlogger.go @@ -0,0 +1,82 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" + "time" +) + +// asyncTimerLogger represents asynchronous logger which processes the log queue each +// 'duration' nanoseconds +type asyncTimerLogger struct { + asyncLogger + interval time.Duration +} + +// NewAsyncLoopLogger creates a new asynchronous loop logger +func NewAsyncTimerLogger(config *logConfig, interval time.Duration) (*asyncTimerLogger, error) { + + if interval <= 0 { + return nil, errors.New("async logger interval should be > 0") + } + + asnTimerLogger := new(asyncTimerLogger) + + asnTimerLogger.asyncLogger = *newAsyncLogger(config) + asnTimerLogger.interval = interval + + go asnTimerLogger.processQueue() + + return asnTimerLogger, nil +} + +func (asnTimerLogger *asyncTimerLogger) processItem() (closed bool) { + asnTimerLogger.queueHasElements.L.Lock() + defer asnTimerLogger.queueHasElements.L.Unlock() + + for asnTimerLogger.msgQueue.Len() == 0 && !asnTimerLogger.Closed() { + asnTimerLogger.queueHasElements.Wait() + } + + if asnTimerLogger.Closed() { + return true + } + + asnTimerLogger.processQueueElement() + return false +} + +func (asnTimerLogger *asyncTimerLogger) processQueue() { + for !asnTimerLogger.Closed() { + closed := asnTimerLogger.processItem() + + if closed { + break + } + + <-time.After(asnTimerLogger.interval) + } +} diff --git a/vendor/github.com/cihub/seelog/behavior_synclogger.go b/vendor/github.com/cihub/seelog/behavior_synclogger.go new file mode 100644 index 00000000..5a022ebc --- /dev/null +++ b/vendor/github.com/cihub/seelog/behavior_synclogger.go @@ -0,0 +1,75 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "fmt" +) + +// syncLogger performs logging in the same goroutine where 'Trace/Debug/...' +// func was called +type syncLogger struct { + commonLogger +} + +// NewSyncLogger creates a new synchronous logger +func NewSyncLogger(config *logConfig) *syncLogger { + syncLogger := new(syncLogger) + + syncLogger.commonLogger = *newCommonLogger(config, syncLogger) + + return syncLogger +} + +func (syncLogger *syncLogger) innerLog( + level LogLevel, + context LogContextInterface, + message fmt.Stringer) { + + syncLogger.processLogMsg(level, message, context) +} + +func (syncLogger *syncLogger) Close() { + syncLogger.m.Lock() + defer syncLogger.m.Unlock() + + if !syncLogger.Closed() { + if err := syncLogger.config.RootDispatcher.Close(); err != nil { + reportInternalError(err) + } + syncLogger.closedM.Lock() + syncLogger.closed = true + syncLogger.closedM.Unlock() + } +} + +func (syncLogger *syncLogger) Flush() { + syncLogger.m.Lock() + defer syncLogger.m.Unlock() + + if !syncLogger.Closed() { + syncLogger.config.RootDispatcher.Flush() + } +} diff --git a/vendor/github.com/cihub/seelog/cfg_config.go b/vendor/github.com/cihub/seelog/cfg_config.go new file mode 100644 index 00000000..76554fca --- /dev/null +++ b/vendor/github.com/cihub/seelog/cfg_config.go @@ -0,0 +1,212 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "os" +) + +// LoggerFromConfigAsFile creates logger with config from file. File should contain valid seelog xml. +func LoggerFromConfigAsFile(fileName string) (LoggerInterface, error) { + file, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer file.Close() + + conf, err := configFromReader(file) + if err != nil { + return nil, err + } + + return createLoggerFromFullConfig(conf) +} + +// LoggerFromConfigAsBytes creates a logger with config from bytes stream. Bytes should contain valid seelog xml. +func LoggerFromConfigAsBytes(data []byte) (LoggerInterface, error) { + conf, err := configFromReader(bytes.NewBuffer(data)) + if err != nil { + return nil, err + } + + return createLoggerFromFullConfig(conf) +} + +// LoggerFromConfigAsString creates a logger with config from a string. String should contain valid seelog xml. +func LoggerFromConfigAsString(data string) (LoggerInterface, error) { + return LoggerFromConfigAsBytes([]byte(data)) +} + +// LoggerFromParamConfigAsFile does the same as LoggerFromConfigAsFile, but includes special parser options. +// See 'CfgParseParams' comments. +func LoggerFromParamConfigAsFile(fileName string, parserParams *CfgParseParams) (LoggerInterface, error) { + file, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer file.Close() + + conf, err := configFromReaderWithConfig(file, parserParams) + if err != nil { + return nil, err + } + + return createLoggerFromFullConfig(conf) +} + +// LoggerFromParamConfigAsBytes does the same as LoggerFromConfigAsBytes, but includes special parser options. +// See 'CfgParseParams' comments. +func LoggerFromParamConfigAsBytes(data []byte, parserParams *CfgParseParams) (LoggerInterface, error) { + conf, err := configFromReaderWithConfig(bytes.NewBuffer(data), parserParams) + if err != nil { + return nil, err + } + + return createLoggerFromFullConfig(conf) +} + +// LoggerFromParamConfigAsString does the same as LoggerFromConfigAsString, but includes special parser options. +// See 'CfgParseParams' comments. +func LoggerFromParamConfigAsString(data string, parserParams *CfgParseParams) (LoggerInterface, error) { + return LoggerFromParamConfigAsBytes([]byte(data), parserParams) +} + +// LoggerFromWriterWithMinLevel is shortcut for LoggerFromWriterWithMinLevelAndFormat(output, minLevel, DefaultMsgFormat) +func LoggerFromWriterWithMinLevel(output io.Writer, minLevel LogLevel) (LoggerInterface, error) { + return LoggerFromWriterWithMinLevelAndFormat(output, minLevel, DefaultMsgFormat) +} + +// LoggerFromWriterWithMinLevelAndFormat creates a proxy logger that uses io.Writer as the +// receiver with minimal level = minLevel and with specified format. +// +// All messages with level more or equal to minLevel will be written to output and +// formatted using the default seelog format. +// +// Can be called for usage with non-Seelog systems +func LoggerFromWriterWithMinLevelAndFormat(output io.Writer, minLevel LogLevel, format string) (LoggerInterface, error) { + constraints, err := NewMinMaxConstraints(minLevel, CriticalLvl) + if err != nil { + return nil, err + } + formatter, err := NewFormatter(format) + if err != nil { + return nil, err + } + dispatcher, err := NewSplitDispatcher(formatter, []interface{}{output}) + if err != nil { + return nil, err + } + + conf, err := newFullLoggerConfig(constraints, make([]*LogLevelException, 0), dispatcher, syncloggerTypeFromString, nil, nil) + if err != nil { + return nil, err + } + + return createLoggerFromFullConfig(conf) +} + +// LoggerFromXMLDecoder creates logger with config from a XML decoder starting from a specific node. +// It should contain valid seelog xml, except for root node name. +func LoggerFromXMLDecoder(xmlParser *xml.Decoder, rootNode xml.Token) (LoggerInterface, error) { + conf, err := configFromXMLDecoder(xmlParser, rootNode) + if err != nil { + return nil, err + } + + return createLoggerFromFullConfig(conf) +} + +// LoggerFromCustomReceiver creates a proxy logger that uses a CustomReceiver as the +// receiver. +// +// All messages will be sent to the specified custom receiver without additional +// formatting ('%Msg' format is used). +// +// Check CustomReceiver, RegisterReceiver for additional info. +// +// NOTE 1: CustomReceiver.AfterParse is only called when a receiver is instantiated +// by the config parser while parsing config. So, if you are not planning to use the +// same CustomReceiver for both proxying (via LoggerFromCustomReceiver call) and +// loading from config, just leave AfterParse implementation empty. +// +// NOTE 2: Unlike RegisterReceiver, LoggerFromCustomReceiver takes an already initialized +// instance that implements CustomReceiver. So, fill it with data and perform any initialization +// logic before calling this func and it won't be lost. +// +// So: +// * RegisterReceiver takes value just to get the reflect.Type from it and then +// instantiate it as many times as config is reloaded. +// +// * LoggerFromCustomReceiver takes value and uses it without modification and +// reinstantiation, directy passing it to the dispatcher tree. +func LoggerFromCustomReceiver(receiver CustomReceiver) (LoggerInterface, error) { + constraints, err := NewMinMaxConstraints(TraceLvl, CriticalLvl) + if err != nil { + return nil, err + } + + output, err := NewCustomReceiverDispatcherByValue(msgonlyformatter, receiver, "user-proxy", CustomReceiverInitArgs{}) + if err != nil { + return nil, err + } + dispatcher, err := NewSplitDispatcher(msgonlyformatter, []interface{}{output}) + if err != nil { + return nil, err + } + + conf, err := newFullLoggerConfig(constraints, make([]*LogLevelException, 0), dispatcher, syncloggerTypeFromString, nil, nil) + if err != nil { + return nil, err + } + + return createLoggerFromFullConfig(conf) +} + +func CloneLogger(logger LoggerInterface) (LoggerInterface, error) { + switch logger := logger.(type) { + default: + return nil, fmt.Errorf("unexpected type %T", logger) + case *asyncAdaptiveLogger: + clone, err := NewAsyncAdaptiveLogger(logger.commonLogger.config, logger.minInterval, logger.maxInterval, logger.criticalMsgCount) + if err != nil { + return nil, err + } + return clone, nil + case *asyncLoopLogger: + return NewAsyncLoopLogger(logger.commonLogger.config), nil + case *asyncTimerLogger: + clone, err := NewAsyncTimerLogger(logger.commonLogger.config, logger.interval) + if err != nil { + return nil, err + } + return clone, nil + case *syncLogger: + return NewSyncLogger(logger.commonLogger.config), nil + } +} diff --git a/vendor/github.com/cihub/seelog/cfg_errors.go b/vendor/github.com/cihub/seelog/cfg_errors.go new file mode 100644 index 00000000..c1fb4d10 --- /dev/null +++ b/vendor/github.com/cihub/seelog/cfg_errors.go @@ -0,0 +1,61 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" +) + +var ( + errNodeMustHaveChildren = errors.New("node must have children") + errNodeCannotHaveChildren = errors.New("node cannot have children") +) + +type unexpectedChildElementError struct { + baseError +} + +func newUnexpectedChildElementError(msg string) *unexpectedChildElementError { + custmsg := "Unexpected child element: " + msg + return &unexpectedChildElementError{baseError{message: custmsg}} +} + +type missingArgumentError struct { + baseError +} + +func newMissingArgumentError(nodeName, attrName string) *missingArgumentError { + custmsg := "Output '" + nodeName + "' has no '" + attrName + "' attribute" + return &missingArgumentError{baseError{message: custmsg}} +} + +type unexpectedAttributeError struct { + baseError +} + +func newUnexpectedAttributeError(nodeName, attr string) *unexpectedAttributeError { + custmsg := nodeName + " has unexpected attribute: " + attr + return &unexpectedAttributeError{baseError{message: custmsg}} +} diff --git a/vendor/github.com/cihub/seelog/cfg_logconfig.go b/vendor/github.com/cihub/seelog/cfg_logconfig.go new file mode 100644 index 00000000..6ba6f9a9 --- /dev/null +++ b/vendor/github.com/cihub/seelog/cfg_logconfig.go @@ -0,0 +1,141 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" +) + +type loggerTypeFromString uint8 + +const ( + syncloggerTypeFromString = iota + asyncLooploggerTypeFromString + asyncTimerloggerTypeFromString + adaptiveLoggerTypeFromString + defaultloggerTypeFromString = asyncLooploggerTypeFromString +) + +const ( + syncloggerTypeFromStringStr = "sync" + asyncloggerTypeFromStringStr = "asyncloop" + asyncTimerloggerTypeFromStringStr = "asynctimer" + adaptiveLoggerTypeFromStringStr = "adaptive" +) + +// asyncTimerLoggerData represents specific data for async timer logger +type asyncTimerLoggerData struct { + AsyncInterval uint32 +} + +// adaptiveLoggerData represents specific data for adaptive timer logger +type adaptiveLoggerData struct { + MinInterval uint32 + MaxInterval uint32 + CriticalMsgCount uint32 +} + +var loggerTypeToStringRepresentations = map[loggerTypeFromString]string{ + syncloggerTypeFromString: syncloggerTypeFromStringStr, + asyncLooploggerTypeFromString: asyncloggerTypeFromStringStr, + asyncTimerloggerTypeFromString: asyncTimerloggerTypeFromStringStr, + adaptiveLoggerTypeFromString: adaptiveLoggerTypeFromStringStr, +} + +// getLoggerTypeFromString parses a string and returns a corresponding logger type, if successful. +func getLoggerTypeFromString(logTypeString string) (level loggerTypeFromString, found bool) { + for logType, logTypeStr := range loggerTypeToStringRepresentations { + if logTypeStr == logTypeString { + return logType, true + } + } + + return 0, false +} + +// logConfig stores logging configuration. Contains messages dispatcher, allowed log level rules +// (general constraints and exceptions) +type logConfig struct { + Constraints logLevelConstraints // General log level rules (>min and ' element. It takes the 'name' attribute + // of the element and tries to find a match in two places: + // 1) CfgParseParams.CustomReceiverProducers map + // 2) Global type map, filled by RegisterReceiver + // + // If a match is found in the CustomReceiverProducers map, parser calls the corresponding producer func + // passing the init args to it. The func takes exactly the same args as CustomReceiver.AfterParse. + // The producer func must return a correct receiver or an error. If case of error, seelog will behave + // in the same way as with any other config error. + // + // You may use this param to set custom producers in case you need to pass some context when instantiating + // a custom receiver or if you frequently change custom receivers with different parameters or in any other + // situation where package-level registering (RegisterReceiver) is not an option for you. + CustomReceiverProducers map[string]CustomReceiverProducer +} + +func (cfg *CfgParseParams) String() string { + return fmt.Sprintf("CfgParams: {custom_recs=%d}", len(cfg.CustomReceiverProducers)) +} + +type elementMapEntry struct { + constructor func(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) +} + +var elementMap map[string]elementMapEntry +var predefinedFormats map[string]*formatter + +func init() { + elementMap = map[string]elementMapEntry{ + fileWriterID: {createfileWriter}, + splitterDispatcherID: {createSplitter}, + customReceiverID: {createCustomReceiver}, + filterDispatcherID: {createFilter}, + consoleWriterID: {createConsoleWriter}, + rollingfileWriterID: {createRollingFileWriter}, + bufferedWriterID: {createbufferedWriter}, + smtpWriterID: {createSMTPWriter}, + connWriterID: {createconnWriter}, + } + + err := fillPredefinedFormats() + if err != nil { + panic(fmt.Sprintf("Seelog couldn't start: predefined formats creation failed. Error: %s", err.Error())) + } +} + +func fillPredefinedFormats() error { + predefinedFormatsWithoutPrefix := map[string]string{ + "xml-debug": `%Lev%Msg%RelFile%Func%Line`, + "xml-debug-short": `%Ns%l%Msg

%RelFile

%Func`, + "xml": `%Lev%Msg`, + "xml-short": `%Ns%l%Msg`, + + "json-debug": `{"time":%Ns,"lev":"%Lev","msg":"%Msg","path":"%RelFile","func":"%Func","line":"%Line"}`, + "json-debug-short": `{"t":%Ns,"l":"%Lev","m":"%Msg","p":"%RelFile","f":"%Func"}`, + "json": `{"time":%Ns,"lev":"%Lev","msg":"%Msg"}`, + "json-short": `{"t":%Ns,"l":"%Lev","m":"%Msg"}`, + + "debug": `[%LEVEL] %RelFile:%Func.%Line %Date %Time %Msg%n`, + "debug-short": `[%LEVEL] %Date %Time %Msg%n`, + "fast": `%Ns %l %Msg%n`, + } + + predefinedFormats = make(map[string]*formatter) + + for formatKey, format := range predefinedFormatsWithoutPrefix { + formatter, err := NewFormatter(format) + if err != nil { + return err + } + + predefinedFormats[predefinedPrefix+formatKey] = formatter + } + + return nil +} + +// configFromXMLDecoder parses data from a given XML decoder. +// Returns parsed config which can be used to create logger in case no errors occured. +// Returns error if format is incorrect or anything happened. +func configFromXMLDecoder(xmlParser *xml.Decoder, rootNode xml.Token) (*configForParsing, error) { + return configFromXMLDecoderWithConfig(xmlParser, rootNode, nil) +} + +// configFromXMLDecoderWithConfig parses data from a given XML decoder. +// Returns parsed config which can be used to create logger in case no errors occured. +// Returns error if format is incorrect or anything happened. +func configFromXMLDecoderWithConfig(xmlParser *xml.Decoder, rootNode xml.Token, cfg *CfgParseParams) (*configForParsing, error) { + _, ok := rootNode.(xml.StartElement) + if !ok { + return nil, errors.New("rootNode must be XML startElement") + } + + config, err := unmarshalNode(xmlParser, rootNode) + if err != nil { + return nil, err + } + if config == nil { + return nil, errors.New("xml has no content") + } + + return configFromXMLNodeWithConfig(config, cfg) +} + +// configFromReader parses data from a given reader. +// Returns parsed config which can be used to create logger in case no errors occured. +// Returns error if format is incorrect or anything happened. +func configFromReader(reader io.Reader) (*configForParsing, error) { + return configFromReaderWithConfig(reader, nil) +} + +// configFromReaderWithConfig parses data from a given reader. +// Returns parsed config which can be used to create logger in case no errors occured. +// Returns error if format is incorrect or anything happened. +func configFromReaderWithConfig(reader io.Reader, cfg *CfgParseParams) (*configForParsing, error) { + config, err := unmarshalConfig(reader) + if err != nil { + return nil, err + } + + if config.name != seelogConfigID { + return nil, errors.New("root xml tag must be '" + seelogConfigID + "'") + } + + return configFromXMLNodeWithConfig(config, cfg) +} + +func configFromXMLNodeWithConfig(config *xmlNode, cfg *CfgParseParams) (*configForParsing, error) { + err := checkUnexpectedAttribute( + config, + minLevelID, + maxLevelID, + levelsID, + loggerTypeFromStringAttr, + asyncLoggerIntervalAttr, + adaptLoggerMinIntervalAttr, + adaptLoggerMaxIntervalAttr, + adaptLoggerCriticalMsgCountAttr, + ) + if err != nil { + return nil, err + } + + err = checkExpectedElements(config, optionalElement(outputsID), optionalElement(formatsID), optionalElement(exceptionsID)) + if err != nil { + return nil, err + } + + constraints, err := getConstraints(config) + if err != nil { + return nil, err + } + + exceptions, err := getExceptions(config) + if err != nil { + return nil, err + } + err = checkDistinctExceptions(exceptions) + if err != nil { + return nil, err + } + + formats, err := getFormats(config) + if err != nil { + return nil, err + } + + dispatcher, err := getOutputsTree(config, formats, cfg) + if err != nil { + // If we open several files, but then fail to parse the config, we should close + // those files before reporting that config is invalid. + if dispatcher != nil { + dispatcher.Close() + } + + return nil, err + } + + loggerType, logData, err := getloggerTypeFromStringData(config) + if err != nil { + return nil, err + } + + return newFullLoggerConfig(constraints, exceptions, dispatcher, loggerType, logData, cfg) +} + +func getConstraints(node *xmlNode) (logLevelConstraints, error) { + minLevelStr, isMinLevel := node.attributes[minLevelID] + maxLevelStr, isMaxLevel := node.attributes[maxLevelID] + levelsStr, isLevels := node.attributes[levelsID] + + if isLevels && (isMinLevel && isMaxLevel) { + return nil, errors.New("for level declaration use '" + levelsID + "'' OR '" + minLevelID + + "', '" + maxLevelID + "'") + } + + offString := LogLevel(Off).String() + + if (isLevels && strings.TrimSpace(levelsStr) == offString) || + (isMinLevel && !isMaxLevel && minLevelStr == offString) { + + return NewOffConstraints() + } + + if isLevels { + levels, err := parseLevels(levelsStr) + if err != nil { + return nil, err + } + return NewListConstraints(levels) + } + + var minLevel = LogLevel(TraceLvl) + if isMinLevel { + found := true + minLevel, found = LogLevelFromString(minLevelStr) + if !found { + return nil, errors.New("declared " + minLevelID + " not found: " + minLevelStr) + } + } + + var maxLevel = LogLevel(CriticalLvl) + if isMaxLevel { + found := true + maxLevel, found = LogLevelFromString(maxLevelStr) + if !found { + return nil, errors.New("declared " + maxLevelID + " not found: " + maxLevelStr) + } + } + + return NewMinMaxConstraints(minLevel, maxLevel) +} + +func parseLevels(str string) ([]LogLevel, error) { + levelsStrArr := strings.Split(strings.Replace(str, " ", "", -1), ",") + var levels []LogLevel + for _, levelStr := range levelsStrArr { + level, found := LogLevelFromString(levelStr) + if !found { + return nil, errors.New("declared level not found: " + levelStr) + } + + levels = append(levels, level) + } + + return levels, nil +} + +func getExceptions(config *xmlNode) ([]*LogLevelException, error) { + var exceptions []*LogLevelException + + var exceptionsNode *xmlNode + for _, child := range config.children { + if child.name == exceptionsID { + exceptionsNode = child + break + } + } + + if exceptionsNode == nil { + return exceptions, nil + } + + err := checkUnexpectedAttribute(exceptionsNode) + if err != nil { + return nil, err + } + + err = checkExpectedElements(exceptionsNode, multipleMandatoryElements("exception")) + if err != nil { + return nil, err + } + + for _, exceptionNode := range exceptionsNode.children { + if exceptionNode.name != exceptionID { + return nil, errors.New("incorrect nested element in exceptions section: " + exceptionNode.name) + } + + err := checkUnexpectedAttribute(exceptionNode, minLevelID, maxLevelID, levelsID, funcPatternID, filePatternID) + if err != nil { + return nil, err + } + + constraints, err := getConstraints(exceptionNode) + if err != nil { + return nil, errors.New("incorrect " + exceptionsID + " node: " + err.Error()) + } + + funcPattern, isFuncPattern := exceptionNode.attributes[funcPatternID] + filePattern, isFilePattern := exceptionNode.attributes[filePatternID] + if !isFuncPattern { + funcPattern = "*" + } + if !isFilePattern { + filePattern = "*" + } + + exception, err := NewLogLevelException(funcPattern, filePattern, constraints) + if err != nil { + return nil, errors.New("incorrect exception node: " + err.Error()) + } + + exceptions = append(exceptions, exception) + } + + return exceptions, nil +} + +func checkDistinctExceptions(exceptions []*LogLevelException) error { + for i, exception := range exceptions { + for j, exception1 := range exceptions { + if i == j { + continue + } + + if exception.FuncPattern() == exception1.FuncPattern() && + exception.FilePattern() == exception1.FilePattern() { + + return fmt.Errorf("there are two or more duplicate exceptions. Func: %v, file %v", + exception.FuncPattern(), exception.FilePattern()) + } + } + } + + return nil +} + +func getFormats(config *xmlNode) (map[string]*formatter, error) { + formats := make(map[string]*formatter, 0) + + var formatsNode *xmlNode + for _, child := range config.children { + if child.name == formatsID { + formatsNode = child + break + } + } + + if formatsNode == nil { + return formats, nil + } + + err := checkUnexpectedAttribute(formatsNode) + if err != nil { + return nil, err + } + + err = checkExpectedElements(formatsNode, multipleMandatoryElements("format")) + if err != nil { + return nil, err + } + + for _, formatNode := range formatsNode.children { + if formatNode.name != formatID { + return nil, errors.New("incorrect nested element in " + formatsID + " section: " + formatNode.name) + } + + err := checkUnexpectedAttribute(formatNode, formatKeyAttrID, formatID) + if err != nil { + return nil, err + } + + id, isID := formatNode.attributes[formatKeyAttrID] + formatStr, isFormat := formatNode.attributes[formatAttrID] + if !isID { + return nil, errors.New("format has no '" + formatKeyAttrID + "' attribute") + } + if !isFormat { + return nil, errors.New("format[" + id + "] has no '" + formatAttrID + "' attribute") + } + + formatter, err := NewFormatter(formatStr) + if err != nil { + return nil, err + } + + formats[id] = formatter + } + + return formats, nil +} + +func getloggerTypeFromStringData(config *xmlNode) (logType loggerTypeFromString, logData interface{}, err error) { + logTypeStr, loggerTypeExists := config.attributes[loggerTypeFromStringAttr] + + if !loggerTypeExists { + return defaultloggerTypeFromString, nil, nil + } + + logType, found := getLoggerTypeFromString(logTypeStr) + + if !found { + return 0, nil, fmt.Errorf("unknown logger type: %s", logTypeStr) + } + + if logType == asyncTimerloggerTypeFromString { + intervalStr, intervalExists := config.attributes[asyncLoggerIntervalAttr] + if !intervalExists { + return 0, nil, newMissingArgumentError(config.name, asyncLoggerIntervalAttr) + } + + interval, err := strconv.ParseUint(intervalStr, 10, 32) + if err != nil { + return 0, nil, err + } + + logData = asyncTimerLoggerData{uint32(interval)} + } else if logType == adaptiveLoggerTypeFromString { + + // Min interval + minIntStr, minIntExists := config.attributes[adaptLoggerMinIntervalAttr] + if !minIntExists { + return 0, nil, newMissingArgumentError(config.name, adaptLoggerMinIntervalAttr) + } + minInterval, err := strconv.ParseUint(minIntStr, 10, 32) + if err != nil { + return 0, nil, err + } + + // Max interval + maxIntStr, maxIntExists := config.attributes[adaptLoggerMaxIntervalAttr] + if !maxIntExists { + return 0, nil, newMissingArgumentError(config.name, adaptLoggerMaxIntervalAttr) + } + maxInterval, err := strconv.ParseUint(maxIntStr, 10, 32) + if err != nil { + return 0, nil, err + } + + // Critical msg count + criticalMsgCountStr, criticalMsgCountExists := config.attributes[adaptLoggerCriticalMsgCountAttr] + if !criticalMsgCountExists { + return 0, nil, newMissingArgumentError(config.name, adaptLoggerCriticalMsgCountAttr) + } + criticalMsgCount, err := strconv.ParseUint(criticalMsgCountStr, 10, 32) + if err != nil { + return 0, nil, err + } + + logData = adaptiveLoggerData{uint32(minInterval), uint32(maxInterval), uint32(criticalMsgCount)} + } + + return logType, logData, nil +} + +func getOutputsTree(config *xmlNode, formats map[string]*formatter, cfg *CfgParseParams) (dispatcherInterface, error) { + var outputsNode *xmlNode + for _, child := range config.children { + if child.name == outputsID { + outputsNode = child + break + } + } + + if outputsNode != nil { + err := checkUnexpectedAttribute(outputsNode, outputFormatID) + if err != nil { + return nil, err + } + + formatter, err := getCurrentFormat(outputsNode, DefaultFormatter, formats) + if err != nil { + return nil, err + } + + output, err := createSplitter(outputsNode, formatter, formats, cfg) + if err != nil { + return nil, err + } + + dispatcher, ok := output.(dispatcherInterface) + if ok { + return dispatcher, nil + } + } + + console, err := NewConsoleWriter() + if err != nil { + return nil, err + } + return NewSplitDispatcher(DefaultFormatter, []interface{}{console}) +} + +func getCurrentFormat(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter) (*formatter, error) { + formatID, isFormatID := node.attributes[outputFormatID] + if !isFormatID { + return formatFromParent, nil + } + + format, ok := formats[formatID] + if ok { + return format, nil + } + + // Test for predefined format match + pdFormat, pdOk := predefinedFormats[formatID] + + if !pdOk { + return nil, errors.New("formatid = '" + formatID + "' doesn't exist") + } + + return pdFormat, nil +} + +func createInnerReceivers(node *xmlNode, format *formatter, formats map[string]*formatter, cfg *CfgParseParams) ([]interface{}, error) { + var outputs []interface{} + for _, childNode := range node.children { + entry, ok := elementMap[childNode.name] + if !ok { + return nil, errors.New("unnknown tag '" + childNode.name + "' in outputs section") + } + + output, err := entry.constructor(childNode, format, formats, cfg) + if err != nil { + return nil, err + } + + outputs = append(outputs, output) + } + + return outputs, nil +} + +func createSplitter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID) + if err != nil { + return nil, err + } + + if !node.hasChildren() { + return nil, errNodeMustHaveChildren + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + receivers, err := createInnerReceivers(node, currentFormat, formats, cfg) + if err != nil { + return nil, err + } + + return NewSplitDispatcher(currentFormat, receivers) +} + +func createCustomReceiver(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + dataCustomPrefixes := make(map[string]string) + // Expecting only 'formatid', 'name' and 'data-' attrs + for attr, attrval := range node.attributes { + isExpected := false + if attr == outputFormatID || + attr == customNameAttrID { + isExpected = true + } + if strings.HasPrefix(attr, customNameDataAttrPrefix) { + dataCustomPrefixes[attr[len(customNameDataAttrPrefix):]] = attrval + isExpected = true + } + if !isExpected { + return nil, newUnexpectedAttributeError(node.name, attr) + } + } + + if node.hasChildren() { + return nil, errNodeCannotHaveChildren + } + customName, hasCustomName := node.attributes[customNameAttrID] + if !hasCustomName { + return nil, newMissingArgumentError(node.name, customNameAttrID) + } + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + args := CustomReceiverInitArgs{ + XmlCustomAttrs: dataCustomPrefixes, + } + + if cfg != nil && cfg.CustomReceiverProducers != nil { + if prod, ok := cfg.CustomReceiverProducers[customName]; ok { + rec, err := prod(args) + if err != nil { + return nil, err + } + creceiver, err := NewCustomReceiverDispatcherByValue(currentFormat, rec, customName, args) + if err != nil { + return nil, err + } + err = rec.AfterParse(args) + if err != nil { + return nil, err + } + return creceiver, nil + } + } + + return NewCustomReceiverDispatcher(currentFormat, customName, args) +} + +func createFilter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID, filterLevelsAttrID) + if err != nil { + return nil, err + } + + if !node.hasChildren() { + return nil, errNodeMustHaveChildren + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + levelsStr, isLevels := node.attributes[filterLevelsAttrID] + if !isLevels { + return nil, newMissingArgumentError(node.name, filterLevelsAttrID) + } + + levels, err := parseLevels(levelsStr) + if err != nil { + return nil, err + } + + receivers, err := createInnerReceivers(node, currentFormat, formats, cfg) + if err != nil { + return nil, err + } + + return NewFilterDispatcher(currentFormat, receivers, levels...) +} + +func createfileWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID, pathID) + if err != nil { + return nil, err + } + + if node.hasChildren() { + return nil, errNodeCannotHaveChildren + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + path, isPath := node.attributes[pathID] + if !isPath { + return nil, newMissingArgumentError(node.name, pathID) + } + + fileWriter, err := NewFileWriter(path) + if err != nil { + return nil, err + } + + return NewFormattedWriter(fileWriter, currentFormat) +} + +// Creates new SMTP writer if encountered in the config file. +func createSMTPWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID, senderaddressID, senderNameID, hostNameID, hostPortID, userNameID, userPassID, subjectID) + if err != nil { + return nil, err + } + // Node must have children. + if !node.hasChildren() { + return nil, errNodeMustHaveChildren + } + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + senderAddress, ok := node.attributes[senderaddressID] + if !ok { + return nil, newMissingArgumentError(node.name, senderaddressID) + } + senderName, ok := node.attributes[senderNameID] + if !ok { + return nil, newMissingArgumentError(node.name, senderNameID) + } + // Process child nodes scanning for recipient email addresses and/or CA certificate paths. + var recipientAddresses []string + var caCertDirPaths []string + var mailHeaders []string + for _, childNode := range node.children { + switch childNode.name { + // Extract recipient address from child nodes. + case recipientID: + address, ok := childNode.attributes[addressID] + if !ok { + return nil, newMissingArgumentError(childNode.name, addressID) + } + recipientAddresses = append(recipientAddresses, address) + // Extract CA certificate file path from child nodes. + case cACertDirpathID: + path, ok := childNode.attributes[pathID] + if !ok { + return nil, newMissingArgumentError(childNode.name, pathID) + } + caCertDirPaths = append(caCertDirPaths, path) + + // Extract email headers from child nodes. + case mailHeaderID: + headerName, ok := childNode.attributes[mailHeaderNameID] + if !ok { + return nil, newMissingArgumentError(childNode.name, mailHeaderNameID) + } + + headerValue, ok := childNode.attributes[mailHeaderValueID] + if !ok { + return nil, newMissingArgumentError(childNode.name, mailHeaderValueID) + } + + // Build header line + mailHeaders = append(mailHeaders, fmt.Sprintf("%s: %s", headerName, headerValue)) + default: + return nil, newUnexpectedChildElementError(childNode.name) + } + } + hostName, ok := node.attributes[hostNameID] + if !ok { + return nil, newMissingArgumentError(node.name, hostNameID) + } + + hostPort, ok := node.attributes[hostPortID] + if !ok { + return nil, newMissingArgumentError(node.name, hostPortID) + } + + // Check if the string can really be converted into int. + if _, err := strconv.Atoi(hostPort); err != nil { + return nil, errors.New("invalid host port number") + } + + userName, ok := node.attributes[userNameID] + if !ok { + return nil, newMissingArgumentError(node.name, userNameID) + } + + userPass, ok := node.attributes[userPassID] + if !ok { + return nil, newMissingArgumentError(node.name, userPassID) + } + + // subject is optionally set by configuration. + // default value is defined by DefaultSubjectPhrase constant in the writers_smtpwriter.go + var subjectPhrase = DefaultSubjectPhrase + + subject, ok := node.attributes[subjectID] + if ok { + subjectPhrase = subject + } + + smtpWriter := NewSMTPWriter( + senderAddress, + senderName, + recipientAddresses, + hostName, + hostPort, + userName, + userPass, + caCertDirPaths, + subjectPhrase, + mailHeaders, + ) + + return NewFormattedWriter(smtpWriter, currentFormat) +} + +func createConsoleWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID) + if err != nil { + return nil, err + } + + if node.hasChildren() { + return nil, errNodeCannotHaveChildren + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + consoleWriter, err := NewConsoleWriter() + if err != nil { + return nil, err + } + + return NewFormattedWriter(consoleWriter, currentFormat) +} + +func createconnWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + if node.hasChildren() { + return nil, errNodeCannotHaveChildren + } + + err := checkUnexpectedAttribute(node, outputFormatID, connWriterAddrAttr, connWriterNetAttr, connWriterReconnectOnMsgAttr, connWriterUseTLSAttr, connWriterInsecureSkipVerifyAttr) + if err != nil { + return nil, err + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + addr, isAddr := node.attributes[connWriterAddrAttr] + if !isAddr { + return nil, newMissingArgumentError(node.name, connWriterAddrAttr) + } + + net, isNet := node.attributes[connWriterNetAttr] + if !isNet { + return nil, newMissingArgumentError(node.name, connWriterNetAttr) + } + + reconnectOnMsg := false + reconnectOnMsgStr, isReconnectOnMsgStr := node.attributes[connWriterReconnectOnMsgAttr] + if isReconnectOnMsgStr { + if reconnectOnMsgStr == "true" { + reconnectOnMsg = true + } else if reconnectOnMsgStr == "false" { + reconnectOnMsg = false + } else { + return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterReconnectOnMsgAttr + "' attribute value") + } + } + + useTLS := false + useTLSStr, isUseTLSStr := node.attributes[connWriterUseTLSAttr] + if isUseTLSStr { + if useTLSStr == "true" { + useTLS = true + } else if useTLSStr == "false" { + useTLS = false + } else { + return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterUseTLSAttr + "' attribute value") + } + if useTLS { + insecureSkipVerify := false + insecureSkipVerifyStr, isInsecureSkipVerify := node.attributes[connWriterInsecureSkipVerifyAttr] + if isInsecureSkipVerify { + if insecureSkipVerifyStr == "true" { + insecureSkipVerify = true + } else if insecureSkipVerifyStr == "false" { + insecureSkipVerify = false + } else { + return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterInsecureSkipVerifyAttr + "' attribute value") + } + } + config := tls.Config{InsecureSkipVerify: insecureSkipVerify} + connWriter := newTLSWriter(net, addr, reconnectOnMsg, &config) + return NewFormattedWriter(connWriter, currentFormat) + } + } + + connWriter := NewConnWriter(net, addr, reconnectOnMsg) + + return NewFormattedWriter(connWriter, currentFormat) +} + +func createRollingFileWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + if node.hasChildren() { + return nil, errNodeCannotHaveChildren + } + + rollingTypeStr, isRollingType := node.attributes[rollingFileTypeAttr] + if !isRollingType { + return nil, newMissingArgumentError(node.name, rollingFileTypeAttr) + } + + rollingType, ok := rollingTypeFromString(rollingTypeStr) + if !ok { + return nil, errors.New("unknown rolling file type: " + rollingTypeStr) + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + path, isPath := node.attributes[rollingFilePathAttr] + if !isPath { + return nil, newMissingArgumentError(node.name, rollingFilePathAttr) + } + + rollingArchiveStr, archiveAttrExists := node.attributes[rollingFileArchiveAttr] + + var rArchiveType rollingArchiveType + var rArchivePath string + var rArchiveExploded bool = false + if !archiveAttrExists { + rArchiveType = rollingArchiveNone + rArchivePath = "" + } else { + rArchiveType, ok = rollingArchiveTypeFromString(rollingArchiveStr) + if !ok { + return nil, errors.New("unknown rolling archive type: " + rollingArchiveStr) + } + + if rArchiveType == rollingArchiveNone { + rArchivePath = "" + } else { + if rArchiveExplodedAttr, ok := node.attributes[rollingFileArchiveExplodedAttr]; ok { + if rArchiveExploded, err = strconv.ParseBool(rArchiveExplodedAttr); err != nil { + return nil, fmt.Errorf("archive exploded should be true or false, but was %v", + rArchiveExploded) + } + } + + rArchivePath, ok = node.attributes[rollingFileArchivePathAttr] + if ok { + if rArchivePath == "" { + return nil, fmt.Errorf("empty archive path is not supported") + } + } else { + if rArchiveExploded { + rArchivePath = rollingArchiveDefaultExplodedName + + } else { + rArchivePath, err = rollingArchiveTypeDefaultName(rArchiveType, false) + if err != nil { + return nil, err + } + } + } + } + } + + nameMode := rollingNameMode(rollingNameModePostfix) + nameModeStr, ok := node.attributes[rollingFileNameModeAttr] + if ok { + mode, found := rollingNameModeFromString(nameModeStr) + if !found { + return nil, errors.New("unknown rolling filename mode: " + nameModeStr) + } else { + nameMode = mode + } + } + + if rollingType == rollingTypeSize { + err := checkUnexpectedAttribute(node, outputFormatID, rollingFileTypeAttr, rollingFilePathAttr, + rollingFileMaxSizeAttr, rollingFileMaxRollsAttr, rollingFileArchiveAttr, + rollingFileArchivePathAttr, rollingFileArchiveExplodedAttr, rollingFileNameModeAttr) + if err != nil { + return nil, err + } + + maxSizeStr, ok := node.attributes[rollingFileMaxSizeAttr] + if !ok { + return nil, newMissingArgumentError(node.name, rollingFileMaxSizeAttr) + } + + maxSize, err := strconv.ParseInt(maxSizeStr, 10, 64) + if err != nil { + return nil, err + } + + maxRolls := 0 + maxRollsStr, ok := node.attributes[rollingFileMaxRollsAttr] + if ok { + maxRolls, err = strconv.Atoi(maxRollsStr) + if err != nil { + return nil, err + } + } + + rollingWriter, err := NewRollingFileWriterSize(path, rArchiveType, rArchivePath, maxSize, maxRolls, nameMode, rArchiveExploded) + if err != nil { + return nil, err + } + + return NewFormattedWriter(rollingWriter, currentFormat) + + } else if rollingType == rollingTypeTime { + err := checkUnexpectedAttribute(node, outputFormatID, rollingFileTypeAttr, rollingFilePathAttr, + rollingFileDataPatternAttr, rollingFileArchiveAttr, rollingFileMaxRollsAttr, + rollingFileArchivePathAttr, rollingFileArchiveExplodedAttr, rollingFileNameModeAttr, + rollingFileFullNameAttr) + if err != nil { + return nil, err + } + + maxRolls := 0 + maxRollsStr, ok := node.attributes[rollingFileMaxRollsAttr] + if ok { + maxRolls, err = strconv.Atoi(maxRollsStr) + if err != nil { + return nil, err + } + } + + fullName := false + fn, ok := node.attributes[rollingFileFullNameAttr] + if ok { + if fn == "true" { + fullName = true + } else if fn == "false" { + fullName = false + } else { + return nil, errors.New("node '" + node.name + "' has incorrect '" + rollingFileFullNameAttr + "' attribute value") + } + } + + dataPattern, ok := node.attributes[rollingFileDataPatternAttr] + if !ok { + return nil, newMissingArgumentError(node.name, rollingFileDataPatternAttr) + } + + rollingWriter, err := NewRollingFileWriterTime(path, rArchiveType, rArchivePath, maxRolls, dataPattern, nameMode, rArchiveExploded, fullName) + if err != nil { + return nil, err + } + + return NewFormattedWriter(rollingWriter, currentFormat) + } + + return nil, errors.New("incorrect rolling writer type " + rollingTypeStr) +} + +func createbufferedWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID, bufferedSizeAttr, bufferedFlushPeriodAttr) + if err != nil { + return nil, err + } + + if !node.hasChildren() { + return nil, errNodeMustHaveChildren + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + sizeStr, isSize := node.attributes[bufferedSizeAttr] + if !isSize { + return nil, newMissingArgumentError(node.name, bufferedSizeAttr) + } + + size, err := strconv.Atoi(sizeStr) + if err != nil { + return nil, err + } + + flushPeriod := 0 + flushPeriodStr, isFlushPeriod := node.attributes[bufferedFlushPeriodAttr] + if isFlushPeriod { + flushPeriod, err = strconv.Atoi(flushPeriodStr) + if err != nil { + return nil, err + } + } + + // Inner writer couldn't have its own format, so we pass 'currentFormat' as its parent format + receivers, err := createInnerReceivers(node, currentFormat, formats, cfg) + if err != nil { + return nil, err + } + + formattedWriter, ok := receivers[0].(*formattedWriter) + if !ok { + return nil, errors.New("buffered writer's child is not writer") + } + + // ... and then we check that it hasn't changed + if formattedWriter.Format() != currentFormat { + return nil, errors.New("inner writer cannot have his own format") + } + + bufferedWriter, err := NewBufferedWriter(formattedWriter.Writer(), size, time.Duration(flushPeriod)) + if err != nil { + return nil, err + } + + return NewFormattedWriter(bufferedWriter, currentFormat) +} + +// Returns an error if node has any attributes not listed in expectedAttrs. +func checkUnexpectedAttribute(node *xmlNode, expectedAttrs ...string) error { + for attr := range node.attributes { + isExpected := false + for _, expected := range expectedAttrs { + if attr == expected { + isExpected = true + break + } + } + if !isExpected { + return newUnexpectedAttributeError(node.name, attr) + } + } + + return nil +} + +type expectedElementInfo struct { + name string + mandatory bool + multiple bool +} + +func optionalElement(name string) expectedElementInfo { + return expectedElementInfo{name, false, false} +} +func mandatoryElement(name string) expectedElementInfo { + return expectedElementInfo{name, true, false} +} +func multipleElements(name string) expectedElementInfo { + return expectedElementInfo{name, false, true} +} +func multipleMandatoryElements(name string) expectedElementInfo { + return expectedElementInfo{name, true, true} +} + +func checkExpectedElements(node *xmlNode, elements ...expectedElementInfo) error { + for _, element := range elements { + count := 0 + for _, child := range node.children { + if child.name == element.name { + count++ + } + } + + if count == 0 && element.mandatory { + return errors.New(node.name + " does not have mandatory subnode - " + element.name) + } + if count > 1 && !element.multiple { + return errors.New(node.name + " has more then one subnode - " + element.name) + } + } + + for _, child := range node.children { + isExpected := false + for _, element := range elements { + if child.name == element.name { + isExpected = true + } + } + + if !isExpected { + return errors.New(node.name + " has unexpected child: " + child.name) + } + } + + return nil +} diff --git a/vendor/github.com/cihub/seelog/common_closer.go b/vendor/github.com/cihub/seelog/common_closer.go new file mode 100644 index 00000000..1319c221 --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_closer.go @@ -0,0 +1,25 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog diff --git a/vendor/github.com/cihub/seelog/common_constraints.go b/vendor/github.com/cihub/seelog/common_constraints.go new file mode 100644 index 00000000..7ec2fe5b --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_constraints.go @@ -0,0 +1,162 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" + "fmt" + "strings" +) + +// Represents constraints which form a general rule for log levels selection +type logLevelConstraints interface { + IsAllowed(level LogLevel) bool +} + +// A minMaxConstraints represents constraints which use minimal and maximal allowed log levels. +type minMaxConstraints struct { + min LogLevel + max LogLevel +} + +// NewMinMaxConstraints creates a new minMaxConstraints struct with the specified min and max levels. +func NewMinMaxConstraints(min LogLevel, max LogLevel) (*minMaxConstraints, error) { + if min > max { + return nil, fmt.Errorf("min level can't be greater than max. Got min: %d, max: %d", min, max) + } + if min < TraceLvl || min > CriticalLvl { + return nil, fmt.Errorf("min level can't be less than Trace or greater than Critical. Got min: %d", min) + } + if max < TraceLvl || max > CriticalLvl { + return nil, fmt.Errorf("max level can't be less than Trace or greater than Critical. Got max: %d", max) + } + + return &minMaxConstraints{min, max}, nil +} + +// IsAllowed returns true, if log level is in [min, max] range (inclusive). +func (minMaxConstr *minMaxConstraints) IsAllowed(level LogLevel) bool { + return level >= minMaxConstr.min && level <= minMaxConstr.max +} + +func (minMaxConstr *minMaxConstraints) String() string { + return fmt.Sprintf("Min: %s. Max: %s", minMaxConstr.min, minMaxConstr.max) +} + +//======================================================= + +// A listConstraints represents constraints which use allowed log levels list. +type listConstraints struct { + allowedLevels map[LogLevel]bool +} + +// NewListConstraints creates a new listConstraints struct with the specified allowed levels. +func NewListConstraints(allowList []LogLevel) (*listConstraints, error) { + if allowList == nil { + return nil, errors.New("list can't be nil") + } + + allowLevels, err := createMapFromList(allowList) + if err != nil { + return nil, err + } + err = validateOffLevel(allowLevels) + if err != nil { + return nil, err + } + + return &listConstraints{allowLevels}, nil +} + +func (listConstr *listConstraints) String() string { + allowedList := "List: " + + listLevel := make([]string, len(listConstr.allowedLevels)) + + var logLevel LogLevel + i := 0 + for logLevel = TraceLvl; logLevel <= Off; logLevel++ { + if listConstr.allowedLevels[logLevel] { + listLevel[i] = logLevel.String() + i++ + } + } + + allowedList += strings.Join(listLevel, ",") + + return allowedList +} + +func createMapFromList(allowedList []LogLevel) (map[LogLevel]bool, error) { + allowedLevels := make(map[LogLevel]bool, 0) + for _, level := range allowedList { + if level < TraceLvl || level > Off { + return nil, fmt.Errorf("level can't be less than Trace or greater than Critical. Got level: %d", level) + } + allowedLevels[level] = true + } + return allowedLevels, nil +} +func validateOffLevel(allowedLevels map[LogLevel]bool) error { + if _, ok := allowedLevels[Off]; ok && len(allowedLevels) > 1 { + return errors.New("logLevel Off cant be mixed with other levels") + } + + return nil +} + +// IsAllowed returns true, if log level is in allowed log levels list. +// If the list contains the only item 'common.Off' then IsAllowed will always return false for any input values. +func (listConstr *listConstraints) IsAllowed(level LogLevel) bool { + for l := range listConstr.allowedLevels { + if l == level && level != Off { + return true + } + } + + return false +} + +// AllowedLevels returns allowed levels configuration as a map. +func (listConstr *listConstraints) AllowedLevels() map[LogLevel]bool { + return listConstr.allowedLevels +} + +//======================================================= + +type offConstraints struct { +} + +func NewOffConstraints() (*offConstraints, error) { + return &offConstraints{}, nil +} + +func (offConstr *offConstraints) IsAllowed(level LogLevel) bool { + return false +} + +func (offConstr *offConstraints) String() string { + return "Off constraint" +} diff --git a/vendor/github.com/cihub/seelog/common_context.go b/vendor/github.com/cihub/seelog/common_context.go new file mode 100644 index 00000000..230a76ca --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_context.go @@ -0,0 +1,234 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + "time" +) + +var ( + workingDir = "/" + stackCache map[uintptr]*logContext + stackCacheLock sync.RWMutex +) + +func init() { + wd, err := os.Getwd() + if err == nil { + workingDir = filepath.ToSlash(wd) + "/" + } + stackCache = make(map[uintptr]*logContext) +} + +// Represents runtime caller context. +type LogContextInterface interface { + // Caller's function name. + Func() string + // Caller's line number. + Line() int + // Caller's file short path (in slashed form). + ShortPath() string + // Caller's file full path (in slashed form). + FullPath() string + // Caller's file name (without path). + FileName() string + // True if the context is correct and may be used. + // If false, then an error in context evaluation occurred and + // all its other data may be corrupted. + IsValid() bool + // Time when log function was called. + CallTime() time.Time + // Custom context that can be set by calling logger.SetContext + CustomContext() interface{} +} + +// Returns context of the caller +func currentContext(custom interface{}) (LogContextInterface, error) { + return specifyContext(1, custom) +} + +func extractCallerInfo(skip int) (*logContext, error) { + var stack [1]uintptr + if runtime.Callers(skip+1, stack[:]) != 1 { + return nil, errors.New("error during runtime.Callers") + } + pc := stack[0] + + // do we have a cache entry? + stackCacheLock.RLock() + ctx, ok := stackCache[pc] + stackCacheLock.RUnlock() + if ok { + return ctx, nil + } + + // look up the details of the given caller + funcInfo := runtime.FuncForPC(pc) + if funcInfo == nil { + return nil, errors.New("error during runtime.FuncForPC") + } + + var shortPath string + fullPath, line := funcInfo.FileLine(pc) + if strings.HasPrefix(fullPath, workingDir) { + shortPath = fullPath[len(workingDir):] + } else { + shortPath = fullPath + } + funcName := funcInfo.Name() + if strings.HasPrefix(funcName, workingDir) { + funcName = funcName[len(workingDir):] + } + + ctx = &logContext{ + funcName: funcName, + line: line, + shortPath: shortPath, + fullPath: fullPath, + fileName: filepath.Base(fullPath), + } + + // save the details in the cache; note that it's possible we might + // have written an entry into the map in between the test above and + // this section, but the behaviour is still correct + stackCacheLock.Lock() + stackCache[pc] = ctx + stackCacheLock.Unlock() + return ctx, nil +} + +// Returns context of the function with placed "skip" stack frames of the caller +// If skip == 0 then behaves like currentContext +// Context is returned in any situation, even if error occurs. But, if an error +// occurs, the returned context is an error context, which contains no paths +// or names, but states that they can't be extracted. +func specifyContext(skip int, custom interface{}) (LogContextInterface, error) { + callTime := time.Now() + if skip < 0 { + err := fmt.Errorf("can not skip negative stack frames") + return &errorContext{callTime, err}, err + } + caller, err := extractCallerInfo(skip + 2) + if err != nil { + return &errorContext{callTime, err}, err + } + ctx := new(logContext) + *ctx = *caller + ctx.callTime = callTime + ctx.custom = custom + return ctx, nil +} + +// Represents a normal runtime caller context. +type logContext struct { + funcName string + line int + shortPath string + fullPath string + fileName string + callTime time.Time + custom interface{} +} + +func (context *logContext) IsValid() bool { + return true +} + +func (context *logContext) Func() string { + return context.funcName +} + +func (context *logContext) Line() int { + return context.line +} + +func (context *logContext) ShortPath() string { + return context.shortPath +} + +func (context *logContext) FullPath() string { + return context.fullPath +} + +func (context *logContext) FileName() string { + return context.fileName +} + +func (context *logContext) CallTime() time.Time { + return context.callTime +} + +func (context *logContext) CustomContext() interface{} { + return context.custom +} + +// Represents an error context +type errorContext struct { + errorTime time.Time + err error +} + +func (errContext *errorContext) getErrorText(prefix string) string { + return fmt.Sprintf("%s() error: %s", prefix, errContext.err) +} + +func (errContext *errorContext) IsValid() bool { + return false +} + +func (errContext *errorContext) Line() int { + return -1 +} + +func (errContext *errorContext) Func() string { + return errContext.getErrorText("Func") +} + +func (errContext *errorContext) ShortPath() string { + return errContext.getErrorText("ShortPath") +} + +func (errContext *errorContext) FullPath() string { + return errContext.getErrorText("FullPath") +} + +func (errContext *errorContext) FileName() string { + return errContext.getErrorText("FileName") +} + +func (errContext *errorContext) CallTime() time.Time { + return errContext.errorTime +} + +func (errContext *errorContext) CustomContext() interface{} { + return nil +} diff --git a/vendor/github.com/cihub/seelog/common_exception.go b/vendor/github.com/cihub/seelog/common_exception.go new file mode 100644 index 00000000..9acc2750 --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_exception.go @@ -0,0 +1,194 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" + "fmt" + "regexp" + "strings" +) + +// Used in rules creation to validate input file and func filters +var ( + fileFormatValidator = regexp.MustCompile(`[a-zA-Z0-9\\/ _\*\.]*`) + funcFormatValidator = regexp.MustCompile(`[a-zA-Z0-9_\*\.]*`) +) + +// LogLevelException represents an exceptional case used when you need some specific files or funcs to +// override general constraints and to use their own. +type LogLevelException struct { + funcPatternParts []string + filePatternParts []string + + funcPattern string + filePattern string + + constraints logLevelConstraints +} + +// NewLogLevelException creates a new exception. +func NewLogLevelException(funcPattern string, filePattern string, constraints logLevelConstraints) (*LogLevelException, error) { + if constraints == nil { + return nil, errors.New("constraints can not be nil") + } + + exception := new(LogLevelException) + + err := exception.initFuncPatternParts(funcPattern) + if err != nil { + return nil, err + } + exception.funcPattern = strings.Join(exception.funcPatternParts, "") + + err = exception.initFilePatternParts(filePattern) + if err != nil { + return nil, err + } + exception.filePattern = strings.Join(exception.filePatternParts, "") + + exception.constraints = constraints + + return exception, nil +} + +// MatchesContext returns true if context matches the patterns of this LogLevelException +func (logLevelEx *LogLevelException) MatchesContext(context LogContextInterface) bool { + return logLevelEx.match(context.Func(), context.FullPath()) +} + +// IsAllowed returns true if log level is allowed according to the constraints of this LogLevelException +func (logLevelEx *LogLevelException) IsAllowed(level LogLevel) bool { + return logLevelEx.constraints.IsAllowed(level) +} + +// FuncPattern returns the function pattern of a exception +func (logLevelEx *LogLevelException) FuncPattern() string { + return logLevelEx.funcPattern +} + +// FuncPattern returns the file pattern of a exception +func (logLevelEx *LogLevelException) FilePattern() string { + return logLevelEx.filePattern +} + +// initFuncPatternParts checks whether the func filter has a correct format and splits funcPattern on parts +func (logLevelEx *LogLevelException) initFuncPatternParts(funcPattern string) (err error) { + + if funcFormatValidator.FindString(funcPattern) != funcPattern { + return errors.New("func path \"" + funcPattern + "\" contains incorrect symbols. Only a-z A-Z 0-9 _ * . allowed)") + } + + logLevelEx.funcPatternParts = splitPattern(funcPattern) + return nil +} + +// Checks whether the file filter has a correct format and splits file patterns using splitPattern. +func (logLevelEx *LogLevelException) initFilePatternParts(filePattern string) (err error) { + + if fileFormatValidator.FindString(filePattern) != filePattern { + return errors.New("file path \"" + filePattern + "\" contains incorrect symbols. Only a-z A-Z 0-9 \\ / _ * . allowed)") + } + + logLevelEx.filePatternParts = splitPattern(filePattern) + return err +} + +func (logLevelEx *LogLevelException) match(funcPath string, filePath string) bool { + if !stringMatchesPattern(logLevelEx.funcPatternParts, funcPath) { + return false + } + return stringMatchesPattern(logLevelEx.filePatternParts, filePath) +} + +func (logLevelEx *LogLevelException) String() string { + str := fmt.Sprintf("Func: %s File: %s", logLevelEx.funcPattern, logLevelEx.filePattern) + + if logLevelEx.constraints != nil { + str += fmt.Sprintf("Constr: %s", logLevelEx.constraints) + } else { + str += "nil" + } + + return str +} + +// splitPattern splits pattern into strings and asterisks. Example: "ab*cde**f" -> ["ab", "*", "cde", "*", "f"] +func splitPattern(pattern string) []string { + var patternParts []string + var lastChar rune + for _, char := range pattern { + if char == '*' { + if lastChar != '*' { + patternParts = append(patternParts, "*") + } + } else { + if len(patternParts) != 0 && lastChar != '*' { + patternParts[len(patternParts)-1] += string(char) + } else { + patternParts = append(patternParts, string(char)) + } + } + lastChar = char + } + + return patternParts +} + +// stringMatchesPattern check whether testString matches pattern with asterisks. +// Standard regexp functionality is not used here because of performance issues. +func stringMatchesPattern(patternparts []string, testString string) bool { + if len(patternparts) == 0 { + return len(testString) == 0 + } + + part := patternparts[0] + if part != "*" { + index := strings.Index(testString, part) + if index == 0 { + return stringMatchesPattern(patternparts[1:], testString[len(part):]) + } + } else { + if len(patternparts) == 1 { + return true + } + + newTestString := testString + part = patternparts[1] + for { + index := strings.Index(newTestString, part) + if index == -1 { + break + } + + newTestString = newTestString[index+len(part):] + result := stringMatchesPattern(patternparts[2:], newTestString) + if result { + return true + } + } + } + return false +} diff --git a/vendor/github.com/cihub/seelog/common_flusher.go b/vendor/github.com/cihub/seelog/common_flusher.go new file mode 100644 index 00000000..0ef077c8 --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_flusher.go @@ -0,0 +1,31 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +// flusherInterface represents all objects that have to do cleanup +// at certain moments of time (e.g. before app shutdown to avoid data loss) +type flusherInterface interface { + Flush() +} diff --git a/vendor/github.com/cihub/seelog/common_loglevel.go b/vendor/github.com/cihub/seelog/common_loglevel.go new file mode 100644 index 00000000..d54ecf27 --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_loglevel.go @@ -0,0 +1,81 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +// Log level type +type LogLevel uint8 + +// Log levels +const ( + TraceLvl = iota + DebugLvl + InfoLvl + WarnLvl + ErrorLvl + CriticalLvl + Off +) + +// Log level string representations (used in configuration files) +const ( + TraceStr = "trace" + DebugStr = "debug" + InfoStr = "info" + WarnStr = "warn" + ErrorStr = "error" + CriticalStr = "critical" + OffStr = "off" +) + +var levelToStringRepresentations = map[LogLevel]string{ + TraceLvl: TraceStr, + DebugLvl: DebugStr, + InfoLvl: InfoStr, + WarnLvl: WarnStr, + ErrorLvl: ErrorStr, + CriticalLvl: CriticalStr, + Off: OffStr, +} + +// LogLevelFromString parses a string and returns a corresponding log level, if sucessfull. +func LogLevelFromString(levelStr string) (level LogLevel, found bool) { + for lvl, lvlStr := range levelToStringRepresentations { + if lvlStr == levelStr { + return lvl, true + } + } + + return 0, false +} + +// LogLevelToString returns seelog string representation for a specified level. Returns "" for invalid log levels. +func (level LogLevel) String() string { + levelStr, ok := levelToStringRepresentations[level] + if ok { + return levelStr + } + + return "" +} diff --git a/vendor/github.com/cihub/seelog/dispatch_custom.go b/vendor/github.com/cihub/seelog/dispatch_custom.go new file mode 100644 index 00000000..383a7705 --- /dev/null +++ b/vendor/github.com/cihub/seelog/dispatch_custom.go @@ -0,0 +1,242 @@ +// Copyright (c) 2013 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" + "fmt" + "reflect" + "sort" +) + +var registeredReceivers = make(map[string]reflect.Type) + +// RegisterReceiver records a custom receiver type, identified by a value +// of that type (second argument), under the specified name. Registered +// names can be used in the "name" attribute of config items. +// +// RegisterReceiver takes the type of the receiver argument, without taking +// the value into the account. So do NOT enter any data to the second argument +// and only call it like: +// RegisterReceiver("somename", &MyReceiverType{}) +// +// After that, when a '' config tag with this name is used, +// a receiver of the specified type would be instantiated. Check +// CustomReceiver comments for interface details. +// +// NOTE 1: RegisterReceiver fails if you attempt to register different types +// with the same name. +// +// NOTE 2: RegisterReceiver registers those receivers that must be used in +// the configuration files ( items). Basically it is just the way +// you tell seelog config parser what should it do when it meets a +// tag with a specific name and data attributes. +// +// But If you are only using seelog as a proxy to an already instantiated +// CustomReceiver (via LoggerFromCustomReceiver func), you should not call RegisterReceiver. +func RegisterReceiver(name string, receiver CustomReceiver) { + newType := reflect.TypeOf(reflect.ValueOf(receiver).Elem().Interface()) + if t, ok := registeredReceivers[name]; ok && t != newType { + panic(fmt.Sprintf("duplicate types for %s: %s != %s", name, t, newType)) + } + registeredReceivers[name] = newType +} + +func customReceiverByName(name string) (creceiver CustomReceiver, err error) { + rt, ok := registeredReceivers[name] + if !ok { + return nil, fmt.Errorf("custom receiver name not registered: '%s'", name) + } + v, ok := reflect.New(rt).Interface().(CustomReceiver) + if !ok { + return nil, fmt.Errorf("cannot instantiate receiver with name='%s'", name) + } + return v, nil +} + +// CustomReceiverInitArgs represent arguments passed to the CustomReceiver.Init +// func when custom receiver is being initialized. +type CustomReceiverInitArgs struct { + // XmlCustomAttrs represent '' xml config item attributes that + // start with "data-". Map keys will be the attribute names without the "data-". + // Map values will the those attribute values. + // + // E.g. if you have a '' + // you will get map with 2 key-value pairs: "attr1"->"a1", "attr2"->"a2" + // + // Note that in custom items you can only use allowed attributes, like "name" and + // your custom attributes, starting with "data-". Any other will lead to a + // parsing error. + XmlCustomAttrs map[string]string +} + +// CustomReceiver is the interface that external custom seelog message receivers +// must implement in order to be able to process seelog messages. Those receivers +// are set in the xml config file using the tag. Check receivers reference +// wiki section on that. +// +// Use seelog.RegisterReceiver on the receiver type before using it. +type CustomReceiver interface { + // ReceiveMessage is called when the custom receiver gets seelog message from + // a parent dispatcher. + // + // Message, level and context args represent all data that was included in the seelog + // message at the time it was logged. + // + // The formatting is already applied to the message and depends on the config + // like with any other receiver. + // + // If you would like to inform seelog of an error that happened during the handling of + // the message, return a non-nil error. This way you'll end up seeing your error like + // any other internal seelog error. + ReceiveMessage(message string, level LogLevel, context LogContextInterface) error + + // AfterParse is called immediately after your custom receiver is instantiated by + // the xml config parser. So, if you need to do any startup logic after config parsing, + // like opening file or allocating any resources after the receiver is instantiated, do it here. + // + // If this func returns a non-nil error, then the loading procedure will fail. E.g. + // if you are loading a seelog xml config, the parser would not finish the loading + // procedure and inform about an error like with any other config error. + // + // If your custom logger needs some configuration, you can use custom attributes in + // your config. Check CustomReceiverInitArgs.XmlCustomAttrs comments. + // + // IMPORTANT: This func is NOT called when the LoggerFromCustomReceiver func is used + // to create seelog proxy logger using the custom receiver. This func is only called when + // receiver is instantiated from a config. + AfterParse(initArgs CustomReceiverInitArgs) error + + // Flush is called when the custom receiver gets a 'flush' directive from a + // parent receiver. If custom receiver implements some kind of buffering or + // queing, then the appropriate reaction on a flush message is synchronous + // flushing of all those queues/buffers. If custom receiver doesn't have + // such mechanisms, then flush implementation may be left empty. + Flush() + + // Close is called when the custom receiver gets a 'close' directive from a + // parent receiver. This happens when a top-level seelog dispatcher is sending + // 'close' to all child nodes and it means that current seelog logger is being closed. + // If you need to do any cleanup after your custom receiver is done, you should do + // it here. + Close() error +} + +type customReceiverDispatcher struct { + formatter *formatter + innerReceiver CustomReceiver + customReceiverName string + usedArgs CustomReceiverInitArgs +} + +// NewCustomReceiverDispatcher creates a customReceiverDispatcher which dispatches data to a specific receiver created +// using a tag in the config file. +func NewCustomReceiverDispatcher(formatter *formatter, customReceiverName string, cArgs CustomReceiverInitArgs) (*customReceiverDispatcher, error) { + if formatter == nil { + return nil, errors.New("formatter cannot be nil") + } + if len(customReceiverName) == 0 { + return nil, errors.New("custom receiver name cannot be empty") + } + + creceiver, err := customReceiverByName(customReceiverName) + if err != nil { + return nil, err + } + err = creceiver.AfterParse(cArgs) + if err != nil { + return nil, err + } + disp := &customReceiverDispatcher{formatter, creceiver, customReceiverName, cArgs} + + return disp, nil +} + +// NewCustomReceiverDispatcherByValue is basically the same as NewCustomReceiverDispatcher, but using +// a specific CustomReceiver value instead of instantiating a new one by type. +func NewCustomReceiverDispatcherByValue(formatter *formatter, customReceiver CustomReceiver, name string, cArgs CustomReceiverInitArgs) (*customReceiverDispatcher, error) { + if formatter == nil { + return nil, errors.New("formatter cannot be nil") + } + if customReceiver == nil { + return nil, errors.New("customReceiver cannot be nil") + } + disp := &customReceiverDispatcher{formatter, customReceiver, name, cArgs} + + return disp, nil +} + +// CustomReceiver implementation. Check CustomReceiver comments. +func (disp *customReceiverDispatcher) Dispatch( + message string, + level LogLevel, + context LogContextInterface, + errorFunc func(err error)) { + + defer func() { + if err := recover(); err != nil { + errorFunc(fmt.Errorf("panic in custom receiver '%s'.Dispatch: %s", reflect.TypeOf(disp.innerReceiver), err)) + } + }() + + err := disp.innerReceiver.ReceiveMessage(disp.formatter.Format(message, level, context), level, context) + if err != nil { + errorFunc(err) + } +} + +// CustomReceiver implementation. Check CustomReceiver comments. +func (disp *customReceiverDispatcher) Flush() { + disp.innerReceiver.Flush() +} + +// CustomReceiver implementation. Check CustomReceiver comments. +func (disp *customReceiverDispatcher) Close() error { + disp.innerReceiver.Flush() + + err := disp.innerReceiver.Close() + if err != nil { + return err + } + + return nil +} + +func (disp *customReceiverDispatcher) String() string { + datas := "" + skeys := make([]string, 0, len(disp.usedArgs.XmlCustomAttrs)) + for i := range disp.usedArgs.XmlCustomAttrs { + skeys = append(skeys, i) + } + sort.Strings(skeys) + for _, key := range skeys { + datas += fmt.Sprintf("<%s, %s> ", key, disp.usedArgs.XmlCustomAttrs[key]) + } + + str := fmt.Sprintf("Custom receiver %s [fmt='%s'],[data='%s'],[inner='%s']\n", + disp.customReceiverName, disp.formatter.String(), datas, disp.innerReceiver) + + return str +} diff --git a/vendor/github.com/cihub/seelog/dispatch_dispatcher.go b/vendor/github.com/cihub/seelog/dispatch_dispatcher.go new file mode 100644 index 00000000..2bd3b4a4 --- /dev/null +++ b/vendor/github.com/cihub/seelog/dispatch_dispatcher.go @@ -0,0 +1,189 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" + "fmt" + "io" +) + +// A dispatcherInterface is used to dispatch message to all underlying receivers. +// Dispatch logic depends on given context and log level. Any errors are reported using errorFunc. +// Also, as underlying receivers may have a state, dispatcher has a ShuttingDown method which performs +// an immediate cleanup of all data that is stored in the receivers +type dispatcherInterface interface { + flusherInterface + io.Closer + Dispatch(message string, level LogLevel, context LogContextInterface, errorFunc func(err error)) +} + +type dispatcher struct { + formatter *formatter + writers []*formattedWriter + dispatchers []dispatcherInterface +} + +// Creates a dispatcher which dispatches data to a list of receivers. +// Each receiver should be either a Dispatcher or io.Writer, otherwise an error will be returned +func createDispatcher(formatter *formatter, receivers []interface{}) (*dispatcher, error) { + if formatter == nil { + return nil, errors.New("formatter cannot be nil") + } + if receivers == nil || len(receivers) == 0 { + return nil, errors.New("receivers cannot be nil or empty") + } + + disp := &dispatcher{formatter, make([]*formattedWriter, 0), make([]dispatcherInterface, 0)} + for _, receiver := range receivers { + writer, ok := receiver.(*formattedWriter) + if ok { + disp.writers = append(disp.writers, writer) + continue + } + + ioWriter, ok := receiver.(io.Writer) + if ok { + writer, err := NewFormattedWriter(ioWriter, disp.formatter) + if err != nil { + return nil, err + } + disp.writers = append(disp.writers, writer) + continue + } + + dispInterface, ok := receiver.(dispatcherInterface) + if ok { + disp.dispatchers = append(disp.dispatchers, dispInterface) + continue + } + + return nil, errors.New("method can receive either io.Writer or dispatcherInterface") + } + + return disp, nil +} + +func (disp *dispatcher) Dispatch( + message string, + level LogLevel, + context LogContextInterface, + errorFunc func(err error)) { + + for _, writer := range disp.writers { + err := writer.Write(message, level, context) + if err != nil { + errorFunc(err) + } + } + + for _, dispInterface := range disp.dispatchers { + dispInterface.Dispatch(message, level, context, errorFunc) + } +} + +// Flush goes through all underlying writers which implement flusherInterface interface +// and closes them. Recursively performs the same action for underlying dispatchers +func (disp *dispatcher) Flush() { + for _, disp := range disp.Dispatchers() { + disp.Flush() + } + + for _, formatWriter := range disp.Writers() { + flusher, ok := formatWriter.Writer().(flusherInterface) + if ok { + flusher.Flush() + } + } +} + +// Close goes through all underlying writers which implement io.Closer interface +// and closes them. Recursively performs the same action for underlying dispatchers +// Before closing, writers are flushed to prevent loss of any buffered data, so +// a call to Flush() func before Close() is not necessary +func (disp *dispatcher) Close() error { + for _, disp := range disp.Dispatchers() { + disp.Flush() + err := disp.Close() + if err != nil { + return err + } + } + + for _, formatWriter := range disp.Writers() { + flusher, ok := formatWriter.Writer().(flusherInterface) + if ok { + flusher.Flush() + } + + closer, ok := formatWriter.Writer().(io.Closer) + if ok { + err := closer.Close() + if err != nil { + return err + } + } + } + + return nil +} + +func (disp *dispatcher) Writers() []*formattedWriter { + return disp.writers +} + +func (disp *dispatcher) Dispatchers() []dispatcherInterface { + return disp.dispatchers +} + +func (disp *dispatcher) String() string { + str := "formatter: " + disp.formatter.String() + "\n" + + str += " ->Dispatchers:" + + if len(disp.dispatchers) == 0 { + str += "none\n" + } else { + str += "\n" + + for _, disp := range disp.dispatchers { + str += fmt.Sprintf(" ->%s", disp) + } + } + + str += " ->Writers:" + + if len(disp.writers) == 0 { + str += "none\n" + } else { + str += "\n" + + for _, writer := range disp.writers { + str += fmt.Sprintf(" ->%s\n", writer) + } + } + + return str +} diff --git a/vendor/github.com/cihub/seelog/dispatch_filterdispatcher.go b/vendor/github.com/cihub/seelog/dispatch_filterdispatcher.go new file mode 100644 index 00000000..9de8a722 --- /dev/null +++ b/vendor/github.com/cihub/seelog/dispatch_filterdispatcher.go @@ -0,0 +1,66 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "fmt" +) + +// A filterDispatcher writes the given message to underlying receivers only if message log level +// is in the allowed list. +type filterDispatcher struct { + *dispatcher + allowList map[LogLevel]bool +} + +// NewFilterDispatcher creates a new filterDispatcher using a list of allowed levels. +func NewFilterDispatcher(formatter *formatter, receivers []interface{}, allowList ...LogLevel) (*filterDispatcher, error) { + disp, err := createDispatcher(formatter, receivers) + if err != nil { + return nil, err + } + + allows := make(map[LogLevel]bool) + for _, allowLevel := range allowList { + allows[allowLevel] = true + } + + return &filterDispatcher{disp, allows}, nil +} + +func (filter *filterDispatcher) Dispatch( + message string, + level LogLevel, + context LogContextInterface, + errorFunc func(err error)) { + isAllowed, ok := filter.allowList[level] + if ok && isAllowed { + filter.dispatcher.Dispatch(message, level, context, errorFunc) + } +} + +func (filter *filterDispatcher) String() string { + return fmt.Sprintf("filterDispatcher ->\n%s", filter.dispatcher) +} diff --git a/vendor/github.com/cihub/seelog/dispatch_splitdispatcher.go b/vendor/github.com/cihub/seelog/dispatch_splitdispatcher.go new file mode 100644 index 00000000..1d0fe7ea --- /dev/null +++ b/vendor/github.com/cihub/seelog/dispatch_splitdispatcher.go @@ -0,0 +1,47 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "fmt" +) + +// A splitDispatcher just writes the given message to underlying receivers. (Splits the message stream.) +type splitDispatcher struct { + *dispatcher +} + +func NewSplitDispatcher(formatter *formatter, receivers []interface{}) (*splitDispatcher, error) { + disp, err := createDispatcher(formatter, receivers) + if err != nil { + return nil, err + } + + return &splitDispatcher{disp}, nil +} + +func (splitter *splitDispatcher) String() string { + return fmt.Sprintf("splitDispatcher ->\n%s", splitter.dispatcher.String()) +} diff --git a/vendor/github.com/cihub/seelog/doc.go b/vendor/github.com/cihub/seelog/doc.go new file mode 100644 index 00000000..2734c9cb --- /dev/null +++ b/vendor/github.com/cihub/seelog/doc.go @@ -0,0 +1,175 @@ +// Copyright (c) 2014 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/* +Package seelog implements logging functionality with flexible dispatching, filtering, and formatting. + +Creation + +To create a logger, use one of the following constructors: + func LoggerFromConfigAsBytes + func LoggerFromConfigAsFile + func LoggerFromConfigAsString + func LoggerFromWriterWithMinLevel + func LoggerFromWriterWithMinLevelAndFormat + func LoggerFromCustomReceiver (check https://github.com/cihub/seelog/wiki/Custom-receivers) +Example: + import log "github.com/cihub/seelog" + + func main() { + logger, err := log.LoggerFromConfigAsFile("seelog.xml") + if err != nil { + panic(err) + } + defer logger.Flush() + ... use logger ... + } +The "defer" line is important because if you are using asynchronous logger behavior, without this line you may end up losing some +messages when you close your application because they are processed in another non-blocking goroutine. To avoid that you +explicitly defer flushing all messages before closing. + +Usage + +Logger created using one of the LoggerFrom* funcs can be used directly by calling one of the main log funcs. +Example: + import log "github.com/cihub/seelog" + + func main() { + logger, err := log.LoggerFromConfigAsFile("seelog.xml") + if err != nil { + panic(err) + } + defer logger.Flush() + logger.Trace("test") + logger.Debugf("var = %s", "abc") + } + +Having loggers as variables is convenient if you are writing your own package with internal logging or if you have +several loggers with different options. +But for most standalone apps it is more convenient to use package level funcs and vars. There is a package level +var 'Current' made for it. You can replace it with another logger using 'ReplaceLogger' and then use package level funcs: + import log "github.com/cihub/seelog" + + func main() { + logger, err := log.LoggerFromConfigAsFile("seelog.xml") + if err != nil { + panic(err) + } + log.ReplaceLogger(logger) + defer log.Flush() + log.Trace("test") + log.Debugf("var = %s", "abc") + } +Last lines + log.Trace("test") + log.Debugf("var = %s", "abc") +do the same as + log.Current.Trace("test") + log.Current.Debugf("var = %s", "abc") +In this example the 'Current' logger was replaced using a 'ReplaceLogger' call and became equal to 'logger' variable created from config. +This way you are able to use package level funcs instead of passing the logger variable. + +Configuration + +Main seelog point is to configure logger via config files and not the code. +The configuration is read by LoggerFrom* funcs. These funcs read xml configuration from different sources and try +to create a logger using it. + +All the configuration features are covered in detail in the official wiki: https://github.com/cihub/seelog/wiki. +There are many sections covering different aspects of seelog, but the most important for understanding configs are: + https://github.com/cihub/seelog/wiki/Constraints-and-exceptions + https://github.com/cihub/seelog/wiki/Dispatchers-and-receivers + https://github.com/cihub/seelog/wiki/Formatting + https://github.com/cihub/seelog/wiki/Logger-types +After you understand these concepts, check the 'Reference' section on the main wiki page to get the up-to-date +list of dispatchers, receivers, formats, and logger types. + +Here is an example config with all these features: + + + + + + + + + + + + + + + + + + + + + +This config represents a logger with adaptive timeout between log messages (check logger types reference) which +logs to console, all.log, and errors.log depending on the log level. Its output formats also depend on log level. This logger will only +use log level 'debug' and higher (minlevel is set) for all files with names that don't start with 'test'. For files starting with 'test' +this logger prohibits all levels below 'error'. + +Configuration using code + +Although configuration using code is not recommended, it is sometimes needed and it is possible to do with seelog. Basically, what +you need to do to get started is to create constraints, exceptions and a dispatcher tree (same as with config). Most of the New* +functions in this package are used to provide such capabilities. + +Here is an example of configuration in code, that demonstrates an async loop logger that logs to a simple split dispatcher with +a console receiver using a specified format and is filtered using a top-level min-max constraints and one expection for +the 'main.go' file. So, this is basically a demonstration of configuration of most of the features: + + package main + + import log "github.com/cihub/seelog" + + func main() { + defer log.Flush() + log.Info("Hello from Seelog!") + + consoleWriter, _ := log.NewConsoleWriter() + formatter, _ := log.NewFormatter("%Level %Msg %File%n") + root, _ := log.NewSplitDispatcher(formatter, []interface{}{consoleWriter}) + constraints, _ := log.NewMinMaxConstraints(log.TraceLvl, log.CriticalLvl) + specificConstraints, _ := log.NewListConstraints([]log.LogLevel{log.InfoLvl, log.ErrorLvl}) + ex, _ := log.NewLogLevelException("*", "*main.go", specificConstraints) + exceptions := []*log.LogLevelException{ex} + + logger := log.NewAsyncLoopLogger(log.NewLoggerConfig(constraints, exceptions, root)) + log.ReplaceLogger(logger) + + log.Trace("This should not be seen") + log.Debug("This should not be seen") + log.Info("Test") + log.Error("Test2") + } + +Examples + +To learn seelog features faster you should check the examples package: https://github.com/cihub/seelog-examples +It contains many example configs and usecases. +*/ +package seelog diff --git a/vendor/github.com/cihub/seelog/format.go b/vendor/github.com/cihub/seelog/format.go new file mode 100644 index 00000000..ec47b457 --- /dev/null +++ b/vendor/github.com/cihub/seelog/format.go @@ -0,0 +1,466 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "bytes" + "errors" + "fmt" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// FormatterSymbol is a special symbol used in config files to mark special format aliases. +const ( + FormatterSymbol = '%' +) + +const ( + formatterParameterStart = '(' + formatterParameterEnd = ')' +) + +// Time and date formats used for %Date and %Time aliases. +const ( + DateDefaultFormat = "2006-01-02" + TimeFormat = "15:04:05" +) + +var DefaultMsgFormat = "%Ns [%Level] %Msg%n" + +var ( + DefaultFormatter *formatter + msgonlyformatter *formatter +) + +func init() { + var err error + if DefaultFormatter, err = NewFormatter(DefaultMsgFormat); err != nil { + reportInternalError(fmt.Errorf("error during creating DefaultFormatter: %s", err)) + } + if msgonlyformatter, err = NewFormatter("%Msg"); err != nil { + reportInternalError(fmt.Errorf("error during creating msgonlyformatter: %s", err)) + } +} + +// FormatterFunc represents one formatter object that starts with '%' sign in the 'format' attribute +// of the 'format' config item. These special symbols are replaced with context values or special +// strings when message is written to byte receiver. +// +// Check https://github.com/cihub/seelog/wiki/Formatting for details. +// Full list (with descriptions) of formatters: https://github.com/cihub/seelog/wiki/Format-reference +// +// FormatterFunc takes raw log message, level, log context and returns a string, number (of any type) or any object +// that can be evaluated as string. +type FormatterFunc func(message string, level LogLevel, context LogContextInterface) interface{} + +// FormatterFuncCreator is a factory of FormatterFunc objects. It is used to generate parameterized +// formatters (such as %Date or %EscM) and custom user formatters. +type FormatterFuncCreator func(param string) FormatterFunc + +var formatterFuncs = map[string]FormatterFunc{ + "Level": formatterLevel, + "Lev": formatterLev, + "LEVEL": formatterLEVEL, + "LEV": formatterLEV, + "l": formatterl, + "Msg": formatterMsg, + "FullPath": formatterFullPath, + "File": formatterFile, + "RelFile": formatterRelFile, + "Func": FormatterFunction, + "FuncShort": FormatterFunctionShort, + "Line": formatterLine, + "Time": formatterTime, + "UTCTime": formatterUTCTime, + "Ns": formatterNs, + "UTCNs": formatterUTCNs, + "r": formatterr, + "n": formattern, + "t": formattert, +} + +var formatterFuncsParameterized = map[string]FormatterFuncCreator{ + "Date": createDateTimeFormatterFunc, + "UTCDate": createUTCDateTimeFormatterFunc, + "EscM": createANSIEscapeFunc, +} + +func errorAliasReserved(name string) error { + return fmt.Errorf("cannot use '%s' as custom formatter name. Name is reserved", name) +} + +// RegisterCustomFormatter registers a new custom formatter factory with a given name. If returned error is nil, +// then this name (prepended by '%' symbol) can be used in 'format' attributes in configuration and +// it will be treated like the standard parameterized formatter identifiers. +// +// RegisterCustomFormatter needs to be called before creating a logger for it to take effect. The general recommendation +// is to call it once in 'init' func of your application or any initializer func. +// +// For usage examples, check https://github.com/cihub/seelog/wiki/Custom-formatters. +// +// Name must only consist of letters (unicode.IsLetter). +// +// Name must not be one of the already registered standard formatter names +// (https://github.com/cihub/seelog/wiki/Format-reference) and previously registered +// custom format names. To avoid any potential name conflicts (in future releases), it is recommended +// to start your custom formatter name with a namespace (e.g. 'MyCompanySomething') or a 'Custom' keyword. +func RegisterCustomFormatter(name string, creator FormatterFuncCreator) error { + if _, ok := formatterFuncs[name]; ok { + return errorAliasReserved(name) + } + if _, ok := formatterFuncsParameterized[name]; ok { + return errorAliasReserved(name) + } + formatterFuncsParameterized[name] = creator + return nil +} + +// formatter is used to write messages in a specific format, inserting such additional data +// as log level, date/time, etc. +type formatter struct { + fmtStringOriginal string + fmtString string + formatterFuncs []FormatterFunc +} + +// NewFormatter creates a new formatter using a format string +func NewFormatter(formatString string) (*formatter, error) { + fmtr := new(formatter) + fmtr.fmtStringOriginal = formatString + if err := buildFormatterFuncs(fmtr); err != nil { + return nil, err + } + return fmtr, nil +} + +func buildFormatterFuncs(formatter *formatter) error { + var ( + fsbuf = new(bytes.Buffer) + fsolm1 = len(formatter.fmtStringOriginal) - 1 + ) + for i := 0; i <= fsolm1; i++ { + if char := formatter.fmtStringOriginal[i]; char != FormatterSymbol { + fsbuf.WriteByte(char) + continue + } + // Check if the index is at the end of the string. + if i == fsolm1 { + return fmt.Errorf("format error: %c cannot be last symbol", FormatterSymbol) + } + // Check if the formatter symbol is doubled and skip it as nonmatching. + if formatter.fmtStringOriginal[i+1] == FormatterSymbol { + fsbuf.WriteRune(FormatterSymbol) + i++ + continue + } + function, ni, err := formatter.extractFormatterFunc(i + 1) + if err != nil { + return err + } + // Append formatting string "%v". + fsbuf.Write([]byte{37, 118}) + i = ni + formatter.formatterFuncs = append(formatter.formatterFuncs, function) + } + formatter.fmtString = fsbuf.String() + return nil +} + +func (formatter *formatter) extractFormatterFunc(index int) (FormatterFunc, int, error) { + letterSequence := formatter.extractLetterSequence(index) + if len(letterSequence) == 0 { + return nil, 0, fmt.Errorf("format error: lack of formatter after %c at %d", FormatterSymbol, index) + } + + function, formatterLength, ok := formatter.findFormatterFunc(letterSequence) + if ok { + return function, index + formatterLength - 1, nil + } + + function, formatterLength, ok, err := formatter.findFormatterFuncParametrized(letterSequence, index) + if err != nil { + return nil, 0, err + } + if ok { + return function, index + formatterLength - 1, nil + } + + return nil, 0, errors.New("format error: unrecognized formatter at " + strconv.Itoa(index) + ": " + letterSequence) +} + +func (formatter *formatter) extractLetterSequence(index int) string { + letters := "" + + bytesToParse := []byte(formatter.fmtStringOriginal[index:]) + runeCount := utf8.RuneCount(bytesToParse) + for i := 0; i < runeCount; i++ { + rune, runeSize := utf8.DecodeRune(bytesToParse) + bytesToParse = bytesToParse[runeSize:] + + if unicode.IsLetter(rune) { + letters += string(rune) + } else { + break + } + } + return letters +} + +func (formatter *formatter) findFormatterFunc(letters string) (FormatterFunc, int, bool) { + currentVerb := letters + for i := 0; i < len(letters); i++ { + function, ok := formatterFuncs[currentVerb] + if ok { + return function, len(currentVerb), ok + } + currentVerb = currentVerb[:len(currentVerb)-1] + } + + return nil, 0, false +} + +func (formatter *formatter) findFormatterFuncParametrized(letters string, lettersStartIndex int) (FormatterFunc, int, bool, error) { + currentVerb := letters + for i := 0; i < len(letters); i++ { + functionCreator, ok := formatterFuncsParameterized[currentVerb] + if ok { + parameter := "" + parameterLen := 0 + isVerbEqualsLetters := i == 0 // if not, then letter goes after formatter, and formatter is parameterless + if isVerbEqualsLetters { + userParameter := "" + var err error + userParameter, parameterLen, ok, err = formatter.findparameter(lettersStartIndex + len(currentVerb)) + if ok { + parameter = userParameter + } else if err != nil { + return nil, 0, false, err + } + } + + return functionCreator(parameter), len(currentVerb) + parameterLen, true, nil + } + + currentVerb = currentVerb[:len(currentVerb)-1] + } + + return nil, 0, false, nil +} + +func (formatter *formatter) findparameter(startIndex int) (string, int, bool, error) { + if len(formatter.fmtStringOriginal) == startIndex || formatter.fmtStringOriginal[startIndex] != formatterParameterStart { + return "", 0, false, nil + } + + endIndex := strings.Index(formatter.fmtStringOriginal[startIndex:], string(formatterParameterEnd)) + if endIndex == -1 { + return "", 0, false, fmt.Errorf("Unmatched parenthesis or invalid parameter at %d: %s", + startIndex, formatter.fmtStringOriginal[startIndex:]) + } + endIndex += startIndex + + length := endIndex - startIndex + 1 + + return formatter.fmtStringOriginal[startIndex+1 : endIndex], length, true, nil +} + +// Format processes a message with special formatters, log level, and context. Returns formatted string +// with all formatter identifiers changed to appropriate values. +func (formatter *formatter) Format(message string, level LogLevel, context LogContextInterface) string { + if len(formatter.formatterFuncs) == 0 { + return formatter.fmtString + } + + params := make([]interface{}, len(formatter.formatterFuncs)) + for i, function := range formatter.formatterFuncs { + params[i] = function(message, level, context) + } + + return fmt.Sprintf(formatter.fmtString, params...) +} + +func (formatter *formatter) String() string { + return formatter.fmtStringOriginal +} + +//===================================================== + +const ( + wrongLogLevel = "WRONG_LOGLEVEL" + wrongEscapeCode = "WRONG_ESCAPE" +) + +var levelToString = map[LogLevel]string{ + TraceLvl: "Trace", + DebugLvl: "Debug", + InfoLvl: "Info", + WarnLvl: "Warn", + ErrorLvl: "Error", + CriticalLvl: "Critical", + Off: "Off", +} + +var levelToShortString = map[LogLevel]string{ + TraceLvl: "Trc", + DebugLvl: "Dbg", + InfoLvl: "Inf", + WarnLvl: "Wrn", + ErrorLvl: "Err", + CriticalLvl: "Crt", + Off: "Off", +} + +var levelToShortestString = map[LogLevel]string{ + TraceLvl: "t", + DebugLvl: "d", + InfoLvl: "i", + WarnLvl: "w", + ErrorLvl: "e", + CriticalLvl: "c", + Off: "o", +} + +func formatterLevel(message string, level LogLevel, context LogContextInterface) interface{} { + levelStr, ok := levelToString[level] + if !ok { + return wrongLogLevel + } + return levelStr +} + +func formatterLev(message string, level LogLevel, context LogContextInterface) interface{} { + levelStr, ok := levelToShortString[level] + if !ok { + return wrongLogLevel + } + return levelStr +} + +func formatterLEVEL(message string, level LogLevel, context LogContextInterface) interface{} { + return strings.ToTitle(formatterLevel(message, level, context).(string)) +} + +func formatterLEV(message string, level LogLevel, context LogContextInterface) interface{} { + return strings.ToTitle(formatterLev(message, level, context).(string)) +} + +func formatterl(message string, level LogLevel, context LogContextInterface) interface{} { + levelStr, ok := levelToShortestString[level] + if !ok { + return wrongLogLevel + } + return levelStr +} + +func formatterMsg(message string, level LogLevel, context LogContextInterface) interface{} { + return message +} + +func formatterFullPath(message string, level LogLevel, context LogContextInterface) interface{} { + return context.FullPath() +} + +func formatterFile(message string, level LogLevel, context LogContextInterface) interface{} { + return context.FileName() +} + +func formatterRelFile(message string, level LogLevel, context LogContextInterface) interface{} { + return context.ShortPath() +} + +func FormatterFunction(message string, level LogLevel, context LogContextInterface) interface{} { + return context.Func() +} + +func FormatterFunctionShort(message string, level LogLevel, context LogContextInterface) interface{} { + f := context.Func() + spl := strings.Split(f, ".") + return spl[len(spl)-1] +} + +func formatterLine(message string, level LogLevel, context LogContextInterface) interface{} { + return context.Line() +} + +func formatterTime(message string, level LogLevel, context LogContextInterface) interface{} { + return context.CallTime().Format(TimeFormat) +} + +func formatterUTCTime(message string, level LogLevel, context LogContextInterface) interface{} { + return context.CallTime().UTC().Format(TimeFormat) +} + +func formatterNs(message string, level LogLevel, context LogContextInterface) interface{} { + return context.CallTime().UnixNano() +} + +func formatterUTCNs(message string, level LogLevel, context LogContextInterface) interface{} { + return context.CallTime().UTC().UnixNano() +} + +func formatterr(message string, level LogLevel, context LogContextInterface) interface{} { + return "\r" +} + +func formattern(message string, level LogLevel, context LogContextInterface) interface{} { + return "\n" +} + +func formattert(message string, level LogLevel, context LogContextInterface) interface{} { + return "\t" +} + +func createDateTimeFormatterFunc(dateTimeFormat string) FormatterFunc { + format := dateTimeFormat + if format == "" { + format = DateDefaultFormat + } + return func(message string, level LogLevel, context LogContextInterface) interface{} { + return context.CallTime().Format(format) + } +} + +func createUTCDateTimeFormatterFunc(dateTimeFormat string) FormatterFunc { + format := dateTimeFormat + if format == "" { + format = DateDefaultFormat + } + return func(message string, level LogLevel, context LogContextInterface) interface{} { + return context.CallTime().UTC().Format(format) + } +} + +func createANSIEscapeFunc(escapeCodeString string) FormatterFunc { + return func(message string, level LogLevel, context LogContextInterface) interface{} { + if len(escapeCodeString) == 0 { + return wrongEscapeCode + } + + return fmt.Sprintf("%c[%sm", 0x1B, escapeCodeString) + } +} diff --git a/vendor/github.com/cihub/seelog/internals_baseerror.go b/vendor/github.com/cihub/seelog/internals_baseerror.go new file mode 100644 index 00000000..c0b271d7 --- /dev/null +++ b/vendor/github.com/cihub/seelog/internals_baseerror.go @@ -0,0 +1,10 @@ +package seelog + +// Base struct for custom errors. +type baseError struct { + message string +} + +func (be baseError) Error() string { + return be.message +} diff --git a/vendor/github.com/cihub/seelog/internals_fsutils.go b/vendor/github.com/cihub/seelog/internals_fsutils.go new file mode 100644 index 00000000..c0a0e0e4 --- /dev/null +++ b/vendor/github.com/cihub/seelog/internals_fsutils.go @@ -0,0 +1,320 @@ +package seelog + +import ( + "fmt" + "io" + "os" + "path/filepath" + "sync" +) + +// File and directory permitions. +const ( + defaultFilePermissions = 0666 + defaultDirectoryPermissions = 0767 +) + +const ( + // Max number of directories can be read asynchronously. + maxDirNumberReadAsync = 1000 +) + +type cannotOpenFileError struct { + baseError +} + +func newCannotOpenFileError(fname string) *cannotOpenFileError { + return &cannotOpenFileError{baseError{message: "Cannot open file: " + fname}} +} + +type notDirectoryError struct { + baseError +} + +func newNotDirectoryError(dname string) *notDirectoryError { + return ¬DirectoryError{baseError{message: dname + " is not directory"}} +} + +// fileFilter is a filtering criteria function for '*os.File'. +// Must return 'false' to set aside the given file. +type fileFilter func(os.FileInfo, *os.File) bool + +// filePathFilter is a filtering creteria function for file path. +// Must return 'false' to set aside the given file. +type filePathFilter func(filePath string) bool + +// GetSubdirNames returns a list of directories found in +// the given one with dirPath. +func getSubdirNames(dirPath string) ([]string, error) { + fi, err := os.Stat(dirPath) + if err != nil { + return nil, err + } + if !fi.IsDir() { + return nil, newNotDirectoryError(dirPath) + } + dd, err := os.Open(dirPath) + // Cannot open file. + if err != nil { + if dd != nil { + dd.Close() + } + return nil, err + } + defer dd.Close() + // TODO: Improve performance by buffering reading. + allEntities, err := dd.Readdir(-1) + if err != nil { + return nil, err + } + subDirs := []string{} + for _, entity := range allEntities { + if entity.IsDir() { + subDirs = append(subDirs, entity.Name()) + } + } + return subDirs, nil +} + +// getSubdirAbsPaths recursively visit all the subdirectories +// starting from the given directory and returns absolute paths for them. +func getAllSubdirAbsPaths(dirPath string) (res []string, err error) { + dps, err := getSubdirAbsPaths(dirPath) + if err != nil { + res = []string{} + return + } + res = append(res, dps...) + for _, dp := range dps { + sdps, err := getAllSubdirAbsPaths(dp) + if err != nil { + return []string{}, err + } + res = append(res, sdps...) + } + return +} + +// getSubdirAbsPaths supplies absolute paths for all subdirectiries in a given directory. +// Input: (I1) dirPath - absolute path of a directory in question. +// Out: (O1) - slice of subdir asbolute paths; (O2) - error of the operation. +// Remark: If error (O2) is non-nil then (O1) is nil and vice versa. +func getSubdirAbsPaths(dirPath string) ([]string, error) { + sdns, err := getSubdirNames(dirPath) + if err != nil { + return nil, err + } + rsdns := []string{} + for _, sdn := range sdns { + rsdns = append(rsdns, filepath.Join(dirPath, sdn)) + } + return rsdns, nil +} + +// getOpenFilesInDir supplies a slice of os.File pointers to files located in the directory. +// Remark: Ignores files for which fileFilter returns false +func getOpenFilesInDir(dirPath string, fFilter fileFilter) ([]*os.File, error) { + dfi, err := os.Open(dirPath) + if err != nil { + return nil, newCannotOpenFileError("Cannot open directory " + dirPath) + } + defer dfi.Close() + // Size of read buffer (i.e. chunk of items read at a time). + rbs := 64 + resFiles := []*os.File{} +L: + for { + // Read directory entities by reasonable chuncks + // to prevent overflows on big number of files. + fis, e := dfi.Readdir(rbs) + switch e { + // It's OK. + case nil: + // Do nothing, just continue cycle. + case io.EOF: + break L + // Something went wrong. + default: + return nil, e + } + // THINK: Maybe, use async running. + for _, fi := range fis { + // NB: On Linux this could be a problem as + // there are lots of file types available. + if !fi.IsDir() { + f, e := os.Open(filepath.Join(dirPath, fi.Name())) + if e != nil { + if f != nil { + f.Close() + } + // THINK: Add nil as indicator that a problem occurred. + resFiles = append(resFiles, nil) + continue + } + // Check filter condition. + if fFilter != nil && !fFilter(fi, f) { + continue + } + resFiles = append(resFiles, f) + } + } + } + return resFiles, nil +} + +func isRegular(m os.FileMode) bool { + return m&os.ModeType == 0 +} + +// getDirFilePaths return full paths of the files located in the directory. +// Remark: Ignores files for which fileFilter returns false. +func getDirFilePaths(dirPath string, fpFilter filePathFilter, pathIsName bool) ([]string, error) { + dfi, err := os.Open(dirPath) + if err != nil { + return nil, newCannotOpenFileError("Cannot open directory " + dirPath) + } + defer dfi.Close() + + var absDirPath string + if !filepath.IsAbs(dirPath) { + absDirPath, err = filepath.Abs(dirPath) + if err != nil { + return nil, fmt.Errorf("cannot get absolute path of directory: %s", err.Error()) + } + } else { + absDirPath = dirPath + } + + // TODO: check if dirPath is really directory. + // Size of read buffer (i.e. chunk of items read at a time). + rbs := 2 << 5 + filePaths := []string{} + + var fp string +L: + for { + // Read directory entities by reasonable chuncks + // to prevent overflows on big number of files. + fis, e := dfi.Readdir(rbs) + switch e { + // It's OK. + case nil: + // Do nothing, just continue cycle. + case io.EOF: + break L + // Indicate that something went wrong. + default: + return nil, e + } + // THINK: Maybe, use async running. + for _, fi := range fis { + // NB: Should work on every Windows and non-Windows OS. + if isRegular(fi.Mode()) { + if pathIsName { + fp = fi.Name() + } else { + // Build full path of a file. + fp = filepath.Join(absDirPath, fi.Name()) + } + // Check filter condition. + if fpFilter != nil && !fpFilter(fp) { + continue + } + filePaths = append(filePaths, fp) + } + } + } + return filePaths, nil +} + +// getOpenFilesByDirectoryAsync runs async reading directories 'dirPaths' and inserts pairs +// in map 'filesInDirMap': Key - directory name, value - *os.File slice. +func getOpenFilesByDirectoryAsync( + dirPaths []string, + fFilter fileFilter, + filesInDirMap map[string][]*os.File, +) error { + n := len(dirPaths) + if n > maxDirNumberReadAsync { + return fmt.Errorf("number of input directories to be read exceeded max value %d", maxDirNumberReadAsync) + } + type filesInDirResult struct { + DirName string + Files []*os.File + Error error + } + dirFilesChan := make(chan *filesInDirResult, n) + var wg sync.WaitGroup + // Register n goroutines which are going to do work. + wg.Add(n) + for i := 0; i < n; i++ { + // Launch asynchronously the piece of work. + go func(dirPath string) { + fs, e := getOpenFilesInDir(dirPath, fFilter) + dirFilesChan <- &filesInDirResult{filepath.Base(dirPath), fs, e} + // Mark the current goroutine as finished (work is done). + wg.Done() + }(dirPaths[i]) + } + // Wait for all goroutines to finish their work. + wg.Wait() + // Close the error channel to let for-range clause + // get all the buffered values without blocking and quit in the end. + close(dirFilesChan) + for fidr := range dirFilesChan { + if fidr.Error == nil { + // THINK: What will happen if the key is already present? + filesInDirMap[fidr.DirName] = fidr.Files + } else { + return fidr.Error + } + } + return nil +} + +// fileExists return flag whether a given file exists +// and operation error if an unclassified failure occurs. +func fileExists(path string) (bool, error) { + _, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} + +// createDirectory makes directory with a given name +// making all parent directories if necessary. +func createDirectory(dirPath string) error { + var dPath string + var err error + if !filepath.IsAbs(dirPath) { + dPath, err = filepath.Abs(dirPath) + if err != nil { + return err + } + } else { + dPath = dirPath + } + exists, err := fileExists(dPath) + if err != nil { + return err + } + if exists { + return nil + } + return os.MkdirAll(dPath, os.ModeDir) +} + +// tryRemoveFile gives a try removing the file +// only ignoring an error when the file does not exist. +func tryRemoveFile(filePath string) (err error) { + err = os.Remove(filePath) + if os.IsNotExist(err) { + err = nil + return + } + return +} diff --git a/vendor/github.com/cihub/seelog/internals_xmlnode.go b/vendor/github.com/cihub/seelog/internals_xmlnode.go new file mode 100644 index 00000000..98588493 --- /dev/null +++ b/vendor/github.com/cihub/seelog/internals_xmlnode.go @@ -0,0 +1,175 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "encoding/xml" + "errors" + "fmt" + "io" + "strings" +) + +type xmlNode struct { + name string + attributes map[string]string + children []*xmlNode + value string +} + +func newNode() *xmlNode { + node := new(xmlNode) + node.children = make([]*xmlNode, 0) + node.attributes = make(map[string]string) + return node +} + +func (node *xmlNode) String() string { + str := fmt.Sprintf("<%s", node.name) + + for attrName, attrVal := range node.attributes { + str += fmt.Sprintf(" %s=\"%s\"", attrName, attrVal) + } + + str += ">" + str += node.value + + if len(node.children) != 0 { + for _, child := range node.children { + str += fmt.Sprintf("%s", child) + } + } + + str += fmt.Sprintf("", node.name) + + return str +} + +func (node *xmlNode) unmarshal(startEl xml.StartElement) error { + node.name = startEl.Name.Local + + for _, v := range startEl.Attr { + _, alreadyExists := node.attributes[v.Name.Local] + if alreadyExists { + return errors.New("tag '" + node.name + "' has duplicated attribute: '" + v.Name.Local + "'") + } + node.attributes[v.Name.Local] = v.Value + } + + return nil +} + +func (node *xmlNode) add(child *xmlNode) { + if node.children == nil { + node.children = make([]*xmlNode, 0) + } + + node.children = append(node.children, child) +} + +func (node *xmlNode) hasChildren() bool { + return node.children != nil && len(node.children) > 0 +} + +//============================================= + +func unmarshalConfig(reader io.Reader) (*xmlNode, error) { + xmlParser := xml.NewDecoder(reader) + + config, err := unmarshalNode(xmlParser, nil) + if err != nil { + return nil, err + } + if config == nil { + return nil, errors.New("xml has no content") + } + + nextConfigEntry, err := unmarshalNode(xmlParser, nil) + if nextConfigEntry != nil { + return nil, errors.New("xml contains more than one root element") + } + + return config, nil +} + +func unmarshalNode(xmlParser *xml.Decoder, curToken xml.Token) (node *xmlNode, err error) { + firstLoop := true + for { + var tok xml.Token + if firstLoop && curToken != nil { + tok = curToken + firstLoop = false + } else { + tok, err = getNextToken(xmlParser) + if err != nil || tok == nil { + return + } + } + + switch tt := tok.(type) { + case xml.SyntaxError: + err = errors.New(tt.Error()) + return + case xml.CharData: + value := strings.TrimSpace(string([]byte(tt))) + if node != nil { + node.value += value + } + case xml.StartElement: + if node == nil { + node = newNode() + err := node.unmarshal(tt) + if err != nil { + return nil, err + } + } else { + childNode, childErr := unmarshalNode(xmlParser, tok) + if childErr != nil { + return nil, childErr + } + + if childNode != nil { + node.add(childNode) + } else { + return + } + } + case xml.EndElement: + return + } + } +} + +func getNextToken(xmlParser *xml.Decoder) (tok xml.Token, err error) { + if tok, err = xmlParser.Token(); err != nil { + if err == io.EOF { + err = nil + return + } + return + } + + return +} diff --git a/vendor/github.com/cihub/seelog/log.go b/vendor/github.com/cihub/seelog/log.go new file mode 100644 index 00000000..f775e1fd --- /dev/null +++ b/vendor/github.com/cihub/seelog/log.go @@ -0,0 +1,307 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" + "fmt" + "sync" + "time" +) + +const ( + staticFuncCallDepth = 3 // See 'commonLogger.log' method comments + loggerFuncCallDepth = 3 +) + +// Current is the logger used in all package level convenience funcs like 'Trace', 'Debug', 'Flush', etc. +var Current LoggerInterface + +// Default logger that is created from an empty config: "". It is not closed by a ReplaceLogger call. +var Default LoggerInterface + +// Disabled logger that doesn't produce any output in any circumstances. It is neither closed nor flushed by a ReplaceLogger call. +var Disabled LoggerInterface + +var pkgOperationsMutex *sync.Mutex + +func init() { + pkgOperationsMutex = new(sync.Mutex) + var err error + + if Default == nil { + Default, err = LoggerFromConfigAsBytes([]byte("")) + } + + if Disabled == nil { + Disabled, err = LoggerFromConfigAsBytes([]byte("")) + } + + if err != nil { + panic(fmt.Sprintf("Seelog couldn't start. Error: %s", err.Error())) + } + + Current = Default +} + +func createLoggerFromFullConfig(config *configForParsing) (LoggerInterface, error) { + if config.LogType == syncloggerTypeFromString { + return NewSyncLogger(&config.logConfig), nil + } else if config.LogType == asyncLooploggerTypeFromString { + return NewAsyncLoopLogger(&config.logConfig), nil + } else if config.LogType == asyncTimerloggerTypeFromString { + logData := config.LoggerData + if logData == nil { + return nil, errors.New("async timer data not set") + } + + asyncInt, ok := logData.(asyncTimerLoggerData) + if !ok { + return nil, errors.New("invalid async timer data") + } + + logger, err := NewAsyncTimerLogger(&config.logConfig, time.Duration(asyncInt.AsyncInterval)) + if !ok { + return nil, err + } + + return logger, nil + } else if config.LogType == adaptiveLoggerTypeFromString { + logData := config.LoggerData + if logData == nil { + return nil, errors.New("adaptive logger parameters not set") + } + + adaptData, ok := logData.(adaptiveLoggerData) + if !ok { + return nil, errors.New("invalid adaptive logger parameters") + } + + logger, err := NewAsyncAdaptiveLogger( + &config.logConfig, + time.Duration(adaptData.MinInterval), + time.Duration(adaptData.MaxInterval), + adaptData.CriticalMsgCount, + ) + if err != nil { + return nil, err + } + + return logger, nil + } + return nil, errors.New("invalid config log type/data") +} + +// UseLogger sets the 'Current' package level logger variable to the specified value. +// This variable is used in all Trace/Debug/... package level convenience funcs. +// +// Example: +// +// after calling +// seelog.UseLogger(somelogger) +// the following: +// seelog.Debug("abc") +// will be equal to +// somelogger.Debug("abc") +// +// IMPORTANT: UseLogger do NOT close the previous logger (only flushes it). So if +// you constantly use it to replace loggers and don't close them in other code, you'll +// end up having memory leaks. +// +// To safely replace loggers, use ReplaceLogger. +func UseLogger(logger LoggerInterface) error { + if logger == nil { + return errors.New("logger can not be nil") + } + + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + + oldLogger := Current + Current = logger + + if oldLogger != nil { + oldLogger.Flush() + } + + return nil +} + +// ReplaceLogger acts as UseLogger but the logger that was previously +// used is disposed (except Default and Disabled loggers). +// +// Example: +// import log "github.com/cihub/seelog" +// +// func main() { +// logger, err := log.LoggerFromConfigAsFile("seelog.xml") +// +// if err != nil { +// panic(err) +// } +// +// log.ReplaceLogger(logger) +// defer log.Flush() +// +// log.Trace("test") +// log.Debugf("var = %s", "abc") +// } +func ReplaceLogger(logger LoggerInterface) error { + if logger == nil { + return errors.New("logger can not be nil") + } + + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + + defer func() { + if err := recover(); err != nil { + reportInternalError(fmt.Errorf("recovered from panic during ReplaceLogger: %s", err)) + } + }() + + if Current == Default { + Current.Flush() + } else if Current != nil && !Current.Closed() && Current != Disabled { + Current.Flush() + Current.Close() + } + + Current = logger + + return nil +} + +// Tracef formats message according to format specifier +// and writes to default logger with log level = Trace. +func Tracef(format string, params ...interface{}) { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + Current.traceWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params)) +} + +// Debugf formats message according to format specifier +// and writes to default logger with log level = Debug. +func Debugf(format string, params ...interface{}) { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + Current.debugWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params)) +} + +// Infof formats message according to format specifier +// and writes to default logger with log level = Info. +func Infof(format string, params ...interface{}) { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + Current.infoWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params)) +} + +// Warnf formats message according to format specifier and writes to default logger with log level = Warn +func Warnf(format string, params ...interface{}) error { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + message := newLogFormattedMessage(format, params) + Current.warnWithCallDepth(staticFuncCallDepth, message) + return errors.New(message.String()) +} + +// Errorf formats message according to format specifier and writes to default logger with log level = Error +func Errorf(format string, params ...interface{}) error { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + message := newLogFormattedMessage(format, params) + Current.errorWithCallDepth(staticFuncCallDepth, message) + return errors.New(message.String()) +} + +// Criticalf formats message according to format specifier and writes to default logger with log level = Critical +func Criticalf(format string, params ...interface{}) error { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + message := newLogFormattedMessage(format, params) + Current.criticalWithCallDepth(staticFuncCallDepth, message) + return errors.New(message.String()) +} + +// Trace formats message using the default formats for its operands and writes to default logger with log level = Trace +func Trace(v ...interface{}) { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + Current.traceWithCallDepth(staticFuncCallDepth, newLogMessage(v)) +} + +// Debug formats message using the default formats for its operands and writes to default logger with log level = Debug +func Debug(v ...interface{}) { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + Current.debugWithCallDepth(staticFuncCallDepth, newLogMessage(v)) +} + +// Info formats message using the default formats for its operands and writes to default logger with log level = Info +func Info(v ...interface{}) { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + Current.infoWithCallDepth(staticFuncCallDepth, newLogMessage(v)) +} + +// Warn formats message using the default formats for its operands and writes to default logger with log level = Warn +func Warn(v ...interface{}) error { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + message := newLogMessage(v) + Current.warnWithCallDepth(staticFuncCallDepth, message) + return errors.New(message.String()) +} + +// Error formats message using the default formats for its operands and writes to default logger with log level = Error +func Error(v ...interface{}) error { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + message := newLogMessage(v) + Current.errorWithCallDepth(staticFuncCallDepth, message) + return errors.New(message.String()) +} + +// Critical formats message using the default formats for its operands and writes to default logger with log level = Critical +func Critical(v ...interface{}) error { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + message := newLogMessage(v) + Current.criticalWithCallDepth(staticFuncCallDepth, message) + return errors.New(message.String()) +} + +// Flush immediately processes all currently queued messages and all currently buffered messages. +// It is a blocking call which returns only after the queue is empty and all the buffers are empty. +// +// If Flush is called for a synchronous logger (type='sync'), it only flushes buffers (e.g. '' receivers) +// , because there is no queue. +// +// Call this method when your app is going to shut down not to lose any log messages. +func Flush() { + pkgOperationsMutex.Lock() + defer pkgOperationsMutex.Unlock() + Current.Flush() +} diff --git a/vendor/github.com/cihub/seelog/logger.go b/vendor/github.com/cihub/seelog/logger.go new file mode 100644 index 00000000..fc96aed4 --- /dev/null +++ b/vendor/github.com/cihub/seelog/logger.go @@ -0,0 +1,370 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" + "fmt" + "os" + "sync" +) + +func reportInternalError(err error) { + fmt.Fprintf(os.Stderr, "seelog internal error: %s\n", err) +} + +// LoggerInterface represents structs capable of logging Seelog messages +type LoggerInterface interface { + + // Tracef formats message according to format specifier + // and writes to log with level = Trace. + Tracef(format string, params ...interface{}) + + // Debugf formats message according to format specifier + // and writes to log with level = Debug. + Debugf(format string, params ...interface{}) + + // Infof formats message according to format specifier + // and writes to log with level = Info. + Infof(format string, params ...interface{}) + + // Warnf formats message according to format specifier + // and writes to log with level = Warn. + Warnf(format string, params ...interface{}) error + + // Errorf formats message according to format specifier + // and writes to log with level = Error. + Errorf(format string, params ...interface{}) error + + // Criticalf formats message according to format specifier + // and writes to log with level = Critical. + Criticalf(format string, params ...interface{}) error + + // Trace formats message using the default formats for its operands + // and writes to log with level = Trace + Trace(v ...interface{}) + + // Debug formats message using the default formats for its operands + // and writes to log with level = Debug + Debug(v ...interface{}) + + // Info formats message using the default formats for its operands + // and writes to log with level = Info + Info(v ...interface{}) + + // Warn formats message using the default formats for its operands + // and writes to log with level = Warn + Warn(v ...interface{}) error + + // Error formats message using the default formats for its operands + // and writes to log with level = Error + Error(v ...interface{}) error + + // Critical formats message using the default formats for its operands + // and writes to log with level = Critical + Critical(v ...interface{}) error + + traceWithCallDepth(callDepth int, message fmt.Stringer) + debugWithCallDepth(callDepth int, message fmt.Stringer) + infoWithCallDepth(callDepth int, message fmt.Stringer) + warnWithCallDepth(callDepth int, message fmt.Stringer) + errorWithCallDepth(callDepth int, message fmt.Stringer) + criticalWithCallDepth(callDepth int, message fmt.Stringer) + + // Close flushes all the messages in the logger and closes it. It cannot be used after this operation. + Close() + + // Flush flushes all the messages in the logger. + Flush() + + // Closed returns true if the logger was previously closed. + Closed() bool + + // SetAdditionalStackDepth sets the additional number of frames to skip by runtime.Caller + // when getting function information needed to print seelog format identifiers such as %Func or %File. + // + // This func may be used when you wrap seelog funcs and want to print caller info of you own + // wrappers instead of seelog func callers. In this case you should set depth = 1. If you then + // wrap your wrapper, you should set depth = 2, etc. + // + // NOTE: Incorrect depth value may lead to errors in runtime.Caller evaluation or incorrect + // function/file names in log files. Do not use it if you are not going to wrap seelog funcs. + // You may reset the value to default using a SetAdditionalStackDepth(0) call. + SetAdditionalStackDepth(depth int) error + + // Sets logger context that can be used in formatter funcs and custom receivers + SetContext(context interface{}) +} + +// innerLoggerInterface is an internal logging interface +type innerLoggerInterface interface { + innerLog(level LogLevel, context LogContextInterface, message fmt.Stringer) + Flush() +} + +// [file path][func name][level] -> [allowed] +type allowedContextCache map[string]map[string]map[LogLevel]bool + +// commonLogger contains all common data needed for logging and contains methods used to log messages. +type commonLogger struct { + config *logConfig // Config used for logging + contextCache allowedContextCache // Caches whether log is enabled for specific "full path-func name-level" sets + closed bool // 'true' when all writers are closed, all data is flushed, logger is unusable. Must be accessed while holding closedM + closedM sync.RWMutex + m sync.Mutex // Mutex for main operations + unusedLevels []bool + innerLogger innerLoggerInterface + addStackDepth int // Additional stack depth needed for correct seelog caller context detection + customContext interface{} +} + +func newCommonLogger(config *logConfig, internalLogger innerLoggerInterface) *commonLogger { + cLogger := new(commonLogger) + + cLogger.config = config + cLogger.contextCache = make(allowedContextCache) + cLogger.unusedLevels = make([]bool, Off) + cLogger.fillUnusedLevels() + cLogger.innerLogger = internalLogger + + return cLogger +} + +func (cLogger *commonLogger) SetAdditionalStackDepth(depth int) error { + if depth < 0 { + return fmt.Errorf("negative depth: %d", depth) + } + cLogger.m.Lock() + cLogger.addStackDepth = depth + cLogger.m.Unlock() + return nil +} + +func (cLogger *commonLogger) Tracef(format string, params ...interface{}) { + cLogger.traceWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params)) +} + +func (cLogger *commonLogger) Debugf(format string, params ...interface{}) { + cLogger.debugWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params)) +} + +func (cLogger *commonLogger) Infof(format string, params ...interface{}) { + cLogger.infoWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params)) +} + +func (cLogger *commonLogger) Warnf(format string, params ...interface{}) error { + message := newLogFormattedMessage(format, params) + cLogger.warnWithCallDepth(loggerFuncCallDepth, message) + return errors.New(message.String()) +} + +func (cLogger *commonLogger) Errorf(format string, params ...interface{}) error { + message := newLogFormattedMessage(format, params) + cLogger.errorWithCallDepth(loggerFuncCallDepth, message) + return errors.New(message.String()) +} + +func (cLogger *commonLogger) Criticalf(format string, params ...interface{}) error { + message := newLogFormattedMessage(format, params) + cLogger.criticalWithCallDepth(loggerFuncCallDepth, message) + return errors.New(message.String()) +} + +func (cLogger *commonLogger) Trace(v ...interface{}) { + cLogger.traceWithCallDepth(loggerFuncCallDepth, newLogMessage(v)) +} + +func (cLogger *commonLogger) Debug(v ...interface{}) { + cLogger.debugWithCallDepth(loggerFuncCallDepth, newLogMessage(v)) +} + +func (cLogger *commonLogger) Info(v ...interface{}) { + cLogger.infoWithCallDepth(loggerFuncCallDepth, newLogMessage(v)) +} + +func (cLogger *commonLogger) Warn(v ...interface{}) error { + message := newLogMessage(v) + cLogger.warnWithCallDepth(loggerFuncCallDepth, message) + return errors.New(message.String()) +} + +func (cLogger *commonLogger) Error(v ...interface{}) error { + message := newLogMessage(v) + cLogger.errorWithCallDepth(loggerFuncCallDepth, message) + return errors.New(message.String()) +} + +func (cLogger *commonLogger) Critical(v ...interface{}) error { + message := newLogMessage(v) + cLogger.criticalWithCallDepth(loggerFuncCallDepth, message) + return errors.New(message.String()) +} + +func (cLogger *commonLogger) SetContext(c interface{}) { + cLogger.customContext = c +} + +func (cLogger *commonLogger) traceWithCallDepth(callDepth int, message fmt.Stringer) { + cLogger.log(TraceLvl, message, callDepth) +} + +func (cLogger *commonLogger) debugWithCallDepth(callDepth int, message fmt.Stringer) { + cLogger.log(DebugLvl, message, callDepth) +} + +func (cLogger *commonLogger) infoWithCallDepth(callDepth int, message fmt.Stringer) { + cLogger.log(InfoLvl, message, callDepth) +} + +func (cLogger *commonLogger) warnWithCallDepth(callDepth int, message fmt.Stringer) { + cLogger.log(WarnLvl, message, callDepth) +} + +func (cLogger *commonLogger) errorWithCallDepth(callDepth int, message fmt.Stringer) { + cLogger.log(ErrorLvl, message, callDepth) +} + +func (cLogger *commonLogger) criticalWithCallDepth(callDepth int, message fmt.Stringer) { + cLogger.log(CriticalLvl, message, callDepth) + cLogger.innerLogger.Flush() +} + +func (cLogger *commonLogger) Closed() bool { + cLogger.closedM.RLock() + defer cLogger.closedM.RUnlock() + return cLogger.closed +} + +func (cLogger *commonLogger) fillUnusedLevels() { + for i := 0; i < len(cLogger.unusedLevels); i++ { + cLogger.unusedLevels[i] = true + } + + cLogger.fillUnusedLevelsByContraint(cLogger.config.Constraints) + + for _, exception := range cLogger.config.Exceptions { + cLogger.fillUnusedLevelsByContraint(exception) + } +} + +func (cLogger *commonLogger) fillUnusedLevelsByContraint(constraint logLevelConstraints) { + for i := 0; i < len(cLogger.unusedLevels); i++ { + if constraint.IsAllowed(LogLevel(i)) { + cLogger.unusedLevels[i] = false + } + } +} + +// stackCallDepth is used to indicate the call depth of 'log' func. +// This depth level is used in the runtime.Caller(...) call. See +// common_context.go -> specifyContext, extractCallerInfo for details. +func (cLogger *commonLogger) log(level LogLevel, message fmt.Stringer, stackCallDepth int) { + if cLogger.unusedLevels[level] { + return + } + cLogger.m.Lock() + defer cLogger.m.Unlock() + + if cLogger.Closed() { + return + } + context, _ := specifyContext(stackCallDepth+cLogger.addStackDepth, cLogger.customContext) + // Context errors are not reported because there are situations + // in which context errors are normal Seelog usage cases. For + // example in executables with stripped symbols. + // Error contexts are returned instead. See common_context.go. + /*if err != nil { + reportInternalError(err) + return + }*/ + cLogger.innerLogger.innerLog(level, context, message) +} + +func (cLogger *commonLogger) processLogMsg(level LogLevel, message fmt.Stringer, context LogContextInterface) { + defer func() { + if err := recover(); err != nil { + reportInternalError(fmt.Errorf("recovered from panic during message processing: %s", err)) + } + }() + if cLogger.config.IsAllowed(level, context) { + cLogger.config.RootDispatcher.Dispatch(message.String(), level, context, reportInternalError) + } +} + +func (cLogger *commonLogger) isAllowed(level LogLevel, context LogContextInterface) bool { + funcMap, ok := cLogger.contextCache[context.FullPath()] + if !ok { + funcMap = make(map[string]map[LogLevel]bool, 0) + cLogger.contextCache[context.FullPath()] = funcMap + } + + levelMap, ok := funcMap[context.Func()] + if !ok { + levelMap = make(map[LogLevel]bool, 0) + funcMap[context.Func()] = levelMap + } + + isAllowValue, ok := levelMap[level] + if !ok { + isAllowValue = cLogger.config.IsAllowed(level, context) + levelMap[level] = isAllowValue + } + + return isAllowValue +} + +type logMessage struct { + params []interface{} +} + +type logFormattedMessage struct { + format string + params []interface{} +} + +func newLogMessage(params []interface{}) fmt.Stringer { + message := new(logMessage) + + message.params = params + + return message +} + +func newLogFormattedMessage(format string, params []interface{}) *logFormattedMessage { + message := new(logFormattedMessage) + + message.params = params + message.format = format + + return message +} + +func (message *logMessage) String() string { + return fmt.Sprint(message.params...) +} + +func (message *logFormattedMessage) String() string { + return fmt.Sprintf(message.format, message.params...) +} diff --git a/vendor/github.com/cihub/seelog/writers_bufferedwriter.go b/vendor/github.com/cihub/seelog/writers_bufferedwriter.go new file mode 100644 index 00000000..37d75c82 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_bufferedwriter.go @@ -0,0 +1,161 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "bufio" + "errors" + "fmt" + "io" + "sync" + "time" +) + +// bufferedWriter stores data in memory and flushes it every flushPeriod or when buffer is full +type bufferedWriter struct { + flushPeriod time.Duration // data flushes interval (in microseconds) + bufferMutex *sync.Mutex // mutex for buffer operations syncronization + innerWriter io.Writer // inner writer + buffer *bufio.Writer // buffered wrapper for inner writer + bufferSize int // max size of data chunk in bytes +} + +// NewBufferedWriter creates a new buffered writer struct. +// bufferSize -- size of memory buffer in bytes +// flushPeriod -- period in which data flushes from memory buffer in milliseconds. 0 - turn off this functionality +func NewBufferedWriter(innerWriter io.Writer, bufferSize int, flushPeriod time.Duration) (*bufferedWriter, error) { + + if innerWriter == nil { + return nil, errors.New("argument is nil: innerWriter") + } + if flushPeriod < 0 { + return nil, fmt.Errorf("flushPeriod can not be less than 0. Got: %d", flushPeriod) + } + + if bufferSize <= 0 { + return nil, fmt.Errorf("bufferSize can not be less or equal to 0. Got: %d", bufferSize) + } + + buffer := bufio.NewWriterSize(innerWriter, bufferSize) + + /*if err != nil { + return nil, err + }*/ + + newWriter := new(bufferedWriter) + + newWriter.innerWriter = innerWriter + newWriter.buffer = buffer + newWriter.bufferSize = bufferSize + newWriter.flushPeriod = flushPeriod * 1e6 + newWriter.bufferMutex = new(sync.Mutex) + + if flushPeriod != 0 { + go newWriter.flushPeriodically() + } + + return newWriter, nil +} + +func (bufWriter *bufferedWriter) writeBigChunk(bytes []byte) (n int, err error) { + bufferedLen := bufWriter.buffer.Buffered() + + n, err = bufWriter.flushInner() + if err != nil { + return + } + + written, writeErr := bufWriter.innerWriter.Write(bytes) + return bufferedLen + written, writeErr +} + +// Sends data to buffer manager. Waits until all buffers are full. +func (bufWriter *bufferedWriter) Write(bytes []byte) (n int, err error) { + + bufWriter.bufferMutex.Lock() + defer bufWriter.bufferMutex.Unlock() + + bytesLen := len(bytes) + + if bytesLen > bufWriter.bufferSize { + return bufWriter.writeBigChunk(bytes) + } + + if bytesLen > bufWriter.buffer.Available() { + n, err = bufWriter.flushInner() + if err != nil { + return + } + } + + bufWriter.buffer.Write(bytes) + + return len(bytes), nil +} + +func (bufWriter *bufferedWriter) Close() error { + closer, ok := bufWriter.innerWriter.(io.Closer) + if ok { + return closer.Close() + } + + return nil +} + +func (bufWriter *bufferedWriter) Flush() { + + bufWriter.bufferMutex.Lock() + defer bufWriter.bufferMutex.Unlock() + + bufWriter.flushInner() +} + +func (bufWriter *bufferedWriter) flushInner() (n int, err error) { + bufferedLen := bufWriter.buffer.Buffered() + flushErr := bufWriter.buffer.Flush() + + return bufWriter.buffer.Buffered() - bufferedLen, flushErr +} + +func (bufWriter *bufferedWriter) flushBuffer() { + bufWriter.bufferMutex.Lock() + defer bufWriter.bufferMutex.Unlock() + + bufWriter.buffer.Flush() +} + +func (bufWriter *bufferedWriter) flushPeriodically() { + if bufWriter.flushPeriod > 0 { + ticker := time.NewTicker(bufWriter.flushPeriod) + for { + <-ticker.C + bufWriter.flushBuffer() + } + } +} + +func (bufWriter *bufferedWriter) String() string { + return fmt.Sprintf("bufferedWriter size: %d, flushPeriod: %d", bufWriter.bufferSize, bufWriter.flushPeriod) +} diff --git a/vendor/github.com/cihub/seelog/writers_connwriter.go b/vendor/github.com/cihub/seelog/writers_connwriter.go new file mode 100644 index 00000000..d199894e --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_connwriter.go @@ -0,0 +1,144 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "crypto/tls" + "fmt" + "io" + "net" +) + +// connWriter is used to write to a stream-oriented network connection. +type connWriter struct { + innerWriter io.WriteCloser + reconnectOnMsg bool + reconnect bool + net string + addr string + useTLS bool + configTLS *tls.Config +} + +// Creates writer to the address addr on the network netName. +// Connection will be opened on each write if reconnectOnMsg = true +func NewConnWriter(netName string, addr string, reconnectOnMsg bool) *connWriter { + newWriter := new(connWriter) + + newWriter.net = netName + newWriter.addr = addr + newWriter.reconnectOnMsg = reconnectOnMsg + + return newWriter +} + +// Creates a writer that uses SSL/TLS +func newTLSWriter(netName string, addr string, reconnectOnMsg bool, config *tls.Config) *connWriter { + newWriter := new(connWriter) + + newWriter.net = netName + newWriter.addr = addr + newWriter.reconnectOnMsg = reconnectOnMsg + newWriter.useTLS = true + newWriter.configTLS = config + + return newWriter +} + +func (connWriter *connWriter) Close() error { + if connWriter.innerWriter == nil { + return nil + } + + return connWriter.innerWriter.Close() +} + +func (connWriter *connWriter) Write(bytes []byte) (n int, err error) { + if connWriter.neededConnectOnMsg() { + err = connWriter.connect() + if err != nil { + return 0, err + } + } + + if connWriter.reconnectOnMsg { + defer connWriter.innerWriter.Close() + } + + n, err = connWriter.innerWriter.Write(bytes) + if err != nil { + connWriter.reconnect = true + } + + return +} + +func (connWriter *connWriter) String() string { + return fmt.Sprintf("Conn writer: [%s, %s, %v]", connWriter.net, connWriter.addr, connWriter.reconnectOnMsg) +} + +func (connWriter *connWriter) connect() error { + if connWriter.innerWriter != nil { + connWriter.innerWriter.Close() + connWriter.innerWriter = nil + } + + if connWriter.useTLS { + conn, err := tls.Dial(connWriter.net, connWriter.addr, connWriter.configTLS) + if err != nil { + return err + } + connWriter.innerWriter = conn + + return nil + } + + conn, err := net.Dial(connWriter.net, connWriter.addr) + if err != nil { + return err + } + + tcpConn, ok := conn.(*net.TCPConn) + if ok { + tcpConn.SetKeepAlive(true) + } + + connWriter.innerWriter = conn + + return nil +} + +func (connWriter *connWriter) neededConnectOnMsg() bool { + if connWriter.reconnect { + connWriter.reconnect = false + return true + } + + if connWriter.innerWriter == nil { + return true + } + + return connWriter.reconnectOnMsg +} diff --git a/vendor/github.com/cihub/seelog/writers_consolewriter.go b/vendor/github.com/cihub/seelog/writers_consolewriter.go new file mode 100644 index 00000000..3eb79afa --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_consolewriter.go @@ -0,0 +1,47 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import "fmt" + +// consoleWriter is used to write to console +type consoleWriter struct { +} + +// Creates a new console writer. Returns error, if the console writer couldn't be created. +func NewConsoleWriter() (writer *consoleWriter, err error) { + newWriter := new(consoleWriter) + + return newWriter, nil +} + +// Create folder and file on WriteLog/Write first call +func (console *consoleWriter) Write(bytes []byte) (int, error) { + return fmt.Print(string(bytes)) +} + +func (console *consoleWriter) String() string { + return "Console writer" +} diff --git a/vendor/github.com/cihub/seelog/writers_filewriter.go b/vendor/github.com/cihub/seelog/writers_filewriter.go new file mode 100644 index 00000000..8d3ae270 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_filewriter.go @@ -0,0 +1,92 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "fmt" + "io" + "os" + "path/filepath" +) + +// fileWriter is used to write to a file. +type fileWriter struct { + innerWriter io.WriteCloser + fileName string +} + +// Creates a new file and a corresponding writer. Returns error, if the file couldn't be created. +func NewFileWriter(fileName string) (writer *fileWriter, err error) { + newWriter := new(fileWriter) + newWriter.fileName = fileName + + return newWriter, nil +} + +func (fw *fileWriter) Close() error { + if fw.innerWriter != nil { + err := fw.innerWriter.Close() + if err != nil { + return err + } + fw.innerWriter = nil + } + return nil +} + +// Create folder and file on WriteLog/Write first call +func (fw *fileWriter) Write(bytes []byte) (n int, err error) { + if fw.innerWriter == nil { + if err := fw.createFile(); err != nil { + return 0, err + } + } + return fw.innerWriter.Write(bytes) +} + +func (fw *fileWriter) createFile() error { + folder, _ := filepath.Split(fw.fileName) + var err error + + if 0 != len(folder) { + err = os.MkdirAll(folder, defaultDirectoryPermissions) + if err != nil { + return err + } + } + + // If exists + fw.innerWriter, err = os.OpenFile(fw.fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, defaultFilePermissions) + + if err != nil { + return err + } + + return nil +} + +func (fw *fileWriter) String() string { + return fmt.Sprintf("File writer: %s", fw.fileName) +} diff --git a/vendor/github.com/cihub/seelog/writers_formattedwriter.go b/vendor/github.com/cihub/seelog/writers_formattedwriter.go new file mode 100644 index 00000000..bf44a410 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_formattedwriter.go @@ -0,0 +1,62 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "errors" + "fmt" + "io" +) + +type formattedWriter struct { + writer io.Writer + formatter *formatter +} + +func NewFormattedWriter(writer io.Writer, formatter *formatter) (*formattedWriter, error) { + if formatter == nil { + return nil, errors.New("formatter can not be nil") + } + + return &formattedWriter{writer, formatter}, nil +} + +func (formattedWriter *formattedWriter) Write(message string, level LogLevel, context LogContextInterface) error { + str := formattedWriter.formatter.Format(message, level, context) + _, err := formattedWriter.writer.Write([]byte(str)) + return err +} + +func (formattedWriter *formattedWriter) String() string { + return fmt.Sprintf("writer: %s, format: %s", formattedWriter.writer, formattedWriter.formatter) +} + +func (formattedWriter *formattedWriter) Writer() io.Writer { + return formattedWriter.writer +} + +func (formattedWriter *formattedWriter) Format() *formatter { + return formattedWriter.formatter +} diff --git a/vendor/github.com/cihub/seelog/writers_rollingfilewriter.go b/vendor/github.com/cihub/seelog/writers_rollingfilewriter.go new file mode 100644 index 00000000..9535a579 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_rollingfilewriter.go @@ -0,0 +1,763 @@ +// Copyright (c) 2013 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/cihub/seelog/archive" + "github.com/cihub/seelog/archive/gzip" + "github.com/cihub/seelog/archive/tar" + "github.com/cihub/seelog/archive/zip" +) + +// Common constants +const ( + rollingLogHistoryDelimiter = "." +) + +// Types of the rolling writer: roll by date, by time, etc. +type rollingType uint8 + +const ( + rollingTypeSize = iota + rollingTypeTime +) + +// Types of the rolled file naming mode: prefix, postfix, etc. +type rollingNameMode uint8 + +const ( + rollingNameModePostfix = iota + rollingNameModePrefix +) + +var rollingNameModesStringRepresentation = map[rollingNameMode]string{ + rollingNameModePostfix: "postfix", + rollingNameModePrefix: "prefix", +} + +func rollingNameModeFromString(rollingNameStr string) (rollingNameMode, bool) { + for tp, tpStr := range rollingNameModesStringRepresentation { + if tpStr == rollingNameStr { + return tp, true + } + } + + return 0, false +} + +var rollingTypesStringRepresentation = map[rollingType]string{ + rollingTypeSize: "size", + rollingTypeTime: "date", +} + +func rollingTypeFromString(rollingTypeStr string) (rollingType, bool) { + for tp, tpStr := range rollingTypesStringRepresentation { + if tpStr == rollingTypeStr { + return tp, true + } + } + + return 0, false +} + +// Old logs archivation type. +type rollingArchiveType uint8 + +const ( + rollingArchiveNone = iota + rollingArchiveZip + rollingArchiveGzip +) + +var rollingArchiveTypesStringRepresentation = map[rollingArchiveType]string{ + rollingArchiveNone: "none", + rollingArchiveZip: "zip", + rollingArchiveGzip: "gzip", +} + +type archiver func(f *os.File, exploded bool) archive.WriteCloser + +type unarchiver func(f *os.File) (archive.ReadCloser, error) + +type compressionType struct { + extension string + handleMultipleEntries bool + archiver archiver + unarchiver unarchiver +} + +var compressionTypes = map[rollingArchiveType]compressionType{ + rollingArchiveZip: { + extension: ".zip", + handleMultipleEntries: true, + archiver: func(f *os.File, _ bool) archive.WriteCloser { + return zip.NewWriter(f) + }, + unarchiver: func(f *os.File) (archive.ReadCloser, error) { + fi, err := f.Stat() + if err != nil { + return nil, err + } + r, err := zip.NewReader(f, fi.Size()) + if err != nil { + return nil, err + } + return archive.NopCloser(r), nil + }, + }, + rollingArchiveGzip: { + extension: ".gz", + handleMultipleEntries: false, + archiver: func(f *os.File, exploded bool) archive.WriteCloser { + gw := gzip.NewWriter(f) + if exploded { + return gw + } + return tar.NewWriteMultiCloser(gw, gw) + }, + unarchiver: func(f *os.File) (archive.ReadCloser, error) { + gr, err := gzip.NewReader(f, f.Name()) + if err != nil { + return nil, err + } + + // Determine if the gzip is a tar + tr := tar.NewReader(gr) + _, err = tr.Next() + isTar := err == nil + + // Reset to beginning of file + if _, err := f.Seek(0, os.SEEK_SET); err != nil { + return nil, err + } + gr.Reset(f) + + if isTar { + return archive.NopCloser(tar.NewReader(gr)), nil + } + return gr, nil + }, + }, +} + +func (compressionType *compressionType) rollingArchiveTypeName(name string, exploded bool) string { + if !compressionType.handleMultipleEntries && !exploded { + return name + ".tar" + compressionType.extension + } else { + return name + compressionType.extension + } + +} + +func rollingArchiveTypeFromString(rollingArchiveTypeStr string) (rollingArchiveType, bool) { + for tp, tpStr := range rollingArchiveTypesStringRepresentation { + if tpStr == rollingArchiveTypeStr { + return tp, true + } + } + + return 0, false +} + +// Default names for different archive types +var rollingArchiveDefaultExplodedName = "old" + +func rollingArchiveTypeDefaultName(archiveType rollingArchiveType, exploded bool) (string, error) { + compressionType, ok := compressionTypes[archiveType] + if !ok { + return "", fmt.Errorf("cannot get default filename for archive type = %v", archiveType) + } + return compressionType.rollingArchiveTypeName("log", exploded), nil +} + +// rollerVirtual is an interface that represents all virtual funcs that are +// called in different rolling writer subtypes. +type rollerVirtual interface { + needsToRoll() bool // Returns true if needs to switch to another file. + isFileRollNameValid(rname string) bool // Returns true if logger roll file name (postfix/prefix/etc.) is ok. + sortFileRollNamesAsc(fs []string) ([]string, error) // Sorts logger roll file names in ascending order of their creation by logger. + + // getNewHistoryRollFileName is called whenever we are about to roll the + // current log file. It returns the name the current log file should be + // rolled to. + getNewHistoryRollFileName(otherHistoryFiles []string) string + + getCurrentFileName() string +} + +// rollingFileWriter writes received messages to a file, until time interval passes +// or file exceeds a specified limit. After that the current log file is renamed +// and writer starts to log into a new file. You can set a limit for such renamed +// files count, if you want, and then the rolling writer would delete older ones when +// the files count exceed the specified limit. +type rollingFileWriter struct { + fileName string // log file name + currentDirPath string + currentFile *os.File + currentName string + currentFileSize int64 + rollingType rollingType // Rolling mode (Files roll by size/date/...) + archiveType rollingArchiveType + archivePath string + archiveExploded bool + fullName bool + maxRolls int + nameMode rollingNameMode + self rollerVirtual // Used for virtual calls + rollLock sync.Mutex +} + +func newRollingFileWriter(fpath string, rtype rollingType, atype rollingArchiveType, apath string, maxr int, namemode rollingNameMode, + archiveExploded bool, fullName bool) (*rollingFileWriter, error) { + rw := new(rollingFileWriter) + rw.currentDirPath, rw.fileName = filepath.Split(fpath) + if len(rw.currentDirPath) == 0 { + rw.currentDirPath = "." + } + + rw.rollingType = rtype + rw.archiveType = atype + rw.archivePath = apath + rw.nameMode = namemode + rw.maxRolls = maxr + rw.archiveExploded = archiveExploded + rw.fullName = fullName + return rw, nil +} + +func (rw *rollingFileWriter) hasRollName(file string) bool { + switch rw.nameMode { + case rollingNameModePostfix: + rname := rw.fileName + rollingLogHistoryDelimiter + return strings.HasPrefix(file, rname) + case rollingNameModePrefix: + rname := rollingLogHistoryDelimiter + rw.fileName + return strings.HasSuffix(file, rname) + } + return false +} + +func (rw *rollingFileWriter) createFullFileName(originalName, rollname string) string { + switch rw.nameMode { + case rollingNameModePostfix: + return originalName + rollingLogHistoryDelimiter + rollname + case rollingNameModePrefix: + return rollname + rollingLogHistoryDelimiter + originalName + } + return "" +} + +func (rw *rollingFileWriter) getSortedLogHistory() ([]string, error) { + files, err := getDirFilePaths(rw.currentDirPath, nil, true) + if err != nil { + return nil, err + } + var validRollNames []string + for _, file := range files { + if rw.hasRollName(file) { + rname := rw.getFileRollName(file) + if rw.self.isFileRollNameValid(rname) { + validRollNames = append(validRollNames, rname) + } + } + } + sortedTails, err := rw.self.sortFileRollNamesAsc(validRollNames) + if err != nil { + return nil, err + } + validSortedFiles := make([]string, len(sortedTails)) + for i, v := range sortedTails { + validSortedFiles[i] = rw.createFullFileName(rw.fileName, v) + } + return validSortedFiles, nil +} + +func (rw *rollingFileWriter) createFileAndFolderIfNeeded(first bool) error { + var err error + + if len(rw.currentDirPath) != 0 { + err = os.MkdirAll(rw.currentDirPath, defaultDirectoryPermissions) + + if err != nil { + return err + } + } + rw.currentName = rw.self.getCurrentFileName() + filePath := filepath.Join(rw.currentDirPath, rw.currentName) + + // This will either open the existing file (without truncating it) or + // create if necessary. Append mode avoids any race conditions. + rw.currentFile, err = os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, defaultFilePermissions) + if err != nil { + return err + } + + stat, err := rw.currentFile.Stat() + if err != nil { + rw.currentFile.Close() + rw.currentFile = nil + return err + } + + rw.currentFileSize = stat.Size() + return nil +} + +func (rw *rollingFileWriter) archiveExplodedLogs(logFilename string, compressionType compressionType) (err error) { + closeWithError := func(c io.Closer) { + if cerr := c.Close(); cerr != nil && err == nil { + err = cerr + } + } + + rollPath := filepath.Join(rw.currentDirPath, logFilename) + src, err := os.Open(rollPath) + if err != nil { + return err + } + defer src.Close() // Read-only + + // Buffer to a temporary file on the same partition + // Note: archivePath is a path to a directory when handling exploded logs + dst, err := rw.tempArchiveFile(rw.archivePath) + if err != nil { + return err + } + defer func() { + closeWithError(dst) + if err != nil { + os.Remove(dst.Name()) // Can't do anything when we fail to remove temp file + return + } + + // Finalize archive by swapping the buffered archive into place + err = os.Rename(dst.Name(), filepath.Join(rw.archivePath, + compressionType.rollingArchiveTypeName(logFilename, true))) + }() + + // archive entry + w := compressionType.archiver(dst, true) + defer closeWithError(w) + fi, err := src.Stat() + if err != nil { + return err + } + if err := w.NextFile(logFilename, fi); err != nil { + return err + } + _, err = io.Copy(w, src) + return err +} + +func (rw *rollingFileWriter) archiveUnexplodedLogs(compressionType compressionType, rollsToDelete int, history []string) (err error) { + closeWithError := func(c io.Closer) { + if cerr := c.Close(); cerr != nil && err == nil { + err = cerr + } + } + + // Buffer to a temporary file on the same partition + // Note: archivePath is a path to a file when handling unexploded logs + dst, err := rw.tempArchiveFile(filepath.Dir(rw.archivePath)) + if err != nil { + return err + } + defer func() { + closeWithError(dst) + if err != nil { + os.Remove(dst.Name()) // Can't do anything when we fail to remove temp file + return + } + + // Finalize archive by moving the buffered archive into place + err = os.Rename(dst.Name(), rw.archivePath) + }() + + w := compressionType.archiver(dst, false) + defer closeWithError(w) + + src, err := os.Open(rw.archivePath) + switch { + // Archive exists + case err == nil: + defer src.Close() // Read-only + + r, err := compressionType.unarchiver(src) + if err != nil { + return err + } + defer r.Close() // Read-only + + if err := archive.Copy(w, r); err != nil { + return err + } + + // Failed to stat + case !os.IsNotExist(err): + return err + } + + // Add new files to the archive + for i := 0; i < rollsToDelete; i++ { + rollPath := filepath.Join(rw.currentDirPath, history[i]) + src, err := os.Open(rollPath) + if err != nil { + return err + } + defer src.Close() // Read-only + fi, err := src.Stat() + if err != nil { + return err + } + if err := w.NextFile(src.Name(), fi); err != nil { + return err + } + if _, err := io.Copy(w, src); err != nil { + return err + } + } + return nil +} + +func (rw *rollingFileWriter) deleteOldRolls(history []string) error { + if rw.maxRolls <= 0 { + return nil + } + + rollsToDelete := len(history) - rw.maxRolls + if rollsToDelete <= 0 { + return nil + } + + if rw.archiveType != rollingArchiveNone { + if rw.archiveExploded { + os.MkdirAll(rw.archivePath, defaultDirectoryPermissions) + + // Archive logs + for i := 0; i < rollsToDelete; i++ { + rw.archiveExplodedLogs(history[i], compressionTypes[rw.archiveType]) + } + } else { + os.MkdirAll(filepath.Dir(rw.archivePath), defaultDirectoryPermissions) + + rw.archiveUnexplodedLogs(compressionTypes[rw.archiveType], rollsToDelete, history) + } + } + + var err error + // In all cases (archive files or not) the files should be deleted. + for i := 0; i < rollsToDelete; i++ { + // Try best to delete files without breaking the loop. + if err = tryRemoveFile(filepath.Join(rw.currentDirPath, history[i])); err != nil { + reportInternalError(err) + } + } + + return nil +} + +func (rw *rollingFileWriter) getFileRollName(fileName string) string { + switch rw.nameMode { + case rollingNameModePostfix: + return fileName[len(rw.fileName+rollingLogHistoryDelimiter):] + case rollingNameModePrefix: + return fileName[:len(fileName)-len(rw.fileName+rollingLogHistoryDelimiter)] + } + return "" +} + +func (rw *rollingFileWriter) roll() error { + // First, close current file. + err := rw.currentFile.Close() + if err != nil { + return err + } + rw.currentFile = nil + + // Current history of all previous log files. + // For file roller it may be like this: + // * ... + // * file.log.4 + // * file.log.5 + // * file.log.6 + // + // For date roller it may look like this: + // * ... + // * file.log.11.Aug.13 + // * file.log.15.Aug.13 + // * file.log.16.Aug.13 + // Sorted log history does NOT include current file. + history, err := rw.getSortedLogHistory() + if err != nil { + return err + } + // Renames current file to create a new roll history entry + // For file roller it may be like this: + // * ... + // * file.log.4 + // * file.log.5 + // * file.log.6 + // n file.log.7 <---- RENAMED (from file.log) + newHistoryName := rw.createFullFileName(rw.fileName, + rw.self.getNewHistoryRollFileName(history)) + + err = os.Rename(filepath.Join(rw.currentDirPath, rw.currentName), filepath.Join(rw.currentDirPath, newHistoryName)) + if err != nil { + return err + } + + // Finally, add the newly added history file to the history archive + // and, if after that the archive exceeds the allowed max limit, older rolls + // must the removed/archived. + history = append(history, newHistoryName) + if len(history) > rw.maxRolls { + err = rw.deleteOldRolls(history) + if err != nil { + return err + } + } + + return nil +} + +func (rw *rollingFileWriter) Write(bytes []byte) (n int, err error) { + rw.rollLock.Lock() + defer rw.rollLock.Unlock() + + if rw.self.needsToRoll() { + if err := rw.roll(); err != nil { + return 0, err + } + } + + if rw.currentFile == nil { + err := rw.createFileAndFolderIfNeeded(true) + if err != nil { + return 0, err + } + } + + n, err = rw.currentFile.Write(bytes) + rw.currentFileSize += int64(n) + return n, err +} + +func (rw *rollingFileWriter) Close() error { + if rw.currentFile != nil { + e := rw.currentFile.Close() + if e != nil { + return e + } + rw.currentFile = nil + } + return nil +} + +func (rw *rollingFileWriter) tempArchiveFile(archiveDir string) (*os.File, error) { + tmp := filepath.Join(archiveDir, ".seelog_tmp") + if err := os.MkdirAll(tmp, defaultDirectoryPermissions); err != nil { + return nil, err + } + return ioutil.TempFile(tmp, "archived_logs") +} + +// ============================================================================================= +// Different types of rolling writers +// ============================================================================================= + +// -------------------------------------------------- +// Rolling writer by SIZE +// -------------------------------------------------- + +// rollingFileWriterSize performs roll when file exceeds a specified limit. +type rollingFileWriterSize struct { + *rollingFileWriter + maxFileSize int64 +} + +func NewRollingFileWriterSize(fpath string, atype rollingArchiveType, apath string, maxSize int64, maxRolls int, namemode rollingNameMode, archiveExploded bool) (*rollingFileWriterSize, error) { + rw, err := newRollingFileWriter(fpath, rollingTypeSize, atype, apath, maxRolls, namemode, archiveExploded, false) + if err != nil { + return nil, err + } + rws := &rollingFileWriterSize{rw, maxSize} + rws.self = rws + return rws, nil +} + +func (rws *rollingFileWriterSize) needsToRoll() bool { + return rws.currentFileSize >= rws.maxFileSize +} + +func (rws *rollingFileWriterSize) isFileRollNameValid(rname string) bool { + if len(rname) == 0 { + return false + } + _, err := strconv.Atoi(rname) + return err == nil +} + +type rollSizeFileTailsSlice []string + +func (p rollSizeFileTailsSlice) Len() int { + return len(p) +} +func (p rollSizeFileTailsSlice) Less(i, j int) bool { + v1, _ := strconv.Atoi(p[i]) + v2, _ := strconv.Atoi(p[j]) + return v1 < v2 +} +func (p rollSizeFileTailsSlice) Swap(i, j int) { + p[i], p[j] = p[j], p[i] +} + +func (rws *rollingFileWriterSize) sortFileRollNamesAsc(fs []string) ([]string, error) { + ss := rollSizeFileTailsSlice(fs) + sort.Sort(ss) + return ss, nil +} + +func (rws *rollingFileWriterSize) getNewHistoryRollFileName(otherLogFiles []string) string { + v := 0 + if len(otherLogFiles) != 0 { + latest := otherLogFiles[len(otherLogFiles)-1] + v, _ = strconv.Atoi(rws.getFileRollName(latest)) + } + return fmt.Sprintf("%d", v+1) +} + +func (rws *rollingFileWriterSize) getCurrentFileName() string { + return rws.fileName +} + +func (rws *rollingFileWriterSize) String() string { + return fmt.Sprintf("Rolling file writer (By SIZE): filename: %s, archive: %s, archivefile: %s, maxFileSize: %v, maxRolls: %v", + rws.fileName, + rollingArchiveTypesStringRepresentation[rws.archiveType], + rws.archivePath, + rws.maxFileSize, + rws.maxRolls) +} + +// -------------------------------------------------- +// Rolling writer by TIME +// -------------------------------------------------- + +// rollingFileWriterTime performs roll when a specified time interval has passed. +type rollingFileWriterTime struct { + *rollingFileWriter + timePattern string + currentTimeFileName string +} + +func NewRollingFileWriterTime(fpath string, atype rollingArchiveType, apath string, maxr int, + timePattern string, namemode rollingNameMode, archiveExploded bool, fullName bool) (*rollingFileWriterTime, error) { + + rw, err := newRollingFileWriter(fpath, rollingTypeTime, atype, apath, maxr, namemode, archiveExploded, fullName) + if err != nil { + return nil, err + } + rws := &rollingFileWriterTime{rw, timePattern, ""} + rws.self = rws + return rws, nil +} + +func (rwt *rollingFileWriterTime) needsToRoll() bool { + newName := time.Now().Format(rwt.timePattern) + + if rwt.currentTimeFileName == "" { + // first run; capture the current name + rwt.currentTimeFileName = newName + return false + } + + return newName != rwt.currentTimeFileName +} + +func (rwt *rollingFileWriterTime) isFileRollNameValid(rname string) bool { + if len(rname) == 0 { + return false + } + _, err := time.ParseInLocation(rwt.timePattern, rname, time.Local) + return err == nil +} + +type rollTimeFileTailsSlice struct { + data []string + pattern string +} + +func (p rollTimeFileTailsSlice) Len() int { + return len(p.data) +} + +func (p rollTimeFileTailsSlice) Less(i, j int) bool { + t1, _ := time.ParseInLocation(p.pattern, p.data[i], time.Local) + t2, _ := time.ParseInLocation(p.pattern, p.data[j], time.Local) + return t1.Before(t2) +} + +func (p rollTimeFileTailsSlice) Swap(i, j int) { + p.data[i], p.data[j] = p.data[j], p.data[i] +} + +func (rwt *rollingFileWriterTime) sortFileRollNamesAsc(fs []string) ([]string, error) { + ss := rollTimeFileTailsSlice{data: fs, pattern: rwt.timePattern} + sort.Sort(ss) + return ss.data, nil +} + +func (rwt *rollingFileWriterTime) getNewHistoryRollFileName(_ []string) string { + newFileName := rwt.currentTimeFileName + rwt.currentTimeFileName = time.Now().Format(rwt.timePattern) + return newFileName +} + +func (rwt *rollingFileWriterTime) getCurrentFileName() string { + if rwt.fullName { + return rwt.createFullFileName(rwt.fileName, time.Now().Format(rwt.timePattern)) + } + return rwt.fileName +} + +func (rwt *rollingFileWriterTime) String() string { + return fmt.Sprintf("Rolling file writer (By TIME): filename: %s, archive: %s, archivefile: %s, pattern: %s, maxRolls: %v", + rwt.fileName, + rollingArchiveTypesStringRepresentation[rwt.archiveType], + rwt.archivePath, + rwt.timePattern, + rwt.maxRolls) +} diff --git a/vendor/github.com/cihub/seelog/writers_smtpwriter.go b/vendor/github.com/cihub/seelog/writers_smtpwriter.go new file mode 100644 index 00000000..31b79438 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_smtpwriter.go @@ -0,0 +1,214 @@ +// Copyright (c) 2012 - Cloud Instruments Co., Ltd. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package seelog + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io/ioutil" + "net/smtp" + "path/filepath" + "strings" +) + +const ( + // Default subject phrase for sending emails. + DefaultSubjectPhrase = "Diagnostic message from server: " + + // Message subject pattern composed according to RFC 5321. + rfc5321SubjectPattern = "From: %s <%s>\nSubject: %s\n\n" +) + +// smtpWriter is used to send emails via given SMTP-server. +type smtpWriter struct { + auth smtp.Auth + hostName string + hostPort string + hostNameWithPort string + senderAddress string + senderName string + recipientAddresses []string + caCertDirPaths []string + mailHeaders []string + subject string +} + +// NewSMTPWriter returns a new SMTP-writer. +func NewSMTPWriter(sa, sn string, ras []string, hn, hp, un, pwd string, cacdps []string, subj string, headers []string) *smtpWriter { + return &smtpWriter{ + auth: smtp.PlainAuth("", un, pwd, hn), + hostName: hn, + hostPort: hp, + hostNameWithPort: fmt.Sprintf("%s:%s", hn, hp), + senderAddress: sa, + senderName: sn, + recipientAddresses: ras, + caCertDirPaths: cacdps, + subject: subj, + mailHeaders: headers, + } +} + +func prepareMessage(senderAddr, senderName, subject string, body []byte, headers []string) []byte { + headerLines := fmt.Sprintf(rfc5321SubjectPattern, senderName, senderAddr, subject) + // Build header lines if configured. + if headers != nil && len(headers) > 0 { + headerLines += strings.Join(headers, "\n") + headerLines += "\n" + } + return append([]byte(headerLines), body...) +} + +// getTLSConfig gets paths of PEM files with certificates, +// host server name and tries to create an appropriate TLS.Config. +func getTLSConfig(pemFileDirPaths []string, hostName string) (config *tls.Config, err error) { + if pemFileDirPaths == nil || len(pemFileDirPaths) == 0 { + err = errors.New("invalid PEM file paths") + return + } + pemEncodedContent := []byte{} + var ( + e error + bytes []byte + ) + // Create a file-filter-by-extension, set aside non-pem files. + pemFilePathFilter := func(fp string) bool { + if filepath.Ext(fp) == ".pem" { + return true + } + return false + } + for _, pemFileDirPath := range pemFileDirPaths { + pemFilePaths, err := getDirFilePaths(pemFileDirPath, pemFilePathFilter, false) + if err != nil { + return nil, err + } + + // Put together all the PEM files to decode them as a whole byte slice. + for _, pfp := range pemFilePaths { + if bytes, e = ioutil.ReadFile(pfp); e == nil { + pemEncodedContent = append(pemEncodedContent, bytes...) + } else { + return nil, fmt.Errorf("cannot read file: %s: %s", pfp, e.Error()) + } + } + } + config = &tls.Config{RootCAs: x509.NewCertPool(), ServerName: hostName} + isAppended := config.RootCAs.AppendCertsFromPEM(pemEncodedContent) + if !isAppended { + // Extract this into a separate error. + err = errors.New("invalid PEM content") + return + } + return +} + +// SendMail accepts TLS configuration, connects to the server at addr, +// switches to TLS if possible, authenticates with mechanism a if possible, +// and then sends an email from address from, to addresses to, with message msg. +func sendMailWithTLSConfig(config *tls.Config, addr string, a smtp.Auth, from string, to []string, msg []byte) error { + c, err := smtp.Dial(addr) + if err != nil { + return err + } + // Check if the server supports STARTTLS extension. + if ok, _ := c.Extension("STARTTLS"); ok { + if err = c.StartTLS(config); err != nil { + return err + } + } + // Check if the server supports AUTH extension and use given smtp.Auth. + if a != nil { + if isSupported, _ := c.Extension("AUTH"); isSupported { + if err = c.Auth(a); err != nil { + return err + } + } + } + // Portion of code from the official smtp.SendMail function, + // see http://golang.org/src/pkg/net/smtp/smtp.go. + if err = c.Mail(from); err != nil { + return err + } + for _, addr := range to { + if err = c.Rcpt(addr); err != nil { + return err + } + } + w, err := c.Data() + if err != nil { + return err + } + _, err = w.Write(msg) + if err != nil { + return err + } + err = w.Close() + if err != nil { + return err + } + return c.Quit() +} + +// Write pushes a text message properly composed according to RFC 5321 +// to a post server, which sends it to the recipients. +func (smtpw *smtpWriter) Write(data []byte) (int, error) { + var err error + + if smtpw.caCertDirPaths == nil { + err = smtp.SendMail( + smtpw.hostNameWithPort, + smtpw.auth, + smtpw.senderAddress, + smtpw.recipientAddresses, + prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders), + ) + } else { + config, e := getTLSConfig(smtpw.caCertDirPaths, smtpw.hostName) + if e != nil { + return 0, e + } + err = sendMailWithTLSConfig( + config, + smtpw.hostNameWithPort, + smtpw.auth, + smtpw.senderAddress, + smtpw.recipientAddresses, + prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders), + ) + } + if err != nil { + return 0, err + } + return len(data), nil +} + +// Close closes down SMTP-connection. +func (smtpw *smtpWriter) Close() error { + // Do nothing as Write method opens and closes connection automatically. + return nil +} diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE new file mode 100644 index 00000000..c8364161 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2012-2016 Dave Collins + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go new file mode 100644 index 00000000..8a4a6589 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go @@ -0,0 +1,152 @@ +// Copyright (c) 2015-2016 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when the code is not running on Google App Engine, compiled by GopherJS, and +// "-tags safe" is not added to the go build command line. The "disableunsafe" +// tag is deprecated and thus should not be used. +// +build !js,!appengine,!safe,!disableunsafe + +package spew + +import ( + "reflect" + "unsafe" +) + +const ( + // UnsafeDisabled is a build-time constant which specifies whether or + // not access to the unsafe package is available. + UnsafeDisabled = false + + // ptrSize is the size of a pointer on the current arch. + ptrSize = unsafe.Sizeof((*byte)(nil)) +) + +var ( + // offsetPtr, offsetScalar, and offsetFlag are the offsets for the + // internal reflect.Value fields. These values are valid before golang + // commit ecccf07e7f9d which changed the format. The are also valid + // after commit 82f48826c6c7 which changed the format again to mirror + // the original format. Code in the init function updates these offsets + // as necessary. + offsetPtr = uintptr(ptrSize) + offsetScalar = uintptr(0) + offsetFlag = uintptr(ptrSize * 2) + + // flagKindWidth and flagKindShift indicate various bits that the + // reflect package uses internally to track kind information. + // + // flagRO indicates whether or not the value field of a reflect.Value is + // read-only. + // + // flagIndir indicates whether the value field of a reflect.Value is + // the actual data or a pointer to the data. + // + // These values are valid before golang commit 90a7c3c86944 which + // changed their positions. Code in the init function updates these + // flags as necessary. + flagKindWidth = uintptr(5) + flagKindShift = uintptr(flagKindWidth - 1) + flagRO = uintptr(1 << 0) + flagIndir = uintptr(1 << 1) +) + +func init() { + // Older versions of reflect.Value stored small integers directly in the + // ptr field (which is named val in the older versions). Versions + // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named + // scalar for this purpose which unfortunately came before the flag + // field, so the offset of the flag field is different for those + // versions. + // + // This code constructs a new reflect.Value from a known small integer + // and checks if the size of the reflect.Value struct indicates it has + // the scalar field. When it does, the offsets are updated accordingly. + vv := reflect.ValueOf(0xf00) + if unsafe.Sizeof(vv) == (ptrSize * 4) { + offsetScalar = ptrSize * 2 + offsetFlag = ptrSize * 3 + } + + // Commit 90a7c3c86944 changed the flag positions such that the low + // order bits are the kind. This code extracts the kind from the flags + // field and ensures it's the correct type. When it's not, the flag + // order has been changed to the newer format, so the flags are updated + // accordingly. + upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) + upfv := *(*uintptr)(upf) + flagKindMask := uintptr((1<>flagKindShift != uintptr(reflect.Int) { + flagKindShift = 0 + flagRO = 1 << 5 + flagIndir = 1 << 6 + + // Commit adf9b30e5594 modified the flags to separate the + // flagRO flag into two bits which specifies whether or not the + // field is embedded. This causes flagIndir to move over a bit + // and means that flagRO is the combination of either of the + // original flagRO bit and the new bit. + // + // This code detects the change by extracting what used to be + // the indirect bit to ensure it's set. When it's not, the flag + // order has been changed to the newer format, so the flags are + // updated accordingly. + if upfv&flagIndir == 0 { + flagRO = 3 << 5 + flagIndir = 1 << 7 + } + } +} + +// unsafeReflectValue converts the passed reflect.Value into a one that bypasses +// the typical safety restrictions preventing access to unaddressable and +// unexported data. It works by digging the raw pointer to the underlying +// value out of the protected value and generating a new unprotected (unsafe) +// reflect.Value to it. +// +// This allows us to check for implementations of the Stringer and error +// interfaces to be used for pretty printing ordinarily unaddressable and +// inaccessible values such as unexported struct fields. +func unsafeReflectValue(v reflect.Value) (rv reflect.Value) { + indirects := 1 + vt := v.Type() + upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) + rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) + if rvf&flagIndir != 0 { + vt = reflect.PtrTo(v.Type()) + indirects++ + } else if offsetScalar != 0 { + // The value is in the scalar field when it's not one of the + // reference types. + switch vt.Kind() { + case reflect.Uintptr: + case reflect.Chan: + case reflect.Func: + case reflect.Map: + case reflect.Ptr: + case reflect.UnsafePointer: + default: + upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + + offsetScalar) + } + } + + pv := reflect.NewAt(vt, upv) + rv = pv + for i := 0; i < indirects; i++ { + rv = rv.Elem() + } + return rv +} diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go new file mode 100644 index 00000000..1fe3cf3d --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go @@ -0,0 +1,38 @@ +// Copyright (c) 2015-2016 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when the code is running on Google App Engine, compiled by GopherJS, or +// "-tags safe" is added to the go build command line. The "disableunsafe" +// tag is deprecated and thus should not be used. +// +build js appengine safe disableunsafe + +package spew + +import "reflect" + +const ( + // UnsafeDisabled is a build-time constant which specifies whether or + // not access to the unsafe package is available. + UnsafeDisabled = true +) + +// unsafeReflectValue typically converts the passed reflect.Value into a one +// that bypasses the typical safety restrictions preventing access to +// unaddressable and unexported data. However, doing this relies on access to +// the unsafe package. This is a stub version which simply returns the passed +// reflect.Value when the unsafe package is not available. +func unsafeReflectValue(v reflect.Value) reflect.Value { + return v +} diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go new file mode 100644 index 00000000..7c519ff4 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/common.go @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "io" + "reflect" + "sort" + "strconv" +) + +// Some constants in the form of bytes to avoid string overhead. This mirrors +// the technique used in the fmt package. +var ( + panicBytes = []byte("(PANIC=") + plusBytes = []byte("+") + iBytes = []byte("i") + trueBytes = []byte("true") + falseBytes = []byte("false") + interfaceBytes = []byte("(interface {})") + commaNewlineBytes = []byte(",\n") + newlineBytes = []byte("\n") + openBraceBytes = []byte("{") + openBraceNewlineBytes = []byte("{\n") + closeBraceBytes = []byte("}") + asteriskBytes = []byte("*") + colonBytes = []byte(":") + colonSpaceBytes = []byte(": ") + openParenBytes = []byte("(") + closeParenBytes = []byte(")") + spaceBytes = []byte(" ") + pointerChainBytes = []byte("->") + nilAngleBytes = []byte("") + maxNewlineBytes = []byte("\n") + maxShortBytes = []byte("") + circularBytes = []byte("") + circularShortBytes = []byte("") + invalidAngleBytes = []byte("") + openBracketBytes = []byte("[") + closeBracketBytes = []byte("]") + percentBytes = []byte("%") + precisionBytes = []byte(".") + openAngleBytes = []byte("<") + closeAngleBytes = []byte(">") + openMapBytes = []byte("map[") + closeMapBytes = []byte("]") + lenEqualsBytes = []byte("len=") + capEqualsBytes = []byte("cap=") +) + +// hexDigits is used to map a decimal value to a hex digit. +var hexDigits = "0123456789abcdef" + +// catchPanic handles any panics that might occur during the handleMethods +// calls. +func catchPanic(w io.Writer, v reflect.Value) { + if err := recover(); err != nil { + w.Write(panicBytes) + fmt.Fprintf(w, "%v", err) + w.Write(closeParenBytes) + } +} + +// handleMethods attempts to call the Error and String methods on the underlying +// type the passed reflect.Value represents and outputes the result to Writer w. +// +// It handles panics in any called methods by catching and displaying the error +// as the formatted value. +func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) { + // We need an interface to check if the type implements the error or + // Stringer interface. However, the reflect package won't give us an + // interface on certain things like unexported struct fields in order + // to enforce visibility rules. We use unsafe, when it's available, + // to bypass these restrictions since this package does not mutate the + // values. + if !v.CanInterface() { + if UnsafeDisabled { + return false + } + + v = unsafeReflectValue(v) + } + + // Choose whether or not to do error and Stringer interface lookups against + // the base type or a pointer to the base type depending on settings. + // Technically calling one of these methods with a pointer receiver can + // mutate the value, however, types which choose to satisify an error or + // Stringer interface with a pointer receiver should not be mutating their + // state inside these interface methods. + if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() { + v = unsafeReflectValue(v) + } + if v.CanAddr() { + v = v.Addr() + } + + // Is it an error or Stringer? + switch iface := v.Interface().(type) { + case error: + defer catchPanic(w, v) + if cs.ContinueOnMethod { + w.Write(openParenBytes) + w.Write([]byte(iface.Error())) + w.Write(closeParenBytes) + w.Write(spaceBytes) + return false + } + + w.Write([]byte(iface.Error())) + return true + + case fmt.Stringer: + defer catchPanic(w, v) + if cs.ContinueOnMethod { + w.Write(openParenBytes) + w.Write([]byte(iface.String())) + w.Write(closeParenBytes) + w.Write(spaceBytes) + return false + } + w.Write([]byte(iface.String())) + return true + } + return false +} + +// printBool outputs a boolean value as true or false to Writer w. +func printBool(w io.Writer, val bool) { + if val { + w.Write(trueBytes) + } else { + w.Write(falseBytes) + } +} + +// printInt outputs a signed integer value to Writer w. +func printInt(w io.Writer, val int64, base int) { + w.Write([]byte(strconv.FormatInt(val, base))) +} + +// printUint outputs an unsigned integer value to Writer w. +func printUint(w io.Writer, val uint64, base int) { + w.Write([]byte(strconv.FormatUint(val, base))) +} + +// printFloat outputs a floating point value using the specified precision, +// which is expected to be 32 or 64bit, to Writer w. +func printFloat(w io.Writer, val float64, precision int) { + w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) +} + +// printComplex outputs a complex value using the specified float precision +// for the real and imaginary parts to Writer w. +func printComplex(w io.Writer, c complex128, floatPrecision int) { + r := real(c) + w.Write(openParenBytes) + w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) + i := imag(c) + if i >= 0 { + w.Write(plusBytes) + } + w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) + w.Write(iBytes) + w.Write(closeParenBytes) +} + +// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x' +// prefix to Writer w. +func printHexPtr(w io.Writer, p uintptr) { + // Null pointer. + num := uint64(p) + if num == 0 { + w.Write(nilAngleBytes) + return + } + + // Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix + buf := make([]byte, 18) + + // It's simpler to construct the hex string right to left. + base := uint64(16) + i := len(buf) - 1 + for num >= base { + buf[i] = hexDigits[num%base] + num /= base + i-- + } + buf[i] = hexDigits[num] + + // Add '0x' prefix. + i-- + buf[i] = 'x' + i-- + buf[i] = '0' + + // Strip unused leading bytes. + buf = buf[i:] + w.Write(buf) +} + +// valuesSorter implements sort.Interface to allow a slice of reflect.Value +// elements to be sorted. +type valuesSorter struct { + values []reflect.Value + strings []string // either nil or same len and values + cs *ConfigState +} + +// newValuesSorter initializes a valuesSorter instance, which holds a set of +// surrogate keys on which the data should be sorted. It uses flags in +// ConfigState to decide if and how to populate those surrogate keys. +func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface { + vs := &valuesSorter{values: values, cs: cs} + if canSortSimply(vs.values[0].Kind()) { + return vs + } + if !cs.DisableMethods { + vs.strings = make([]string, len(values)) + for i := range vs.values { + b := bytes.Buffer{} + if !handleMethods(cs, &b, vs.values[i]) { + vs.strings = nil + break + } + vs.strings[i] = b.String() + } + } + if vs.strings == nil && cs.SpewKeys { + vs.strings = make([]string, len(values)) + for i := range vs.values { + vs.strings[i] = Sprintf("%#v", vs.values[i].Interface()) + } + } + return vs +} + +// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted +// directly, or whether it should be considered for sorting by surrogate keys +// (if the ConfigState allows it). +func canSortSimply(kind reflect.Kind) bool { + // This switch parallels valueSortLess, except for the default case. + switch kind { + case reflect.Bool: + return true + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return true + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return true + case reflect.Float32, reflect.Float64: + return true + case reflect.String: + return true + case reflect.Uintptr: + return true + case reflect.Array: + return true + } + return false +} + +// Len returns the number of values in the slice. It is part of the +// sort.Interface implementation. +func (s *valuesSorter) Len() int { + return len(s.values) +} + +// Swap swaps the values at the passed indices. It is part of the +// sort.Interface implementation. +func (s *valuesSorter) Swap(i, j int) { + s.values[i], s.values[j] = s.values[j], s.values[i] + if s.strings != nil { + s.strings[i], s.strings[j] = s.strings[j], s.strings[i] + } +} + +// valueSortLess returns whether the first value should sort before the second +// value. It is used by valueSorter.Less as part of the sort.Interface +// implementation. +func valueSortLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Bool: + return !a.Bool() && b.Bool() + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return a.Int() < b.Int() + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return a.Uint() < b.Uint() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.String: + return a.String() < b.String() + case reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Array: + // Compare the contents of both arrays. + l := a.Len() + for i := 0; i < l; i++ { + av := a.Index(i) + bv := b.Index(i) + if av.Interface() == bv.Interface() { + continue + } + return valueSortLess(av, bv) + } + } + return a.String() < b.String() +} + +// Less returns whether the value at index i should sort before the +// value at index j. It is part of the sort.Interface implementation. +func (s *valuesSorter) Less(i, j int) bool { + if s.strings == nil { + return valueSortLess(s.values[i], s.values[j]) + } + return s.strings[i] < s.strings[j] +} + +// sortValues is a sort function that handles both native types and any type that +// can be converted to error or Stringer. Other inputs are sorted according to +// their Value.String() value to ensure display stability. +func sortValues(values []reflect.Value, cs *ConfigState) { + if len(values) == 0 { + return + } + sort.Sort(newValuesSorter(values, cs)) +} diff --git a/vendor/github.com/davecgh/go-spew/spew/config.go b/vendor/github.com/davecgh/go-spew/spew/config.go new file mode 100644 index 00000000..2e3d22f3 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/config.go @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "io" + "os" +) + +// ConfigState houses the configuration options used by spew to format and +// display values. There is a global instance, Config, that is used to control +// all top-level Formatter and Dump functionality. Each ConfigState instance +// provides methods equivalent to the top-level functions. +// +// The zero value for ConfigState provides no indentation. You would typically +// want to set it to a space or a tab. +// +// Alternatively, you can use NewDefaultConfig to get a ConfigState instance +// with default settings. See the documentation of NewDefaultConfig for default +// values. +type ConfigState struct { + // Indent specifies the string to use for each indentation level. The + // global config instance that all top-level functions use set this to a + // single space by default. If you would like more indentation, you might + // set this to a tab with "\t" or perhaps two spaces with " ". + Indent string + + // MaxDepth controls the maximum number of levels to descend into nested + // data structures. The default, 0, means there is no limit. + // + // NOTE: Circular data structures are properly detected, so it is not + // necessary to set this value unless you specifically want to limit deeply + // nested data structures. + MaxDepth int + + // DisableMethods specifies whether or not error and Stringer interfaces are + // invoked for types that implement them. + DisableMethods bool + + // DisablePointerMethods specifies whether or not to check for and invoke + // error and Stringer interfaces on types which only accept a pointer + // receiver when the current type is not a pointer. + // + // NOTE: This might be an unsafe action since calling one of these methods + // with a pointer receiver could technically mutate the value, however, + // in practice, types which choose to satisify an error or Stringer + // interface with a pointer receiver should not be mutating their state + // inside these interface methods. As a result, this option relies on + // access to the unsafe package, so it will not have any effect when + // running in environments without access to the unsafe package such as + // Google App Engine or with the "safe" build tag specified. + DisablePointerMethods bool + + // DisablePointerAddresses specifies whether to disable the printing of + // pointer addresses. This is useful when diffing data structures in tests. + DisablePointerAddresses bool + + // DisableCapacities specifies whether to disable the printing of capacities + // for arrays, slices, maps and channels. This is useful when diffing + // data structures in tests. + DisableCapacities bool + + // ContinueOnMethod specifies whether or not recursion should continue once + // a custom error or Stringer interface is invoked. The default, false, + // means it will print the results of invoking the custom error or Stringer + // interface and return immediately instead of continuing to recurse into + // the internals of the data type. + // + // NOTE: This flag does not have any effect if method invocation is disabled + // via the DisableMethods or DisablePointerMethods options. + ContinueOnMethod bool + + // SortKeys specifies map keys should be sorted before being printed. Use + // this to have a more deterministic, diffable output. Note that only + // native types (bool, int, uint, floats, uintptr and string) and types + // that support the error or Stringer interfaces (if methods are + // enabled) are supported, with other types sorted according to the + // reflect.Value.String() output which guarantees display stability. + SortKeys bool + + // SpewKeys specifies that, as a last resort attempt, map keys should + // be spewed to strings and sorted by those strings. This is only + // considered if SortKeys is true. + SpewKeys bool +} + +// Config is the active configuration of the top-level functions. +// The configuration can be changed by modifying the contents of spew.Config. +var Config = ConfigState{Indent: " "} + +// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the formatted string as a value that satisfies error. See NewFormatter +// for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) { + return fmt.Errorf(format, c.convertArgs(a)...) +} + +// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprint(w, c.convertArgs(a)...) +} + +// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(w, format, c.convertArgs(a)...) +} + +// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it +// passed with a Formatter interface returned by c.NewFormatter. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprintln(w, c.convertArgs(a)...) +} + +// Print is a wrapper for fmt.Print that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Print(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Print(a ...interface{}) (n int, err error) { + return fmt.Print(c.convertArgs(a)...) +} + +// Printf is a wrapper for fmt.Printf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) { + return fmt.Printf(format, c.convertArgs(a)...) +} + +// Println is a wrapper for fmt.Println that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Println(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Println(a ...interface{}) (n int, err error) { + return fmt.Println(c.convertArgs(a)...) +} + +// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprint(a ...interface{}) string { + return fmt.Sprint(c.convertArgs(a)...) +} + +// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprintf(format string, a ...interface{}) string { + return fmt.Sprintf(format, c.convertArgs(a)...) +} + +// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it +// were passed with a Formatter interface returned by c.NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprintln(a ...interface{}) string { + return fmt.Sprintln(c.convertArgs(a)...) +} + +/* +NewFormatter returns a custom formatter that satisfies the fmt.Formatter +interface. As a result, it integrates cleanly with standard fmt package +printing functions. The formatter is useful for inline printing of smaller data +types similar to the standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Typically this function shouldn't be called directly. It is much easier to make +use of the custom formatter by calling one of the convenience functions such as +c.Printf, c.Println, or c.Printf. +*/ +func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter { + return newFormatter(c, v) +} + +// Fdump formats and displays the passed arguments to io.Writer w. It formats +// exactly the same as Dump. +func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) { + fdump(c, w, a...) +} + +/* +Dump displays the passed parameters to standard out with newlines, customizable +indentation, and additional debug information such as complete types and all +pointer addresses used to indirect to the final value. It provides the +following features over the built-in printing facilities provided by the fmt +package: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output + +The configuration options are controlled by modifying the public members +of c. See ConfigState for options documentation. + +See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to +get the formatted result as a string. +*/ +func (c *ConfigState) Dump(a ...interface{}) { + fdump(c, os.Stdout, a...) +} + +// Sdump returns a string with the passed arguments formatted exactly the same +// as Dump. +func (c *ConfigState) Sdump(a ...interface{}) string { + var buf bytes.Buffer + fdump(c, &buf, a...) + return buf.String() +} + +// convertArgs accepts a slice of arguments and returns a slice of the same +// length with each argument converted to a spew Formatter interface using +// the ConfigState associated with s. +func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) { + formatters = make([]interface{}, len(args)) + for index, arg := range args { + formatters[index] = newFormatter(c, arg) + } + return formatters +} + +// NewDefaultConfig returns a ConfigState with the following default settings. +// +// Indent: " " +// MaxDepth: 0 +// DisableMethods: false +// DisablePointerMethods: false +// ContinueOnMethod: false +// SortKeys: false +func NewDefaultConfig() *ConfigState { + return &ConfigState{Indent: " "} +} diff --git a/vendor/github.com/davecgh/go-spew/spew/doc.go b/vendor/github.com/davecgh/go-spew/spew/doc.go new file mode 100644 index 00000000..aacaac6f --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/doc.go @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +Package spew implements a deep pretty printer for Go data structures to aid in +debugging. + +A quick overview of the additional features spew provides over the built-in +printing facilities for Go data types are as follows: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output (only when using + Dump style) + +There are two different approaches spew allows for dumping Go data structures: + + * Dump style which prints with newlines, customizable indentation, + and additional debug information such as types and all pointer addresses + used to indirect to the final value + * A custom Formatter interface that integrates cleanly with the standard fmt + package and replaces %v, %+v, %#v, and %#+v to provide inline printing + similar to the default %v while providing the additional functionality + outlined above and passing unsupported format verbs such as %x and %q + along to fmt + +Quick Start + +This section demonstrates how to quickly get started with spew. See the +sections below for further details on formatting and configuration options. + +To dump a variable with full newlines, indentation, type, and pointer +information use Dump, Fdump, or Sdump: + spew.Dump(myVar1, myVar2, ...) + spew.Fdump(someWriter, myVar1, myVar2, ...) + str := spew.Sdump(myVar1, myVar2, ...) + +Alternatively, if you would prefer to use format strings with a compacted inline +printing style, use the convenience wrappers Printf, Fprintf, etc with +%v (most compact), %+v (adds pointer addresses), %#v (adds types), or +%#+v (adds types and pointer addresses): + spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + +Configuration Options + +Configuration of spew is handled by fields in the ConfigState type. For +convenience, all of the top-level functions use a global state available +via the spew.Config global. + +It is also possible to create a ConfigState instance that provides methods +equivalent to the top-level functions. This allows concurrent configuration +options. See the ConfigState documentation for more details. + +The following configuration options are available: + * Indent + String to use for each indentation level for Dump functions. + It is a single space by default. A popular alternative is "\t". + + * MaxDepth + Maximum number of levels to descend into nested data structures. + There is no limit by default. + + * DisableMethods + Disables invocation of error and Stringer interface methods. + Method invocation is enabled by default. + + * DisablePointerMethods + Disables invocation of error and Stringer interface methods on types + which only accept pointer receivers from non-pointer variables. + Pointer method invocation is enabled by default. + + * DisablePointerAddresses + DisablePointerAddresses specifies whether to disable the printing of + pointer addresses. This is useful when diffing data structures in tests. + + * DisableCapacities + DisableCapacities specifies whether to disable the printing of + capacities for arrays, slices, maps and channels. This is useful when + diffing data structures in tests. + + * ContinueOnMethod + Enables recursion into types after invoking error and Stringer interface + methods. Recursion after method invocation is disabled by default. + + * SortKeys + Specifies map keys should be sorted before being printed. Use + this to have a more deterministic, diffable output. Note that + only native types (bool, int, uint, floats, uintptr and string) + and types which implement error or Stringer interfaces are + supported with other types sorted according to the + reflect.Value.String() output which guarantees display + stability. Natural map order is used by default. + + * SpewKeys + Specifies that, as a last resort attempt, map keys should be + spewed to strings and sorted by those strings. This is only + considered if SortKeys is true. + +Dump Usage + +Simply call spew.Dump with a list of variables you want to dump: + + spew.Dump(myVar1, myVar2, ...) + +You may also call spew.Fdump if you would prefer to output to an arbitrary +io.Writer. For example, to dump to standard error: + + spew.Fdump(os.Stderr, myVar1, myVar2, ...) + +A third option is to call spew.Sdump to get the formatted output as a string: + + str := spew.Sdump(myVar1, myVar2, ...) + +Sample Dump Output + +See the Dump example for details on the setup of the types and variables being +shown here. + + (main.Foo) { + unexportedField: (*main.Bar)(0xf84002e210)({ + flag: (main.Flag) flagTwo, + data: (uintptr) + }), + ExportedField: (map[interface {}]interface {}) (len=1) { + (string) (len=3) "one": (bool) true + } + } + +Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C +command as shown. + ([]uint8) (len=32 cap=32) { + 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | + 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| + 00000020 31 32 |12| + } + +Custom Formatter + +Spew provides a custom formatter that implements the fmt.Formatter interface +so that it integrates cleanly with standard fmt package printing functions. The +formatter is useful for inline printing of smaller data types similar to the +standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Custom Formatter Usage + +The simplest way to make use of the spew custom formatter is to call one of the +convenience functions such as spew.Printf, spew.Println, or spew.Printf. The +functions have syntax you are most likely already familiar with: + + spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + spew.Println(myVar, myVar2) + spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + +See the Index for the full list convenience functions. + +Sample Formatter Output + +Double pointer to a uint8: + %v: <**>5 + %+v: <**>(0xf8400420d0->0xf8400420c8)5 + %#v: (**uint8)5 + %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5 + +Pointer to circular struct with a uint8 field and a pointer to itself: + %v: <*>{1 <*>} + %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)} + %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)} + %#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)} + +See the Printf example for details on the setup of variables being shown +here. + +Errors + +Since it is possible for custom Stringer/error interfaces to panic, spew +detects them and handles them internally by printing the panic information +inline with the output. Since spew is intended to provide deep pretty printing +capabilities on structures, it intentionally does not return any errors. +*/ +package spew diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go new file mode 100644 index 00000000..df1d582a --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/dump.go @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "os" + "reflect" + "regexp" + "strconv" + "strings" +) + +var ( + // uint8Type is a reflect.Type representing a uint8. It is used to + // convert cgo types to uint8 slices for hexdumping. + uint8Type = reflect.TypeOf(uint8(0)) + + // cCharRE is a regular expression that matches a cgo char. + // It is used to detect character arrays to hexdump them. + cCharRE = regexp.MustCompile("^.*\\._Ctype_char$") + + // cUnsignedCharRE is a regular expression that matches a cgo unsigned + // char. It is used to detect unsigned character arrays to hexdump + // them. + cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$") + + // cUint8tCharRE is a regular expression that matches a cgo uint8_t. + // It is used to detect uint8_t arrays to hexdump them. + cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$") +) + +// dumpState contains information about the state of a dump operation. +type dumpState struct { + w io.Writer + depth int + pointers map[uintptr]int + ignoreNextType bool + ignoreNextIndent bool + cs *ConfigState +} + +// indent performs indentation according to the depth level and cs.Indent +// option. +func (d *dumpState) indent() { + if d.ignoreNextIndent { + d.ignoreNextIndent = false + return + } + d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth)) +} + +// unpackValue returns values inside of non-nil interfaces when possible. +// This is useful for data types like structs, arrays, slices, and maps which +// can contain varying types packed inside an interface. +func (d *dumpState) unpackValue(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Interface && !v.IsNil() { + v = v.Elem() + } + return v +} + +// dumpPtr handles formatting of pointers by indirecting them as necessary. +func (d *dumpState) dumpPtr(v reflect.Value) { + // Remove pointers at or below the current depth from map used to detect + // circular refs. + for k, depth := range d.pointers { + if depth >= d.depth { + delete(d.pointers, k) + } + } + + // Keep list of all dereferenced pointers to show later. + pointerChain := make([]uintptr, 0) + + // Figure out how many levels of indirection there are by dereferencing + // pointers and unpacking interfaces down the chain while detecting circular + // references. + nilFound := false + cycleFound := false + indirects := 0 + ve := v + for ve.Kind() == reflect.Ptr { + if ve.IsNil() { + nilFound = true + break + } + indirects++ + addr := ve.Pointer() + pointerChain = append(pointerChain, addr) + if pd, ok := d.pointers[addr]; ok && pd < d.depth { + cycleFound = true + indirects-- + break + } + d.pointers[addr] = d.depth + + ve = ve.Elem() + if ve.Kind() == reflect.Interface { + if ve.IsNil() { + nilFound = true + break + } + ve = ve.Elem() + } + } + + // Display type information. + d.w.Write(openParenBytes) + d.w.Write(bytes.Repeat(asteriskBytes, indirects)) + d.w.Write([]byte(ve.Type().String())) + d.w.Write(closeParenBytes) + + // Display pointer information. + if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 { + d.w.Write(openParenBytes) + for i, addr := range pointerChain { + if i > 0 { + d.w.Write(pointerChainBytes) + } + printHexPtr(d.w, addr) + } + d.w.Write(closeParenBytes) + } + + // Display dereferenced value. + d.w.Write(openParenBytes) + switch { + case nilFound == true: + d.w.Write(nilAngleBytes) + + case cycleFound == true: + d.w.Write(circularBytes) + + default: + d.ignoreNextType = true + d.dump(ve) + } + d.w.Write(closeParenBytes) +} + +// dumpSlice handles formatting of arrays and slices. Byte (uint8 under +// reflection) arrays and slices are dumped in hexdump -C fashion. +func (d *dumpState) dumpSlice(v reflect.Value) { + // Determine whether this type should be hex dumped or not. Also, + // for types which should be hexdumped, try to use the underlying data + // first, then fall back to trying to convert them to a uint8 slice. + var buf []uint8 + doConvert := false + doHexDump := false + numEntries := v.Len() + if numEntries > 0 { + vt := v.Index(0).Type() + vts := vt.String() + switch { + // C types that need to be converted. + case cCharRE.MatchString(vts): + fallthrough + case cUnsignedCharRE.MatchString(vts): + fallthrough + case cUint8tCharRE.MatchString(vts): + doConvert = true + + // Try to use existing uint8 slices and fall back to converting + // and copying if that fails. + case vt.Kind() == reflect.Uint8: + // We need an addressable interface to convert the type + // to a byte slice. However, the reflect package won't + // give us an interface on certain things like + // unexported struct fields in order to enforce + // visibility rules. We use unsafe, when available, to + // bypass these restrictions since this package does not + // mutate the values. + vs := v + if !vs.CanInterface() || !vs.CanAddr() { + vs = unsafeReflectValue(vs) + } + if !UnsafeDisabled { + vs = vs.Slice(0, numEntries) + + // Use the existing uint8 slice if it can be + // type asserted. + iface := vs.Interface() + if slice, ok := iface.([]uint8); ok { + buf = slice + doHexDump = true + break + } + } + + // The underlying data needs to be converted if it can't + // be type asserted to a uint8 slice. + doConvert = true + } + + // Copy and convert the underlying type if needed. + if doConvert && vt.ConvertibleTo(uint8Type) { + // Convert and copy each element into a uint8 byte + // slice. + buf = make([]uint8, numEntries) + for i := 0; i < numEntries; i++ { + vv := v.Index(i) + buf[i] = uint8(vv.Convert(uint8Type).Uint()) + } + doHexDump = true + } + } + + // Hexdump the entire slice as needed. + if doHexDump { + indent := strings.Repeat(d.cs.Indent, d.depth) + str := indent + hex.Dump(buf) + str = strings.Replace(str, "\n", "\n"+indent, -1) + str = strings.TrimRight(str, d.cs.Indent) + d.w.Write([]byte(str)) + return + } + + // Recursively call dump for each item. + for i := 0; i < numEntries; i++ { + d.dump(d.unpackValue(v.Index(i))) + if i < (numEntries - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } +} + +// dump is the main workhorse for dumping a value. It uses the passed reflect +// value to figure out what kind of object we are dealing with and formats it +// appropriately. It is a recursive function, however circular data structures +// are detected and handled properly. +func (d *dumpState) dump(v reflect.Value) { + // Handle invalid reflect values immediately. + kind := v.Kind() + if kind == reflect.Invalid { + d.w.Write(invalidAngleBytes) + return + } + + // Handle pointers specially. + if kind == reflect.Ptr { + d.indent() + d.dumpPtr(v) + return + } + + // Print type information unless already handled elsewhere. + if !d.ignoreNextType { + d.indent() + d.w.Write(openParenBytes) + d.w.Write([]byte(v.Type().String())) + d.w.Write(closeParenBytes) + d.w.Write(spaceBytes) + } + d.ignoreNextType = false + + // Display length and capacity if the built-in len and cap functions + // work with the value's kind and the len/cap itself is non-zero. + valueLen, valueCap := 0, 0 + switch v.Kind() { + case reflect.Array, reflect.Slice, reflect.Chan: + valueLen, valueCap = v.Len(), v.Cap() + case reflect.Map, reflect.String: + valueLen = v.Len() + } + if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 { + d.w.Write(openParenBytes) + if valueLen != 0 { + d.w.Write(lenEqualsBytes) + printInt(d.w, int64(valueLen), 10) + } + if !d.cs.DisableCapacities && valueCap != 0 { + if valueLen != 0 { + d.w.Write(spaceBytes) + } + d.w.Write(capEqualsBytes) + printInt(d.w, int64(valueCap), 10) + } + d.w.Write(closeParenBytes) + d.w.Write(spaceBytes) + } + + // Call Stringer/error interfaces if they exist and the handle methods flag + // is enabled + if !d.cs.DisableMethods { + if (kind != reflect.Invalid) && (kind != reflect.Interface) { + if handled := handleMethods(d.cs, d.w, v); handled { + return + } + } + } + + switch kind { + case reflect.Invalid: + // Do nothing. We should never get here since invalid has already + // been handled above. + + case reflect.Bool: + printBool(d.w, v.Bool()) + + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + printInt(d.w, v.Int(), 10) + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + printUint(d.w, v.Uint(), 10) + + case reflect.Float32: + printFloat(d.w, v.Float(), 32) + + case reflect.Float64: + printFloat(d.w, v.Float(), 64) + + case reflect.Complex64: + printComplex(d.w, v.Complex(), 32) + + case reflect.Complex128: + printComplex(d.w, v.Complex(), 64) + + case reflect.Slice: + if v.IsNil() { + d.w.Write(nilAngleBytes) + break + } + fallthrough + + case reflect.Array: + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + d.dumpSlice(v) + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.String: + d.w.Write([]byte(strconv.Quote(v.String()))) + + case reflect.Interface: + // The only time we should get here is for nil interfaces due to + // unpackValue calls. + if v.IsNil() { + d.w.Write(nilAngleBytes) + } + + case reflect.Ptr: + // Do nothing. We should never get here since pointers have already + // been handled above. + + case reflect.Map: + // nil maps should be indicated as different than empty maps + if v.IsNil() { + d.w.Write(nilAngleBytes) + break + } + + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + numEntries := v.Len() + keys := v.MapKeys() + if d.cs.SortKeys { + sortValues(keys, d.cs) + } + for i, key := range keys { + d.dump(d.unpackValue(key)) + d.w.Write(colonSpaceBytes) + d.ignoreNextIndent = true + d.dump(d.unpackValue(v.MapIndex(key))) + if i < (numEntries - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.Struct: + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + vt := v.Type() + numFields := v.NumField() + for i := 0; i < numFields; i++ { + d.indent() + vtf := vt.Field(i) + d.w.Write([]byte(vtf.Name)) + d.w.Write(colonSpaceBytes) + d.ignoreNextIndent = true + d.dump(d.unpackValue(v.Field(i))) + if i < (numFields - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.Uintptr: + printHexPtr(d.w, uintptr(v.Uint())) + + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + printHexPtr(d.w, v.Pointer()) + + // There were not any other types at the time this code was written, but + // fall back to letting the default fmt package handle it in case any new + // types are added. + default: + if v.CanInterface() { + fmt.Fprintf(d.w, "%v", v.Interface()) + } else { + fmt.Fprintf(d.w, "%v", v.String()) + } + } +} + +// fdump is a helper function to consolidate the logic from the various public +// methods which take varying writers and config states. +func fdump(cs *ConfigState, w io.Writer, a ...interface{}) { + for _, arg := range a { + if arg == nil { + w.Write(interfaceBytes) + w.Write(spaceBytes) + w.Write(nilAngleBytes) + w.Write(newlineBytes) + continue + } + + d := dumpState{w: w, cs: cs} + d.pointers = make(map[uintptr]int) + d.dump(reflect.ValueOf(arg)) + d.w.Write(newlineBytes) + } +} + +// Fdump formats and displays the passed arguments to io.Writer w. It formats +// exactly the same as Dump. +func Fdump(w io.Writer, a ...interface{}) { + fdump(&Config, w, a...) +} + +// Sdump returns a string with the passed arguments formatted exactly the same +// as Dump. +func Sdump(a ...interface{}) string { + var buf bytes.Buffer + fdump(&Config, &buf, a...) + return buf.String() +} + +/* +Dump displays the passed parameters to standard out with newlines, customizable +indentation, and additional debug information such as complete types and all +pointer addresses used to indirect to the final value. It provides the +following features over the built-in printing facilities provided by the fmt +package: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output + +The configuration options are controlled by an exported package global, +spew.Config. See ConfigState for options documentation. + +See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to +get the formatted result as a string. +*/ +func Dump(a ...interface{}) { + fdump(&Config, os.Stdout, a...) +} diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go new file mode 100644 index 00000000..c49875ba --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/format.go @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "reflect" + "strconv" + "strings" +) + +// supportedFlags is a list of all the character flags supported by fmt package. +const supportedFlags = "0-+# " + +// formatState implements the fmt.Formatter interface and contains information +// about the state of a formatting operation. The NewFormatter function can +// be used to get a new Formatter which can be used directly as arguments +// in standard fmt package printing calls. +type formatState struct { + value interface{} + fs fmt.State + depth int + pointers map[uintptr]int + ignoreNextType bool + cs *ConfigState +} + +// buildDefaultFormat recreates the original format string without precision +// and width information to pass in to fmt.Sprintf in the case of an +// unrecognized type. Unless new types are added to the language, this +// function won't ever be called. +func (f *formatState) buildDefaultFormat() (format string) { + buf := bytes.NewBuffer(percentBytes) + + for _, flag := range supportedFlags { + if f.fs.Flag(int(flag)) { + buf.WriteRune(flag) + } + } + + buf.WriteRune('v') + + format = buf.String() + return format +} + +// constructOrigFormat recreates the original format string including precision +// and width information to pass along to the standard fmt package. This allows +// automatic deferral of all format strings this package doesn't support. +func (f *formatState) constructOrigFormat(verb rune) (format string) { + buf := bytes.NewBuffer(percentBytes) + + for _, flag := range supportedFlags { + if f.fs.Flag(int(flag)) { + buf.WriteRune(flag) + } + } + + if width, ok := f.fs.Width(); ok { + buf.WriteString(strconv.Itoa(width)) + } + + if precision, ok := f.fs.Precision(); ok { + buf.Write(precisionBytes) + buf.WriteString(strconv.Itoa(precision)) + } + + buf.WriteRune(verb) + + format = buf.String() + return format +} + +// unpackValue returns values inside of non-nil interfaces when possible and +// ensures that types for values which have been unpacked from an interface +// are displayed when the show types flag is also set. +// This is useful for data types like structs, arrays, slices, and maps which +// can contain varying types packed inside an interface. +func (f *formatState) unpackValue(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Interface { + f.ignoreNextType = false + if !v.IsNil() { + v = v.Elem() + } + } + return v +} + +// formatPtr handles formatting of pointers by indirecting them as necessary. +func (f *formatState) formatPtr(v reflect.Value) { + // Display nil if top level pointer is nil. + showTypes := f.fs.Flag('#') + if v.IsNil() && (!showTypes || f.ignoreNextType) { + f.fs.Write(nilAngleBytes) + return + } + + // Remove pointers at or below the current depth from map used to detect + // circular refs. + for k, depth := range f.pointers { + if depth >= f.depth { + delete(f.pointers, k) + } + } + + // Keep list of all dereferenced pointers to possibly show later. + pointerChain := make([]uintptr, 0) + + // Figure out how many levels of indirection there are by derferencing + // pointers and unpacking interfaces down the chain while detecting circular + // references. + nilFound := false + cycleFound := false + indirects := 0 + ve := v + for ve.Kind() == reflect.Ptr { + if ve.IsNil() { + nilFound = true + break + } + indirects++ + addr := ve.Pointer() + pointerChain = append(pointerChain, addr) + if pd, ok := f.pointers[addr]; ok && pd < f.depth { + cycleFound = true + indirects-- + break + } + f.pointers[addr] = f.depth + + ve = ve.Elem() + if ve.Kind() == reflect.Interface { + if ve.IsNil() { + nilFound = true + break + } + ve = ve.Elem() + } + } + + // Display type or indirection level depending on flags. + if showTypes && !f.ignoreNextType { + f.fs.Write(openParenBytes) + f.fs.Write(bytes.Repeat(asteriskBytes, indirects)) + f.fs.Write([]byte(ve.Type().String())) + f.fs.Write(closeParenBytes) + } else { + if nilFound || cycleFound { + indirects += strings.Count(ve.Type().String(), "*") + } + f.fs.Write(openAngleBytes) + f.fs.Write([]byte(strings.Repeat("*", indirects))) + f.fs.Write(closeAngleBytes) + } + + // Display pointer information depending on flags. + if f.fs.Flag('+') && (len(pointerChain) > 0) { + f.fs.Write(openParenBytes) + for i, addr := range pointerChain { + if i > 0 { + f.fs.Write(pointerChainBytes) + } + printHexPtr(f.fs, addr) + } + f.fs.Write(closeParenBytes) + } + + // Display dereferenced value. + switch { + case nilFound == true: + f.fs.Write(nilAngleBytes) + + case cycleFound == true: + f.fs.Write(circularShortBytes) + + default: + f.ignoreNextType = true + f.format(ve) + } +} + +// format is the main workhorse for providing the Formatter interface. It +// uses the passed reflect value to figure out what kind of object we are +// dealing with and formats it appropriately. It is a recursive function, +// however circular data structures are detected and handled properly. +func (f *formatState) format(v reflect.Value) { + // Handle invalid reflect values immediately. + kind := v.Kind() + if kind == reflect.Invalid { + f.fs.Write(invalidAngleBytes) + return + } + + // Handle pointers specially. + if kind == reflect.Ptr { + f.formatPtr(v) + return + } + + // Print type information unless already handled elsewhere. + if !f.ignoreNextType && f.fs.Flag('#') { + f.fs.Write(openParenBytes) + f.fs.Write([]byte(v.Type().String())) + f.fs.Write(closeParenBytes) + } + f.ignoreNextType = false + + // Call Stringer/error interfaces if they exist and the handle methods + // flag is enabled. + if !f.cs.DisableMethods { + if (kind != reflect.Invalid) && (kind != reflect.Interface) { + if handled := handleMethods(f.cs, f.fs, v); handled { + return + } + } + } + + switch kind { + case reflect.Invalid: + // Do nothing. We should never get here since invalid has already + // been handled above. + + case reflect.Bool: + printBool(f.fs, v.Bool()) + + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + printInt(f.fs, v.Int(), 10) + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + printUint(f.fs, v.Uint(), 10) + + case reflect.Float32: + printFloat(f.fs, v.Float(), 32) + + case reflect.Float64: + printFloat(f.fs, v.Float(), 64) + + case reflect.Complex64: + printComplex(f.fs, v.Complex(), 32) + + case reflect.Complex128: + printComplex(f.fs, v.Complex(), 64) + + case reflect.Slice: + if v.IsNil() { + f.fs.Write(nilAngleBytes) + break + } + fallthrough + + case reflect.Array: + f.fs.Write(openBracketBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + numEntries := v.Len() + for i := 0; i < numEntries; i++ { + if i > 0 { + f.fs.Write(spaceBytes) + } + f.ignoreNextType = true + f.format(f.unpackValue(v.Index(i))) + } + } + f.depth-- + f.fs.Write(closeBracketBytes) + + case reflect.String: + f.fs.Write([]byte(v.String())) + + case reflect.Interface: + // The only time we should get here is for nil interfaces due to + // unpackValue calls. + if v.IsNil() { + f.fs.Write(nilAngleBytes) + } + + case reflect.Ptr: + // Do nothing. We should never get here since pointers have already + // been handled above. + + case reflect.Map: + // nil maps should be indicated as different than empty maps + if v.IsNil() { + f.fs.Write(nilAngleBytes) + break + } + + f.fs.Write(openMapBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + keys := v.MapKeys() + if f.cs.SortKeys { + sortValues(keys, f.cs) + } + for i, key := range keys { + if i > 0 { + f.fs.Write(spaceBytes) + } + f.ignoreNextType = true + f.format(f.unpackValue(key)) + f.fs.Write(colonBytes) + f.ignoreNextType = true + f.format(f.unpackValue(v.MapIndex(key))) + } + } + f.depth-- + f.fs.Write(closeMapBytes) + + case reflect.Struct: + numFields := v.NumField() + f.fs.Write(openBraceBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + vt := v.Type() + for i := 0; i < numFields; i++ { + if i > 0 { + f.fs.Write(spaceBytes) + } + vtf := vt.Field(i) + if f.fs.Flag('+') || f.fs.Flag('#') { + f.fs.Write([]byte(vtf.Name)) + f.fs.Write(colonBytes) + } + f.format(f.unpackValue(v.Field(i))) + } + } + f.depth-- + f.fs.Write(closeBraceBytes) + + case reflect.Uintptr: + printHexPtr(f.fs, uintptr(v.Uint())) + + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + printHexPtr(f.fs, v.Pointer()) + + // There were not any other types at the time this code was written, but + // fall back to letting the default fmt package handle it if any get added. + default: + format := f.buildDefaultFormat() + if v.CanInterface() { + fmt.Fprintf(f.fs, format, v.Interface()) + } else { + fmt.Fprintf(f.fs, format, v.String()) + } + } +} + +// Format satisfies the fmt.Formatter interface. See NewFormatter for usage +// details. +func (f *formatState) Format(fs fmt.State, verb rune) { + f.fs = fs + + // Use standard formatting for verbs that are not v. + if verb != 'v' { + format := f.constructOrigFormat(verb) + fmt.Fprintf(fs, format, f.value) + return + } + + if f.value == nil { + if fs.Flag('#') { + fs.Write(interfaceBytes) + } + fs.Write(nilAngleBytes) + return + } + + f.format(reflect.ValueOf(f.value)) +} + +// newFormatter is a helper function to consolidate the logic from the various +// public methods which take varying config states. +func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter { + fs := &formatState{value: v, cs: cs} + fs.pointers = make(map[uintptr]int) + return fs +} + +/* +NewFormatter returns a custom formatter that satisfies the fmt.Formatter +interface. As a result, it integrates cleanly with standard fmt package +printing functions. The formatter is useful for inline printing of smaller data +types similar to the standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Typically this function shouldn't be called directly. It is much easier to make +use of the custom formatter by calling one of the convenience functions such as +Printf, Println, or Fprintf. +*/ +func NewFormatter(v interface{}) fmt.Formatter { + return newFormatter(&Config, v) +} diff --git a/vendor/github.com/davecgh/go-spew/spew/spew.go b/vendor/github.com/davecgh/go-spew/spew/spew.go new file mode 100644 index 00000000..32c0e338 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/spew.go @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "fmt" + "io" +) + +// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the formatted string as a value that satisfies error. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Errorf(format string, a ...interface{}) (err error) { + return fmt.Errorf(format, convertArgs(a)...) +} + +// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprint(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprint(w, convertArgs(a)...) +} + +// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(w, format, convertArgs(a)...) +} + +// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it +// passed with a default Formatter interface returned by NewFormatter. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprintln(w, convertArgs(a)...) +} + +// Print is a wrapper for fmt.Print that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b)) +func Print(a ...interface{}) (n int, err error) { + return fmt.Print(convertArgs(a)...) +} + +// Printf is a wrapper for fmt.Printf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Printf(format string, a ...interface{}) (n int, err error) { + return fmt.Printf(format, convertArgs(a)...) +} + +// Println is a wrapper for fmt.Println that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b)) +func Println(a ...interface{}) (n int, err error) { + return fmt.Println(convertArgs(a)...) +} + +// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprint(a ...interface{}) string { + return fmt.Sprint(convertArgs(a)...) +} + +// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprintf(format string, a ...interface{}) string { + return fmt.Sprintf(format, convertArgs(a)...) +} + +// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it +// were passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprintln(a ...interface{}) string { + return fmt.Sprintln(convertArgs(a)...) +} + +// convertArgs accepts a slice of arguments and returns a slice of the same +// length with each argument converted to a default spew Formatter interface. +func convertArgs(args []interface{}) (formatters []interface{}) { + formatters = make([]interface{}, len(args)) + for index, arg := range args { + formatters[index] = NewFormatter(arg) + } + return formatters +} diff --git a/vendor/github.com/dchest/uniuri/.travis.yml b/vendor/github.com/dchest/uniuri/.travis.yml new file mode 100644 index 00000000..245a2f51 --- /dev/null +++ b/vendor/github.com/dchest/uniuri/.travis.yml @@ -0,0 +1,6 @@ +language: go + +go: + - 1.3 + - 1.4 + - tip diff --git a/vendor/github.com/dchest/uniuri/COPYING b/vendor/github.com/dchest/uniuri/COPYING new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/vendor/github.com/dchest/uniuri/COPYING @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/vendor/github.com/dchest/uniuri/README.md b/vendor/github.com/dchest/uniuri/README.md new file mode 100644 index 00000000..b321a5fa --- /dev/null +++ b/vendor/github.com/dchest/uniuri/README.md @@ -0,0 +1,97 @@ +Package uniuri +===================== + +[![Build Status](https://travis-ci.org/dchest/uniuri.svg)](https://travis-ci.org/dchest/uniuri) + +```go +import "github.com/dchest/uniuri" +``` + +Package uniuri generates random strings good for use in URIs to identify +unique objects. + +Example usage: + +```go +s := uniuri.New() // s is now "apHCJBl7L1OmC57n" +``` + +A standard string created by New() is 16 bytes in length and consists of +Latin upper and lowercase letters, and numbers (from the set of 62 allowed +characters), which means that it has ~95 bits of entropy. To get more +entropy, you can use NewLen(UUIDLen), which returns 20-byte string, giving +~119 bits of entropy, or any other desired length. + +Functions read from crypto/rand random source, and panic if they fail to +read from it. + + +Constants +--------- + +```go +const ( + // StdLen is a standard length of uniuri string to achive ~95 bits of entropy. + StdLen = 16 + // UUIDLen is a length of uniuri string to achive ~119 bits of entropy, closest + // to what can be losslessly converted to UUIDv4 (122 bits). + UUIDLen = 20 +) + +``` + + + +Variables +--------- + +```go +var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") +``` + + +StdChars is a set of standard characters allowed in uniuri string. + + +Functions +--------- + +### func New + +```go +func New() string +``` + +New returns a new random string of the standard length, consisting of +standard characters. + +### func NewLen + +```go +func NewLen(length int) string +``` + +NewLen returns a new random string of the provided length, consisting of +standard characters. + +### func NewLenChars + +```go +func NewLenChars(length int, chars []byte) string +``` + +NewLenChars returns a new random string of the provided length, consisting +of the provided byte slice of allowed characters (maximum 256). + + + +Public domain dedication +------------------------ + +Written in 2011-2014 by Dmitry Chestnykh + +The author(s) have dedicated all copyright and related and +neighboring rights to this software to the public domain +worldwide. Distributed without any warranty. +http://creativecommons.org/publicdomain/zero/1.0/ + diff --git a/vendor/github.com/dchest/uniuri/uniuri.go b/vendor/github.com/dchest/uniuri/uniuri.go new file mode 100644 index 00000000..6393446c --- /dev/null +++ b/vendor/github.com/dchest/uniuri/uniuri.go @@ -0,0 +1,81 @@ +// Written in 2011-2014 by Dmitry Chestnykh +// +// The author(s) have dedicated all copyright and related and +// neighboring rights to this software to the public domain +// worldwide. Distributed without any warranty. +// http://creativecommons.org/publicdomain/zero/1.0/ + +// Package uniuri generates random strings good for use in URIs to identify +// unique objects. +// +// Example usage: +// +// s := uniuri.New() // s is now "apHCJBl7L1OmC57n" +// +// A standard string created by New() is 16 bytes in length and consists of +// Latin upper and lowercase letters, and numbers (from the set of 62 allowed +// characters), which means that it has ~95 bits of entropy. To get more +// entropy, you can use NewLen(UUIDLen), which returns 20-byte string, giving +// ~119 bits of entropy, or any other desired length. +// +// Functions read from crypto/rand random source, and panic if they fail to +// read from it. +package uniuri + +import "crypto/rand" + +const ( + // StdLen is a standard length of uniuri string to achive ~95 bits of entropy. + StdLen = 16 + // UUIDLen is a length of uniuri string to achive ~119 bits of entropy, closest + // to what can be losslessly converted to UUIDv4 (122 bits). + UUIDLen = 20 +) + +// StdChars is a set of standard characters allowed in uniuri string. +var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") + +// New returns a new random string of the standard length, consisting of +// standard characters. +func New() string { + return NewLenChars(StdLen, StdChars) +} + +// NewLen returns a new random string of the provided length, consisting of +// standard characters. +func NewLen(length int) string { + return NewLenChars(length, StdChars) +} + +// NewLenChars returns a new random string of the provided length, consisting +// of the provided byte slice of allowed characters (maximum 256). +func NewLenChars(length int, chars []byte) string { + if length == 0 { + return "" + } + clen := len(chars) + if clen < 2 || clen > 256 { + panic("uniuri: wrong charset length for NewLenChars") + } + maxrb := 255 - (256 % clen) + b := make([]byte, length) + r := make([]byte, length+(length/4)) // storage for random bytes. + i := 0 + for { + if _, err := rand.Read(r); err != nil { + panic("uniuri: error reading random bytes: " + err.Error()) + } + for _, rb := range r { + c := int(rb) + if c > maxrb { + // Skip this number to avoid modulo bias. + continue + } + b[i] = chars[c%clen] + i++ + if i == length { + return string(b) + } + } + } +} diff --git a/vendor/github.com/garyburd/redigo/LICENSE b/vendor/github.com/garyburd/redigo/LICENSE new file mode 100644 index 00000000..67db8588 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/vendor/github.com/garyburd/redigo/internal/commandinfo.go b/vendor/github.com/garyburd/redigo/internal/commandinfo.go new file mode 100644 index 00000000..11e58425 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/internal/commandinfo.go @@ -0,0 +1,54 @@ +// Copyright 2014 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package internal // import "github.com/garyburd/redigo/internal" + +import ( + "strings" +) + +const ( + WatchState = 1 << iota + MultiState + SubscribeState + MonitorState +) + +type CommandInfo struct { + Set, Clear int +} + +var commandInfos = map[string]CommandInfo{ + "WATCH": {Set: WatchState}, + "UNWATCH": {Clear: WatchState}, + "MULTI": {Set: MultiState}, + "EXEC": {Clear: WatchState | MultiState}, + "DISCARD": {Clear: WatchState | MultiState}, + "PSUBSCRIBE": {Set: SubscribeState}, + "SUBSCRIBE": {Set: SubscribeState}, + "MONITOR": {Set: MonitorState}, +} + +func init() { + for n, ci := range commandInfos { + commandInfos[strings.ToLower(n)] = ci + } +} + +func LookupCommandInfo(commandName string) CommandInfo { + if ci, ok := commandInfos[commandName]; ok { + return ci + } + return commandInfos[strings.ToUpper(commandName)] +} diff --git a/vendor/github.com/garyburd/redigo/redis/conn.go b/vendor/github.com/garyburd/redigo/redis/conn.go new file mode 100644 index 00000000..5aa0f32f --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/conn.go @@ -0,0 +1,673 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bufio" + "bytes" + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "net/url" + "regexp" + "strconv" + "sync" + "time" +) + +var ( + _ ConnWithTimeout = (*conn)(nil) +) + +// conn is the low-level implementation of Conn +type conn struct { + // Shared + mu sync.Mutex + pending int + err error + conn net.Conn + + // Read + readTimeout time.Duration + br *bufio.Reader + + // Write + writeTimeout time.Duration + bw *bufio.Writer + + // Scratch space for formatting argument length. + // '*' or '$', length, "\r\n" + lenScratch [32]byte + + // Scratch space for formatting integers and floats. + numScratch [40]byte +} + +// DialTimeout acts like Dial but takes timeouts for establishing the +// connection to the server, writing a command and reading a reply. +// +// Deprecated: Use Dial with options instead. +func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { + return Dial(network, address, + DialConnectTimeout(connectTimeout), + DialReadTimeout(readTimeout), + DialWriteTimeout(writeTimeout)) +} + +// DialOption specifies an option for dialing a Redis server. +type DialOption struct { + f func(*dialOptions) +} + +type dialOptions struct { + readTimeout time.Duration + writeTimeout time.Duration + dialer *net.Dialer + dial func(network, addr string) (net.Conn, error) + db int + password string + useTLS bool + skipVerify bool + tlsConfig *tls.Config +} + +// DialReadTimeout specifies the timeout for reading a single command reply. +func DialReadTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.readTimeout = d + }} +} + +// DialWriteTimeout specifies the timeout for writing a single command. +func DialWriteTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.writeTimeout = d + }} +} + +// DialConnectTimeout specifies the timeout for connecting to the Redis server when +// no DialNetDial option is specified. +func DialConnectTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.dialer.Timeout = d + }} +} + +// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server +// when no DialNetDial option is specified. +// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then +// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected. +func DialKeepAlive(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.dialer.KeepAlive = d + }} +} + +// DialNetDial specifies a custom dial function for creating TCP +// connections, otherwise a net.Dialer customized via the other options is used. +// DialNetDial overrides DialConnectTimeout and DialKeepAlive. +func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { + return DialOption{func(do *dialOptions) { + do.dial = dial + }} +} + +// DialDatabase specifies the database to select when dialing a connection. +func DialDatabase(db int) DialOption { + return DialOption{func(do *dialOptions) { + do.db = db + }} +} + +// DialPassword specifies the password to use when connecting to +// the Redis server. +func DialPassword(password string) DialOption { + return DialOption{func(do *dialOptions) { + do.password = password + }} +} + +// DialTLSConfig specifies the config to use when a TLS connection is dialed. +// Has no effect when not dialing a TLS connection. +func DialTLSConfig(c *tls.Config) DialOption { + return DialOption{func(do *dialOptions) { + do.tlsConfig = c + }} +} + +// DialTLSSkipVerify disables server name verification when connecting over +// TLS. Has no effect when not dialing a TLS connection. +func DialTLSSkipVerify(skip bool) DialOption { + return DialOption{func(do *dialOptions) { + do.skipVerify = skip + }} +} + +// DialUseTLS specifies whether TLS should be used when connecting to the +// server. This option is ignore by DialURL. +func DialUseTLS(useTLS bool) DialOption { + return DialOption{func(do *dialOptions) { + do.useTLS = useTLS + }} +} + +// Dial connects to the Redis server at the given network and +// address using the specified options. +func Dial(network, address string, options ...DialOption) (Conn, error) { + do := dialOptions{ + dialer: &net.Dialer{ + KeepAlive: time.Minute * 5, + }, + } + for _, option := range options { + option.f(&do) + } + if do.dial == nil { + do.dial = do.dialer.Dial + } + + netConn, err := do.dial(network, address) + if err != nil { + return nil, err + } + + if do.useTLS { + var tlsConfig *tls.Config + if do.tlsConfig == nil { + tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify} + } else { + tlsConfig = cloneTLSConfig(do.tlsConfig) + } + if tlsConfig.ServerName == "" { + host, _, err := net.SplitHostPort(address) + if err != nil { + netConn.Close() + return nil, err + } + tlsConfig.ServerName = host + } + + tlsConn := tls.Client(netConn, tlsConfig) + if err := tlsConn.Handshake(); err != nil { + netConn.Close() + return nil, err + } + netConn = tlsConn + } + + c := &conn{ + conn: netConn, + bw: bufio.NewWriter(netConn), + br: bufio.NewReader(netConn), + readTimeout: do.readTimeout, + writeTimeout: do.writeTimeout, + } + + if do.password != "" { + if _, err := c.Do("AUTH", do.password); err != nil { + netConn.Close() + return nil, err + } + } + + if do.db != 0 { + if _, err := c.Do("SELECT", do.db); err != nil { + netConn.Close() + return nil, err + } + } + + return c, nil +} + +var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) + +// DialURL connects to a Redis server at the given URL using the Redis +// URI scheme. URLs should follow the draft IANA specification for the +// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). +func DialURL(rawurl string, options ...DialOption) (Conn, error) { + u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + + if u.Scheme != "redis" && u.Scheme != "rediss" { + return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) + } + + // As per the IANA draft spec, the host defaults to localhost and + // the port defaults to 6379. + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + // assume port is missing + host = u.Host + port = "6379" + } + if host == "" { + host = "localhost" + } + address := net.JoinHostPort(host, port) + + if u.User != nil { + password, isSet := u.User.Password() + if isSet { + options = append(options, DialPassword(password)) + } + } + + match := pathDBRegexp.FindStringSubmatch(u.Path) + if len(match) == 2 { + db := 0 + if len(match[1]) > 0 { + db, err = strconv.Atoi(match[1]) + if err != nil { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + } + if db != 0 { + options = append(options, DialDatabase(db)) + } + } else if u.Path != "" { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + + options = append(options, DialUseTLS(u.Scheme == "rediss")) + + return Dial("tcp", address, options...) +} + +// NewConn returns a new Redigo connection for the given net connection. +func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { + return &conn{ + conn: netConn, + bw: bufio.NewWriter(netConn), + br: bufio.NewReader(netConn), + readTimeout: readTimeout, + writeTimeout: writeTimeout, + } +} + +func (c *conn) Close() error { + c.mu.Lock() + err := c.err + if c.err == nil { + c.err = errors.New("redigo: closed") + err = c.conn.Close() + } + c.mu.Unlock() + return err +} + +func (c *conn) fatal(err error) error { + c.mu.Lock() + if c.err == nil { + c.err = err + // Close connection to force errors on subsequent calls and to unblock + // other reader or writer. + c.conn.Close() + } + c.mu.Unlock() + return err +} + +func (c *conn) Err() error { + c.mu.Lock() + err := c.err + c.mu.Unlock() + return err +} + +func (c *conn) writeLen(prefix byte, n int) error { + c.lenScratch[len(c.lenScratch)-1] = '\n' + c.lenScratch[len(c.lenScratch)-2] = '\r' + i := len(c.lenScratch) - 3 + for { + c.lenScratch[i] = byte('0' + n%10) + i -= 1 + n = n / 10 + if n == 0 { + break + } + } + c.lenScratch[i] = prefix + _, err := c.bw.Write(c.lenScratch[i:]) + return err +} + +func (c *conn) writeString(s string) error { + c.writeLen('$', len(s)) + c.bw.WriteString(s) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *conn) writeBytes(p []byte) error { + c.writeLen('$', len(p)) + c.bw.Write(p) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *conn) writeInt64(n int64) error { + return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) +} + +func (c *conn) writeFloat64(n float64) error { + return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) +} + +func (c *conn) writeCommand(cmd string, args []interface{}) error { + c.writeLen('*', 1+len(args)) + if err := c.writeString(cmd); err != nil { + return err + } + for _, arg := range args { + if err := c.writeArg(arg, true); err != nil { + return err + } + } + return nil +} + +func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) { + switch arg := arg.(type) { + case string: + return c.writeString(arg) + case []byte: + return c.writeBytes(arg) + case int: + return c.writeInt64(int64(arg)) + case int64: + return c.writeInt64(arg) + case float64: + return c.writeFloat64(arg) + case bool: + if arg { + return c.writeString("1") + } else { + return c.writeString("0") + } + case nil: + return c.writeString("") + case Argument: + if argumentTypeOK { + return c.writeArg(arg.RedisArg(), false) + } + // See comment in default clause below. + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + return c.writeBytes(buf.Bytes()) + default: + // This default clause is intended to handle builtin numeric types. + // The function should return an error for other types, but this is not + // done for compatibility with previous versions of the package. + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + return c.writeBytes(buf.Bytes()) + } +} + +type protocolError string + +func (pe protocolError) Error() string { + return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) +} + +func (c *conn) readLine() ([]byte, error) { + p, err := c.br.ReadSlice('\n') + if err == bufio.ErrBufferFull { + return nil, protocolError("long response line") + } + if err != nil { + return nil, err + } + i := len(p) - 2 + if i < 0 || p[i] != '\r' { + return nil, protocolError("bad response line terminator") + } + return p[:i], nil +} + +// parseLen parses bulk string and array lengths. +func parseLen(p []byte) (int, error) { + if len(p) == 0 { + return -1, protocolError("malformed length") + } + + if p[0] == '-' && len(p) == 2 && p[1] == '1' { + // handle $-1 and $-1 null replies. + return -1, nil + } + + var n int + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return -1, protocolError("illegal bytes in length") + } + n += int(b - '0') + } + + return n, nil +} + +// parseInt parses an integer reply. +func parseInt(p []byte) (interface{}, error) { + if len(p) == 0 { + return 0, protocolError("malformed integer") + } + + var negate bool + if p[0] == '-' { + negate = true + p = p[1:] + if len(p) == 0 { + return 0, protocolError("malformed integer") + } + } + + var n int64 + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return 0, protocolError("illegal bytes in length") + } + n += int64(b - '0') + } + + if negate { + n = -n + } + return n, nil +} + +var ( + okReply interface{} = "OK" + pongReply interface{} = "PONG" +) + +func (c *conn) readReply() (interface{}, error) { + line, err := c.readLine() + if err != nil { + return nil, err + } + if len(line) == 0 { + return nil, protocolError("short response line") + } + switch line[0] { + case '+': + switch { + case len(line) == 3 && line[1] == 'O' && line[2] == 'K': + // Avoid allocation for frequent "+OK" response. + return okReply, nil + case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': + // Avoid allocation in PING command benchmarks :) + return pongReply, nil + default: + return string(line[1:]), nil + } + case '-': + return Error(string(line[1:])), nil + case ':': + return parseInt(line[1:]) + case '$': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + p := make([]byte, n) + _, err = io.ReadFull(c.br, p) + if err != nil { + return nil, err + } + if line, err := c.readLine(); err != nil { + return nil, err + } else if len(line) != 0 { + return nil, protocolError("bad bulk string format") + } + return p, nil + case '*': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + r := make([]interface{}, n) + for i := range r { + r[i], err = c.readReply() + if err != nil { + return nil, err + } + } + return r, nil + } + return nil, protocolError("unexpected response line") +} + +func (c *conn) Send(cmd string, args ...interface{}) error { + c.mu.Lock() + c.pending += 1 + c.mu.Unlock() + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + if err := c.writeCommand(cmd, args); err != nil { + return c.fatal(err) + } + return nil +} + +func (c *conn) Flush() error { + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + if err := c.bw.Flush(); err != nil { + return c.fatal(err) + } + return nil +} + +func (c *conn) Receive() (interface{}, error) { + return c.ReceiveWithTimeout(c.readTimeout) +} + +func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { + var deadline time.Time + if timeout != 0 { + deadline = time.Now().Add(timeout) + } + c.conn.SetReadDeadline(deadline) + + if reply, err = c.readReply(); err != nil { + return nil, c.fatal(err) + } + // When using pub/sub, the number of receives can be greater than the + // number of sends. To enable normal use of the connection after + // unsubscribing from all channels, we do not decrement pending to a + // negative value. + // + // The pending field is decremented after the reply is read to handle the + // case where Receive is called before Send. + c.mu.Lock() + if c.pending > 0 { + c.pending -= 1 + } + c.mu.Unlock() + if err, ok := reply.(Error); ok { + return nil, err + } + return +} + +func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { + return c.DoWithTimeout(c.readTimeout, cmd, args...) +} + +func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { + c.mu.Lock() + pending := c.pending + c.pending = 0 + c.mu.Unlock() + + if cmd == "" && pending == 0 { + return nil, nil + } + + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + + if cmd != "" { + if err := c.writeCommand(cmd, args); err != nil { + return nil, c.fatal(err) + } + } + + if err := c.bw.Flush(); err != nil { + return nil, c.fatal(err) + } + + var deadline time.Time + if readTimeout != 0 { + deadline = time.Now().Add(readTimeout) + } + c.conn.SetReadDeadline(deadline) + + if cmd == "" { + reply := make([]interface{}, pending) + for i := range reply { + r, e := c.readReply() + if e != nil { + return nil, c.fatal(e) + } + reply[i] = r + } + return reply, nil + } + + var err error + var reply interface{} + for i := 0; i <= pending; i++ { + var e error + if reply, e = c.readReply(); e != nil { + return nil, c.fatal(e) + } + if e, ok := reply.(Error); ok && err == nil { + err = e + } + } + return reply, err +} diff --git a/vendor/github.com/garyburd/redigo/redis/doc.go b/vendor/github.com/garyburd/redigo/redis/doc.go new file mode 100644 index 00000000..1d19c166 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/doc.go @@ -0,0 +1,177 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// Package redis is a client for the Redis database. +// +// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more +// documentation about this package. +// +// Connections +// +// The Conn interface is the primary interface for working with Redis. +// Applications create connections by calling the Dial, DialWithTimeout or +// NewConn functions. In the future, functions will be added for creating +// sharded and other types of connections. +// +// The application must call the connection Close method when the application +// is done with the connection. +// +// Executing Commands +// +// The Conn interface has a generic method for executing Redis commands: +// +// Do(commandName string, args ...interface{}) (reply interface{}, err error) +// +// The Redis command reference (http://redis.io/commands) lists the available +// commands. An example of using the Redis APPEND command is: +// +// n, err := conn.Do("APPEND", "key", "value") +// +// The Do method converts command arguments to bulk strings for transmission +// to the server as follows: +// +// Go Type Conversion +// []byte Sent as is +// string Sent as is +// int, int64 strconv.FormatInt(v) +// float64 strconv.FormatFloat(v, 'g', -1, 64) +// bool true -> "1", false -> "0" +// nil "" +// all other types fmt.Fprint(w, v) +// +// Redis command reply types are represented using the following Go types: +// +// Redis type Go type +// error redis.Error +// integer int64 +// simple string string +// bulk string []byte or nil if value not present. +// array []interface{} or nil if value not present. +// +// Use type assertions or the reply helper functions to convert from +// interface{} to the specific Go type for the command result. +// +// Pipelining +// +// Connections support pipelining using the Send, Flush and Receive methods. +// +// Send(commandName string, args ...interface{}) error +// Flush() error +// Receive() (reply interface{}, err error) +// +// Send writes the command to the connection's output buffer. Flush flushes the +// connection's output buffer to the server. Receive reads a single reply from +// the server. The following example shows a simple pipeline. +// +// c.Send("SET", "foo", "bar") +// c.Send("GET", "foo") +// c.Flush() +// c.Receive() // reply from SET +// v, err = c.Receive() // reply from GET +// +// The Do method combines the functionality of the Send, Flush and Receive +// methods. The Do method starts by writing the command and flushing the output +// buffer. Next, the Do method receives all pending replies including the reply +// for the command just sent by Do. If any of the received replies is an error, +// then Do returns the error. If there are no errors, then Do returns the last +// reply. If the command argument to the Do method is "", then the Do method +// will flush the output buffer and receive pending replies without sending a +// command. +// +// Use the Send and Do methods to implement pipelined transactions. +// +// c.Send("MULTI") +// c.Send("INCR", "foo") +// c.Send("INCR", "bar") +// r, err := c.Do("EXEC") +// fmt.Println(r) // prints [1, 1] +// +// Concurrency +// +// Connections support one concurrent caller to the Receive method and one +// concurrent caller to the Send and Flush methods. No other concurrency is +// supported including concurrent calls to the Do method. +// +// For full concurrent access to Redis, use the thread-safe Pool to get, use +// and release a connection from within a goroutine. Connections returned from +// a Pool have the concurrency restrictions described in the previous +// paragraph. +// +// Publish and Subscribe +// +// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. +// +// c.Send("SUBSCRIBE", "example") +// c.Flush() +// for { +// reply, err := c.Receive() +// if err != nil { +// return err +// } +// // process pushed message +// } +// +// The PubSubConn type wraps a Conn with convenience methods for implementing +// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods +// send and flush a subscription management command. The receive method +// converts a pushed message to convenient types for use in a type switch. +// +// psc := redis.PubSubConn{Conn: c} +// psc.Subscribe("example") +// for { +// switch v := psc.Receive().(type) { +// case redis.Message: +// fmt.Printf("%s: message: %s\n", v.Channel, v.Data) +// case redis.Subscription: +// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) +// case error: +// return v +// } +// } +// +// Reply Helpers +// +// The Bool, Int, Bytes, String, Strings and Values functions convert a reply +// to a value of a specific type. To allow convenient wrapping of calls to the +// connection Do and Receive methods, the functions take a second argument of +// type error. If the error is non-nil, then the helper function returns the +// error. If the error is nil, the function converts the reply to the specified +// type: +// +// exists, err := redis.Bool(c.Do("EXISTS", "foo")) +// if err != nil { +// // handle error return from c.Do or type conversion error. +// } +// +// The Scan function converts elements of a array reply to Go types: +// +// var value1 int +// var value2 string +// reply, err := redis.Values(c.Do("MGET", "key1", "key2")) +// if err != nil { +// // handle error +// } +// if _, err := redis.Scan(reply, &value1, &value2); err != nil { +// // handle error +// } +// +// Errors +// +// Connection methods return error replies from the server as type redis.Error. +// +// Call the connection Err() method to determine if the connection encountered +// non-recoverable error such as a network error or protocol parsing error. If +// Err() returns a non-nil value, then the connection is not usable and should +// be closed. +package redis // import "github.com/garyburd/redigo/redis" diff --git a/vendor/github.com/garyburd/redigo/redis/go16.go b/vendor/github.com/garyburd/redigo/redis/go16.go new file mode 100644 index 00000000..f6b1a7cc --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/go16.go @@ -0,0 +1,27 @@ +// +build !go1.7 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + } +} diff --git a/vendor/github.com/garyburd/redigo/redis/go17.go b/vendor/github.com/garyburd/redigo/redis/go17.go new file mode 100644 index 00000000..5f363791 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/go17.go @@ -0,0 +1,29 @@ +// +build go1.7,!go1.8 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, + Renegotiation: cfg.Renegotiation, + } +} diff --git a/vendor/github.com/garyburd/redigo/redis/go18.go b/vendor/github.com/garyburd/redigo/redis/go18.go new file mode 100644 index 00000000..558363be --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/go18.go @@ -0,0 +1,9 @@ +// +build go1.8 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return cfg.Clone() +} diff --git a/vendor/github.com/garyburd/redigo/redis/log.go b/vendor/github.com/garyburd/redigo/redis/log.go new file mode 100644 index 00000000..b2996611 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/log.go @@ -0,0 +1,134 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bytes" + "fmt" + "log" + "time" +) + +var ( + _ ConnWithTimeout = (*loggingConn)(nil) +) + +// NewLoggingConn returns a logging wrapper around a connection. +func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { + if prefix != "" { + prefix = prefix + "." + } + return &loggingConn{conn, logger, prefix} +} + +type loggingConn struct { + Conn + logger *log.Logger + prefix string +} + +func (c *loggingConn) Close() error { + err := c.Conn.Close() + var buf bytes.Buffer + fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) + c.logger.Output(2, buf.String()) + return err +} + +func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { + const chop = 32 + switch v := v.(type) { + case []byte: + if len(v) > chop { + fmt.Fprintf(buf, "%q...", v[:chop]) + } else { + fmt.Fprintf(buf, "%q", v) + } + case string: + if len(v) > chop { + fmt.Fprintf(buf, "%q...", v[:chop]) + } else { + fmt.Fprintf(buf, "%q", v) + } + case []interface{}: + if len(v) == 0 { + buf.WriteString("[]") + } else { + sep := "[" + fin := "]" + if len(v) > chop { + v = v[:chop] + fin = "...]" + } + for _, vv := range v { + buf.WriteString(sep) + c.printValue(buf, vv) + sep = ", " + } + buf.WriteString(fin) + } + default: + fmt.Fprint(buf, v) + } +} + +func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { + var buf bytes.Buffer + fmt.Fprintf(&buf, "%s%s(", c.prefix, method) + if method != "Receive" { + buf.WriteString(commandName) + for _, arg := range args { + buf.WriteString(", ") + c.printValue(&buf, arg) + } + } + buf.WriteString(") -> (") + if method != "Send" { + c.printValue(&buf, reply) + buf.WriteString(", ") + } + fmt.Fprintf(&buf, "%v)", err) + c.logger.Output(3, buf.String()) +} + +func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { + reply, err := c.Conn.Do(commandName, args...) + c.print("Do", commandName, args, reply, err) + return reply, err +} + +func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { + reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...) + c.print("DoWithTimeout", commandName, args, reply, err) + return reply, err +} + +func (c *loggingConn) Send(commandName string, args ...interface{}) error { + err := c.Conn.Send(commandName, args...) + c.print("Send", commandName, args, nil, err) + return err +} + +func (c *loggingConn) Receive() (interface{}, error) { + reply, err := c.Conn.Receive() + c.print("Receive", "", nil, reply, err) + return reply, err +} + +func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { + reply, err := ReceiveWithTimeout(c.Conn, timeout) + c.print("ReceiveWithTimeout", "", nil, reply, err) + return reply, err +} diff --git a/vendor/github.com/garyburd/redigo/redis/pool.go b/vendor/github.com/garyburd/redigo/redis/pool.go new file mode 100644 index 00000000..3e6f4260 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/pool.go @@ -0,0 +1,527 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bytes" + "crypto/rand" + "crypto/sha1" + "errors" + "io" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/garyburd/redigo/internal" +) + +var ( + _ ConnWithTimeout = (*pooledConnection)(nil) + _ ConnWithTimeout = (*errorConnection)(nil) +) + +var nowFunc = time.Now // for testing + +// ErrPoolExhausted is returned from a pool connection method (Do, Send, +// Receive, Flush, Err) when the maximum number of database connections in the +// pool has been reached. +var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") + +var ( + errPoolClosed = errors.New("redigo: connection pool closed") + errConnClosed = errors.New("redigo: connection closed") +) + +// Pool maintains a pool of connections. The application calls the Get method +// to get a connection from the pool and the connection's Close method to +// return the connection's resources to the pool. +// +// The following example shows how to use a pool in a web application. The +// application creates a pool at application startup and makes it available to +// request handlers using a package level variable. The pool configuration used +// here is an example, not a recommendation. +// +// func newPool(addr string) *redis.Pool { +// return &redis.Pool{ +// MaxIdle: 3, +// IdleTimeout: 240 * time.Second, +// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, +// } +// } +// +// var ( +// pool *redis.Pool +// redisServer = flag.String("redisServer", ":6379", "") +// ) +// +// func main() { +// flag.Parse() +// pool = newPool(*redisServer) +// ... +// } +// +// A request handler gets a connection from the pool and closes the connection +// when the handler is done: +// +// func serveHome(w http.ResponseWriter, r *http.Request) { +// conn := pool.Get() +// defer conn.Close() +// ... +// } +// +// Use the Dial function to authenticate connections with the AUTH command or +// select a database with the SELECT command: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// Dial: func () (redis.Conn, error) { +// c, err := redis.Dial("tcp", server) +// if err != nil { +// return nil, err +// } +// if _, err := c.Do("AUTH", password); err != nil { +// c.Close() +// return nil, err +// } +// if _, err := c.Do("SELECT", db); err != nil { +// c.Close() +// return nil, err +// } +// return c, nil +// }, +// } +// +// Use the TestOnBorrow function to check the health of an idle connection +// before the connection is returned to the application. This example PINGs +// connections that have been idle more than a minute: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// TestOnBorrow: func(c redis.Conn, t time.Time) error { +// if time.Since(t) < time.Minute { +// return nil +// } +// _, err := c.Do("PING") +// return err +// }, +// } +// +type Pool struct { + // Dial is an application supplied function for creating and configuring a + // connection. + // + // The connection returned from Dial must not be in a special state + // (subscribed to pubsub channel, transaction started, ...). + Dial func() (Conn, error) + + // TestOnBorrow is an optional application supplied function for checking + // the health of an idle connection before the connection is used again by + // the application. Argument t is the time that the connection was returned + // to the pool. If the function returns an error, then the connection is + // closed. + TestOnBorrow func(c Conn, t time.Time) error + + // Maximum number of idle connections in the pool. + MaxIdle int + + // Maximum number of connections allocated by the pool at a given time. + // When zero, there is no limit on the number of connections in the pool. + MaxActive int + + // Close connections after remaining idle for this duration. If the value + // is zero, then idle connections are not closed. Applications should set + // the timeout to a value less than the server's timeout. + IdleTimeout time.Duration + + // If Wait is true and the pool is at the MaxActive limit, then Get() waits + // for a connection to be returned to the pool before returning. + Wait bool + + chInitialized uint32 // set to 1 when field ch is initialized + + mu sync.Mutex // mu protects the following fields + closed bool // set to true when the pool is closed. + active int // the number of open connections in the pool + ch chan struct{} // limits open connections when p.Wait is true + idle idleList // idle connections +} + +// NewPool creates a new pool. +// +// Deprecated: Initialize the Pool directory as shown in the example. +func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { + return &Pool{Dial: newFn, MaxIdle: maxIdle} +} + +// Get gets a connection. The application must close the returned connection. +// This method always returns a valid connection so that applications can defer +// error handling to the first use of the connection. If there is an error +// getting an underlying connection, then the connection Err, Do, Send, Flush +// and Receive methods return that error. +func (p *Pool) Get() Conn { + c, err := p.get(nil) + if err != nil { + return errorConnection{err} + } + return &pooledConnection{p: p, c: c} +} + +// PoolStats contains pool statistics. +type PoolStats struct { + // ActiveCount is the number of connections in the pool. The count includes + // idle connections and connections in use. + ActiveCount int + // IdleCount is the number of idle connections in the pool. + IdleCount int +} + +// Stats returns pool's statistics. +func (p *Pool) Stats() PoolStats { + p.mu.Lock() + stats := PoolStats{ + ActiveCount: p.active, + IdleCount: p.idle.count, + } + p.mu.Unlock() + + return stats +} + +// ActiveCount returns the number of connections in the pool. The count +// includes idle connections and connections in use. +func (p *Pool) ActiveCount() int { + p.mu.Lock() + active := p.active + p.mu.Unlock() + return active +} + +// IdleCount returns the number of idle connections in the pool. +func (p *Pool) IdleCount() int { + p.mu.Lock() + idle := p.idle.count + p.mu.Unlock() + return idle +} + +// Close releases the resources used by the pool. +func (p *Pool) Close() error { + p.mu.Lock() + if p.closed { + p.mu.Unlock() + return nil + } + p.closed = true + p.active -= p.idle.count + ic := p.idle.front + p.idle.count = 0 + p.idle.front, p.idle.back = nil, nil + if p.ch != nil { + close(p.ch) + } + p.mu.Unlock() + for ; ic != nil; ic = ic.next { + ic.c.Close() + } + return nil +} + +func (p *Pool) lazyInit() { + // Fast path. + if atomic.LoadUint32(&p.chInitialized) == 1 { + return + } + // Slow path. + p.mu.Lock() + if p.chInitialized == 0 { + p.ch = make(chan struct{}, p.MaxActive) + if p.closed { + close(p.ch) + } else { + for i := 0; i < p.MaxActive; i++ { + p.ch <- struct{}{} + } + } + atomic.StoreUint32(&p.chInitialized, 1) + } + p.mu.Unlock() +} + +// get prunes stale connections and returns a connection from the idle list or +// creates a new connection. +func (p *Pool) get(ctx interface { + Done() <-chan struct{} + Err() error +}) (Conn, error) { + + // Handle limit for p.Wait == true. + if p.Wait && p.MaxActive > 0 { + p.lazyInit() + if ctx == nil { + <-p.ch + } else { + select { + case <-p.ch: + case <-ctx.Done(): + return nil, ctx.Err() + } + } + } + + p.mu.Lock() + + // Prune stale connections at the back of the idle list. + if p.IdleTimeout > 0 { + n := p.idle.count + for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ { + c := p.idle.back.c + p.idle.popBack() + p.mu.Unlock() + c.Close() + p.mu.Lock() + p.active-- + } + } + + // Get idle connection from the front of idle list. + for p.idle.front != nil { + ic := p.idle.front + p.idle.popFront() + p.mu.Unlock() + if p.TestOnBorrow == nil || p.TestOnBorrow(ic.c, ic.t) == nil { + return ic.c, nil + } + ic.c.Close() + p.mu.Lock() + p.active-- + } + + // Check for pool closed before dialing a new connection. + if p.closed { + p.mu.Unlock() + return nil, errors.New("redigo: get on closed pool") + } + + // Handle limit for p.Wait == false. + if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive { + p.mu.Unlock() + return nil, ErrPoolExhausted + } + + p.active++ + p.mu.Unlock() + c, err := p.Dial() + if err != nil { + c = nil + p.mu.Lock() + p.active-- + if p.ch != nil && !p.closed { + p.ch <- struct{}{} + } + p.mu.Unlock() + } + return c, err +} + +func (p *Pool) put(c Conn, forceClose bool) error { + p.mu.Lock() + if !p.closed && !forceClose { + p.idle.pushFront(&idleConn{t: nowFunc(), c: c}) + if p.idle.count > p.MaxIdle { + c = p.idle.back.c + p.idle.popBack() + } else { + c = nil + } + } + + if c != nil { + p.mu.Unlock() + c.Close() + p.mu.Lock() + p.active-- + } + + if p.ch != nil && !p.closed { + p.ch <- struct{}{} + } + p.mu.Unlock() + return nil +} + +type pooledConnection struct { + p *Pool + c Conn + state int +} + +var ( + sentinel []byte + sentinelOnce sync.Once +) + +func initSentinel() { + p := make([]byte, 64) + if _, err := rand.Read(p); err == nil { + sentinel = p + } else { + h := sha1.New() + io.WriteString(h, "Oops, rand failed. Use time instead.") + io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) + sentinel = h.Sum(nil) + } +} + +func (pc *pooledConnection) Close() error { + c := pc.c + if _, ok := c.(errorConnection); ok { + return nil + } + pc.c = errorConnection{errConnClosed} + + if pc.state&internal.MultiState != 0 { + c.Send("DISCARD") + pc.state &^= (internal.MultiState | internal.WatchState) + } else if pc.state&internal.WatchState != 0 { + c.Send("UNWATCH") + pc.state &^= internal.WatchState + } + if pc.state&internal.SubscribeState != 0 { + c.Send("UNSUBSCRIBE") + c.Send("PUNSUBSCRIBE") + // To detect the end of the message stream, ask the server to echo + // a sentinel value and read until we see that value. + sentinelOnce.Do(initSentinel) + c.Send("ECHO", sentinel) + c.Flush() + for { + p, err := c.Receive() + if err != nil { + break + } + if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { + pc.state &^= internal.SubscribeState + break + } + } + } + c.Do("") + pc.p.put(c, pc.state != 0 || c.Err() != nil) + return nil +} + +func (pc *pooledConnection) Err() error { + return pc.c.Err() +} + +func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { + ci := internal.LookupCommandInfo(commandName) + pc.state = (pc.state | ci.Set) &^ ci.Clear + return pc.c.Do(commandName, args...) +} + +func (pc *pooledConnection) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) { + cwt, ok := pc.c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + ci := internal.LookupCommandInfo(commandName) + pc.state = (pc.state | ci.Set) &^ ci.Clear + return cwt.DoWithTimeout(timeout, commandName, args...) +} + +func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { + ci := internal.LookupCommandInfo(commandName) + pc.state = (pc.state | ci.Set) &^ ci.Clear + return pc.c.Send(commandName, args...) +} + +func (pc *pooledConnection) Flush() error { + return pc.c.Flush() +} + +func (pc *pooledConnection) Receive() (reply interface{}, err error) { + return pc.c.Receive() +} + +func (pc *pooledConnection) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { + cwt, ok := pc.c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.ReceiveWithTimeout(timeout) +} + +type errorConnection struct{ err error } + +func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } +func (ec errorConnection) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) { + return nil, ec.err +} +func (ec errorConnection) Send(string, ...interface{}) error { return ec.err } +func (ec errorConnection) Err() error { return ec.err } +func (ec errorConnection) Close() error { return nil } +func (ec errorConnection) Flush() error { return ec.err } +func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err } +func (ec errorConnection) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err } + +type idleList struct { + count int + front, back *idleConn +} + +type idleConn struct { + c Conn + t time.Time + next, prev *idleConn +} + +func (l *idleList) pushFront(ic *idleConn) { + ic.next = l.front + ic.prev = nil + if l.count == 0 { + l.back = ic + } else { + l.front.prev = ic + } + l.front = ic + l.count++ + return +} + +func (l *idleList) popFront() { + ic := l.front + l.count-- + if l.count == 0 { + l.front, l.back = nil, nil + } else { + ic.next.prev = nil + l.front = ic.next + } + ic.next, ic.prev = nil, nil +} + +func (l *idleList) popBack() { + ic := l.back + l.count-- + if l.count == 0 { + l.front, l.back = nil, nil + } else { + ic.prev.next = nil + l.back = ic.prev + } + ic.next, ic.prev = nil, nil +} diff --git a/vendor/github.com/garyburd/redigo/redis/pool17.go b/vendor/github.com/garyburd/redigo/redis/pool17.go new file mode 100644 index 00000000..57a22644 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/pool17.go @@ -0,0 +1,35 @@ +// Copyright 2018 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// +build go1.7 + +package redis + +import "context" + +// GetContext gets a connection using the provided context. +// +// The provided Context must be non-nil. If the context expires before the +// connection is complete, an error is returned. Any expiration on the context +// will not affect the returned connection. +// +// If the function completes without error, then the application must close the +// returned connection. +func (p *Pool) GetContext(ctx context.Context) (Conn, error) { + c, err := p.get(ctx) + if err != nil { + return errorConnection{err}, err + } + return &pooledConnection{p: p, c: c}, nil +} diff --git a/vendor/github.com/garyburd/redigo/redis/pubsub.go b/vendor/github.com/garyburd/redigo/redis/pubsub.go new file mode 100644 index 00000000..f0ac8253 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/pubsub.go @@ -0,0 +1,157 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "time" +) + +// Subscription represents a subscribe or unsubscribe notification. +type Subscription struct { + // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" + Kind string + + // The channel that was changed. + Channel string + + // The current number of subscriptions for connection. + Count int +} + +// Message represents a message notification. +type Message struct { + // The originating channel. + Channel string + + // The message data. + Data []byte +} + +// PMessage represents a pmessage notification. +type PMessage struct { + // The matched pattern. + Pattern string + + // The originating channel. + Channel string + + // The message data. + Data []byte +} + +// Pong represents a pubsub pong notification. +type Pong struct { + Data string +} + +// PubSubConn wraps a Conn with convenience methods for subscribers. +type PubSubConn struct { + Conn Conn +} + +// Close closes the connection. +func (c PubSubConn) Close() error { + return c.Conn.Close() +} + +// Subscribe subscribes the connection to the specified channels. +func (c PubSubConn) Subscribe(channel ...interface{}) error { + c.Conn.Send("SUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// PSubscribe subscribes the connection to the given patterns. +func (c PubSubConn) PSubscribe(channel ...interface{}) error { + c.Conn.Send("PSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// Unsubscribe unsubscribes the connection from the given channels, or from all +// of them if none is given. +func (c PubSubConn) Unsubscribe(channel ...interface{}) error { + c.Conn.Send("UNSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// PUnsubscribe unsubscribes the connection from the given patterns, or from all +// of them if none is given. +func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { + c.Conn.Send("PUNSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// Ping sends a PING to the server with the specified data. +// +// The connection must be subscribed to at least one channel or pattern when +// calling this method. +func (c PubSubConn) Ping(data string) error { + c.Conn.Send("PING", data) + return c.Conn.Flush() +} + +// Receive returns a pushed message as a Subscription, Message, PMessage, Pong +// or error. The return value is intended to be used directly in a type switch +// as illustrated in the PubSubConn example. +func (c PubSubConn) Receive() interface{} { + return c.receiveInternal(c.Conn.Receive()) +} + +// ReceiveWithTimeout is like Receive, but it allows the application to +// override the connection's default timeout. +func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} { + return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout)) +} + +func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} { + reply, err := Values(replyArg, errArg) + if err != nil { + return err + } + + var kind string + reply, err = Scan(reply, &kind) + if err != nil { + return err + } + + switch kind { + case "message": + var m Message + if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { + return err + } + return m + case "pmessage": + var pm PMessage + if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { + return err + } + return pm + case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": + s := Subscription{Kind: kind} + if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { + return err + } + return s + case "pong": + var p Pong + if _, err := Scan(reply, &p.Data); err != nil { + return err + } + return p + } + return errors.New("redigo: unknown pubsub notification") +} diff --git a/vendor/github.com/garyburd/redigo/redis/redis.go b/vendor/github.com/garyburd/redigo/redis/redis.go new file mode 100644 index 00000000..141fa4a9 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/redis.go @@ -0,0 +1,117 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "time" +) + +// Error represents an error returned in a command reply. +type Error string + +func (err Error) Error() string { return string(err) } + +// Conn represents a connection to a Redis server. +type Conn interface { + // Close closes the connection. + Close() error + + // Err returns a non-nil value when the connection is not usable. + Err() error + + // Do sends a command to the server and returns the received reply. + Do(commandName string, args ...interface{}) (reply interface{}, err error) + + // Send writes the command to the client's output buffer. + Send(commandName string, args ...interface{}) error + + // Flush flushes the output buffer to the Redis server. + Flush() error + + // Receive receives a single reply from the Redis server + Receive() (reply interface{}, err error) +} + +// Argument is the interface implemented by an object which wants to control how +// the object is converted to Redis bulk strings. +type Argument interface { + // RedisArg returns a value to be encoded as a bulk string per the + // conversions listed in the section 'Executing Commands'. + // Implementations should typically return a []byte or string. + RedisArg() interface{} +} + +// Scanner is implemented by an object which wants to control its value is +// interpreted when read from Redis. +type Scanner interface { + // RedisScan assigns a value from a Redis value. The argument src is one of + // the reply types listed in the section `Executing Commands`. + // + // An error should be returned if the value cannot be stored without + // loss of information. + RedisScan(src interface{}) error +} + +// ConnWithTimeout is an optional interface that allows the caller to override +// a connection's default read timeout. This interface is useful for executing +// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the +// server. +// +// A connection's default read timeout is set with the DialReadTimeout dial +// option. Applications should rely on the default timeout for commands that do +// not block at the server. +// +// All of the Conn implementations in this package satisfy the ConnWithTimeout +// interface. +// +// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify +// use of this interface. +type ConnWithTimeout interface { + Conn + + // Do sends a command to the server and returns the received reply. + // The timeout overrides the read timeout set when dialing the + // connection. + DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) + + // Receive receives a single reply from the Redis server. The timeout + // overrides the read timeout set when dialing the connection. + ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) +} + +var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout") + +// DoWithTimeout executes a Redis command with the specified read timeout. If +// the connection does not satisfy the ConnWithTimeout interface, then an error +// is returned. +func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { + cwt, ok := c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.DoWithTimeout(timeout, cmd, args...) +} + +// ReceiveWithTimeout receives a reply with the specified read timeout. If the +// connection does not satisfy the ConnWithTimeout interface, then an error is +// returned. +func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) { + cwt, ok := c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.ReceiveWithTimeout(timeout) +} diff --git a/vendor/github.com/garyburd/redigo/redis/reply.go b/vendor/github.com/garyburd/redigo/redis/reply.go new file mode 100644 index 00000000..c2b3b2b6 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/reply.go @@ -0,0 +1,479 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "fmt" + "strconv" +) + +// ErrNil indicates that a reply value is nil. +var ErrNil = errors.New("redigo: nil returned") + +// Int is a helper that converts a command reply to an integer. If err is not +// equal to nil, then Int returns 0, err. Otherwise, Int converts the +// reply to an int as follows: +// +// Reply type Result +// integer int(reply), nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int(reply interface{}, err error) (int, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + x := int(reply) + if int64(x) != reply { + return 0, strconv.ErrRange + } + return x, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 0) + return int(n), err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) +} + +// Int64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int64(reply interface{}, err error) (int64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + return reply, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) +} + +var errNegativeInt = errors.New("redigo: unexpected value for Uint64") + +// Uint64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Uint64(reply interface{}, err error) (uint64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + if reply < 0 { + return 0, errNegativeInt + } + return uint64(reply), nil + case []byte: + n, err := strconv.ParseUint(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) +} + +// Float64 is a helper that converts a command reply to 64 bit float. If err is +// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts +// the reply to an int as follows: +// +// Reply type Result +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Float64(reply interface{}, err error) (float64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case []byte: + n, err := strconv.ParseFloat(string(reply), 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) +} + +// String is a helper that converts a command reply to a string. If err is not +// equal to nil, then String returns "", err. Otherwise String converts the +// reply to a string as follows: +// +// Reply type Result +// bulk string string(reply), nil +// simple string reply, nil +// nil "", ErrNil +// other "", error +func String(reply interface{}, err error) (string, error) { + if err != nil { + return "", err + } + switch reply := reply.(type) { + case []byte: + return string(reply), nil + case string: + return reply, nil + case nil: + return "", ErrNil + case Error: + return "", reply + } + return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) +} + +// Bytes is a helper that converts a command reply to a slice of bytes. If err +// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts +// the reply to a slice of bytes as follows: +// +// Reply type Result +// bulk string reply, nil +// simple string []byte(reply), nil +// nil nil, ErrNil +// other nil, error +func Bytes(reply interface{}, err error) ([]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []byte: + return reply, nil + case string: + return []byte(reply), nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) +} + +// Bool is a helper that converts a command reply to a boolean. If err is not +// equal to nil, then Bool returns false, err. Otherwise Bool converts the +// reply to boolean as follows: +// +// Reply type Result +// integer value != 0, nil +// bulk string strconv.ParseBool(reply) +// nil false, ErrNil +// other false, error +func Bool(reply interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + switch reply := reply.(type) { + case int64: + return reply != 0, nil + case []byte: + return strconv.ParseBool(string(reply)) + case nil: + return false, ErrNil + case Error: + return false, reply + } + return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) +} + +// MultiBulk is a helper that converts an array command reply to a []interface{}. +// +// Deprecated: Use Values instead. +func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } + +// Values is a helper that converts an array command reply to a []interface{}. +// If err is not equal to nil, then Values returns nil, err. Otherwise, Values +// converts the reply as follows: +// +// Reply type Result +// array reply, nil +// nil nil, ErrNil +// other nil, error +func Values(reply interface{}, err error) ([]interface{}, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + return reply, nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) +} + +func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error { + if err != nil { + return err + } + switch reply := reply.(type) { + case []interface{}: + makeSlice(len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + if err := assign(i, reply[i]); err != nil { + return err + } + } + return nil + case nil: + return ErrNil + case Error: + return reply + } + return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply) +} + +// Float64s is a helper that converts an array command reply to a []float64. If +// err is not equal to nil, then Float64s returns nil, err. Nil array items are +// converted to 0 in the output slice. Floats64 returns an error if an array +// item is not a bulk string or nil. +func Float64s(reply interface{}, err error) ([]float64, error) { + var result []float64 + err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error { + p, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v) + } + f, err := strconv.ParseFloat(string(p), 64) + result[i] = f + return err + }) + return result, err +} + +// Strings is a helper that converts an array command reply to a []string. If +// err is not equal to nil, then Strings returns nil, err. Nil array items are +// converted to "" in the output slice. Strings returns an error if an array +// item is not a bulk string or nil. +func Strings(reply interface{}, err error) ([]string, error) { + var result []string + err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case string: + result[i] = v + return nil + case []byte: + result[i] = string(v) + return nil + default: + return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v) + } + }) + return result, err +} + +// ByteSlices is a helper that converts an array command reply to a [][]byte. +// If err is not equal to nil, then ByteSlices returns nil, err. Nil array +// items are stay nil. ByteSlices returns an error if an array item is not a +// bulk string or nil. +func ByteSlices(reply interface{}, err error) ([][]byte, error) { + var result [][]byte + err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error { + p, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v) + } + result[i] = p + return nil + }) + return result, err +} + +// Int64s is a helper that converts an array command reply to a []int64. +// If err is not equal to nil, then Int64s returns nil, err. Nil array +// items are stay nil. Int64s returns an error if an array item is not a +// bulk string or nil. +func Int64s(reply interface{}, err error) ([]int64, error) { + var result []int64 + err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case int64: + result[i] = v + return nil + case []byte: + n, err := strconv.ParseInt(string(v), 10, 64) + result[i] = n + return err + default: + return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v) + } + }) + return result, err +} + +// Ints is a helper that converts an array command reply to a []in. +// If err is not equal to nil, then Ints returns nil, err. Nil array +// items are stay nil. Ints returns an error if an array item is not a +// bulk string or nil. +func Ints(reply interface{}, err error) ([]int, error) { + var result []int + err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case int64: + n := int(v) + if int64(n) != v { + return strconv.ErrRange + } + result[i] = n + return nil + case []byte: + n, err := strconv.Atoi(string(v)) + result[i] = n + return err + default: + return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v) + } + }) + return result, err +} + +// StringMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. +// Requires an even number of values in result. +func StringMap(result interface{}, err error) (map[string]string, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: StringMap expects even number of values result") + } + m := make(map[string]string, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, okKey := values[i].([]byte) + value, okValue := values[i+1].([]byte) + if !okKey || !okValue { + return nil, errors.New("redigo: StringMap key not a bulk string value") + } + m[string(key)] = string(value) + } + return m, nil +} + +// IntMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]int. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func IntMap(result interface{}, err error) (map[string]int, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: IntMap expects even number of values result") + } + m := make(map[string]int, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].([]byte) + if !ok { + return nil, errors.New("redigo: IntMap key not a bulk string value") + } + value, err := Int(values[i+1], nil) + if err != nil { + return nil, err + } + m[string(key)] = value + } + return m, nil +} + +// Int64Map is a helper that converts an array of strings (alternating key, value) +// into a map[string]int64. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func Int64Map(result interface{}, err error) (map[string]int64, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: Int64Map expects even number of values result") + } + m := make(map[string]int64, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].([]byte) + if !ok { + return nil, errors.New("redigo: Int64Map key not a bulk string value") + } + value, err := Int64(values[i+1], nil) + if err != nil { + return nil, err + } + m[string(key)] = value + } + return m, nil +} + +// Positions is a helper that converts an array of positions (lat, long) +// into a [][2]float64. The GEOPOS command returns replies in this format. +func Positions(result interface{}, err error) ([]*[2]float64, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + positions := make([]*[2]float64, len(values)) + for i := range values { + if values[i] == nil { + continue + } + p, ok := values[i].([]interface{}) + if !ok { + return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i]) + } + if len(p) != 2 { + return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p)) + } + lat, err := Float64(p[0], nil) + if err != nil { + return nil, err + } + long, err := Float64(p[1], nil) + if err != nil { + return nil, err + } + positions[i] = &[2]float64{lat, long} + } + return positions, nil +} diff --git a/vendor/github.com/garyburd/redigo/redis/scan.go b/vendor/github.com/garyburd/redigo/redis/scan.go new file mode 100644 index 00000000..ef9551bd --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/scan.go @@ -0,0 +1,585 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "sync" +) + +func ensureLen(d reflect.Value, n int) { + if n > d.Cap() { + d.Set(reflect.MakeSlice(d.Type(), n, n)) + } else { + d.SetLen(n) + } +} + +func cannotConvert(d reflect.Value, s interface{}) error { + var sname string + switch s.(type) { + case string: + sname = "Redis simple string" + case Error: + sname = "Redis error" + case int64: + sname = "Redis integer" + case []byte: + sname = "Redis bulk string" + case []interface{}: + sname = "Redis array" + default: + sname = reflect.TypeOf(s).String() + } + return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) +} + +func convertAssignBulkString(d reflect.Value, s []byte) (err error) { + switch d.Type().Kind() { + case reflect.Float32, reflect.Float64: + var x float64 + x, err = strconv.ParseFloat(string(s), d.Type().Bits()) + d.SetFloat(x) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + var x int64 + x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) + d.SetInt(x) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + var x uint64 + x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) + d.SetUint(x) + case reflect.Bool: + var x bool + x, err = strconv.ParseBool(string(s)) + d.SetBool(x) + case reflect.String: + d.SetString(string(s)) + case reflect.Slice: + if d.Type().Elem().Kind() != reflect.Uint8 { + err = cannotConvert(d, s) + } else { + d.SetBytes(s) + } + default: + err = cannotConvert(d, s) + } + return +} + +func convertAssignInt(d reflect.Value, s int64) (err error) { + switch d.Type().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + d.SetInt(s) + if d.Int() != s { + err = strconv.ErrRange + d.SetInt(0) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if s < 0 { + err = strconv.ErrRange + } else { + x := uint64(s) + d.SetUint(x) + if d.Uint() != x { + err = strconv.ErrRange + d.SetUint(0) + } + } + case reflect.Bool: + d.SetBool(s != 0) + default: + err = cannotConvert(d, s) + } + return +} + +func convertAssignValue(d reflect.Value, s interface{}) (err error) { + if d.Kind() != reflect.Ptr { + if d.CanAddr() { + d2 := d.Addr() + if d2.CanInterface() { + if scanner, ok := d2.Interface().(Scanner); ok { + return scanner.RedisScan(s) + } + } + } + } else if d.CanInterface() { + // Already a reflect.Ptr + if d.IsNil() { + d.Set(reflect.New(d.Type().Elem())) + } + if scanner, ok := d.Interface().(Scanner); ok { + return scanner.RedisScan(s) + } + } + + switch s := s.(type) { + case []byte: + err = convertAssignBulkString(d, s) + case int64: + err = convertAssignInt(d, s) + default: + err = cannotConvert(d, s) + } + return err +} + +func convertAssignArray(d reflect.Value, s []interface{}) error { + if d.Type().Kind() != reflect.Slice { + return cannotConvert(d, s) + } + ensureLen(d, len(s)) + for i := 0; i < len(s); i++ { + if err := convertAssignValue(d.Index(i), s[i]); err != nil { + return err + } + } + return nil +} + +func convertAssign(d interface{}, s interface{}) (err error) { + if scanner, ok := d.(Scanner); ok { + return scanner.RedisScan(s) + } + + // Handle the most common destination types using type switches and + // fall back to reflection for all other types. + switch s := s.(type) { + case nil: + // ignore + case []byte: + switch d := d.(type) { + case *string: + *d = string(s) + case *int: + *d, err = strconv.Atoi(string(s)) + case *bool: + *d, err = strconv.ParseBool(string(s)) + case *[]byte: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignBulkString(d.Elem(), s) + } + } + case int64: + switch d := d.(type) { + case *int: + x := int(s) + if int64(x) != s { + err = strconv.ErrRange + x = 0 + } + *d = x + case *bool: + *d = s != 0 + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignInt(d.Elem(), s) + } + } + case string: + switch d := d.(type) { + case *string: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + err = cannotConvert(reflect.ValueOf(d), s) + } + case []interface{}: + switch d := d.(type) { + case *[]interface{}: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignArray(d.Elem(), s) + } + } + case Error: + err = s + default: + err = cannotConvert(reflect.ValueOf(d), s) + } + return +} + +// Scan copies from src to the values pointed at by dest. +// +// Scan uses RedisScan if available otherwise: +// +// The values pointed at by dest must be an integer, float, boolean, string, +// []byte, interface{} or slices of these types. Scan uses the standard strconv +// package to convert bulk strings to numeric and boolean types. +// +// If a dest value is nil, then the corresponding src value is skipped. +// +// If a src element is nil, then the corresponding dest value is not modified. +// +// To enable easy use of Scan in a loop, Scan returns the slice of src +// following the copied values. +func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { + if len(src) < len(dest) { + return nil, errors.New("redigo.Scan: array short") + } + var err error + for i, d := range dest { + err = convertAssign(d, src[i]) + if err != nil { + err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) + break + } + } + return src[len(dest):], err +} + +type fieldSpec struct { + name string + index []int + omitEmpty bool +} + +type structSpec struct { + m map[string]*fieldSpec + l []*fieldSpec +} + +func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { + return ss.m[string(name)] +} + +func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + switch { + case f.PkgPath != "" && !f.Anonymous: + // Ignore unexported fields. + case f.Anonymous: + // TODO: Handle pointers. Requires change to decoder and + // protection against infinite recursion. + if f.Type.Kind() == reflect.Struct { + compileStructSpec(f.Type, depth, append(index, i), ss) + } + default: + fs := &fieldSpec{name: f.Name} + tag := f.Tag.Get("redis") + p := strings.Split(tag, ",") + if len(p) > 0 { + if p[0] == "-" { + continue + } + if len(p[0]) > 0 { + fs.name = p[0] + } + for _, s := range p[1:] { + switch s { + case "omitempty": + fs.omitEmpty = true + default: + panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) + } + } + } + d, found := depth[fs.name] + if !found { + d = 1 << 30 + } + switch { + case len(index) == d: + // At same depth, remove from result. + delete(ss.m, fs.name) + j := 0 + for i := 0; i < len(ss.l); i++ { + if fs.name != ss.l[i].name { + ss.l[j] = ss.l[i] + j += 1 + } + } + ss.l = ss.l[:j] + case len(index) < d: + fs.index = make([]int, len(index)+1) + copy(fs.index, index) + fs.index[len(index)] = i + depth[fs.name] = len(index) + ss.m[fs.name] = fs + ss.l = append(ss.l, fs) + } + } + } +} + +var ( + structSpecMutex sync.RWMutex + structSpecCache = make(map[reflect.Type]*structSpec) + defaultFieldSpec = &fieldSpec{} +) + +func structSpecForType(t reflect.Type) *structSpec { + + structSpecMutex.RLock() + ss, found := structSpecCache[t] + structSpecMutex.RUnlock() + if found { + return ss + } + + structSpecMutex.Lock() + defer structSpecMutex.Unlock() + ss, found = structSpecCache[t] + if found { + return ss + } + + ss = &structSpec{m: make(map[string]*fieldSpec)} + compileStructSpec(t, make(map[string]int), nil, ss) + structSpecCache[t] = ss + return ss +} + +var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") + +// ScanStruct scans alternating names and values from src to a struct. The +// HGETALL and CONFIG GET commands return replies in this format. +// +// ScanStruct uses exported field names to match values in the response. Use +// 'redis' field tag to override the name: +// +// Field int `redis:"myName"` +// +// Fields with the tag redis:"-" are ignored. +// +// Each field uses RedisScan if available otherwise: +// Integer, float, boolean, string and []byte fields are supported. Scan uses the +// standard strconv package to convert bulk string values to numeric and +// boolean types. +// +// If a src element is nil, then the corresponding field is not modified. +func ScanStruct(src []interface{}, dest interface{}) error { + d := reflect.ValueOf(dest) + if d.Kind() != reflect.Ptr || d.IsNil() { + return errScanStructValue + } + d = d.Elem() + if d.Kind() != reflect.Struct { + return errScanStructValue + } + ss := structSpecForType(d.Type()) + + if len(src)%2 != 0 { + return errors.New("redigo.ScanStruct: number of values not a multiple of 2") + } + + for i := 0; i < len(src); i += 2 { + s := src[i+1] + if s == nil { + continue + } + name, ok := src[i].([]byte) + if !ok { + return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) + } + fs := ss.fieldSpec(name) + if fs == nil { + continue + } + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) + } + } + return nil +} + +var ( + errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") +) + +// ScanSlice scans src to the slice pointed to by dest. The elements the dest +// slice must be integer, float, boolean, string, struct or pointer to struct +// values. +// +// Struct fields must be integer, float, boolean or string values. All struct +// fields are used unless a subset is specified using fieldNames. +func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { + d := reflect.ValueOf(dest) + if d.Kind() != reflect.Ptr || d.IsNil() { + return errScanSliceValue + } + d = d.Elem() + if d.Kind() != reflect.Slice { + return errScanSliceValue + } + + isPtr := false + t := d.Type().Elem() + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { + isPtr = true + t = t.Elem() + } + + if t.Kind() != reflect.Struct { + ensureLen(d, len(src)) + for i, s := range src { + if s == nil { + continue + } + if err := convertAssignValue(d.Index(i), s); err != nil { + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) + } + } + return nil + } + + ss := structSpecForType(t) + fss := ss.l + if len(fieldNames) > 0 { + fss = make([]*fieldSpec, len(fieldNames)) + for i, name := range fieldNames { + fss[i] = ss.m[name] + if fss[i] == nil { + return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) + } + } + } + + if len(fss) == 0 { + return errors.New("redigo.ScanSlice: no struct fields") + } + + n := len(src) / len(fss) + if n*len(fss) != len(src) { + return errors.New("redigo.ScanSlice: length not a multiple of struct field count") + } + + ensureLen(d, n) + for i := 0; i < n; i++ { + d := d.Index(i) + if isPtr { + if d.IsNil() { + d.Set(reflect.New(t)) + } + d = d.Elem() + } + for j, fs := range fss { + s := src[i*len(fss)+j] + if s == nil { + continue + } + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) + } + } + } + return nil +} + +// Args is a helper for constructing command arguments from structured values. +type Args []interface{} + +// Add returns the result of appending value to args. +func (args Args) Add(value ...interface{}) Args { + return append(args, value...) +} + +// AddFlat returns the result of appending the flattened value of v to args. +// +// Maps are flattened by appending the alternating keys and map values to args. +// +// Slices are flattened by appending the slice elements to args. +// +// Structs are flattened by appending the alternating names and values of +// exported fields to args. If v is a nil struct pointer, then nothing is +// appended. The 'redis' field tag overrides struct field names. See ScanStruct +// for more information on the use of the 'redis' field tag. +// +// Other types are appended to args as is. +func (args Args) AddFlat(v interface{}) Args { + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Struct: + args = flattenStruct(args, rv) + case reflect.Slice: + for i := 0; i < rv.Len(); i++ { + args = append(args, rv.Index(i).Interface()) + } + case reflect.Map: + for _, k := range rv.MapKeys() { + args = append(args, k.Interface(), rv.MapIndex(k).Interface()) + } + case reflect.Ptr: + if rv.Type().Elem().Kind() == reflect.Struct { + if !rv.IsNil() { + args = flattenStruct(args, rv.Elem()) + } + } else { + args = append(args, v) + } + default: + args = append(args, v) + } + return args +} + +func flattenStruct(args Args, v reflect.Value) Args { + ss := structSpecForType(v.Type()) + for _, fs := range ss.l { + fv := v.FieldByIndex(fs.index) + if fs.omitEmpty { + var empty = false + switch fv.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + empty = fv.Len() == 0 + case reflect.Bool: + empty = !fv.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + empty = fv.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + empty = fv.Uint() == 0 + case reflect.Float32, reflect.Float64: + empty = fv.Float() == 0 + case reflect.Interface, reflect.Ptr: + empty = fv.IsNil() + } + if empty { + continue + } + } + args = append(args, fs.name, fv.Interface()) + } + return args +} diff --git a/vendor/github.com/garyburd/redigo/redis/script.go b/vendor/github.com/garyburd/redigo/redis/script.go new file mode 100644 index 00000000..0ef1c821 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/redis/script.go @@ -0,0 +1,91 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "crypto/sha1" + "encoding/hex" + "io" + "strings" +) + +// Script encapsulates the source, hash and key count for a Lua script. See +// http://redis.io/commands/eval for information on scripts in Redis. +type Script struct { + keyCount int + src string + hash string +} + +// NewScript returns a new script object. If keyCount is greater than or equal +// to zero, then the count is automatically inserted in the EVAL command +// argument list. If keyCount is less than zero, then the application supplies +// the count as the first value in the keysAndArgs argument to the Do, Send and +// SendHash methods. +func NewScript(keyCount int, src string) *Script { + h := sha1.New() + io.WriteString(h, src) + return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} +} + +func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { + var args []interface{} + if s.keyCount < 0 { + args = make([]interface{}, 1+len(keysAndArgs)) + args[0] = spec + copy(args[1:], keysAndArgs) + } else { + args = make([]interface{}, 2+len(keysAndArgs)) + args[0] = spec + args[1] = s.keyCount + copy(args[2:], keysAndArgs) + } + return args +} + +// Hash returns the script hash. +func (s *Script) Hash() string { + return s.hash +} + +// Do evaluates the script. Under the covers, Do optimistically evaluates the +// script using the EVALSHA command. If the command fails because the script is +// not loaded, then Do evaluates the script using the EVAL command (thus +// causing the script to load). +func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { + v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) + if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { + v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) + } + return v, err +} + +// SendHash evaluates the script without waiting for the reply. The script is +// evaluated with the EVALSHA command. The application must ensure that the +// script is loaded by a previous call to Send, Do or Load methods. +func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { + return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) +} + +// Send evaluates the script without waiting for the reply. +func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { + return c.Send("EVAL", s.args(s.src, keysAndArgs)...) +} + +// Load loads the script without evaluating it. +func (s *Script) Load(c Conn) error { + _, err := c.Do("SCRIPT", "LOAD", s.src) + return err +} diff --git a/vendor/github.com/gocraft/health/LICENSE b/vendor/github.com/gocraft/health/LICENSE new file mode 100644 index 00000000..bc9d4641 --- /dev/null +++ b/vendor/github.com/gocraft/health/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 Jonathan Novak + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/gocraft/health/README.md b/vendor/github.com/gocraft/health/README.md new file mode 100644 index 00000000..a8f18f53 --- /dev/null +++ b/vendor/github.com/gocraft/health/README.md @@ -0,0 +1,409 @@ +# gocraft/health [![GoDoc](https://godoc.org/github.com/gocraft/health?status.png)](https://godoc.org/github.com/gocraft/health) + +gocraft/health allows you to instrument your service for logging and metrics, and then send that instrumentation to log files, StatsD, Bugsnag, or to be polled and aggregated via a JSON API. + +gocraft/health also ships with a New Relic-like aggregator (called healthd) that shows you your slowest endpoints, top error producers, top throughput endpoints, and so on. + +## Instrumenting your service + +### Make a new stream with sinks + +First, you'll want to make a new Stream and attach your sinks to it. Streams are commonly saved in a global variable. + +```go +import ( + "github.com/gocraft/health" + "github.com/gocraft/health/sinks/bugsnag" + "os" +) + +// Save the stream as a global variable +var stream = health.NewStream() + +// In your main func, initiailze the stream with your sinks. +func main() { + // Log to stdout! (can also use WriterSink to write to a log file, Syslog, etc) + stream.AddSink(&health.WriterSink{os.Stdout}) + + // Log to StatsD! + statsdSink, err = health.NewStatsDSink("127.0.0.1:8125", "myapp") + if err != nil { + stream.EventErr("new_statsd_sink", err) + return + } + stream.AddSink(statsdSink) + + // Expose instrumentation in this app on a JSON endpoint that healthd can poll! + sink := health.NewJsonPollingSink(time.Minute, time.Minute*5) + stream.AddSink(sink) + sink.StartServer(addr) + + // Send errors to bugsnag! + stream.AddSink(bugsnag.NewSink(&bugsnag.Config{APIKey: "myApiKey"})) + + // Now that your stream is setup, start a web server or something... +} +``` + +### Jobs + +gocraft/health excels at instrumenting services that perform *jobs*. Examples of jobs: serving an HTTP request, serving an RPC request, or processing a message from a work queue. Jobs are encoded semantically into gocraft/health in order to provide out-of-the-box answers to questions like, "what is my slowest endpoint?" + +Jobs serve three functions: +* Jobs record a timing (eg, it took 21ms to complete this job) +* Jobs record a status (eg, did the job complete successfully or was there an error?) +* Jobs group instrumentation inside that job together so that you can analyze it later. + +Let's say you're writing a web service that processes JSON requests/responses. You might write something like this: + +```go +import ( + "github.com/gocraft/health" + "net/http" +) +var stream = health.NewStream() +func main() { + // setup stream with sinks + stream.AddSink(&health.WriterSink{os.Stdout}) + http.HandleFunc("/users", getUsers) +} + +func getUsers(rw http.ResponseWriter, r *http.Request) { + // All logging and instrumentation should be within the context of a job! + job := stream.NewJob("get_users") + + err := fetchUsersFromDatabase(r) + if err != nil { + // When in your job's context, you can log errors, events, timings, etc. + job.EventErr("fetch_user_from_database", err) + } + + // When done with the job, call job.Complete with a completion status. + if err == nil { + job.Complete(health.Success) + } else { + job.Complete(health.Error) + } +} + +``` + +(This example is just used for illustration -- in practice, you'll probably want to use middleware to create your job if you have more than a few endpoints.) + +There are five types of completion statuses: +* **Success** - Your job completed successfully. +* **Error** - Some library call resulted in an error that prevented you from successfully completing your job. +* **Panic** - Some code paniced! +* **ValidationError** - Your code was fine, but the user passed in bad inputs, and so the job wasn't completed successfully. +* **Junk** - The job wasn't completed successfully, but not really because of an Error or ValidationError. For instance, maybe there's just a 404 (not found) or 401 (unauthorized) request to your app. This status code might not apply to all apps. + +### Events, Timings, Gauges, and Errors + +Within jobs, you can emit events, timings, gauges, and errors. The first argument of each of these methods is supposed to be a *key*. Camel case with dots is good because it works with other metrics stores like StatsD. Each method has a basic version as well as a version that accepts keys/values. + +#### Events + +```go +// Events. Notice the camel case with dots. +// (This is helpful when you want to use StatsD sinks) +job.Event("starting_server") +job.Event("proccess_user.by_email.gmail") + +// Event with keys and values: +job.EventKv("failover.started", health.Kvs{"from_ip": fmt.Sprint(currentIP)}) +``` + +* For the WriterSink, an event is just like logging to a file: +``` +[2015-03-11T22:53:22.115855203Z]: job:/api/v2/user_stories event:starting_request kvs:[path:/api/v2/user_stories request-id:F8a8bQOWmRpO6ky] +``` + +* For the StatsD sink (and other metrics sinks), an event is like incrementing a counter. + +#### Timings + +```go +// Timings: +startTime := time.Now() +// Do something... +job.Timing("fetch_user", time.Since(startTime).Nanoseconds()) // NOTE: Nanoseconds! + +// Timings also support keys/values: +job.TimingKv("fetch_user", time.Since(startTime).Nanoseconds(), + health.Kvs{"user_email": userEmail}) +``` + +* NOTE: All timing values are in nanoseconds. +* For the WriterSink, a timing is just like logging to a file: +``` +[2014-12-17T20:36:24.136663759Z]: job:/api/v2/user_stories event:dbr.select time:371 μs kvs:[request-id:F8a8bQOWmRpO6ky sql:SELECT COUNT(*) FROM user_stories WHERE (subdomain_id = 1221) AND (deleted_at IS NULL) AND (ticket_id IN (38327))] +``` + +* For the StatsD sink, we'll send it to StatsD as a timing. +* The JSON polling sink will compute a summary of your timings: min, max, avg, stddev, count, sum. + +#### Gauges + +```go +// Gauges: +job.Gauge("num_goroutines", numRunningGoroutines()) + +// Timings also support keys/values: +job.GaugeKv("num_goroutines", numRunningGoroutines(), + health.Kvs{"dispatcher": dispatcherStatus()}) +``` + +* For the WriterSink, a timing is just like logging to a file: +``` +[2014-12-17T20:36:24.136663759Z]: job:/api/v2/user_stories event:num_goroutines gauge:17 kvs:[request-id:F8a8bQOWmRpO6ky dispatcher:running] +``` + +* For the StatsD sink, we'll send it to StatsD as a gauge. + +#### Errors + +```go +// Errors: +err := someFunc(user.Email) +if err != nil { + return job.EventErr("some_func", err) +} + +// And with keys/Values: +job.EventErrKv("some_func", err, health.Kvs{"email": user.Email}) +``` + +* For the WriterSink, and error will just log to the file with the error: +``` +job:/api/v2/user_stories event:load_session.populate err:not_found kvs:[request-id:F8a8bQOWmRpO6ky] +``` + +* For metrics sinks, Errors are just like Events +* The JSON polling sink and healthd will let you see which errors are trending. +* For the Bugsnag sink, we'll push each error to bugsnag. + +Errors will capture a stacktrace by default so that you can diagnose it in things like Bugsnag. If an error is common or not worth sending to something like Bugsnag, you can mute it. This will cause health to not capture a stack trace or send it to bugsnag: + +```go +i, err := strconv.ParseInt(userInput, 10, 0) +if err != nil { + // Mute this error! It's pretty common and + // does not indicate a problem with our code! + job.EventErr("myfunc.parse_int", health.Mute(err)) + i = 2 // We have a default anyway. No big deal. +} +``` + +Since error handling is so prevalent in Go code, you'll have sitations where multiple functions have the option of loggin the same root error. The best practice that we've identified is to just not think about it and log it on every level of the call stack. Keep in mind that gocraft/health will handle this intelligently and only send one error to Bugsnag, have a correct root backtrace, and so on. + +```go +func showUser(ctx *Context) error { + user, err := ctx.getUser() + if err != nil { + // But we'll just log it here too! + return ctx.EventErr("show_user.get_user", err) + } +} + +func getUser(ctx *Context) (*User, error) { + var u User + err := ctx.db.Select("SELECT * FROM users WHERE id = ?", ctx.userID).LoadStruct(&u) + if err != nil { + // Original error is here: + return nil, ctx.EventErr("get_user.select", err) + } + return &u, nil +} +``` + +### Keys and Values + +Most objects and methods in health work with key/value pairs. Key/value pairs are just maps of strings to strings. Keys and values are only relevant right now for logging sinks: The keys and values will be printed on each line written. + +You can add keys/values to a stream. This is useful for things like hostname or pid. They keys/values will show up on every future event/timing/error. +```go +stream := health.NewStream() +stream.KeyValue("hostname", hostname) +stream.KeyValue("pid", pid) +``` + +You can add keys/values to a job. This is useful for things like a request-id or the current user id: +```go +job.KeyValue("request_id", makeRequestID()) +if user != nil { + job.KeyValue("user_id", fmt.Sprint(user.ID)) +} +``` + +And as previously discussed, each individual event/timing/error can have its own keys and values. + +### Writing your own Sink + +If you need a custom sink, you can just implement the Sink interface: + +```go +type Sink interface { + EmitEvent(job string, event string, kvs map[string]string) + EmitEventErr(job string, event string, err error, kvs map[string]string) + EmitTiming(job string, event string, nanoseconds int64, kvs map[string]string) + EmitGauge(job string, event string, value float64, kvs map[string]string) + EmitComplete(job string, status CompletionStatus, nanoseconds int64, kvs map[string]string) +} +``` + +If you do implement a custom sink that you think would be valuable to other people, I'd be interested in including it in this package. Get in touch via an issue or send a pull requset. + +### Miscellaneous logging + +If you need to, you can log via a stream directly without creating a job. This will emit events under a job named 'general'. This is useful during application initialization: + +```go +stream := NewStream() +stream.EventKv("starting_app", health.Kvs{"listen_ip": listenIP}) +``` + +## healthd and healthtop + +We've built a set of tools to give you New Relic-like application performance monitoring for your Go app. It can show you things like your slowest endpoints, top error producers, top throughput endpoints, and so on. + +These tools are completely optional -- health is super useful without them. But with them, it becomes even better. + + +![Healthtop Screenshot](https://gocraft.github.io/health/images/healthtop.png) + +### Add a JsonPollingSink to your stream + +```go +// Make sink and add it to stream: +sink := health.NewJsonPollingSink(time.Minute, time.Minute*5) +stream.AddSink(sink) + +// Start the HTTP server! This will expose metrics via a JSON API. +// NOTE: this won't interfere with your main app (if it also serves HTTP), +// since it starts a separate net/http server. +// In prod, addr should be a private network interface and port, like "10.2.1.4:5020" +// In local dev, it can be something like "127.0.0.1:5020" +sink.StartServer(addr) +``` + +Once you start your app, you can browse to ```/health``` endpoint (eg, ```127.0.0.1:5020/health```) to see your metrics. Per the initialization options above, your metrics are aggregated in 1-minute chunks. We'll keep 5 minutes worth of data in memory. Nothing is ever persisted to disk. + + +### Start healthd + +healthd will poll multiple services that are exposing a ```/health``` endpoint and aggregate that data. It will then expose that data via its own JSON API. You can query the healthd API to answer questions like 'what are my slowest endpoints'? + +Install the healthd binary: + +```bash +go get github.com/gocraft/health/cmd/healthd +``` + +Now you can run it. It accepts two main inputs as environment variables: + +* **HEALTHD_MONITORED_HOSTPORTS**: comma separated list of hostports that represent your services running the JsonPollingSink. Example: ```HEALTHD_MONITORED_HOSTPORTS=10.18.23.130:5020,10.18.23.131:5020``` +* **HEALTHD_SERVER_HOSTPORT**: interface and port where you want to expose the healthd endpoints. Example: ```HEALTHD_SERVER_HOSTPORT=10.18.23.132:5032``` + +Putting those together: +```bash +HEALTHD_MONITORED_HOSTPORTS=10.18.23.130:5020,10.18.23.131:5020 HEALTHD_SERVER_HOSTPORT=10.18.23.132:5030 healthd +``` + +Of course, in local development mode, you can do something like this: +```bash +HEALTHD_MONITORED_HOSTPORTS=:5020 HEALTHD_SERVER_HOSTPORT=:5032 healthd +``` + +Great! To get a sense of the type of data healthd serves, you can manually navigate to: + +* ```/jobs```: Lists top jobs +* ```/aggregations```: Provides a time series of aggregations +* ```/aggregations/overall```: Squishes all time series aggregations into one aggregation. +* ```/hosts```: Lists all monitored hosts and their statuses. + +However, viewing raw JSON is just to give you a sense of the data. See the next section... + +### Use healthtop to query healthd + +healthtop is a command-line tool that repeatedly queries a healthd and displays the results. + +Install the healthtop binary: + +```bash +go get github.com/gocraft/health/cmd/healthtop +``` + +See your top jobs: + +```bash +healthtop jobs +``` + +![Healthtop Screenshot](https://gocraft.github.io/health/images/healthtop.png) + +(By default, healthop will query healthd on localhost:5032 -- if this is not the case, you can use the source option: ```healthtop --source=10.28.3.132:5032 jobs```) + +You can sort your top jobs by a variety of things: + +```bash +$ healthtop jobs --sort +Error: flag needs an argument: --sort +Usage of jobs: + -h, --help=false: help for jobs + --name="": name is a partial match on the name + --sort="name": sort ∈ {name, count, count_success, count_XXX, min, max, avg} + --source="localhost:5032": source is the host:port of the healthd to query. ex: localhost:5031 + +$ healthtop jobs --sort=count_error +``` + + +See your hosts: + +```bash +healthtop hosts +``` + +![Healthtop Screenshot](https://gocraft.github.io/health/images/healthtop_hosts.png) + +To get help: + +```bash +healthtop help +``` + +## Current Status and Contributing + +Currently, the core instrumentation component is very solid. Healthd is good. healthtop is functional but could use some love. + +Request for contributions: + +health core: + +* A way to do fine-grained histograms with variable binning. + +healthd & healthtop + +* A web UI that is built into healthd +* Keep track of multiple service types so that we can use one healthd to monitor multiple types of applications +* Ability to drill into specific jobs to see top errors +* tests +* general love + +If anything here interests you, let me know by opening an issue and we can collaborate on it. + +## gocraft + +gocraft offers a toolkit for building web apps. Currently these packages are available: + +* [gocraft/web](https://github.com/gocraft/web) - Go Router + Middleware. Your Contexts. +* [gocraft/dbr](https://github.com/gocraft/dbr) - Additions to Go's database/sql for super fast performance and convenience. +* [gocraft/health](https://github.com/gocraft/health) - Instrument your web apps with logging and metrics. +* [gocraft/work](https://github.com/gocraft/work) - Process background jobs in Go. + +These packages were developed by the [engineering team](https://eng.uservoice.com) at [UserVoice](https://www.uservoice.com) and currently power much of its infrastructure and tech stack. + +## Authors + +* Jonathan Novak -- [https://github.com/cypriss](https://github.com/cypriss) +* Sponsored by [UserVoice](https://eng.uservoice.com) diff --git a/vendor/github.com/gocraft/health/TODO b/vendor/github.com/gocraft/health/TODO new file mode 100644 index 00000000..2a34fd8b --- /dev/null +++ b/vendor/github.com/gocraft/health/TODO @@ -0,0 +1,6 @@ + - make sure bugsnag works w/o Hostname and ReleaseStage (eg defaults work) + - make sure healthtop default sort works or at least figure out what it is + - in readme make sure I have the right syntax for http.Handle. + - in readme make sure I get samples of how everything is logged. + - screenshots for healthtop + - remove self-logging in healthd \ No newline at end of file diff --git a/vendor/github.com/gocraft/health/aggregator.go b/vendor/github.com/gocraft/health/aggregator.go new file mode 100644 index 00000000..951aacc6 --- /dev/null +++ b/vendor/github.com/gocraft/health/aggregator.go @@ -0,0 +1,172 @@ +package health + +import ( + "time" +) + +type aggregator struct { + // How long is each aggregation interval. Eg, 1 minute + intervalDuration time.Duration + + // Retain controls how many metrics interval we keep. Eg, 5 minutes + retain time.Duration + + // maxIntervals is the maximum length of intervals. + // It is retain / interval. + maxIntervals int + + // intervals is a slice of the retained intervals + intervalAggregations []*IntervalAggregation +} + +func startAggregator(intervalDuration time.Duration, retain time.Duration, sink *JsonPollingSink) { + cmdChan := sink.cmdChan + doneChan := sink.doneChan + intervalsChanChan := sink.intervalsChanChan + ticker := time.Tick(1 * time.Second) + + agg := newAggregator(intervalDuration, retain) + +AGGREGATE_LOOP: + for { + select { + case <-doneChan: + sink.doneDoneChan <- 1 + break AGGREGATE_LOOP + case cmd := <-cmdChan: + if cmd.Kind == cmdKindEvent { + agg.EmitEvent(cmd.Job, cmd.Event) + } else if cmd.Kind == cmdKindEventErr { + agg.EmitEventErr(cmd.Job, cmd.Event, cmd.Err) + } else if cmd.Kind == cmdKindTiming { + agg.EmitTiming(cmd.Job, cmd.Event, cmd.Nanos) + } else if cmd.Kind == cmdKindGauge { + agg.EmitGauge(cmd.Job, cmd.Event, cmd.Value) + } else if cmd.Kind == cmdKindComplete { + agg.EmitComplete(cmd.Job, cmd.Status, cmd.Nanos) + } + case <-ticker: + agg.getIntervalAggregation() // this has the side effect of sliding the interval window if necessary. + case intervalsChan := <-intervalsChanChan: + intervalsChan <- agg.memorySafeIntervals() + } + } +} + +func newAggregator(intervalDuration time.Duration, retain time.Duration) *aggregator { + maxIntervals := int(retain / intervalDuration) + return &aggregator{ + intervalDuration: intervalDuration, + retain: retain, + maxIntervals: maxIntervals, + intervalAggregations: make([]*IntervalAggregation, 0, maxIntervals), + } +} + +func (a *aggregator) memorySafeIntervals() []*IntervalAggregation { + ret := make([]*IntervalAggregation, 0, len(a.intervalAggregations)) + curAgg := a.getIntervalAggregation() + + for _, intAgg := range a.intervalAggregations { + if intAgg == curAgg { + ret = append(ret, intAgg.Clone()) + } else { + ret = append(ret, intAgg) + } + } + + return ret +} + +func (a *aggregator) EmitEvent(job string, event string) { + intAgg := a.getIntervalAggregation() + intAgg.Events[event] = intAgg.Events[event] + 1 + jobAgg := intAgg.getJobAggregation(job) + jobAgg.Events[event] = jobAgg.Events[event] + 1 + intAgg.SerialNumber++ +} + +func (a *aggregator) EmitEventErr(job string, event string, inputErr error) { + intAgg := a.getIntervalAggregation() + errc := intAgg.getCounterErrs(event) + errc.incrementAndAddError(inputErr) + jobAgg := intAgg.getJobAggregation(job) + jerrc := jobAgg.getCounterErrs(event) + jerrc.incrementAndAddError(inputErr) + intAgg.SerialNumber++ +} + +func (a *aggregator) EmitTiming(job string, event string, nanos int64) { + intAgg := a.getIntervalAggregation() + t := intAgg.getTimers(event) + t.ingest(nanos) + jobAgg := intAgg.getJobAggregation(job) + jt := jobAgg.getTimers(event) + jt.ingest(nanos) + intAgg.SerialNumber++ +} + +func (a *aggregator) EmitGauge(job string, event string, value float64) { + intAgg := a.getIntervalAggregation() + intAgg.Gauges[event] = value + jobAgg := intAgg.getJobAggregation(job) + jobAgg.Gauges[event] = value + intAgg.SerialNumber++ +} + +func (a *aggregator) EmitComplete(job string, status CompletionStatus, nanos int64) { + intAgg := a.getIntervalAggregation() + jobAgg := intAgg.getJobAggregation(job) + jobAgg.ingest(status, nanos) + intAgg.SerialNumber++ +} + +func (a *aggregator) getIntervalAggregation() *IntervalAggregation { + intervalStart := now().Truncate(a.intervalDuration) + + n := len(a.intervalAggregations) + if n > 0 && a.intervalAggregations[n-1].IntervalStart == intervalStart { + return a.intervalAggregations[n-1] + } + + return a.createIntervalAggregation(intervalStart) +} + +func (a *aggregator) createIntervalAggregation(interval time.Time) *IntervalAggregation { + // Make new interval: + current := NewIntervalAggregation(interval) + + // If we've reached our max intervals, and we're going to shift everything down, then set the last one + n := len(a.intervalAggregations) + if n == a.maxIntervals { + for i := 1; i < n; i++ { + a.intervalAggregations[i-1] = a.intervalAggregations[i] + } + a.intervalAggregations[n-1] = current + } else { + a.intervalAggregations = append(a.intervalAggregations, current) + } + + return current +} + +var nowMock time.Time + +func now() time.Time { + if nowMock.IsZero() { + return time.Now() + } + return nowMock +} + +func setNowMock(t string) { + var err error + nowMock, err = time.Parse(time.RFC3339, t) + if err != nil { + panic(err) + } +} + +func resetNowMock() { + nowMock = time.Time{} +} diff --git a/vendor/github.com/gocraft/health/error.go b/vendor/github.com/gocraft/health/error.go new file mode 100644 index 00000000..712e1727 --- /dev/null +++ b/vendor/github.com/gocraft/health/error.go @@ -0,0 +1,36 @@ +package health + +import ( + "github.com/gocraft/health/stack" +) + +type MutedError struct { + Err error +} + +type UnmutedError struct { + Err error + Stack *stack.Trace + Emitted bool +} + +func (e *MutedError) Error() string { + return e.Err.Error() +} + +func (e *UnmutedError) Error() string { + return e.Err.Error() +} + +func Mute(err error) *MutedError { + return &MutedError{Err: err} +} + +func wrapErr(err error) error { + switch err := err.(type) { + case *MutedError, *UnmutedError: + return err + default: + return &UnmutedError{Err: err, Stack: stack.NewTrace(2)} + } +} diff --git a/vendor/github.com/gocraft/health/health.go b/vendor/github.com/gocraft/health/health.go new file mode 100644 index 00000000..22ee16f7 --- /dev/null +++ b/vendor/github.com/gocraft/health/health.go @@ -0,0 +1,245 @@ +package health + +import ( + "errors" + "fmt" + "runtime" + "time" +) + +// This is primarily used as syntactic sugar for libs outside this app for passing in maps easily. +// We don't rely on it internally b/c I don't want to tie interfaces to the 'health' package. +type Kvs map[string]string + +type EventReceiver interface { + Event(eventName string) + EventKv(eventName string, kvs map[string]string) + EventErr(eventName string, err error) error + EventErrKv(eventName string, err error, kvs map[string]string) error + Timing(eventName string, nanoseconds int64) + TimingKv(eventName string, nanoseconds int64, kvs map[string]string) + Gauge(eventName string, value float64) + GaugeKv(eventName string, value float64, kvs map[string]string) +} + +type Stream struct { + Sinks []Sink + KeyValues map[string]string + *Job +} + +type Job struct { + Stream *Stream + JobName string + KeyValues map[string]string + Start time.Time +} + +type CompletionStatus int + +const ( + Success CompletionStatus = iota + ValidationError + Panic + Error + Junk +) + +var completionStatusToString = map[CompletionStatus]string{ + Success: "success", + ValidationError: "validation_error", + Panic: "panic", + Error: "error", + Junk: "junk", +} + +func (cs CompletionStatus) String() string { + return completionStatusToString[cs] +} + +type Sink interface { + EmitEvent(job string, event string, kvs map[string]string) + EmitEventErr(job string, event string, err error, kvs map[string]string) + EmitTiming(job string, event string, nanoseconds int64, kvs map[string]string) + EmitComplete(job string, status CompletionStatus, nanoseconds int64, kvs map[string]string) + EmitGauge(job string, event string, value float64, kvs map[string]string) +} + +func NewStream() *Stream { + s := &Stream{} + s.Job = s.NewJob("general") + return s +} + +func (s *Stream) AddSink(sink Sink) *Stream { + s.Sinks = append(s.Sinks, sink) + return s +} + +func (s *Stream) KeyValue(key string, value string) *Stream { + if s.KeyValues == nil { + s.KeyValues = make(map[string]string) + } + s.KeyValues[key] = value + return s +} + +func (s *Stream) NewJob(name string) *Job { + return &Job{ + Stream: s, + JobName: name, + Start: time.Now(), + } +} + +func (j *Job) KeyValue(key string, value string) *Job { + if j.KeyValues == nil { + j.KeyValues = make(map[string]string) + } + j.KeyValues[key] = value + return j +} + +func (j *Job) Event(eventName string) { + allKvs := j.mergedKeyValues(nil) + for _, sink := range j.Stream.Sinks { + sink.EmitEvent(j.JobName, eventName, allKvs) + } +} + +func (j *Job) EventKv(eventName string, kvs map[string]string) { + allKvs := j.mergedKeyValues(kvs) + for _, sink := range j.Stream.Sinks { + sink.EmitEvent(j.JobName, eventName, allKvs) + } +} + +func (j *Job) EventErr(eventName string, err error) error { + err = wrapErr(err) + allKvs := j.mergedKeyValues(nil) + for _, sink := range j.Stream.Sinks { + sink.EmitEventErr(j.JobName, eventName, err, allKvs) + } + if err, ok := err.(*UnmutedError); ok { + err.Emitted = true + } + return err +} + +func (j *Job) EventErrKv(eventName string, err error, kvs map[string]string) error { + err = wrapErr(err) + allKvs := j.mergedKeyValues(kvs) + for _, sink := range j.Stream.Sinks { + sink.EmitEventErr(j.JobName, eventName, err, allKvs) + } + if err, ok := err.(*UnmutedError); ok { + err.Emitted = true + } + return err +} + +func (j *Job) Timing(eventName string, nanoseconds int64) { + allKvs := j.mergedKeyValues(nil) + for _, sink := range j.Stream.Sinks { + sink.EmitTiming(j.JobName, eventName, nanoseconds, allKvs) + } +} + +func (j *Job) TimingKv(eventName string, nanoseconds int64, kvs map[string]string) { + allKvs := j.mergedKeyValues(kvs) + for _, sink := range j.Stream.Sinks { + sink.EmitTiming(j.JobName, eventName, nanoseconds, allKvs) + } +} + +func (j *Job) Gauge(eventName string, value float64) { + allKvs := j.mergedKeyValues(nil) + for _, sink := range j.Stream.Sinks { + sink.EmitGauge(j.JobName, eventName, value, allKvs) + } +} + +func (j *Job) GaugeKv(eventName string, value float64, kvs map[string]string) { + allKvs := j.mergedKeyValues(kvs) + for _, sink := range j.Stream.Sinks { + sink.EmitGauge(j.JobName, eventName, value, allKvs) + } +} + +func (j *Job) Complete(status CompletionStatus) { + allKvs := j.mergedKeyValues(nil) + for _, sink := range j.Stream.Sinks { + sink.EmitComplete(j.JobName, status, time.Since(j.Start).Nanoseconds(), allKvs) + } +} + +func (j *Job) CompleteKv(status CompletionStatus, kvs map[string]string) { + allKvs := j.mergedKeyValues(kvs) + for _, sink := range j.Stream.Sinks { + sink.EmitComplete(j.JobName, status, time.Since(j.Start).Nanoseconds(), allKvs) + } +} + +func (j *Job) mergedKeyValues(instanceKvs map[string]string) map[string]string { + var allKvs map[string]string + + // Count how many maps actually have contents in them. If it's 0 or 1, we won't allocate a new map. + // Also, optimistically set allKvs. We might use it or we might overwrite the value with a newly made map. + var kvCount = 0 + if len(j.KeyValues) > 0 { + kvCount += 1 + allKvs = j.KeyValues + } + if len(j.Stream.KeyValues) > 0 { + kvCount += 1 + allKvs = j.Stream.KeyValues + } + if len(instanceKvs) > 0 { + kvCount += 1 + allKvs = instanceKvs + } + + if kvCount > 1 { + allKvs = make(map[string]string) + for k, v := range j.Stream.KeyValues { + allKvs[k] = v + } + for k, v := range j.KeyValues { + allKvs[k] = v + } + for k, v := range instanceKvs { + allKvs[k] = v + } + } + + return allKvs +} + +func (s *Stream) Run(jobName string, f func() error) error { + j := s.NewJob(jobName) + return j.Run(f) +} + +func (j *Job) Run(f func() error) (err error) { + defer func() { + if r := recover(); r != nil { + stack := make([]byte, 4096) + stack = stack[:runtime.Stack(stack, false)] + + // recovered value from panic() is an interface{}, and it might not be `error` + // do not simply type-assert here + err = errors.New(fmt.Sprint(r)) + j.EventErrKv("panic", err, Kvs{"stack": string(stack)}) + j.Complete(Panic) + } + }() + + err = f() + if err != nil { + j.Complete(Error) + } else { + j.Complete(Success) + } + + return +} diff --git a/vendor/github.com/gocraft/health/identifier.go b/vendor/github.com/gocraft/health/identifier.go new file mode 100644 index 00000000..6b36d6be --- /dev/null +++ b/vendor/github.com/gocraft/health/identifier.go @@ -0,0 +1,18 @@ +package health + +import ( + "fmt" + "os" +) + +var Identifier = getIdentifier() + +func getIdentifier() string { + pid := os.Getpid() + host, err := os.Hostname() + if err != nil { + host = "hostname_errored" + } + + return fmt.Sprintf("%s.%d", host, pid) +} diff --git a/vendor/github.com/gocraft/health/interval_aggregation.go b/vendor/github.com/gocraft/health/interval_aggregation.go new file mode 100644 index 00000000..6c2b37c5 --- /dev/null +++ b/vendor/github.com/gocraft/health/interval_aggregation.go @@ -0,0 +1,165 @@ +package health + +import ( + "reflect" + "time" +) + +// IntervalAggregation will hold data for a given aggregation interval. +type IntervalAggregation struct { + // The start time of the interval + IntervalStart time.Time `json:"interval_start"` + + // SerialNumber increments every time the aggregation changes. It does not increment if the aggregation does not change. + SerialNumber int64 `json:"serial_number"` + + // Jobs hold a map of job name -> data about that job. + // This includes both primary-job information (success vs error, et all) as well as + // scoping timers/counters by the job. + Jobs map[string]*JobAggregation `json:"jobs"` + + // aggregationMaps will hold event/timer information that is not nested per-job. + aggregationMaps +} + +type aggregationMaps struct { + Timers map[string]*TimerAggregation `json:"timers"` + Gauges map[string]float64 `json:"gauges"` + Events map[string]int64 `json:"events"` + EventErrs map[string]*ErrorCounter `json:"event_errs"` +} + +type JobAggregation struct { + aggregationMaps + TimerAggregation + + CountSuccess int64 `json:"count_success"` + CountValidationError int64 `json:"count_validation_error"` + CountPanic int64 `json:"count_panic"` + CountError int64 `json:"count_error"` + CountJunk int64 `json:"count_junk"` +} + +type TimerAggregation struct { + Count int64 `json:"count"` + NanosSum int64 `json:"nanos_sum"` + NanosSumSquares float64 `json:"nanos_sum_squares"` // 3seconds^2 overflows an int64 + NanosMin int64 `json:"nanos_min"` + NanosMax int64 `json:"nanos_max"` +} + +type ErrorCounter struct { + Count int64 `json:"count"` + + // Let's keep a ring buffer of some errors. I feel like this isn't the best data structure / plan of attack here but works for now. + errorSamples [5]error + errorSampleIndex int +} + +func NewIntervalAggregation(intervalStart time.Time) *IntervalAggregation { + intAgg := &IntervalAggregation{ + IntervalStart: intervalStart, + Jobs: make(map[string]*JobAggregation), + } + intAgg.initAggregationMaps() + + return intAgg +} + +func (am *aggregationMaps) initAggregationMaps() { + am.Timers = make(map[string]*TimerAggregation) + am.Gauges = make(map[string]float64) + am.Events = make(map[string]int64) + am.EventErrs = make(map[string]*ErrorCounter) +} + +func (am *aggregationMaps) getCounterErrs(event string) *ErrorCounter { + ce := am.EventErrs[event] + if ce == nil { + ce = &ErrorCounter{} + am.EventErrs[event] = ce + } + return ce +} + +func (am *aggregationMaps) getTimers(event string) *TimerAggregation { + t := am.Timers[event] + if t == nil { + t = &TimerAggregation{} + am.Timers[event] = t + } + return t +} + +func (ec *ErrorCounter) incrementAndAddError(inputErr error) { + ec.Count++ + ec.addError(inputErr) +} + +func (ec *ErrorCounter) addError(inputErr error) { + lastErr := ec.errorSamples[ec.errorSampleIndex] + if lastErr == nil { + ec.errorSamples[ec.errorSampleIndex] = inputErr + } else if !reflect.DeepEqual(lastErr, inputErr) { + n := len(ec.errorSamples) + ec.errorSampleIndex = (ec.errorSampleIndex + 1) % n + ec.errorSamples[ec.errorSampleIndex] = inputErr + } +} + +func (ec *ErrorCounter) getErrorSamples() []error { + // Count how many non-nil errors are there so we can make a slice of the right size + count := 0 + for _, e := range ec.errorSamples { + if e != nil { + count++ + } + } + ret := make([]error, 0, count) + + // Put non-nil errors in slice + for _, e := range ec.errorSamples { + if e != nil { + ret = append(ret, e) + } + } + return ret +} + +func (ia *IntervalAggregation) getJobAggregation(job string) *JobAggregation { + jobAgg := ia.Jobs[job] + if jobAgg == nil { + jobAgg = &JobAggregation{} + jobAgg.initAggregationMaps() + ia.Jobs[job] = jobAgg + } + return jobAgg +} + +func (a *TimerAggregation) ingest(nanos int64) { + a.Count++ + a.NanosSum += nanos + floatNano := float64(nanos) + a.NanosSumSquares += (floatNano * floatNano) + if a.Count == 1 || nanos < a.NanosMin { + a.NanosMin = nanos + } + if a.Count == 1 || nanos > a.NanosMax { + a.NanosMax = nanos + } +} + +func (a *JobAggregation) ingest(status CompletionStatus, nanos int64) { + a.TimerAggregation.ingest(nanos) + if status == Success { + a.CountSuccess++ + } else if status == ValidationError { + a.CountValidationError++ + } else if status == Panic { + a.CountPanic++ + } else if status == Error { + a.CountError++ + } else if status == Junk { + a.CountJunk++ + } +} diff --git a/vendor/github.com/gocraft/health/interval_aggregation_clone.go b/vendor/github.com/gocraft/health/interval_aggregation_clone.go new file mode 100644 index 00000000..15356e9a --- /dev/null +++ b/vendor/github.com/gocraft/health/interval_aggregation_clone.go @@ -0,0 +1,65 @@ +package health + +// Clone does a deep clone of ia, duplicating all maps and whatnot. +func (ia *IntervalAggregation) Clone() *IntervalAggregation { + dup := &IntervalAggregation{} + dup.IntervalStart = ia.IntervalStart + dup.SerialNumber = ia.SerialNumber + dup.aggregationMaps = *ia.aggregationMaps.Clone() + + dup.Jobs = make(map[string]*JobAggregation) + for k, v := range ia.Jobs { + dup.Jobs[k] = v.Clone() + } + + return dup +} + +func (am *aggregationMaps) Clone() *aggregationMaps { + dup := &aggregationMaps{} + + dup.initAggregationMaps() + + for k, v := range am.Events { + dup.Events[k] = v + } + + for k, v := range am.Gauges { + dup.Gauges[k] = v + } + + for k, v := range am.Timers { + dup.Timers[k] = v.Clone() + } + + for k, v := range am.EventErrs { + dup.EventErrs[k] = v.Clone() + } + + return dup +} + +func (ta *TimerAggregation) Clone() *TimerAggregation { + var dup = *ta + return &dup +} + +func (ec *ErrorCounter) Clone() *ErrorCounter { + var dup = *ec + return &dup +} + +func (ja *JobAggregation) Clone() *JobAggregation { + dup := &JobAggregation{ + CountSuccess: ja.CountSuccess, + CountValidationError: ja.CountValidationError, + CountPanic: ja.CountPanic, + CountError: ja.CountError, + CountJunk: ja.CountJunk, + } + + dup.aggregationMaps = *ja.aggregationMaps.Clone() + dup.TimerAggregation = ja.TimerAggregation + + return dup +} diff --git a/vendor/github.com/gocraft/health/interval_aggregation_merge.go b/vendor/github.com/gocraft/health/interval_aggregation_merge.go new file mode 100644 index 00000000..eaa776ea --- /dev/null +++ b/vendor/github.com/gocraft/health/interval_aggregation_merge.go @@ -0,0 +1,72 @@ +package health + +// Merge merges intAgg into ia, mutating ia. +// Requires that ia and intAgg are a fully valid with no nil maps. +func (ia *IntervalAggregation) Merge(intAgg *IntervalAggregation) { + ia.aggregationMaps.merge(&intAgg.aggregationMaps) + + for k, v := range intAgg.Jobs { + if existingJob, ok := ia.Jobs[k]; ok { + existingJob.merge(v) + } else { + ia.Jobs[k] = v.Clone() + } + } + + ia.SerialNumber++ +} + +func (intoJob *JobAggregation) merge(fromJob *JobAggregation) { + intoJob.aggregationMaps.merge(&fromJob.aggregationMaps) + intoJob.TimerAggregation.merge(&fromJob.TimerAggregation) + intoJob.CountSuccess += fromJob.CountSuccess + intoJob.CountValidationError += fromJob.CountValidationError + intoJob.CountPanic += fromJob.CountPanic + intoJob.CountError += fromJob.CountError + intoJob.CountJunk += fromJob.CountJunk +} + +func (intoTa *TimerAggregation) merge(fromTa *TimerAggregation) { + intoTa.Count += fromTa.Count + intoTa.NanosSum += fromTa.NanosSum + intoTa.NanosSumSquares += fromTa.NanosSumSquares + if fromTa.NanosMin < intoTa.NanosMin { + intoTa.NanosMin = fromTa.NanosMin + } + if fromTa.NanosMax > intoTa.NanosMax { + intoTa.NanosMax = fromTa.NanosMax + } +} + +func (intoAm *aggregationMaps) merge(fromAm *aggregationMaps) { + for k, v := range fromAm.Events { + intoAm.Events[k] += v + } + + for k, v := range fromAm.Gauges { + intoAm.Gauges[k] = v + } + + for k, v := range fromAm.Timers { + if existingTimer, ok := intoAm.Timers[k]; ok { + existingTimer.merge(v) + } else { + intoAm.Timers[k] = v.Clone() + } + } + + for k, v := range fromAm.EventErrs { + if existingErrCounter, ok := intoAm.EventErrs[k]; ok { + existingErrCounter.Count += v.Count + + // merging two ring buffers given our shitty implementation is problematic. + for _, err := range v.errorSamples { + if err != nil { + existingErrCounter.addError(err) + } + } + } else { + intoAm.EventErrs[k] = v.Clone() + } + } +} diff --git a/vendor/github.com/gocraft/health/json_polling_sink.go b/vendor/github.com/gocraft/health/json_polling_sink.go new file mode 100644 index 00000000..f0cc401f --- /dev/null +++ b/vendor/github.com/gocraft/health/json_polling_sink.go @@ -0,0 +1,80 @@ +package health + +import ( + "time" +) + +type JsonPollingSink struct { + intervalDuration time.Duration + cmdChan chan *emitCmd + doneChan chan int + doneDoneChan chan int + intervalsChanChan chan chan []*IntervalAggregation +} + +type cmdKind int + +const ( + cmdKindEvent cmdKind = iota + cmdKindEventErr + cmdKindTiming + cmdKindGauge + cmdKindComplete +) + +type emitCmd struct { + Kind cmdKind + Job string + Event string + Err error + Nanos int64 + Value float64 + Status CompletionStatus +} + +func NewJsonPollingSink(intervalDuration time.Duration, retain time.Duration) *JsonPollingSink { + const buffSize = 4096 // random-ass-guess + + s := &JsonPollingSink{ + intervalDuration: intervalDuration, + cmdChan: make(chan *emitCmd, buffSize), + doneChan: make(chan int), + doneDoneChan: make(chan int), + intervalsChanChan: make(chan chan []*IntervalAggregation), + } + + go startAggregator(intervalDuration, retain, s) + + return s +} + +func (s *JsonPollingSink) ShutdownServer() { + s.doneChan <- 1 + <-s.doneDoneChan +} + +func (s *JsonPollingSink) EmitEvent(job string, event string, kvs map[string]string) { + s.cmdChan <- &emitCmd{Kind: cmdKindEvent, Job: job, Event: event} +} + +func (s *JsonPollingSink) EmitEventErr(job string, event string, inputErr error, kvs map[string]string) { + s.cmdChan <- &emitCmd{Kind: cmdKindEventErr, Job: job, Event: event, Err: inputErr} +} + +func (s *JsonPollingSink) EmitTiming(job string, event string, nanos int64, kvs map[string]string) { + s.cmdChan <- &emitCmd{Kind: cmdKindTiming, Job: job, Event: event, Nanos: nanos} +} + +func (s *JsonPollingSink) EmitGauge(job string, event string, value float64, kvs map[string]string) { + s.cmdChan <- &emitCmd{Kind: cmdKindGauge, Job: job, Event: event, Value: value} +} + +func (s *JsonPollingSink) EmitComplete(job string, status CompletionStatus, nanos int64, kvs map[string]string) { + s.cmdChan <- &emitCmd{Kind: cmdKindComplete, Job: job, Status: status, Nanos: nanos} +} + +func (s *JsonPollingSink) GetMetrics() []*IntervalAggregation { + intervalsChan := make(chan []*IntervalAggregation) + s.intervalsChanChan <- intervalsChan + return <-intervalsChan +} diff --git a/vendor/github.com/gocraft/health/json_polling_sink_http.go b/vendor/github.com/gocraft/health/json_polling_sink_http.go new file mode 100644 index 00000000..7bd4a0cf --- /dev/null +++ b/vendor/github.com/gocraft/health/json_polling_sink_http.go @@ -0,0 +1,48 @@ +package health + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +type HealthAggregationsResponse struct { + InstanceId string `json:"instance_id"` + IntervalDuration time.Duration `json:"interval_duration"` + IntervalAggregations []*IntervalAggregation `json:"aggregations"` +} + +func (s *JsonPollingSink) StartServer(addr string) { + go http.ListenAndServe(addr, s) +} + +func (s *JsonPollingSink) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + rw.Header().Set("Content-Type", "application/json; charset=utf-8") + if r.URL.Path == "/health" { + metrics := s.GetMetrics() + response := &HealthAggregationsResponse{ + InstanceId: Identifier, + IntervalDuration: s.intervalDuration, + IntervalAggregations: metrics, + } + jsonData, err := json.MarshalIndent(response, "", "\t") + if err != nil { + renderError(rw, err) + return + } + fmt.Fprintf(rw, string(jsonData)) + } else { + renderNotFound(rw) + } +} + +func renderNotFound(rw http.ResponseWriter) { + rw.WriteHeader(404) + fmt.Fprintf(rw, `{"error": "not_found"}`) +} + +func renderError(rw http.ResponseWriter, err error) { + rw.WriteHeader(500) + fmt.Fprintf(rw, `{"error": "%s"}`, err.Error()) +} diff --git a/vendor/github.com/gocraft/health/json_writer_sink.go b/vendor/github.com/gocraft/health/json_writer_sink.go new file mode 100644 index 00000000..b5ff73ef --- /dev/null +++ b/vendor/github.com/gocraft/health/json_writer_sink.go @@ -0,0 +1,90 @@ +package health + +import ( + "encoding/json" + "fmt" + "io" +) + +type JsonWriterSink struct { + io.Writer +} + +func (j *JsonWriterSink) EmitEvent(job string, event string, kvs map[string]string) { + + b, err := json.Marshal(struct { + Job string + Event string + Timestamp string + Kvs map[string]string + }{job, event, timestamp(), kvs}) + + if err != nil { + return + } + j.Write(b) +} + +func (j *JsonWriterSink) EmitEventErr(job string, event string, err error, kvs map[string]string) { + + b, err := json.Marshal(struct { + Job string + Event string + Timestamp string + Err string + Kvs map[string]string + }{job, event, timestamp(), fmt.Sprint(err), kvs}) + + if err != nil { + return + } + j.Write(b) +} + +func (j *JsonWriterSink) EmitTiming(job string, event string, nanoseconds int64, kvs map[string]string) { + + b, err := json.Marshal(struct { + Job string + Event string + Timestamp string + Nanoseconds int64 + Kvs map[string]string + }{job, event, timestamp(), nanoseconds, kvs}) + + if err != nil { + return + } + j.Write(b) +} + +func (j *JsonWriterSink) EmitGauge(job string, event string, value float64, kvs map[string]string) { + + b, err := json.Marshal(struct { + Job string + Event string + Timestamp string + Value float64 + Kvs map[string]string + }{job, event, timestamp(), value, kvs}) + + if err != nil { + return + } + j.Write(b) +} + +func (j *JsonWriterSink) EmitComplete(job string, status CompletionStatus, nanoseconds int64, kvs map[string]string) { + + b, err := json.Marshal(struct { + Job string + Status string + Timestamp string + Nanoseconds int64 + Kvs map[string]string + }{job, status.String(), timestamp(), nanoseconds, kvs}) + + if err != nil { + return + } + j.Write(b) +} diff --git a/vendor/github.com/gocraft/health/stack/frame.go b/vendor/github.com/gocraft/health/stack/frame.go new file mode 100644 index 00000000..a8b042c3 --- /dev/null +++ b/vendor/github.com/gocraft/health/stack/frame.go @@ -0,0 +1,71 @@ +package stack + +import ( + // "bytes" + "fmt" + // "io/ioutil" + "runtime" + "strings" +) + +// Frame contains all necessary information about to generate a line in a callstack. +type Frame struct { + File string + LineNumber int + Name string + Package string + IsSystemPackage bool + ProgramCounter uintptr +} + +// NewFrame popoulates a stack frame object from the program counter. +func NewFrame(pc uintptr) Frame { + frame := Frame{ProgramCounter: pc} + if frame.Func() == nil { + return frame + } + frame.Package, frame.Name = packageAndName(frame.Func()) + + // pc -1 because the program counters we use are usually return addresses, + // and we want to show the line that corresponds to the function call + frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1) + frame.IsSystemPackage = isSystemPackage(frame.File, frame.Package) + + return frame +} + +// Func returns the function that this stackframe corresponds to +func (frame *Frame) Func() *runtime.Func { + if frame.ProgramCounter == 0 { + return nil + } + return runtime.FuncForPC(frame.ProgramCounter) +} + +func (frame *Frame) String() string { + return fmt.Sprintf("%s:%d %s", frame.File, frame.LineNumber, frame.Name) +} + +func packageAndName(fn *runtime.Func) (string, string) { + name := fn.Name() + pkg := "" + + // we first remove the path prefix if there is one. + if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 { + pkg += name[:lastslash] + "/" + name = name[lastslash+1:] + } + if period := strings.Index(name, "."); period >= 0 { + pkg += name[:period] + name = name[period+1:] + } + + return pkg, name +} + +var goroot = runtime.GOROOT() + +// isSystemPackage returns true iff the package is a system package like 'runtime' or 'net/http' +func isSystemPackage(file, pkg string) bool { + return strings.HasPrefix(file, goroot) +} diff --git a/vendor/github.com/gocraft/health/stack/stack.go b/vendor/github.com/gocraft/health/stack/stack.go new file mode 100644 index 00000000..5c7dc61c --- /dev/null +++ b/vendor/github.com/gocraft/health/stack/stack.go @@ -0,0 +1,47 @@ +package stack + +import ( + "bytes" + "runtime" +) + +// MaxStackDepth is the maximum number of stackframes on any error. +var MaxStackDepth = 50 + +type Trace struct { + stack []uintptr + frames []Frame +} + +func NewTrace(skip int) *Trace { + stack := make([]uintptr, MaxStackDepth) + length := runtime.Callers(2+skip, stack) + return &Trace{ + stack: stack[:length], + } +} + +// StackFrames returns an array of frames containing information about the stack. +func (t *Trace) Frames() []Frame { + if t.frames == nil { + t.frames = make([]Frame, len(t.stack)) + + for i, pc := range t.stack { + t.frames[i] = NewFrame(pc) + } + } + + return t.frames +} + +// Stack returns a formatted callstack. +func (t *Trace) Stack() []byte { + buf := bytes.Buffer{} + + for _, frame := range t.Frames() { + buf.WriteString(frame.String()) + buf.WriteRune('\n') + } + + return buf.Bytes() +} diff --git a/vendor/github.com/gocraft/health/statsd_sink.go b/vendor/github.com/gocraft/health/statsd_sink.go new file mode 100644 index 00000000..a0b88682 --- /dev/null +++ b/vendor/github.com/gocraft/health/statsd_sink.go @@ -0,0 +1,370 @@ +package health + +import ( + "bytes" + "net" + "strconv" + "time" +) + +type StatsDSinkSanitizationFunc func(*bytes.Buffer, string) + +type eventKey struct { + job string + event string + suffix string +} + +type prefixBuffer struct { + *bytes.Buffer + prefixLen int +} + +type StatsDSinkOptions struct { + // Prefix is something like "metroid" + // Events emitted to StatsD would be metroid.myevent.wat + // Eg, don't include a trailing dot in the prefix. + // It can be "", that's fine. + Prefix string + + // SanitizationFunc sanitizes jobs and events before sending them to statsd + SanitizationFunc StatsDSinkSanitizationFunc + + // SkipNestedEvents will skip {events,timers,gauges} from sending the job.event version + // and will only send the event version. + SkipNestedEvents bool + + // SkipTopLevelEvents will skip {events,timers,gauges} from sending the event version + // and will only send the job.event version. + SkipTopLevelEvents bool +} + +var defaultStatsDOptions = StatsDSinkOptions{SanitizationFunc: sanitizeKey} + +type StatsDSink struct { + options StatsDSinkOptions + + cmdChan chan statsdEmitCmd + drainDoneChan chan struct{} + stopDoneChan chan struct{} + + flushPeriod time.Duration + + udpBuf bytes.Buffer + timingBuf []byte + + udpConn *net.UDPConn + udpAddr *net.UDPAddr + + // map of {job,event,suffix} to a re-usable buffer prefixed with the key. + // Since each timing/gauge has a unique component (the time), we'll truncate to the prefix, write the timing, + // and write the statsD suffix (eg, "|ms\n"). Then copy that to the UDP buffer. + prefixBuffers map[eventKey]prefixBuffer +} + +type statsdCmdKind int + +const ( + statsdCmdKindEvent statsdCmdKind = iota + statsdCmdKindEventErr + statsdCmdKindTiming + statsdCmdKindGauge + statsdCmdKindComplete + statsdCmdKindFlush + statsdCmdKindDrain + statsdCmdKindStop +) + +type statsdEmitCmd struct { + Kind statsdCmdKind + Job string + Event string + Nanos int64 + Value float64 + Status CompletionStatus +} + +const cmdChanBuffSize = 8192 // random-ass-guess +const maxUdpBytes = 1440 // 1500(Ethernet MTU) - 60(Max UDP header size + +func NewStatsDSink(addr string, options *StatsDSinkOptions) (*StatsDSink, error) { + c, err := net.ListenPacket("udp", ":0") + if err != nil { + return nil, err + } + + ra, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } + + s := &StatsDSink{ + udpConn: c.(*net.UDPConn), + udpAddr: ra, + cmdChan: make(chan statsdEmitCmd, cmdChanBuffSize), + drainDoneChan: make(chan struct{}), + stopDoneChan: make(chan struct{}), + flushPeriod: 100 * time.Millisecond, + prefixBuffers: make(map[eventKey]prefixBuffer), + } + + if options != nil { + s.options = *options + if s.options.SanitizationFunc == nil { + s.options.SanitizationFunc = sanitizeKey + } + } else { + s.options = defaultStatsDOptions + } + + go s.loop() + + return s, nil +} + +func (s *StatsDSink) Stop() { + s.cmdChan <- statsdEmitCmd{Kind: statsdCmdKindStop} + <-s.stopDoneChan +} + +func (s *StatsDSink) Drain() { + s.cmdChan <- statsdEmitCmd{Kind: statsdCmdKindDrain} + <-s.drainDoneChan +} + +func (s *StatsDSink) EmitEvent(job string, event string, kvs map[string]string) { + s.cmdChan <- statsdEmitCmd{Kind: statsdCmdKindEvent, Job: job, Event: event} +} + +func (s *StatsDSink) EmitEventErr(job string, event string, inputErr error, kvs map[string]string) { + s.cmdChan <- statsdEmitCmd{Kind: statsdCmdKindEventErr, Job: job, Event: event} +} + +func (s *StatsDSink) EmitTiming(job string, event string, nanos int64, kvs map[string]string) { + s.cmdChan <- statsdEmitCmd{Kind: statsdCmdKindTiming, Job: job, Event: event, Nanos: nanos} +} + +func (s *StatsDSink) EmitGauge(job string, event string, value float64, kvs map[string]string) { + s.cmdChan <- statsdEmitCmd{Kind: statsdCmdKindGauge, Job: job, Event: event, Value: value} +} + +func (s *StatsDSink) EmitComplete(job string, status CompletionStatus, nanos int64, kvs map[string]string) { + s.cmdChan <- statsdEmitCmd{Kind: statsdCmdKindComplete, Job: job, Status: status, Nanos: nanos} +} + +func (s *StatsDSink) loop() { + cmdChan := s.cmdChan + + ticker := time.NewTicker(s.flushPeriod) + go func() { + for _ = range ticker.C { + cmdChan <- statsdEmitCmd{Kind: statsdCmdKindFlush} + } + }() + +LOOP: + for cmd := range cmdChan { + switch cmd.Kind { + case statsdCmdKindDrain: + DRAIN_LOOP: + for { + select { + case cmd := <-cmdChan: + s.processCmd(&cmd) + default: + s.flush() + s.drainDoneChan <- struct{}{} + break DRAIN_LOOP + } + } + case statsdCmdKindStop: + s.stopDoneChan <- struct{}{} + break LOOP + case statsdCmdKindFlush: + s.flush() + default: + s.processCmd(&cmd) + } + } + + ticker.Stop() +} + +func (s *StatsDSink) processCmd(cmd *statsdEmitCmd) { + switch cmd.Kind { + case statsdCmdKindEvent: + s.processEvent(cmd.Job, cmd.Event) + case statsdCmdKindEventErr: + s.processEventErr(cmd.Job, cmd.Event) + case statsdCmdKindTiming: + s.processTiming(cmd.Job, cmd.Event, cmd.Nanos) + case statsdCmdKindGauge: + s.processGauge(cmd.Job, cmd.Event, cmd.Value) + case statsdCmdKindComplete: + s.processComplete(cmd.Job, cmd.Status, cmd.Nanos) + } +} + +func (s *StatsDSink) processEvent(job string, event string) { + if !s.options.SkipTopLevelEvents { + pb := s.getPrefixBuffer("", event, "") + pb.WriteString("1|c\n") + s.writeStatsDMetric(pb.Bytes()) + } + + if !s.options.SkipNestedEvents { + pb := s.getPrefixBuffer(job, event, "") + pb.WriteString("1|c\n") + s.writeStatsDMetric(pb.Bytes()) + } +} + +func (s *StatsDSink) processEventErr(job string, event string) { + if !s.options.SkipTopLevelEvents { + pb := s.getPrefixBuffer("", event, "error") + pb.WriteString("1|c\n") + s.writeStatsDMetric(pb.Bytes()) + } + + if !s.options.SkipNestedEvents { + pb := s.getPrefixBuffer(job, event, "error") + pb.WriteString("1|c\n") + s.writeStatsDMetric(pb.Bytes()) + } +} + +func (s *StatsDSink) processTiming(job string, event string, nanos int64) { + s.writeNanosToTimingBuf(nanos) + + if !s.options.SkipTopLevelEvents { + pb := s.getPrefixBuffer("", event, "") + pb.Write(s.timingBuf) + pb.WriteString("|ms\n") + s.writeStatsDMetric(pb.Bytes()) + } + + if !s.options.SkipNestedEvents { + pb := s.getPrefixBuffer(job, event, "") + pb.Write(s.timingBuf) + pb.WriteString("|ms\n") + s.writeStatsDMetric(pb.Bytes()) + } +} + +func (s *StatsDSink) processGauge(job string, event string, value float64) { + s.timingBuf = s.timingBuf[0:0] + prec := 2 + if (value < 0.1) && (value > -0.1) { + prec = -1 + } + s.timingBuf = strconv.AppendFloat(s.timingBuf, value, 'f', prec, 64) + + if !s.options.SkipTopLevelEvents { + pb := s.getPrefixBuffer("", event, "") + pb.Write(s.timingBuf) + pb.WriteString("|g\n") + s.writeStatsDMetric(pb.Bytes()) + } + + if !s.options.SkipNestedEvents { + pb := s.getPrefixBuffer(job, event, "") + pb.Write(s.timingBuf) + pb.WriteString("|g\n") + s.writeStatsDMetric(pb.Bytes()) + } +} + +func (s *StatsDSink) processComplete(job string, status CompletionStatus, nanos int64) { + s.writeNanosToTimingBuf(nanos) + statusString := status.String() + + pb := s.getPrefixBuffer(job, "", statusString) + pb.Write(s.timingBuf) + pb.WriteString("|ms\n") + s.writeStatsDMetric(pb.Bytes()) +} + +func (s *StatsDSink) flush() { + if s.udpBuf.Len() > 0 { + s.udpConn.WriteToUDP(s.udpBuf.Bytes(), s.udpAddr) + s.udpBuf.Truncate(0) + } +} + +// assumes b is a well-formed statsd metric like "job.event:1|c\n" (including newline) +func (s *StatsDSink) writeStatsDMetric(b []byte) { + lenb := len(b) + + if lenb == 0 { + return + } + + // single metric exceeds limit. sad day. + if lenb > maxUdpBytes { + return + } + + lenUdpBuf := s.udpBuf.Len() + + if (lenb + lenUdpBuf) > maxUdpBytes { + s.udpConn.WriteToUDP(s.udpBuf.Bytes(), s.udpAddr) + s.udpBuf.Truncate(0) + } + + s.udpBuf.Write(b) +} + +func (s *StatsDSink) getPrefixBuffer(job, event, suffix string) prefixBuffer { + key := eventKey{job, event, suffix} + + b, ok := s.prefixBuffers[key] + if !ok { + b.Buffer = &bytes.Buffer{} + s.writeSanitizedKeys(b.Buffer, s.options.Prefix, job, event, suffix) + b.WriteByte(':') + b.prefixLen = b.Len() + + // 123456789.99|ms\n 16 bytes. timing value represents 11 days max + b.Grow(16) + s.prefixBuffers[key] = b + } else { + b.Truncate(b.prefixLen) + } + + return b +} + +func (s *StatsDSink) writeSanitizedKeys(b *bytes.Buffer, keys ...string) { + needDot := false + for _, k := range keys { + if k != "" { + if needDot { + b.WriteByte('.') + } + s.options.SanitizationFunc(b, k) + needDot = true + } + } +} + +func (s *StatsDSink) writeNanosToTimingBuf(nanos int64) { + s.timingBuf = s.timingBuf[0:0] + if nanos >= 10e6 { + // More than 10 milliseconds. We'll just print as an integer + s.timingBuf = strconv.AppendInt(s.timingBuf, nanos/1e6, 10) + } else { + s.timingBuf = strconv.AppendFloat(s.timingBuf, float64(nanos)/float64(time.Millisecond), 'f', 2, 64) + } +} + +func sanitizeKey(b *bytes.Buffer, s string) { + b.Grow(len(s) + 1) + for i := 0; i < len(s); i++ { + si := s[i] + if ('A' <= si && si <= 'Z') || ('a' <= si && si <= 'z') || ('0' <= si && s[i] <= '9') || si == '_' || si == '.' { + b.WriteByte(si) + } else { + b.WriteByte('$') + } + } +} diff --git a/vendor/github.com/gocraft/health/writer_sink.go b/vendor/github.com/gocraft/health/writer_sink.go new file mode 100644 index 00000000..84e01f96 --- /dev/null +++ b/vendor/github.com/gocraft/health/writer_sink.go @@ -0,0 +1,134 @@ +package health + +import ( + "bytes" + "fmt" + "io" + "sort" + "time" +) + +// This sink writes bytes in a format that a human might like to read in a logfile +// This can be used to log to Stdout: +// .AddSink(&WriterSink{os.Stdout}) +// And to a file: +// f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) +// .AddSink(&WriterSink{f}) +// And to syslog: +// w, err := syslog.New(LOG_INFO, "wat") +// .AddSink(&WriterSink{w}) +type WriterSink struct { + io.Writer +} + +func (s *WriterSink) EmitEvent(job string, event string, kvs map[string]string) { + var b bytes.Buffer + b.WriteRune('[') + b.WriteString(timestamp()) + b.WriteString("]: job:") + b.WriteString(job) + b.WriteString(" event:") + b.WriteString(event) + writeMapConsistently(&b, kvs) + b.WriteRune('\n') + s.Writer.Write(b.Bytes()) +} + +func (s *WriterSink) EmitEventErr(job string, event string, inputErr error, kvs map[string]string) { + var b bytes.Buffer + b.WriteRune('[') + b.WriteString(timestamp()) + b.WriteString("]: job:") + b.WriteString(job) + b.WriteString(" event:") + b.WriteString(event) + b.WriteString(" err:") + b.WriteString(inputErr.Error()) + writeMapConsistently(&b, kvs) + b.WriteRune('\n') + s.Writer.Write(b.Bytes()) +} + +func (s *WriterSink) EmitTiming(job string, event string, nanos int64, kvs map[string]string) { + var b bytes.Buffer + b.WriteRune('[') + b.WriteString(timestamp()) + b.WriteString("]: job:") + b.WriteString(job) + b.WriteString(" event:") + b.WriteString(event) + b.WriteString(" time:") + writeNanoseconds(&b, nanos) + writeMapConsistently(&b, kvs) + b.WriteRune('\n') + s.Writer.Write(b.Bytes()) +} + +func (s *WriterSink) EmitGauge(job string, event string, value float64, kvs map[string]string) { + var b bytes.Buffer + b.WriteRune('[') + b.WriteString(timestamp()) + b.WriteString("]: job:") + b.WriteString(job) + b.WriteString(" event:") + b.WriteString(event) + b.WriteString(" gauge:") + fmt.Fprintf(&b, "%g", value) + writeMapConsistently(&b, kvs) + b.WriteRune('\n') + s.Writer.Write(b.Bytes()) +} + +func (s *WriterSink) EmitComplete(job string, status CompletionStatus, nanos int64, kvs map[string]string) { + var b bytes.Buffer + b.WriteRune('[') + b.WriteString(timestamp()) + b.WriteString("]: job:") + b.WriteString(job) + b.WriteString(" status:") + b.WriteString(status.String()) + b.WriteString(" time:") + writeNanoseconds(&b, nanos) + writeMapConsistently(&b, kvs) + b.WriteRune('\n') + s.Writer.Write(b.Bytes()) +} + +func timestamp() string { + return time.Now().UTC().Format(time.RFC3339Nano) +} + +func writeMapConsistently(b *bytes.Buffer, kvs map[string]string) { + if kvs == nil { + return + } + keys := make([]string, 0, len(kvs)) + for k := range kvs { + keys = append(keys, k) + } + sort.Strings(keys) + keysLenMinusOne := len(keys) - 1 + + b.WriteString(" kvs:[") + for i, k := range keys { + b.WriteString(k) + b.WriteRune(':') + b.WriteString(kvs[k]) + + if i != keysLenMinusOne { + b.WriteRune(' ') + } + } + b.WriteRune(']') +} + +func writeNanoseconds(b *bytes.Buffer, nanos int64) { + switch { + case nanos > 2000000: + fmt.Fprintf(b, "%d ms", nanos/1000000) + case nanos > 2000: + fmt.Fprintf(b, "%d μs", nanos/1000) + default: + fmt.Fprintf(b, "%d ns", nanos) + } +} diff --git a/vendor/github.com/gocraft/web/.gitignore b/vendor/github.com/gocraft/web/.gitignore new file mode 100644 index 00000000..fa929750 --- /dev/null +++ b/vendor/github.com/gocraft/web/.gitignore @@ -0,0 +1 @@ +*.out \ No newline at end of file diff --git a/vendor/github.com/gocraft/web/BENCHMARK_RESULTS b/vendor/github.com/gocraft/web/BENCHMARK_RESULTS new file mode 100644 index 00000000..ddeb1363 --- /dev/null +++ b/vendor/github.com/gocraft/web/BENCHMARK_RESULTS @@ -0,0 +1,87 @@ +Result of running `go test speed_test.go -test.bench=.* -test.benchmem=true` on my 2.3 GHz Macbook Pro. + +2014/08/30 commit 056ab3d12ad9e8120f7818454ee5c27c478e2b3d (minor tweaks) +BenchmarkGocraftWeb_Simple 2000000 788 ns/op 347 B/op 7 allocs/op +BenchmarkGocraftWeb_Route15 1000000 2329 ns/op 736 B/op 10 allocs/op +BenchmarkGocraftWeb_Route75 1000000 2339 ns/op 735 B/op 10 allocs/op +BenchmarkGocraftWeb_Route150 1000000 2331 ns/op 737 B/op 10 allocs/op +BenchmarkGocraftWeb_Route300 1000000 2329 ns/op 736 B/op 10 allocs/op +BenchmarkGocraftWeb_Route3000 1000000 2392 ns/op 738 B/op 10 allocs/op +BenchmarkGocraftWeb_Middleware 200000 6533 ns/op 1690 B/op 23 allocs/op +BenchmarkGocraftWeb_Generic 1000000 1428 ns/op 486 B/op 9 allocs/op +BenchmarkGocraftWeb_Composite 200000 8018 ns/op 1851 B/op 23 allocs/op + +// +// Everything below is via a slower Macbook Air (1.8 GHz): +// + +2013/12/15 commit a32b88c2a5e1df88a16cd909f800460ff79edbaf (playing with how dynamic handlers are invoked. middleware/composite seem to be mutually exclusive for 5k ns/op) +BenchmarkGocraftWeb_Simple 1000000 1216 ns/op 333 B/op 7 allocs/op +BenchmarkGocraftWeb_Route15 500000 3337 ns/op 612 B/op 9 allocs/op +BenchmarkGocraftWeb_Route75 500000 3234 ns/op 612 B/op 9 allocs/op +BenchmarkGocraftWeb_Route150 500000 3298 ns/op 612 B/op 9 allocs/op +BenchmarkGocraftWeb_Route300 500000 3217 ns/op 612 B/op 9 allocs/op +BenchmarkGocraftWeb_Route3000 500000 3478 ns/op 615 B/op 9 allocs/op +BenchmarkGocraftWeb_Middleware 200000 14566 ns/op 779 B/op 16 allocs/op +BenchmarkGocraftWeb_Generic 1000000 1885 ns/op 457 B/op 9 allocs/op +BenchmarkGocraftWeb_Composite 200000 9750 ns/op 917 B/op 16 allocs/op + +2013/12/15 commit 51a79f5dae0bc4847a5eb7cc8b44b6eeb70c13fc (fast generic action handlers. Don't call generic action handlers via reflection) +BenchmarkGocraftWeb_Simple 1000000 1271 ns/op 333 B/op 7 allocs/op +BenchmarkGocraftWeb_Route15 500000 2955 ns/op 633 B/op 9 allocs/op +BenchmarkGocraftWeb_Route75 500000 2959 ns/op 633 B/op 9 allocs/op +BenchmarkGocraftWeb_Route150 500000 2982 ns/op 633 B/op 9 allocs/op +BenchmarkGocraftWeb_Route300 500000 3046 ns/op 633 B/op 9 allocs/op +BenchmarkGocraftWeb_Route3000 500000 3459 ns/op 633 B/op 9 allocs/op +BenchmarkGocraftWeb_Middleware 200000 9438 ns/op 779 B/op 16 allocs/op +BenchmarkGocraftWeb_Generic 1000000 1933 ns/op 457 B/op 9 allocs/op +BenchmarkGocraftWeb_Composite 100000 15030 ns/op 1072 B/op 17 allocs/op + +2013/12/15 commit 0e41f71c706db24fd56979361bb5cfa19b4828e2 (fast generic middleware. Don't call generic middleware via reflection) +BenchmarkGocraftWeb_Simple 1000000 2036 ns/op 366 B/op 8 allocs/op +BenchmarkGocraftWeb_Route15 500000 3080 ns/op 713 B/op 10 allocs/op +BenchmarkGocraftWeb_Route75 500000 3452 ns/op 713 B/op 10 allocs/op +BenchmarkGocraftWeb_Route150 500000 3199 ns/op 713 B/op 10 allocs/op +BenchmarkGocraftWeb_Route300 500000 3212 ns/op 713 B/op 10 allocs/op +BenchmarkGocraftWeb_Route3000 500000 3667 ns/op 713 B/op 10 allocs/op +BenchmarkGocraftWeb_Middleware 200000 8888 ns/op 862 B/op 17 allocs/op +BenchmarkGocraftWeb_Generic 1000000 2853 ns/op 490 B/op 10 allocs/op +BenchmarkGocraftWeb_Composite 200000 12352 ns/op 1152 B/op 18 allocs/op + +2013/12/15 commit e75a372bb80dc50627730b52adfcbdc4a6a723f5 (optimization pass. reduce allocations. manual closure.) +BenchmarkGocraftWeb_Simple 1000000 2042 ns/op 366 B/op 8 allocs/op +BenchmarkGocraftWeb_Route15 500000 3203 ns/op 713 B/op 10 allocs/op +BenchmarkGocraftWeb_Route75 500000 3237 ns/op 713 B/op 10 allocs/op +BenchmarkGocraftWeb_Route150 500000 3231 ns/op 713 B/op 10 allocs/op +BenchmarkGocraftWeb_Route300 500000 3263 ns/op 713 B/op 10 allocs/op +BenchmarkGocraftWeb_Route3000 500000 3719 ns/op 713 B/op 10 allocs/op +BenchmarkGocraftWeb_Middleware 200000 9148 ns/op 862 B/op 17 allocs/op +BenchmarkGocraftWeb_Generic 200000 8229 ns/op 680 B/op 16 allocs/op +BenchmarkGocraftWeb_Composite 200000 10038 ns/op 1152 B/op 18 allocs/op + +2013/12/14 commit fb4b15ee25ce11e3ea583f38cecebec8979ebb12 (no functional changes; just revamping benches) +BenchmarkGocraftWeb_Simple 1000000 2517 ns/op 472 B/op 16 allocs/op +BenchmarkGocraftWeb_Route15 500000 3829 ns/op 859 B/op 20 allocs/op +BenchmarkGocraftWeb_Route75 500000 3944 ns/op 859 B/op 20 allocs/op +BenchmarkGocraftWeb_Route150 500000 3977 ns/op 859 B/op 20 allocs/op +BenchmarkGocraftWeb_Route300 500000 3948 ns/op 859 B/op 20 allocs/op +BenchmarkGocraftWeb_Route3000 500000 4492 ns/op 860 B/op 20 allocs/op +BenchmarkGocraftWeb_Middleware 200000 11068 ns/op 1683 B/op 35 allocs/op +BenchmarkGocraftWeb_Generic 200000 9336 ns/op 913 B/op 28 allocs/op +BenchmarkGocraftWeb_Composite 200000 11572 ns/op 1890 B/op 34 allocs/op + +2013/11/27 commit 0991ee88750eb6b054c715ef03cf8f9fba47c6e5 (after implementing regexp segment conditions) +BenchmarkSimple 500000 2749 ns/op 537 B/op 16 allocs/op +BenchmarkRouting 500000 3915 ns/op 843 B/op 19 allocs/op +BenchmarkMiddleware 200000 9281 ns/op 897 B/op 27 allocs/op + +2013/11/25 commit 49658cd6cd345d4e779a7033c6b5d1e3077f56f3 (baseline) +BenchmarkSimple 500000 2760 ns/op 537 B/op 16 allocs/op +BenchmarkRouting 500000 3896 ns/op 843 B/op 19 allocs/op +BenchmarkMiddleware 200000 9177 ns/op 897 B/op 27 allocs/op + + +NOTES ON PROFILING: +go test -bench=BenchmarkGocraftWeb_Composite -benchmem -memprofile mem.out +go tool pprof web.test mem.out +rm myprof* && go build -o myprof examples/prof/main.go && ./myprof && go tool pprof myprof myprof.out \ No newline at end of file diff --git a/vendor/github.com/gocraft/web/LICENSE b/vendor/github.com/gocraft/web/LICENSE new file mode 100644 index 00000000..1eee315f --- /dev/null +++ b/vendor/github.com/gocraft/web/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Jonathan Novak + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/gocraft/web/README.md b/vendor/github.com/gocraft/web/README.md new file mode 100644 index 00000000..5b4fe948 --- /dev/null +++ b/vendor/github.com/gocraft/web/README.md @@ -0,0 +1,394 @@ +# gocraft/web [![GoDoc](https://godoc.org/github.com/gocraft/web?status.png)](https://godoc.org/github.com/gocraft/web) + + +gocraft/web is a Go mux and middleware package. We deal with casting and reflection so YOUR code can be statically typed. And we're fast. + +## Getting Started +From your GOPATH: + +```bash +go get github.com/gocraft/web +``` + +Add a file ```server.go``` - for instance, ```src/myapp/server.go``` + +```go +package main + +import ( + "github.com/gocraft/web" + "fmt" + "net/http" + "strings" +) + +type Context struct { + HelloCount int +} + +func (c *Context) SetHelloCount(rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc) { + c.HelloCount = 3 + next(rw, req) +} + +func (c *Context) SayHello(rw web.ResponseWriter, req *web.Request) { + fmt.Fprint(rw, strings.Repeat("Hello ", c.HelloCount), "World!") +} + +func main() { + router := web.New(Context{}). // Create your router + Middleware(web.LoggerMiddleware). // Use some included middleware + Middleware(web.ShowErrorsMiddleware). // ... + Middleware((*Context).SetHelloCount). // Your own middleware! + Get("/", (*Context).SayHello) // Add a route + http.ListenAndServe("localhost:3000", router) // Start the server! +} +``` + +Run the server. It will be available on ```localhost:3000```: + +```bash +go run src/myapp/server.go +``` + +## Features +* **Super fast and scalable**. Added latency is from 3-9μs per request. Routing performance is O(log(N)) in the number of routes. +* **Your own contexts**. Easily pass information between your middleware and handler with strong static typing. +* **Easy and powerful routing**. Capture path variables. Validate path segments with regexps. Lovely API. +* **Middleware**. Middleware can express almost any web-layer feature. We make it easy. +* **Nested routers, contexts, and middleware**. Your app has an API, and admin area, and a logged out view. Each view needs different contexts and different middleware. We let you express this hierarchy naturally. +* **Embrace Go's net/http package**. Start your server with http.ListenAndServe(), and work directly with http.ResponseWriter and http.Request. +* **Minimal**. The core of gocraft/web is lightweight and minimal. Add optional functionality with our built-in middleware, or write your own middleware. + +## Performance +Performance is a first class concern. Every update to this package has its performance measured and tracked in [BENCHMARK_RESULTS](https://github.com/gocraft/web/blob/master/BENCHMARK_RESULTS). + +For minimal 'hello world' style apps, added latency is about 3μs. This grows to about 10μs for more complex apps (6 middleware functions, 3 levels of contexts, 150+ routes). + + +One key design choice we've made is our choice of routing algorithm. Most competing libraries use simple O(N) iteration over all routes to find a match. This is fine if you have only a handful of routes, but starts to break down as your app gets bigger. We use a tree-based router which grows in complexity at O(log(N)). + +## Application Structure + +### Making your router +The first thing you need to do is make a new router. Routers serve requests and execute middleware. + +```go +router := web.New(YourContext{}) +``` + +### Your context +Wait, what is YourContext{} and why do you need it? It can be any struct you want it to be. Here's an example of one: + +```go +type YourContext struct { + User *User // Assumes you've defined a User type as well +} +``` + +Your context can be empty or it can have various fields in it. The fields can be whatever you want - it's your type! When a new request comes into the router, we'll allocate an instance of this struct and pass it to your middleware and handlers. This allows, for instance, a SetUser middleware to set a User field that can be read in the handlers. + +### Routes and handlers +Once you have your router, you can add routes to it. Standard HTTP verbs are supported. + +```go +router := web.New(YourContext{}) +router.Get("/users", (*YourContext).UsersList) +router.Post("/users", (*YourContext).UsersCreate) +router.Put("/users/:id", (*YourContext).UsersUpdate) +router.Delete("/users/:id", (*YourContext).UsersDelete) +router.Patch("/users/:id", (*YourContext).UsersUpdate) +router.Get("/", (*YourContext).Root) +``` + +What is that funny ```(*YourContext).Root``` notation? It's called a method expression. It lets your handlers look like this: + +```go +func (c *YourContext) Root(rw web.ResponseWriter, req *web.Request) { + if c.User != nil { + fmt.Fprint(rw, "Hello,", c.User.Name) + } else { + fmt.Fprint(rw, "Hello, anonymous person") + } +} +``` + +All method expressions do is return a function that accepts the type as the first argument. So your handler can also look like this: + +```go +func Root(c *YourContext, rw web.ResponseWriter, req *web.Request) {} +``` + +Of course, if you don't need a context for a particular action, you can also do that: + +```go +func Root(rw web.ResponseWriter, req *web.Request) {} +``` + +Note that handlers always need to accept two input parameters: web.ResponseWriter, and *web.Request, both of which wrap the standard http.ResponseWriter and *http.Request, respectively. + +### Middleware +You can add middleware to a router: + +```go +router := web.New(YourContext{}) +router.Middleware((*YourContext).UserRequired) +// add routes, more middleware +``` + +This is what a middleware handler looks like: + +```go +func (c *YourContext) UserRequired(rw web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) { + user := userFromSession(r) // Pretend like this is defined. It reads a session cookie and returns a *User or nil. + if user != nil { + c.User = user + next(rw, r) + } else { + rw.Header().Set("Location", "/") + rw.WriteHeader(http.StatusMovedPermanently) + // do NOT call next() + } +} +``` + +Some things to note about the above example: +* We set fields in the context for future middleware / handlers to use. +* We can call next(), or not. Not calling next() effectively stops the middleware stack. + +Of course, generic middleware without contexts is supported: + +```go +func GenericMiddleware(rw web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) { + // ... +} +``` + +### Nested routers +Nested routers let you run different middleware and use different contexts for different parts of your app. Some common scenarios: +* You want to run an AdminRequired middleware on all your admin routes, but not on API routes. Your context needs a CurrentAdmin field. +* You want to run an OAuth middleware on your API routes. Your context needs an AccessToken field. +* You want to run session handling middleware on ALL your routes. Your context needs a Session field. + +Let's implement that. Your contexts would look like this: + +```go +type Context struct { + Session map[string]string +} + +type AdminContext struct { + *Context + CurrentAdmin *User +} + +type ApiContext struct { + *Context + AccessToken string +} +``` + +Note that we embed a pointer to the parent context in each subcontext. This is required. + +Now that we have our contexts, let's create our routers: + +```go +rootRouter := web.New(Context{}) +rootRouter.Middleware((*Context).LoadSession) + +apiRouter := rootRouter.Subrouter(ApiContext{}, "/api") +apiRouter.Middleware((*ApiContext).OAuth) +apiRouter.Get("/tickets", (*ApiContext).TicketsIndex) + +adminRouter := rootRouter.Subrouter(AdminContext{}, "/admin") +adminRouter.Middleware((*AdminContext).AdminRequired) + +// Given the path namesapce for this router is "/admin", the full path of this route is "/admin/reports" +adminRouter.Get("/reports", (*AdminContext).Reports) +``` + +Note that each time we make a subrouter, we need to supply the context as well as a path namespace. The context CAN be the same as the parent context, and the namespace CAN just be "/" for no namespace. + +### Request lifecycle +The following is a detailed account of the request lifecycle: + +1. A request comes in. Yay! (follow along in ```router_serve.go``` if you'd like) +2. Wrap the default Go http.ResponseWriter and http.Request in a web.ResponseWriter and web.Request, respectively (via structure embedding). +3. Allocate a new root context. This context is passed into your root middleware. +4. Execute middleware on the root router. We do this before we find a route! +5. After all of the root router's middleware is executed, we'll run a 'virtual' routing middleware that determines the target route. + * If the there's no route found, we'll execute the NotFound handler if supplied. Otherwise, we'll write a 404 response and start unwinding the root middlware. +6. Now that we have a target route, we can allocate the context tree of the target router. +7. Start executing middleware on the nested middleware leading up to the final router/route. +8. After all middleware is executed, we'll run another 'virtual' middleware that invokes the final handler corresponding to the target route. +9. Unwind all middleware calls (if there's any code after next() in the middleware, obviously that's going to run at some point). + +### Capturing path params; regexp conditions +You can capture path variables like this: + +```go +router.Get("/suggestions/:suggestion_id/comments/:comment_id") +``` + +In your handler, you can access them like this: + +```go +func (c *YourContext) Root(rw web.ResponseWriter, req *web.Request) { + fmt.Fprint(rw, "Suggestion ID:", req.PathParams["suggestion_id"]) + fmt.Fprint(rw, "Comment ID:", req.PathParams["comment_id"]) +} +``` + +You can also validate the format of your path params with a regexp. For instance, to ensure the 'ids' start with a digit: + +```go +router.Get("/suggestions/:suggestion_id:\\d.*/comments/:comment_id:\\d.*") +``` + +You can match any route past a certain point like this: + +```go +router.Get("/suggestions/:suggestion_id/comments/:comment_id/:*") +``` + +The path params will contain a “*” member with the rest of your path. It is illegal to add any more paths past the “*” path param, as it’s meant to match every path afterwards, in all cases. + +For Example: + /suggestions/123/comments/321/foo/879/bar/834 + +Elicits path params: + * “suggestion_id”: 123, + * “comment_id”: 321, + * “*”: “foo/879/bar/834” + + +One thing you CANNOT currently do is use regexps outside of a path segment. For instance, optional path segments are not supported - you would have to define multiple routes that both point to the same handler. This design decision was made to enable efficient routing. + +### Not Found handlers +If a route isn't found, by default we'll return a 404 status and render the text "Not Found". + +You can supply a custom NotFound handler on your root router: + +```go +router.NotFound((*Context).NotFound) +``` + +Your handler can optionally accept a pointer to the root context. NotFound handlers look like this: + +```go +func (c *Context) NotFound(rw web.ResponseWriter, r *web.Request) { + rw.WriteHeader(http.StatusNotFound) // You probably want to return 404. But you can also redirect or do whatever you want. + fmt.Fprintf(rw, "My Not Found") // Render you own HTML or something! +} +``` + +### OPTIONS handlers +If an [OPTIONS request](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing#Preflight_example) is made and routes with other methods are found for the requested path, then by default we'll return an empty response with an appropriate `Access-Control-Allow-Methods` header. + +You can supply a custom OPTIONS handler on your root router: + +```go +router.OptionsHandler((*Context).OptionsHandler) +``` + +Your handler can optionally accept a pointer to the root context. OPTIONS handlers look like this: + +```go +func (c *Context) OptionsHandler(rw web.ResponseWriter, r *web.Request, methods []string) { + rw.Header().Add("Access-Control-Allow-Methods", strings.Join(methods, ", ")) + rw.Header().Add("Access-Control-Allow-Origin", "*") +} +``` + +### Error handlers +By default, if there's a panic in middleware or a handler, we'll return a 500 status and render the text "Application Error". + +If you use the included middleware ```web.ShowErrorsMiddleware```, a panic will result in a pretty backtrace being rendered in HTML. This is great for development. + +You can also supply a custom Error handler on any router (not just the root router): + +```go +router.Error((*Context).Error) +``` + +Your handler can optionally accept a pointer to its corresponding context. Error handlers look like this: + +```go +func (c *Context) Error(rw web.ResponseWriter, r *web.Request, err interface{}) { + rw.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, "Error", err) +} +``` + +### Included middleware +We ship with three basic pieces of middleware: a logger, an exception printer, and a static file server. To use them: + +```go +router := web.New(Context{}) +router.Middleware(web.LoggerMiddleware). + Middleware(web.ShowErrorsMiddleware) + +// The static middleware serves files. Examples: +// "GET /" will serve an index file at pwd/public/index.html +// "GET /robots.txt" will serve the file at pwd/public/robots.txt +// "GET /images/foo.gif" will serve the file at pwd/public/images/foo.gif +currentRoot, _ := os.Getwd() +router.Middleware(web.StaticMiddleware(path.Join(currentRoot, "public"), web.StaticOption{IndexFile: "index.html"})) +``` + +NOTE: You might not want to use web.ShowErrorsMiddleware in production. You can easily do something like this: +```go +router := web.New(Context{}) +router.Middleware(web.LoggerMiddleware) +if MyEnvironment == "development" { + router.Middleware(web.ShowErrorsMiddleware) +} +// ... +``` + +### Starting your server +Since web.Router implements http.Handler (eg, ServeHTTP(ResponseWriter, *Request)), you can easily plug it in to the standard Go http machinery: + +```go +router := web.New(Context{}) +// ... Add routes and such. +http.ListenAndServe("localhost:8080", router) +``` + +### Rendering responses +So now you routed a request to a handler. You have a web.ResponseWriter (http.ResponseWriter) and web.Request (http.Request). Now what? + +```go +// You can print to the ResponseWriter! +fmt.Fprintf(rw, "I'm a web page!") +``` + +This is currently where the implementation of this library stops. I recommend you read the documentation of [net/http](http://golang.org/pkg/net/http/). + +## Extra Middlware +This package is going to keep the built-in middlware simple and lean. Extra middleware can be found across the web: +* [https://github.com/corneldamian/json-binding](https://github.com/corneldamian/json-binding) - mapping JSON request into a struct and response to json + +If you'd like me to link to your middleware, let me know with a pull request to this README. + +## gocraft + +gocraft offers a toolkit for building web apps. Currently these packages are available: + +* [gocraft/web](https://github.com/gocraft/web) - Go Router + Middleware. Your Contexts. +* [gocraft/dbr](https://github.com/gocraft/dbr) - Additions to Go's database/sql for super fast performance and convenience. +* [gocraft/health](https://github.com/gocraft/health) - Instrument your web apps with logging and metrics. +* [gocraft/work](https://github.com/gocraft/work) - Process background jobs in Go. + +These packages were developed by the [engineering team](https://eng.uservoice.com) at [UserVoice](https://www.uservoice.com) and currently power much of its infrastructure and tech stack. + +## Thanks & Authors +I use code/got inspiration from these excellent libraries: +* [Revel](https://github.com/robfig/revel) - pathtree routing. +* [Traffic](https://github.com/pilu/traffic) - inspiration, show errors middleware. +* [Martini](https://github.com/codegangsta/martini) - static file serving. +* [gorilla/mux](http://www.gorillatoolkit.org/pkg/mux) - inspiration. + +Authors: +* Jonathan Novak -- [https://github.com/cypriss](https://github.com/cypriss) +* Sponsored by [UserVoice](https://eng.uservoice.com) diff --git a/vendor/github.com/gocraft/web/cover.sh b/vendor/github.com/gocraft/web/cover.sh new file mode 100644 index 00000000..8f73173e --- /dev/null +++ b/vendor/github.com/gocraft/web/cover.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +go test -covermode=count -coverprofile=count.out . +go tool cover -html=count.out +go tool cover -func=count.out \ No newline at end of file diff --git a/vendor/github.com/gocraft/web/logger_middleware.go b/vendor/github.com/gocraft/web/logger_middleware.go new file mode 100644 index 00000000..5fa33585 --- /dev/null +++ b/vendor/github.com/gocraft/web/logger_middleware.go @@ -0,0 +1,32 @@ +package web + +import ( + "log" + "os" + "time" +) + +// Logger can be set to your own logger. Logger only applies to the LoggerMiddleware. +var Logger = log.New(os.Stdout, "", 0) + +// LoggerMiddleware is generic middleware that will log requests to Logger (by default, Stdout). +func LoggerMiddleware(rw ResponseWriter, req *Request, next NextMiddlewareFunc) { + startTime := time.Now() + + next(rw, req) + + duration := time.Since(startTime).Nanoseconds() + var durationUnits string + switch { + case duration > 2000000: + durationUnits = "ms" + duration /= 1000000 + case duration > 1000: + durationUnits = "μs" + duration /= 1000 + default: + durationUnits = "ns" + } + + Logger.Printf("[%d %s] %d '%s'\n", duration, durationUnits, rw.StatusCode(), req.URL.Path) +} diff --git a/vendor/github.com/gocraft/web/options_handler.go b/vendor/github.com/gocraft/web/options_handler.go new file mode 100644 index 00000000..6490499c --- /dev/null +++ b/vendor/github.com/gocraft/web/options_handler.go @@ -0,0 +1,18 @@ +package web + +import ( + "net/http" + "reflect" + "strings" +) + +func (r *Router) genericOptionsHandler(ctx reflect.Value, methods []string) func(rw ResponseWriter, req *Request) { + return func(rw ResponseWriter, req *Request) { + if r.optionsHandler.IsValid() { + invoke(r.optionsHandler, ctx, []reflect.Value{reflect.ValueOf(rw), reflect.ValueOf(req), reflect.ValueOf(methods)}) + } else { + rw.Header().Add("Access-Control-Allow-Methods", strings.Join(methods, ", ")) + rw.WriteHeader(http.StatusOK) + } + } +} diff --git a/vendor/github.com/gocraft/web/panic_handler.go b/vendor/github.com/gocraft/web/panic_handler.go new file mode 100644 index 00000000..bb2403ca --- /dev/null +++ b/vendor/github.com/gocraft/web/panic_handler.go @@ -0,0 +1,27 @@ +package web + +import ( + "log" + "os" +) + +// PanicReporter can receive panics that happen when serving a request and report them to a log of some sort. +type PanicReporter interface { + // Panic is called with the URL of the request, the result of calling recover, and the stack. + Panic(url string, err interface{}, stack string) +} + +// PanicHandler will be logged to in panic conditions (eg, division by zero in an app handler). +// Applications can set web.PanicHandler = your own logger, if they wish. +// In terms of logging the requests / responses, see logger_middleware. That is a completely separate system. +var PanicHandler = PanicReporter(logPanicReporter{ + log: log.New(os.Stderr, "ERROR ", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile), +}) + +type logPanicReporter struct { + log *log.Logger +} + +func (l logPanicReporter) Panic(url string, err interface{}, stack string) { + l.log.Printf("PANIC\nURL: %v\nERROR: %v\nSTACK:\n%s\n", url, err, stack) +} diff --git a/vendor/github.com/gocraft/web/request.go b/vendor/github.com/gocraft/web/request.go new file mode 100644 index 00000000..c4366dec --- /dev/null +++ b/vendor/github.com/gocraft/web/request.go @@ -0,0 +1,36 @@ +package web + +import ( + "net/http" + "reflect" +) + +// Request wraps net/http's Request and gocraf/web specific fields. In particular, PathParams is used to access +// captures params in your URL. A Request is sent to handlers on each request. +type Request struct { + *http.Request + + // PathParams exists if you have wildcards in your URL that you need to capture. + // Eg, /users/:id/tickets/:ticket_id and /users/1/tickets/33 would yield the map {id: "3", ticket_id: "33"} + PathParams map[string]string + + // The actual route that got invoked + route *route + + rootContext reflect.Value // Root context. Set immediately. + targetContext reflect.Value // The target context corresponding to the route. Not set until root middleware is done. +} + +// IsRouted can be called from middleware to determine if the request has been routed yet. +func (r *Request) IsRouted() bool { + return r.route != nil +} + +// RoutePath returns the routed path string. Eg, if a route was registered with +// router.Get("/suggestions/:suggestion_id/comments", f), then RoutePath will return "/suggestions/:suggestion_id/comments". +func (r *Request) RoutePath() string { + if r.route != nil { + return r.route.Path + } + return "" +} diff --git a/vendor/github.com/gocraft/web/response_writer.go b/vendor/github.com/gocraft/web/response_writer.go new file mode 100644 index 00000000..819d1bf8 --- /dev/null +++ b/vendor/github.com/gocraft/web/response_writer.go @@ -0,0 +1,76 @@ +package web + +import ( + "bufio" + "fmt" + "net" + "net/http" +) + +// ResponseWriter includes net/http's ResponseWriter and adds a StatusCode() method to obtain the written status code. +// A ResponseWriter is sent to handlers on each request. +type ResponseWriter interface { + http.ResponseWriter + http.Flusher + http.Hijacker + http.CloseNotifier + + // StatusCode returns the written status code, or 0 if none has been written yet. + StatusCode() int + // Written returns whether the header has been written yet. + Written() bool + // Size returns the size in bytes of the body written so far. + Size() int +} + +type appResponseWriter struct { + http.ResponseWriter + statusCode int + size int +} + +// Don't need this yet because we get it for free: +func (w *appResponseWriter) Write(data []byte) (n int, err error) { + if w.statusCode == 0 { + w.statusCode = http.StatusOK + } + size, err := w.ResponseWriter.Write(data) + w.size += size + return size, err +} + +func (w *appResponseWriter) WriteHeader(statusCode int) { + w.statusCode = statusCode + w.ResponseWriter.WriteHeader(statusCode) +} + +func (w *appResponseWriter) StatusCode() int { + return w.statusCode +} + +func (w *appResponseWriter) Written() bool { + return w.statusCode != 0 +} + +func (w *appResponseWriter) Size() int { + return w.size +} + +func (w *appResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hijacker, ok := w.ResponseWriter.(http.Hijacker) + if !ok { + return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") + } + return hijacker.Hijack() +} + +func (w *appResponseWriter) CloseNotify() <-chan bool { + return w.ResponseWriter.(http.CloseNotifier).CloseNotify() +} + +func (w *appResponseWriter) Flush() { + flusher, ok := w.ResponseWriter.(http.Flusher) + if ok { + flusher.Flush() + } +} diff --git a/vendor/github.com/gocraft/web/router_serve.go b/vendor/github.com/gocraft/web/router_serve.go new file mode 100644 index 00000000..4dbdc53d --- /dev/null +++ b/vendor/github.com/gocraft/web/router_serve.go @@ -0,0 +1,297 @@ +package web + +import ( + "fmt" + "net/http" + "reflect" + "runtime" +) + +type middlewareClosure struct { + appResponseWriter + Request + Routers []*Router + Contexts []reflect.Value + currentMiddlewareIndex int + currentRouterIndex int + currentMiddlewareLen int + RootRouter *Router + Next NextMiddlewareFunc +} + +// This is the entry point for servering all requests. +func (rootRouter *Router) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + + // Manually create a closure. These variables are needed in middlewareStack. + // The reason we put these here instead of in the middleware stack, is Go (as of 1.2) + // creates a heap variable for each varaiable in the closure. To minimize that, we'll + // just have one (closure *middlewareClosure). + var closure middlewareClosure + closure.Request.Request = r + closure.appResponseWriter.ResponseWriter = rw + closure.Routers = make([]*Router, 1, rootRouter.maxChildrenDepth) + closure.Routers[0] = rootRouter + closure.Contexts = make([]reflect.Value, 1, rootRouter.maxChildrenDepth) + closure.Contexts[0] = reflect.New(rootRouter.contextType) + closure.currentMiddlewareLen = len(rootRouter.middleware) + closure.RootRouter = rootRouter + closure.Request.rootContext = closure.Contexts[0] + + // Handle errors + defer func() { + if recovered := recover(); recovered != nil { + rootRouter.handlePanic(&closure.appResponseWriter, &closure.Request, recovered) + } + }() + + next := middlewareStack(&closure) + next(&closure.appResponseWriter, &closure.Request) +} + +// This function executes the middleware stack. It does so creating/returning an anonymous function/closure. +// This closure can be called multiple times (eg, next()). Each time it is called, the next middleware is called. +// Each time a middleware is called, this 'next' function is passed into it, which will/might call it again. +// There are two 'virtual' middlewares in this stack: the route choosing middleware, and the action invoking middleware. +// The route choosing middleware is executed after all root middleware. It picks the route. +// The action invoking middleware is executed after all middleware. It executes the final handler. +func middlewareStack(closure *middlewareClosure) NextMiddlewareFunc { + closure.Next = func(rw ResponseWriter, req *Request) { + if closure.currentRouterIndex >= len(closure.Routers) { + return + } + + // Find middleware to invoke. The goal of this block is to set the middleware variable. If it can't be done, it will be nil. + // Side effects of this block: + // - set currentMiddlewareIndex, currentRouterIndex, currentMiddlewareLen + // - calculate route, setting routers/contexts, and fields in req. + var middleware *middlewareHandler + if closure.currentMiddlewareIndex < closure.currentMiddlewareLen { + middleware = closure.Routers[closure.currentRouterIndex].middleware[closure.currentMiddlewareIndex] + } else { + // We ran out of middleware on the current router + if closure.currentRouterIndex == 0 { + // If we're still on the root router, it's time to actually figure out what the route is. + // Do so, and update the various variables. + // We could also 404 at this point: if so, run NotFound handlers and return. + theRoute, wildcardMap := calculateRoute(closure.RootRouter, req) + + if theRoute == nil && httpMethod(req.Method) == httpMethodOptions { + optionsMethod := req.Header.Get("Access-Control-Request-Method") + methods := make([]string, 0, len(httpMethods)) + var lastLeaf *pathLeaf + for _, method := range httpMethods { + if method == httpMethodOptions { + continue + } + tree := closure.RootRouter.root[method] + leaf, wildcards := tree.Match(req.URL.Path) + if leaf != nil { + methods = append(methods, string(method)) + lastLeaf = leaf + if optionsMethod == string(method) { + wildcardMap = wildcards + } + } + } + + if len(methods) > 0 { + handler := &actionHandler{Generic: true, GenericHandler: closure.RootRouter.genericOptionsHandler(closure.Contexts[0], methods)} + theRoute = &route{Method: httpMethodOptions, Path: lastLeaf.route.Path, Router: lastLeaf.route.Router, Handler: handler} + } + } + + if theRoute == nil { + if closure.RootRouter.notFoundHandler.IsValid() { + invoke(closure.RootRouter.notFoundHandler, closure.Contexts[0], []reflect.Value{reflect.ValueOf(rw), reflect.ValueOf(req)}) + } else { + rw.WriteHeader(http.StatusNotFound) + fmt.Fprintf(rw, DefaultNotFoundResponse) + } + return + } + + closure.Routers = routersFor(theRoute, closure.Routers) + closure.Contexts = contextsFor(closure.Contexts, closure.Routers) + + req.targetContext = closure.Contexts[len(closure.Contexts)-1] + req.route = theRoute + req.PathParams = wildcardMap + } + + closure.currentMiddlewareIndex = 0 + closure.currentRouterIndex++ + routersLen := len(closure.Routers) + for closure.currentRouterIndex < routersLen { + closure.currentMiddlewareLen = len(closure.Routers[closure.currentRouterIndex].middleware) + if closure.currentMiddlewareLen > 0 { + break + } + closure.currentRouterIndex++ + } + if closure.currentRouterIndex < routersLen { + middleware = closure.Routers[closure.currentRouterIndex].middleware[closure.currentMiddlewareIndex] + } else { + // We're done! invoke the action + handler := req.route.Handler + if handler.Generic { + handler.GenericHandler(rw, req) + } else { + handler.DynamicHandler.Call([]reflect.Value{closure.Contexts[len(closure.Contexts)-1], reflect.ValueOf(rw), reflect.ValueOf(req)}) + } + } + } + + closure.currentMiddlewareIndex++ + + // Invoke middleware. + if middleware != nil { + middleware.invoke(closure.Contexts[closure.currentRouterIndex], rw, req, closure.Next) + } + } + + return closure.Next +} + +func (mw *middlewareHandler) invoke(ctx reflect.Value, rw ResponseWriter, req *Request, next NextMiddlewareFunc) { + if mw.Generic { + mw.GenericMiddleware(rw, req, next) + } else { + mw.DynamicMiddleware.Call([]reflect.Value{ctx, reflect.ValueOf(rw), reflect.ValueOf(req), reflect.ValueOf(next)}) + } +} + +// Strange performance characteristics: this hurts benchmark scores. +// func (ah *actionHandler) invoke(ctx reflect.Value, rw ResponseWriter, req *Request) { +// if ah.Generic { +// ah.GenericHandler(rw, req) +// } else { +// ah.DynamicHandler.Call([]reflect.Value{ctx, reflect.ValueOf(rw), reflect.ValueOf(req)}) +// } +// } + +func calculateRoute(rootRouter *Router, req *Request) (*route, map[string]string) { + var leaf *pathLeaf + var wildcardMap map[string]string + method := httpMethod(req.Method) + tree, ok := rootRouter.root[method] + if ok { + leaf, wildcardMap = tree.Match(req.URL.Path) + } + + // If no match and this is a HEAD, route on GET. + if leaf == nil && method == httpMethodHead { + tree, ok := rootRouter.root[httpMethodGet] + if ok { + leaf, wildcardMap = tree.Match(req.URL.Path) + } + } + + if leaf == nil { + return nil, nil + } + + return leaf.route, wildcardMap +} + +// given the route (and target router), return [root router, child router, ..., leaf route's router] +// Use the memory in routers to store this information +func routersFor(route *route, routers []*Router) []*Router { + routers = routers[:0] + curRouter := route.Router + for curRouter != nil { + routers = append(routers, curRouter) + curRouter = curRouter.parent + } + + // Reverse the slice + s := 0 + e := len(routers) - 1 + for s < e { + routers[s], routers[e] = routers[e], routers[s] + s++ + e-- + } + + return routers +} + +// contexts is initially filled with a single context for the root +// routers is [root, child, ..., leaf] with at least 1 element +// Returns [ctx for root, ... ctx for leaf] +// NOTE: if two routers have the same contextType, then they'll share the exact same context. +func contextsFor(contexts []reflect.Value, routers []*Router) []reflect.Value { + routersLen := len(routers) + + for i := 1; i < routersLen; i++ { + var ctx reflect.Value + if routers[i].contextType == routers[i-1].contextType { + ctx = contexts[i-1] + } else { + ctx = reflect.New(routers[i].contextType) + // set the first field to the parent + f := reflect.Indirect(ctx).Field(0) + f.Set(contexts[i-1]) + } + contexts = append(contexts, ctx) + } + + return contexts +} + +// If there's a panic in the root middleware (so that we don't have a route/target), then invoke the root handler or default. +// If there's a panic in other middleware, then invoke the target action's function. +// If there's a panic in the action handler, then invoke the target action's function. +func (rootRouter *Router) handlePanic(rw *appResponseWriter, req *Request, err interface{}) { + var targetRouter *Router // This will be set to the router we want to use the errorHandler on. + var context reflect.Value // this is the context of the target router + + if req.route == nil { + targetRouter = rootRouter + context = req.rootContext + } else { + targetRouter = req.route.Router + context = req.targetContext + + for !targetRouter.errorHandler.IsValid() && targetRouter.parent != nil { + targetRouter = targetRouter.parent + + // Need to set context to the next context, UNLESS the context is the same type. + curContextStruct := reflect.Indirect(context) + if targetRouter.contextType != curContextStruct.Type() { + context = curContextStruct.Field(0) + if reflect.Indirect(context).Type() != targetRouter.contextType { + panic("bug: shouldn't get here") + } + } + } + } + + if targetRouter.errorHandler.IsValid() { + invoke(targetRouter.errorHandler, context, []reflect.Value{reflect.ValueOf(rw), reflect.ValueOf(req), reflect.ValueOf(err)}) + } else { + http.Error(rw, DefaultPanicResponse, http.StatusInternalServerError) + } + + const size = 4096 + stack := make([]byte, size) + stack = stack[:runtime.Stack(stack, false)] + + PanicHandler.Panic(fmt.Sprint(req.URL), err, string(stack)) +} + +func invoke(handler reflect.Value, ctx reflect.Value, values []reflect.Value) { + handlerType := handler.Type() + numIn := handlerType.NumIn() + if numIn == len(values) { + handler.Call(values) + } else { + values = append([]reflect.Value{ctx}, values...) + handler.Call(values) + } +} + +// DefaultNotFoundResponse is the default text rendered when no route is found and no NotFound handlers are present. +var DefaultNotFoundResponse = "Not Found" + +// DefaultPanicResponse is the default text rendered when a panic occurs and no Error handlers are present. +var DefaultPanicResponse = "Application Error" diff --git a/vendor/github.com/gocraft/web/router_setup.go b/vendor/github.com/gocraft/web/router_setup.go new file mode 100644 index 00000000..c248dfbe --- /dev/null +++ b/vendor/github.com/gocraft/web/router_setup.go @@ -0,0 +1,401 @@ +package web + +import ( + "reflect" + "strings" +) + +type httpMethod string + +const ( + httpMethodGet = httpMethod("GET") + httpMethodPost = httpMethod("POST") + httpMethodPut = httpMethod("PUT") + httpMethodDelete = httpMethod("DELETE") + httpMethodPatch = httpMethod("PATCH") + httpMethodHead = httpMethod("HEAD") + httpMethodOptions = httpMethod("OPTIONS") +) + +var httpMethods = []httpMethod{httpMethodGet, httpMethodPost, httpMethodPut, httpMethodDelete, httpMethodPatch, httpMethodHead, httpMethodOptions} + +// Router implements net/http's Handler interface and is what you attach middleware, routes/handlers, and subrouters to. +type Router struct { + // Hierarchy: + parent *Router // nil if root router. + children []*Router + maxChildrenDepth int + + // For each request we'll create one of these objects + contextType reflect.Type + + // Eg, "/" or "/admin". Any routes added to this router will be prefixed with this. + pathPrefix string + + // Routeset contents: + middleware []*middlewareHandler + routes []*route + + // The root pathnode is the same for a tree of Routers + root map[httpMethod]*pathNode + + // This can can be set on any router. The target's ErrorHandler will be invoked if it exists + errorHandler reflect.Value + + // This can only be set on the root handler, since by virtue of not finding a route, we don't have a target. + // (That being said, in the future we could investigate namespace matches) + notFoundHandler reflect.Value + + // This can only be set on the root handler, since by virtue of not finding a route, we don't have a target. + optionsHandler reflect.Value +} + +// NextMiddlewareFunc are functions passed into your middleware. To advance the middleware, call the function. +// You should usually pass the existing ResponseWriter and *Request into the next middlware, but you can +// chose to swap them if you want to modify values or capture things written to the ResponseWriter. +type NextMiddlewareFunc func(ResponseWriter, *Request) + +// GenericMiddleware are middleware that doesn't have or need a context. General purpose middleware, such as +// static file serving, has this signature. If your middlware doesn't need a context, you can use this +// signature to get a small performance boost. +type GenericMiddleware func(ResponseWriter, *Request, NextMiddlewareFunc) + +// GenericHandler are handlers that don't have or need a context. If your handler doesn't need a context, +// you can use this signature to get a small performance boost. +type GenericHandler func(ResponseWriter, *Request) + +type route struct { + Router *Router + Method httpMethod + Path string + Handler *actionHandler +} + +type middlewareHandler struct { + Generic bool + DynamicMiddleware reflect.Value + GenericMiddleware GenericMiddleware +} + +type actionHandler struct { + Generic bool + DynamicHandler reflect.Value + GenericHandler GenericHandler +} + +var emptyInterfaceType = reflect.TypeOf((*interface{})(nil)).Elem() + +// New returns a new router with context type ctx. ctx should be a struct instance, +// whose purpose is to communicate type information. On each request, an instance of this +// context type will be automatically allocated and sent to handlers. +func New(ctx interface{}) *Router { + validateContext(ctx, nil) + + r := &Router{} + r.contextType = reflect.TypeOf(ctx) + r.pathPrefix = "/" + r.maxChildrenDepth = 1 + r.root = make(map[httpMethod]*pathNode) + for _, method := range httpMethods { + r.root[method] = newPathNode() + } + return r +} + +// NewWithPrefix returns a new router (see New) but each route will have an implicit prefix. +// For instance, with pathPrefix = "/api/v2", all routes under this router will begin with "/api/v2". +func NewWithPrefix(ctx interface{}, pathPrefix string) *Router { + r := New(ctx) + r.pathPrefix = pathPrefix + + return r +} + +// Subrouter attaches a new subrouter to the specified router and returns it. +// You can use the same context or pass a new one. If you pass a new one, it must +// embed a pointer to the previous context in the first slot. You can also pass +// a pathPrefix that each route will have. If "" is passed, then no path prefix is applied. +func (r *Router) Subrouter(ctx interface{}, pathPrefix string) *Router { + validateContext(ctx, r.contextType) + + // Create new router, link up hierarchy + newRouter := &Router{parent: r} + r.children = append(r.children, newRouter) + + // Increment maxChildrenDepth if this is the first child of the router + if len(r.children) == 1 { + curParent := r + for curParent != nil { + curParent.maxChildrenDepth = curParent.depth() + curParent = curParent.parent + } + } + + newRouter.contextType = reflect.TypeOf(ctx) + newRouter.pathPrefix = appendPath(r.pathPrefix, pathPrefix) + newRouter.root = r.root + + return newRouter +} + +// Middleware adds the specified middleware tot he router and returns the router. +func (r *Router) Middleware(fn interface{}) *Router { + vfn := reflect.ValueOf(fn) + validateMiddleware(vfn, r.contextType) + if vfn.Type().NumIn() == 3 { + r.middleware = append(r.middleware, &middlewareHandler{Generic: true, GenericMiddleware: fn.(func(ResponseWriter, *Request, NextMiddlewareFunc))}) + } else { + r.middleware = append(r.middleware, &middlewareHandler{Generic: false, DynamicMiddleware: vfn}) + } + + return r +} + +// Error sets the specified function as the error handler (when panics happen) and returns the router. +func (r *Router) Error(fn interface{}) *Router { + vfn := reflect.ValueOf(fn) + validateErrorHandler(vfn, r.contextType) + r.errorHandler = vfn + return r +} + +// NotFound sets the specified function as the not-found handler (when no route matches) and returns the router. +// Note that only the root router can have a NotFound handler. +func (r *Router) NotFound(fn interface{}) *Router { + if r.parent != nil { + panic("You can only set a NotFoundHandler on the root router.") + } + vfn := reflect.ValueOf(fn) + validateNotFoundHandler(vfn, r.contextType) + r.notFoundHandler = vfn + return r +} + +// OptionsHandler sets the specified function as the options handler and returns the router. +// Note that only the root router can have a OptionsHandler handler. +func (r *Router) OptionsHandler(fn interface{}) *Router { + if r.parent != nil { + panic("You can only set an OptionsHandler on the root router.") + } + vfn := reflect.ValueOf(fn) + validateOptionsHandler(vfn, r.contextType) + r.optionsHandler = vfn + return r +} + +// Get will add a route to the router that matches on GET requests and the specified path. +func (r *Router) Get(path string, fn interface{}) *Router { + return r.addRoute(httpMethodGet, path, fn) +} + +// Post will add a route to the router that matches on POST requests and the specified path. +func (r *Router) Post(path string, fn interface{}) *Router { + return r.addRoute(httpMethodPost, path, fn) +} + +// Put will add a route to the router that matches on PUT requests and the specified path. +func (r *Router) Put(path string, fn interface{}) *Router { + return r.addRoute(httpMethodPut, path, fn) +} + +// Delete will add a route to the router that matches on DELETE requests and the specified path. +func (r *Router) Delete(path string, fn interface{}) *Router { + return r.addRoute(httpMethodDelete, path, fn) +} + +// Patch will add a route to the router that matches on PATCH requests and the specified path. +func (r *Router) Patch(path string, fn interface{}) *Router { + return r.addRoute(httpMethodPatch, path, fn) +} + +// Head will add a route to the router that matches on HEAD requests and the specified path. +func (r *Router) Head(path string, fn interface{}) *Router { + return r.addRoute(httpMethodHead, path, fn) +} + +// Options will add a route to the router that matches on OPTIONS requests and the specified path. +func (r *Router) Options(path string, fn interface{}) *Router { + return r.addRoute(httpMethodOptions, path, fn) +} + +func (r *Router) addRoute(method httpMethod, path string, fn interface{}) *Router { + vfn := reflect.ValueOf(fn) + validateHandler(vfn, r.contextType) + fullPath := appendPath(r.pathPrefix, path) + route := &route{Method: method, Path: fullPath, Router: r} + if vfn.Type().NumIn() == 2 { + route.Handler = &actionHandler{Generic: true, GenericHandler: fn.(func(ResponseWriter, *Request))} + } else { + route.Handler = &actionHandler{Generic: false, DynamicHandler: vfn} + } + r.routes = append(r.routes, route) + r.root[method].add(fullPath, route) + return r +} + +// Calculates the max child depth of the node. Leaves return 1. For Parent->Child, Parent is 2. +func (r *Router) depth() int { + max := 0 + for _, child := range r.children { + childDepth := child.depth() + if childDepth > max { + max = childDepth + } + } + return max + 1 +} + +// +// Private methods: +// + +// Panics unless validation is correct +func validateContext(ctx interface{}, parentCtxType reflect.Type) { + ctxType := reflect.TypeOf(ctx) + + if ctxType.Kind() != reflect.Struct { + panic("web: Context needs to be a struct type") + } + + if parentCtxType != nil && parentCtxType != ctxType { + if ctxType.NumField() == 0 { + panic("web: Context needs to have first field be a pointer to parent context") + } + + fldType := ctxType.Field(0).Type + + // Ensure fld is a pointer to parentCtxType + if fldType != reflect.PtrTo(parentCtxType) { + panic("web: Context needs to have first field be a pointer to parent context") + } + } +} + +// Panics unless fn is a proper handler wrt ctxType +// eg, func(ctx *ctxType, writer, request) +func validateHandler(vfn reflect.Value, ctxType reflect.Type) { + var req *Request + var resp func() ResponseWriter + if !isValidHandler(vfn, ctxType, reflect.TypeOf(resp).Out(0), reflect.TypeOf(req)) { + panic(instructiveMessage(vfn, "a handler", "handler", "rw web.ResponseWriter, req *web.Request", ctxType)) + } +} + +func validateErrorHandler(vfn reflect.Value, ctxType reflect.Type) { + var req *Request + var resp func() ResponseWriter + if !isValidHandler(vfn, ctxType, reflect.TypeOf(resp).Out(0), reflect.TypeOf(req), emptyInterfaceType) { + panic(instructiveMessage(vfn, "an error handler", "error handler", "rw web.ResponseWriter, req *web.Request, err interface{}", ctxType)) + } +} + +func validateNotFoundHandler(vfn reflect.Value, ctxType reflect.Type) { + var req *Request + var resp func() ResponseWriter + if !isValidHandler(vfn, ctxType, reflect.TypeOf(resp).Out(0), reflect.TypeOf(req)) { + panic(instructiveMessage(vfn, "a 'not found' handler", "not found handler", "rw web.ResponseWriter, req *web.Request", ctxType)) + } +} + +func validateOptionsHandler(vfn reflect.Value, ctxType reflect.Type) { + var req *Request + var resp func() ResponseWriter + var methods []string + if !isValidHandler(vfn, ctxType, reflect.TypeOf(resp).Out(0), reflect.TypeOf(req), reflect.TypeOf(methods)) { + panic(instructiveMessage(vfn, "an 'options' handler", "options handler", "rw web.ResponseWriter, req *web.Request, methods []string", ctxType)) + } +} + +func validateMiddleware(vfn reflect.Value, ctxType reflect.Type) { + var req *Request + var resp func() ResponseWriter + var n NextMiddlewareFunc + if !isValidHandler(vfn, ctxType, reflect.TypeOf(resp).Out(0), reflect.TypeOf(req), reflect.TypeOf(n)) { + panic(instructiveMessage(vfn, "middleware", "middleware", "rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc", ctxType)) + } +} + +// Ensures vfn is a function, that optionally takes a *ctxType as the first argument, followed by the specified types. Handlers have no return value. +// Returns true if valid, false otherwise. +func isValidHandler(vfn reflect.Value, ctxType reflect.Type, types ...reflect.Type) bool { + fnType := vfn.Type() + + if fnType.Kind() != reflect.Func { + return false + } + + typesStartIdx := 0 + typesLen := len(types) + numIn := fnType.NumIn() + numOut := fnType.NumOut() + + if numOut != 0 { + return false + } + + if numIn == typesLen { + // No context + } else if numIn == (typesLen + 1) { + // context, types + firstArgType := fnType.In(0) + if firstArgType != reflect.PtrTo(ctxType) && firstArgType != emptyInterfaceType { + return false + } + typesStartIdx = 1 + } else { + return false + } + + for _, typeArg := range types { + if fnType.In(typesStartIdx) != typeArg { + return false + } + typesStartIdx++ + } + + return true +} + +// Since it's easy to pass the wrong method to a middleware/handler route, and since the user can't rely on static type checking since we use reflection, +// lets be super helpful about what they did and what they need to do. +// Arguments: +// - vfn is the failed method +// - addingType is for "You are adding {addingType} to a router...". Eg, "middleware" or "a handler" or "an error handler" +// - yourType is for "Your {yourType} function can have...". Eg, "middleware" or "handler" or "error handler" +// - args is like "rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc" +// - NOTE: args can be calculated if you pass in each type. BUT, it doesn't have example argument name, so it has less copy/paste value. +func instructiveMessage(vfn reflect.Value, addingType string, yourType string, args string, ctxType reflect.Type) string { + // Get context type without package. + ctxString := ctxType.String() + splitted := strings.Split(ctxString, ".") + if len(splitted) <= 1 { + ctxString = splitted[0] + } else { + ctxString = splitted[1] + } + + str := "\n" + strings.Repeat("*", 120) + "\n" + str += "* You are adding " + addingType + " to a router with context type '" + ctxString + "'\n" + str += "*\n*\n" + str += "* Your " + yourType + " function can have one of these signatures:\n" + str += "*\n" + str += "* // If you don't need context:\n" + str += "* func YourFunctionName(" + args + ")\n" + str += "*\n" + str += "* // If you want your " + yourType + " to accept a context:\n" + str += "* func (c *" + ctxString + ") YourFunctionName(" + args + ") // or,\n" + str += "* func YourFunctionName(c *" + ctxString + ", " + args + ")\n" + str += "*\n" + str += "* Unfortunately, your function has this signature: " + vfn.Type().String() + "\n" + str += "*\n" + str += strings.Repeat("*", 120) + "\n" + + return str +} + +// Both rootPath/childPath are like "/" and "/users" +// Assumption is that both are well-formed paths. +// Returns a path without a trailing "/" unless the overall path is just "/" +func appendPath(rootPath, childPath string) string { + return strings.TrimRight(rootPath, "/") + "/" + strings.TrimLeft(childPath, "/") +} diff --git a/vendor/github.com/gocraft/web/show_errors_middleware.go b/vendor/github.com/gocraft/web/show_errors_middleware.go new file mode 100644 index 00000000..322fc390 --- /dev/null +++ b/vendor/github.com/gocraft/web/show_errors_middleware.go @@ -0,0 +1,188 @@ +package web + +import ( + "bufio" + "html/template" + "net/http" + "os" + "runtime" + "strings" +) + +// ShowErrorsMiddleware will catch panics and render an HTML page with the stack trace. +// This middleware should only be used in development. +func ShowErrorsMiddleware(rw ResponseWriter, req *Request, next NextMiddlewareFunc) { + defer func() { + if err := recover(); err != nil { + const size = 4096 + stack := make([]byte, size) + stack = stack[:runtime.Stack(stack, false)] + + renderPrettyError(rw, req, err, stack) + } + }() + + next(rw, req) +} + +func renderPrettyError(rw ResponseWriter, req *Request, err interface{}, stack []byte) { + _, filePath, line, _ := runtime.Caller(5) + + data := map[string]interface{}{ + "Error": err, + "Stack": string(stack), + "Params": req.URL.Query(), + "Method": req.Method, + "FilePath": filePath, + "Line": line, + "Lines": readErrorFileLines(filePath, line), + } + + rw.Header().Set("Content-Type", "text/html") + rw.WriteHeader(http.StatusInternalServerError) + + tpl := template.Must(template.New("ErrorPage").Parse(panicPageTpl)) + tpl.Execute(rw, data) +} + +func readErrorFileLines(filePath string, errorLine int) map[int]string { + lines := make(map[int]string) + + file, err := os.Open(filePath) + if err != nil { + return lines + } + + defer file.Close() + + reader := bufio.NewReader(file) + currentLine := 0 + for { + line, err := reader.ReadString('\n') + if err != nil || currentLine > errorLine+5 { + break + } + + currentLine++ + + if currentLine >= errorLine-5 { + lines[currentLine] = strings.Replace(line, "\n", "", -1) + } + } + + return lines +} + +const panicPageTpl string = ` + + + Panic + + + + +
+
+

Error

+
+
+ +
+

{{ .Error }}

+
+ +
+

+ In {{ .FilePath }}:{{ .Line }}

+

+ + + + + + +
+
{{ range $lineNumber, $line :=  .Lines }}{{ $lineNumber }}{{ end }}
+
+
{{ range $lineNumber, $line :=  .Lines }}{{ $line }}
{{ end }}
+
+

Stack

+
{{ .Stack }}
+

Request

+

Method: {{ .Method }}

+

Parameters:

+
    + {{ range $key, $value := .Params }} +
  • {{ $key }}: {{ $value }}
  • + {{ end }} +
+
+ + + ` diff --git a/vendor/github.com/gocraft/web/static_middleware.go b/vendor/github.com/gocraft/web/static_middleware.go new file mode 100644 index 00000000..4d793eb8 --- /dev/null +++ b/vendor/github.com/gocraft/web/static_middleware.go @@ -0,0 +1,88 @@ +package web + +import ( + "net/http" + "path/filepath" + "strings" +) + +// StaticOption configures how StaticMiddlewareDir handles url paths and index files for directories. +// If set, Prefix is removed from the start of the url path before attempting to serve a directory or file. +// If set, IndexFile is the index file to serve when the url path maps to a directory. +type StaticOption struct { + Prefix string + IndexFile string +} + +// StaticMiddleware is the same as StaticMiddlewareFromDir, but accepts a +// path string for backwards compatibility. +func StaticMiddleware(path string, option ...StaticOption) func(ResponseWriter, *Request, NextMiddlewareFunc) { + return StaticMiddlewareFromDir(http.Dir(path), option...) +} + +// StaticMiddlewareFromDir returns a middleware that serves static files from the specified http.FileSystem. +// This middleware is great for development because each file is read from disk each time and no +// special caching or cache headers are sent. +// +// If a path is requested which maps to a folder with an index.html folder on your filesystem, +// then that index.html file will be served. +func StaticMiddlewareFromDir(dir http.FileSystem, options ...StaticOption) func(ResponseWriter, *Request, NextMiddlewareFunc) { + var option StaticOption + if len(options) > 0 { + option = options[0] + } + return func(w ResponseWriter, req *Request, next NextMiddlewareFunc) { + if req.Method != "GET" && req.Method != "HEAD" { + next(w, req) + return + } + + file := req.URL.Path + if option.Prefix != "" { + if !strings.HasPrefix(file, option.Prefix) { + next(w, req) + return + } + file = file[len(option.Prefix):] + } + + f, err := dir.Open(file) + if err != nil { + next(w, req) + return + } + defer f.Close() + + fi, err := f.Stat() + if err != nil { + next(w, req) + return + } + + // If the file is a directory, try to serve an index file. + // If no index is available, DO NOT serve the directory to avoid + // Content-Length issues. Simply skip to the next middleware, and return + // a 404 if no route with the same name is handled. + if fi.IsDir() { + if option.IndexFile != "" { + file = filepath.Join(file, option.IndexFile) + f, err = dir.Open(file) + if err != nil { + next(w, req) + return + } + defer f.Close() + + fi, err = f.Stat() + if err != nil || fi.IsDir() { + next(w, req) + return + } + } else { + next(w, req) + return + } + } + http.ServeContent(w, req.Request, file, fi.ModTime(), f) + } +} diff --git a/vendor/github.com/gocraft/web/tree.go b/vendor/github.com/gocraft/web/tree.go new file mode 100644 index 00000000..61cddb2a --- /dev/null +++ b/vendor/github.com/gocraft/web/tree.go @@ -0,0 +1,231 @@ +package web + +import ( + "regexp" + "strings" +) + +type pathNode struct { + + // Given the next segment s, if edges[s] exists, then we'll look there first. + edges map[string]*pathNode + + // If set, failure to match on edges will match on wildcard + wildcard *pathNode + + // If set, and we have nothing left to match, then we match on this node + leaves []*pathLeaf + + // If true, this pathNode has a pathparam that matches the rest of the path + matchesFullPath bool +} + +// pathLeaf represents a leaf path segment that corresponds to a single route. +// For the route /admin/forums/:forum_id:\d.*/suggestions/:suggestion_id:\d.* +// We'd have wildcards = ["forum_id", "suggestion_id"] +// and regexps = [/\d.*/, /\d.*/] +// For the route /admin/forums/:forum_id/suggestions/:suggestion_id:\d.* +// We'd have wildcards = ["forum_id", "suggestion_id"] +// and regexps = [nil, /\d.*/] +// For the route /admin/forums/:forum_id/suggestions/:suggestion_id +// We'd have wildcards = ["forum_id", "suggestion_id"] +// and regexps = nil +type pathLeaf struct { + // names of wildcards that lead to this leaf. eg, ["category_id"] for the wildcard ":category_id" + wildcards []string + + // regexps corresponding to wildcards. If a segment has regexp contraint, its entry will be nil. + // If the route has no regexp contraints on any segments, then regexps will be nil. + regexps []*regexp.Regexp + + // Pointer back to the route + route *route + + // If true, this leaf has a pathparam that matches the rest of the path + matchesFullPath bool +} + +func newPathNode() *pathNode { + return &pathNode{edges: make(map[string]*pathNode)} +} + +func (pn *pathNode) add(path string, route *route) { + pn.addInternal(splitPath(path), route, nil, nil) +} + +func (pn *pathNode) addInternal(segments []string, route *route, wildcards []string, regexps []*regexp.Regexp) { + if len(segments) == 0 { + allNilRegexps := true + for _, r := range regexps { + if r != nil { + allNilRegexps = false + break + } + } + if allNilRegexps { + regexps = nil + } + + matchesFullPath := false + if len(wildcards) > 0 { + matchesFullPath = wildcards[len(wildcards)-1] == "*" + } + + pn.leaves = append(pn.leaves, &pathLeaf{route: route, wildcards: wildcards, regexps: regexps, matchesFullPath: matchesFullPath}) + } else { // len(segments) >= 1 + seg := segments[0] + wc, wcName, wcRegexpStr := isWildcard(seg) + if wc { + + if pn.wildcard == nil { + pn.wildcard = newPathNode() + } + + if !pn.wildcard.matchesFullPath { + pn.wildcard.matchesFullPath = wcName == "*" + } + + pn.wildcard.addInternal(segments[1:], route, append(wildcards, wcName), append(regexps, compileRegexp(wcRegexpStr))) + } else { + subPn, ok := pn.edges[seg] + if !ok { + subPn = newPathNode() + pn.edges[seg] = subPn + } + subPn.addInternal(segments[1:], route, wildcards, regexps) + } + } +} + +func (pn *pathNode) Match(path string) (leaf *pathLeaf, wildcards map[string]string) { + + // Bail on invalid paths. + if len(path) == 0 || path[0] != '/' { + return nil, nil + } + + return pn.match(splitPath(path), nil) +} + +// Segments is like ["admin", "users"] representing "/admin/users" +// wildcardValues are the actual values accumulated when we match on a wildcard. +func (pn *pathNode) match(segments []string, wildcardValues []string) (leaf *pathLeaf, wildcardMap map[string]string) { + // Handle leaf nodes: + if len(segments) == 0 { + for _, leaf := range pn.leaves { + if leaf.match(wildcardValues) { + return leaf, makeWildcardMap(leaf, wildcardValues) + } + } + return nil, nil + } + + var seg string + seg, segments = segments[0], segments[1:] + + subPn, ok := pn.edges[seg] + if ok { + leaf, wildcardMap = subPn.match(segments, wildcardValues) + } + + if leaf == nil && pn.wildcard != nil { + leaf, wildcardMap = pn.wildcard.match(segments, append(wildcardValues, seg)) + } + + if leaf == nil && pn.matchesFullPath { + for _, leaf := range pn.leaves { + if leaf.matchesFullPath && leaf.match(wildcardValues) { + if len(wildcardValues) > 0 { + wcVals := []string{wildcardValues[len(wildcardValues)-1], seg} + for _, s := range segments { + wcVals = append(wcVals, s) + } + wildcardValues[len(wildcardValues)-1] = strings.Join(wcVals, "/") + } + return leaf, makeWildcardMap(leaf, wildcardValues) + } + } + return nil, nil + } + + return leaf, wildcardMap +} + +func (leaf *pathLeaf) match(wildcardValues []string) bool { + if leaf.regexps == nil { + return true + } + + // Invariant: + if len(leaf.regexps) != len(wildcardValues) { + panic("bug: invariant violated") + } + + for i, r := range leaf.regexps { + if r != nil { + if !r.MatchString(wildcardValues[i]) { + return false + } + } + } + return true +} + +// key is a non-empty path segment like "admin" or ":category_id" or ":category_id:\d+" +// Returns true if it's a wildcard, and if it is, also returns it's name / regexp. +// Eg, (true, "category_id", "\d+") +func isWildcard(key string) (bool, string, string) { + if key[0] == ':' { + substrs := strings.SplitN(key[1:], ":", 2) + if len(substrs) == 1 { + return true, substrs[0], "" + } + + return true, substrs[0], substrs[1] + } + + return false, "", "" +} + +// "/" -> [] +// "/admin" -> ["admin"] +// "/admin/" -> ["admin"] +// "/admin/users" -> ["admin", "users"] +func splitPath(key string) []string { + elements := strings.Split(key, "/") + if elements[0] == "" { + elements = elements[1:] + } + if elements[len(elements)-1] == "" { + elements = elements[:len(elements)-1] + } + return elements +} + +func makeWildcardMap(leaf *pathLeaf, wildcards []string) map[string]string { + if leaf == nil { + return nil + } + + leafWildcards := leaf.wildcards + + if len(wildcards) == 0 || (len(leafWildcards) != len(wildcards)) { + return nil + } + + // At this point, we know that wildcards and leaf.wildcards match in length. + assoc := make(map[string]string) + for i, w := range wildcards { + assoc[leafWildcards[i]] = w + } + + return assoc +} + +func compileRegexp(regStr string) *regexp.Regexp { + if regStr == "" { + return nil + } + + return regexp.MustCompile("^" + regStr + "$") +} diff --git a/vendor/github.com/golang/glog/LICENSE b/vendor/github.com/golang/glog/LICENSE new file mode 100644 index 00000000..37ec93a1 --- /dev/null +++ b/vendor/github.com/golang/glog/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/golang/glog/README b/vendor/github.com/golang/glog/README new file mode 100644 index 00000000..387b4eb6 --- /dev/null +++ b/vendor/github.com/golang/glog/README @@ -0,0 +1,44 @@ +glog +==== + +Leveled execution logs for Go. + +This is an efficient pure Go implementation of leveled logs in the +manner of the open source C++ package + https://github.com/google/glog + +By binding methods to booleans it is possible to use the log package +without paying the expense of evaluating the arguments to the log. +Through the -vmodule flag, the package also provides fine-grained +control over logging at the file level. + +The comment from glog.go introduces the ideas: + + Package glog implements logging analogous to the Google-internal + C++ INFO/ERROR/V setup. It provides functions Info, Warning, + Error, Fatal, plus formatting variants such as Infof. It + also provides V-style logging controlled by the -v and + -vmodule=file=2 flags. + + Basic examples: + + glog.Info("Prepare to repel boarders") + + glog.Fatalf("Initialization failed: %s", err) + + See the documentation for the V function for an explanation + of these examples: + + if glog.V(2) { + glog.Info("Starting transaction...") + } + + glog.V(2).Infoln("Processed", nItems, "elements") + + +The repository contains an open source version of the log package +used inside Google. The master copy of the source lives inside +Google, not here. The code in this repo is for export only and is not itself +under development. Feature requests will be ignored. + +Send bug reports to golang-nuts@googlegroups.com. diff --git a/vendor/github.com/golang/glog/glog.go b/vendor/github.com/golang/glog/glog.go new file mode 100644 index 00000000..54bd7afd --- /dev/null +++ b/vendor/github.com/golang/glog/glog.go @@ -0,0 +1,1180 @@ +// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package glog implements logging analogous to the Google-internal C++ INFO/ERROR/V setup. +// It provides functions Info, Warning, Error, Fatal, plus formatting variants such as +// Infof. It also provides V-style logging controlled by the -v and -vmodule=file=2 flags. +// +// Basic examples: +// +// glog.Info("Prepare to repel boarders") +// +// glog.Fatalf("Initialization failed: %s", err) +// +// See the documentation for the V function for an explanation of these examples: +// +// if glog.V(2) { +// glog.Info("Starting transaction...") +// } +// +// glog.V(2).Infoln("Processed", nItems, "elements") +// +// Log output is buffered and written periodically using Flush. Programs +// should call Flush before exiting to guarantee all log output is written. +// +// By default, all log statements write to files in a temporary directory. +// This package provides several flags that modify this behavior. +// As a result, flag.Parse must be called before any logging is done. +// +// -logtostderr=false +// Logs are written to standard error instead of to files. +// -alsologtostderr=false +// Logs are written to standard error as well as to files. +// -stderrthreshold=ERROR +// Log events at or above this severity are logged to standard +// error as well as to files. +// -log_dir="" +// Log files will be written to this directory instead of the +// default temporary directory. +// +// Other flags provide aids to debugging. +// +// -log_backtrace_at="" +// When set to a file and line number holding a logging statement, +// such as +// -log_backtrace_at=gopherflakes.go:234 +// a stack trace will be written to the Info log whenever execution +// hits that statement. (Unlike with -vmodule, the ".go" must be +// present.) +// -v=0 +// Enable V-leveled logging at the specified level. +// -vmodule="" +// The syntax of the argument is a comma-separated list of pattern=N, +// where pattern is a literal file name (minus the ".go" suffix) or +// "glob" pattern and N is a V level. For instance, +// -vmodule=gopher*=3 +// sets the V level to 3 in all Go files whose names begin "gopher". +// +package glog + +import ( + "bufio" + "bytes" + "errors" + "flag" + "fmt" + "io" + stdLog "log" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +// severity identifies the sort of log: info, warning etc. It also implements +// the flag.Value interface. The -stderrthreshold flag is of type severity and +// should be modified only through the flag.Value interface. The values match +// the corresponding constants in C++. +type severity int32 // sync/atomic int32 + +// These constants identify the log levels in order of increasing severity. +// A message written to a high-severity log file is also written to each +// lower-severity log file. +const ( + infoLog severity = iota + warningLog + errorLog + fatalLog + numSeverity = 4 +) + +const severityChar = "IWEF" + +var severityName = []string{ + infoLog: "INFO", + warningLog: "WARNING", + errorLog: "ERROR", + fatalLog: "FATAL", +} + +// get returns the value of the severity. +func (s *severity) get() severity { + return severity(atomic.LoadInt32((*int32)(s))) +} + +// set sets the value of the severity. +func (s *severity) set(val severity) { + atomic.StoreInt32((*int32)(s), int32(val)) +} + +// String is part of the flag.Value interface. +func (s *severity) String() string { + return strconv.FormatInt(int64(*s), 10) +} + +// Get is part of the flag.Value interface. +func (s *severity) Get() interface{} { + return *s +} + +// Set is part of the flag.Value interface. +func (s *severity) Set(value string) error { + var threshold severity + // Is it a known name? + if v, ok := severityByName(value); ok { + threshold = v + } else { + v, err := strconv.Atoi(value) + if err != nil { + return err + } + threshold = severity(v) + } + logging.stderrThreshold.set(threshold) + return nil +} + +func severityByName(s string) (severity, bool) { + s = strings.ToUpper(s) + for i, name := range severityName { + if name == s { + return severity(i), true + } + } + return 0, false +} + +// OutputStats tracks the number of output lines and bytes written. +type OutputStats struct { + lines int64 + bytes int64 +} + +// Lines returns the number of lines written. +func (s *OutputStats) Lines() int64 { + return atomic.LoadInt64(&s.lines) +} + +// Bytes returns the number of bytes written. +func (s *OutputStats) Bytes() int64 { + return atomic.LoadInt64(&s.bytes) +} + +// Stats tracks the number of lines of output and number of bytes +// per severity level. Values must be read with atomic.LoadInt64. +var Stats struct { + Info, Warning, Error OutputStats +} + +var severityStats = [numSeverity]*OutputStats{ + infoLog: &Stats.Info, + warningLog: &Stats.Warning, + errorLog: &Stats.Error, +} + +// Level is exported because it appears in the arguments to V and is +// the type of the v flag, which can be set programmatically. +// It's a distinct type because we want to discriminate it from logType. +// Variables of type level are only changed under logging.mu. +// The -v flag is read only with atomic ops, so the state of the logging +// module is consistent. + +// Level is treated as a sync/atomic int32. + +// Level specifies a level of verbosity for V logs. *Level implements +// flag.Value; the -v flag is of type Level and should be modified +// only through the flag.Value interface. +type Level int32 + +// get returns the value of the Level. +func (l *Level) get() Level { + return Level(atomic.LoadInt32((*int32)(l))) +} + +// set sets the value of the Level. +func (l *Level) set(val Level) { + atomic.StoreInt32((*int32)(l), int32(val)) +} + +// String is part of the flag.Value interface. +func (l *Level) String() string { + return strconv.FormatInt(int64(*l), 10) +} + +// Get is part of the flag.Value interface. +func (l *Level) Get() interface{} { + return *l +} + +// Set is part of the flag.Value interface. +func (l *Level) Set(value string) error { + v, err := strconv.Atoi(value) + if err != nil { + return err + } + logging.mu.Lock() + defer logging.mu.Unlock() + logging.setVState(Level(v), logging.vmodule.filter, false) + return nil +} + +// moduleSpec represents the setting of the -vmodule flag. +type moduleSpec struct { + filter []modulePat +} + +// modulePat contains a filter for the -vmodule flag. +// It holds a verbosity level and a file pattern to match. +type modulePat struct { + pattern string + literal bool // The pattern is a literal string + level Level +} + +// match reports whether the file matches the pattern. It uses a string +// comparison if the pattern contains no metacharacters. +func (m *modulePat) match(file string) bool { + if m.literal { + return file == m.pattern + } + match, _ := filepath.Match(m.pattern, file) + return match +} + +func (m *moduleSpec) String() string { + // Lock because the type is not atomic. TODO: clean this up. + logging.mu.Lock() + defer logging.mu.Unlock() + var b bytes.Buffer + for i, f := range m.filter { + if i > 0 { + b.WriteRune(',') + } + fmt.Fprintf(&b, "%s=%d", f.pattern, f.level) + } + return b.String() +} + +// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the +// struct is not exported. +func (m *moduleSpec) Get() interface{} { + return nil +} + +var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N") + +// Syntax: -vmodule=recordio=2,file=1,gfs*=3 +func (m *moduleSpec) Set(value string) error { + var filter []modulePat + for _, pat := range strings.Split(value, ",") { + if len(pat) == 0 { + // Empty strings such as from a trailing comma can be ignored. + continue + } + patLev := strings.Split(pat, "=") + if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 { + return errVmoduleSyntax + } + pattern := patLev[0] + v, err := strconv.Atoi(patLev[1]) + if err != nil { + return errors.New("syntax error: expect comma-separated list of filename=N") + } + if v < 0 { + return errors.New("negative value for vmodule level") + } + if v == 0 { + continue // Ignore. It's harmless but no point in paying the overhead. + } + // TODO: check syntax of filter? + filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)}) + } + logging.mu.Lock() + defer logging.mu.Unlock() + logging.setVState(logging.verbosity, filter, true) + return nil +} + +// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters +// that require filepath.Match to be called to match the pattern. +func isLiteral(pattern string) bool { + return !strings.ContainsAny(pattern, `\*?[]`) +} + +// traceLocation represents the setting of the -log_backtrace_at flag. +type traceLocation struct { + file string + line int +} + +// isSet reports whether the trace location has been specified. +// logging.mu is held. +func (t *traceLocation) isSet() bool { + return t.line > 0 +} + +// match reports whether the specified file and line matches the trace location. +// The argument file name is the full path, not the basename specified in the flag. +// logging.mu is held. +func (t *traceLocation) match(file string, line int) bool { + if t.line != line { + return false + } + if i := strings.LastIndex(file, "/"); i >= 0 { + file = file[i+1:] + } + return t.file == file +} + +func (t *traceLocation) String() string { + // Lock because the type is not atomic. TODO: clean this up. + logging.mu.Lock() + defer logging.mu.Unlock() + return fmt.Sprintf("%s:%d", t.file, t.line) +} + +// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the +// struct is not exported +func (t *traceLocation) Get() interface{} { + return nil +} + +var errTraceSyntax = errors.New("syntax error: expect file.go:234") + +// Syntax: -log_backtrace_at=gopherflakes.go:234 +// Note that unlike vmodule the file extension is included here. +func (t *traceLocation) Set(value string) error { + if value == "" { + // Unset. + t.line = 0 + t.file = "" + } + fields := strings.Split(value, ":") + if len(fields) != 2 { + return errTraceSyntax + } + file, line := fields[0], fields[1] + if !strings.Contains(file, ".") { + return errTraceSyntax + } + v, err := strconv.Atoi(line) + if err != nil { + return errTraceSyntax + } + if v <= 0 { + return errors.New("negative or zero value for level") + } + logging.mu.Lock() + defer logging.mu.Unlock() + t.line = v + t.file = file + return nil +} + +// flushSyncWriter is the interface satisfied by logging destinations. +type flushSyncWriter interface { + Flush() error + Sync() error + io.Writer +} + +func init() { + flag.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error instead of files") + flag.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files") + flag.Var(&logging.verbosity, "v", "log level for V logs") + flag.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr") + flag.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") + flag.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") + + // Default stderrThreshold is ERROR. + logging.stderrThreshold = errorLog + + logging.setVState(0, nil, false) + go logging.flushDaemon() +} + +// Flush flushes all pending log I/O. +func Flush() { + logging.lockAndFlushAll() +} + +// loggingT collects all the global state of the logging setup. +type loggingT struct { + // Boolean flags. Not handled atomically because the flag.Value interface + // does not let us avoid the =true, and that shorthand is necessary for + // compatibility. TODO: does this matter enough to fix? Seems unlikely. + toStderr bool // The -logtostderr flag. + alsoToStderr bool // The -alsologtostderr flag. + + // Level flag. Handled atomically. + stderrThreshold severity // The -stderrthreshold flag. + + // freeList is a list of byte buffers, maintained under freeListMu. + freeList *buffer + // freeListMu maintains the free list. It is separate from the main mutex + // so buffers can be grabbed and printed to without holding the main lock, + // for better parallelization. + freeListMu sync.Mutex + + // mu protects the remaining elements of this structure and is + // used to synchronize logging. + mu sync.Mutex + // file holds writer for each of the log types. + file [numSeverity]flushSyncWriter + // pcs is used in V to avoid an allocation when computing the caller's PC. + pcs [1]uintptr + // vmap is a cache of the V Level for each V() call site, identified by PC. + // It is wiped whenever the vmodule flag changes state. + vmap map[uintptr]Level + // filterLength stores the length of the vmodule filter chain. If greater + // than zero, it means vmodule is enabled. It may be read safely + // using sync.LoadInt32, but is only modified under mu. + filterLength int32 + // traceLocation is the state of the -log_backtrace_at flag. + traceLocation traceLocation + // These flags are modified only under lock, although verbosity may be fetched + // safely using atomic.LoadInt32. + vmodule moduleSpec // The state of the -vmodule flag. + verbosity Level // V logging level, the value of the -v flag/ +} + +// buffer holds a byte Buffer for reuse. The zero value is ready for use. +type buffer struct { + bytes.Buffer + tmp [64]byte // temporary byte array for creating headers. + next *buffer +} + +var logging loggingT + +// setVState sets a consistent state for V logging. +// l.mu is held. +func (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool) { + // Turn verbosity off so V will not fire while we are in transition. + logging.verbosity.set(0) + // Ditto for filter length. + atomic.StoreInt32(&logging.filterLength, 0) + + // Set the new filters and wipe the pc->Level map if the filter has changed. + if setFilter { + logging.vmodule.filter = filter + logging.vmap = make(map[uintptr]Level) + } + + // Things are consistent now, so enable filtering and verbosity. + // They are enabled in order opposite to that in V. + atomic.StoreInt32(&logging.filterLength, int32(len(filter))) + logging.verbosity.set(verbosity) +} + +// getBuffer returns a new, ready-to-use buffer. +func (l *loggingT) getBuffer() *buffer { + l.freeListMu.Lock() + b := l.freeList + if b != nil { + l.freeList = b.next + } + l.freeListMu.Unlock() + if b == nil { + b = new(buffer) + } else { + b.next = nil + b.Reset() + } + return b +} + +// putBuffer returns a buffer to the free list. +func (l *loggingT) putBuffer(b *buffer) { + if b.Len() >= 256 { + // Let big buffers die a natural death. + return + } + l.freeListMu.Lock() + b.next = l.freeList + l.freeList = b + l.freeListMu.Unlock() +} + +var timeNow = time.Now // Stubbed out for testing. + +/* +header formats a log header as defined by the C++ implementation. +It returns a buffer containing the formatted header and the user's file and line number. +The depth specifies how many stack frames above lives the source line to be identified in the log message. + +Log lines have this form: + Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... +where the fields are defined as follows: + L A single character, representing the log level (eg 'I' for INFO) + mm The month (zero padded; ie May is '05') + dd The day (zero padded) + hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds + threadid The space-padded thread ID as returned by GetTID() + file The file name + line The line number + msg The user-supplied message +*/ +func (l *loggingT) header(s severity, depth int) (*buffer, string, int) { + _, file, line, ok := runtime.Caller(3 + depth) + if !ok { + file = "???" + line = 1 + } else { + slash := strings.LastIndex(file, "/") + if slash >= 0 { + file = file[slash+1:] + } + } + return l.formatHeader(s, file, line), file, line +} + +// formatHeader formats a log header using the provided file name and line number. +func (l *loggingT) formatHeader(s severity, file string, line int) *buffer { + now := timeNow() + if line < 0 { + line = 0 // not a real line number, but acceptable to someDigits + } + if s > fatalLog { + s = infoLog // for safety. + } + buf := l.getBuffer() + + // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand. + // It's worth about 3X. Fprintf is hard. + _, month, day := now.Date() + hour, minute, second := now.Clock() + // Lmmdd hh:mm:ss.uuuuuu threadid file:line] + buf.tmp[0] = severityChar[s] + buf.twoDigits(1, int(month)) + buf.twoDigits(3, day) + buf.tmp[5] = ' ' + buf.twoDigits(6, hour) + buf.tmp[8] = ':' + buf.twoDigits(9, minute) + buf.tmp[11] = ':' + buf.twoDigits(12, second) + buf.tmp[14] = '.' + buf.nDigits(6, 15, now.Nanosecond()/1000, '0') + buf.tmp[21] = ' ' + buf.nDigits(7, 22, pid, ' ') // TODO: should be TID + buf.tmp[29] = ' ' + buf.Write(buf.tmp[:30]) + buf.WriteString(file) + buf.tmp[0] = ':' + n := buf.someDigits(1, line) + buf.tmp[n+1] = ']' + buf.tmp[n+2] = ' ' + buf.Write(buf.tmp[:n+3]) + return buf +} + +// Some custom tiny helper functions to print the log header efficiently. + +const digits = "0123456789" + +// twoDigits formats a zero-prefixed two-digit integer at buf.tmp[i]. +func (buf *buffer) twoDigits(i, d int) { + buf.tmp[i+1] = digits[d%10] + d /= 10 + buf.tmp[i] = digits[d%10] +} + +// nDigits formats an n-digit integer at buf.tmp[i], +// padding with pad on the left. +// It assumes d >= 0. +func (buf *buffer) nDigits(n, i, d int, pad byte) { + j := n - 1 + for ; j >= 0 && d > 0; j-- { + buf.tmp[i+j] = digits[d%10] + d /= 10 + } + for ; j >= 0; j-- { + buf.tmp[i+j] = pad + } +} + +// someDigits formats a zero-prefixed variable-width integer at buf.tmp[i]. +func (buf *buffer) someDigits(i, d int) int { + // Print into the top, then copy down. We know there's space for at least + // a 10-digit number. + j := len(buf.tmp) + for { + j-- + buf.tmp[j] = digits[d%10] + d /= 10 + if d == 0 { + break + } + } + return copy(buf.tmp[i:], buf.tmp[j:]) +} + +func (l *loggingT) println(s severity, args ...interface{}) { + buf, file, line := l.header(s, 0) + fmt.Fprintln(buf, args...) + l.output(s, buf, file, line, false) +} + +func (l *loggingT) print(s severity, args ...interface{}) { + l.printDepth(s, 1, args...) +} + +func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) { + buf, file, line := l.header(s, depth) + fmt.Fprint(buf, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, false) +} + +func (l *loggingT) printf(s severity, format string, args ...interface{}) { + buf, file, line := l.header(s, 0) + fmt.Fprintf(buf, format, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, false) +} + +// printWithFileLine behaves like print but uses the provided file and line number. If +// alsoLogToStderr is true, the log message always appears on standard error; it +// will also appear in the log file unless --logtostderr is set. +func (l *loggingT) printWithFileLine(s severity, file string, line int, alsoToStderr bool, args ...interface{}) { + buf := l.formatHeader(s, file, line) + fmt.Fprint(buf, args...) + if buf.Bytes()[buf.Len()-1] != '\n' { + buf.WriteByte('\n') + } + l.output(s, buf, file, line, alsoToStderr) +} + +// output writes the data to the log files and releases the buffer. +func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoToStderr bool) { + l.mu.Lock() + if l.traceLocation.isSet() { + if l.traceLocation.match(file, line) { + buf.Write(stacks(false)) + } + } + data := buf.Bytes() + if !flag.Parsed() { + os.Stderr.Write([]byte("ERROR: logging before flag.Parse: ")) + os.Stderr.Write(data) + } else if l.toStderr { + os.Stderr.Write(data) + } else { + if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { + os.Stderr.Write(data) + } + if l.file[s] == nil { + if err := l.createFiles(s); err != nil { + os.Stderr.Write(data) // Make sure the message appears somewhere. + l.exit(err) + } + } + switch s { + case fatalLog: + l.file[fatalLog].Write(data) + fallthrough + case errorLog: + l.file[errorLog].Write(data) + fallthrough + case warningLog: + l.file[warningLog].Write(data) + fallthrough + case infoLog: + l.file[infoLog].Write(data) + } + } + if s == fatalLog { + // If we got here via Exit rather than Fatal, print no stacks. + if atomic.LoadUint32(&fatalNoStacks) > 0 { + l.mu.Unlock() + timeoutFlush(10 * time.Second) + os.Exit(1) + } + // Dump all goroutine stacks before exiting. + // First, make sure we see the trace for the current goroutine on standard error. + // If -logtostderr has been specified, the loop below will do that anyway + // as the first stack in the full dump. + if !l.toStderr { + os.Stderr.Write(stacks(false)) + } + // Write the stack trace for all goroutines to the files. + trace := stacks(true) + logExitFunc = func(error) {} // If we get a write error, we'll still exit below. + for log := fatalLog; log >= infoLog; log-- { + if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set. + f.Write(trace) + } + } + l.mu.Unlock() + timeoutFlush(10 * time.Second) + os.Exit(255) // C++ uses -1, which is silly because it's anded with 255 anyway. + } + l.putBuffer(buf) + l.mu.Unlock() + if stats := severityStats[s]; stats != nil { + atomic.AddInt64(&stats.lines, 1) + atomic.AddInt64(&stats.bytes, int64(len(data))) + } +} + +// timeoutFlush calls Flush and returns when it completes or after timeout +// elapses, whichever happens first. This is needed because the hooks invoked +// by Flush may deadlock when glog.Fatal is called from a hook that holds +// a lock. +func timeoutFlush(timeout time.Duration) { + done := make(chan bool, 1) + go func() { + Flush() // calls logging.lockAndFlushAll() + done <- true + }() + select { + case <-done: + case <-time.After(timeout): + fmt.Fprintln(os.Stderr, "glog: Flush took longer than", timeout) + } +} + +// stacks is a wrapper for runtime.Stack that attempts to recover the data for all goroutines. +func stacks(all bool) []byte { + // We don't know how big the traces are, so grow a few times if they don't fit. Start large, though. + n := 10000 + if all { + n = 100000 + } + var trace []byte + for i := 0; i < 5; i++ { + trace = make([]byte, n) + nbytes := runtime.Stack(trace, all) + if nbytes < len(trace) { + return trace[:nbytes] + } + n *= 2 + } + return trace +} + +// logExitFunc provides a simple mechanism to override the default behavior +// of exiting on error. Used in testing and to guarantee we reach a required exit +// for fatal logs. Instead, exit could be a function rather than a method but that +// would make its use clumsier. +var logExitFunc func(error) + +// exit is called if there is trouble creating or writing log files. +// It flushes the logs and exits the program; there's no point in hanging around. +// l.mu is held. +func (l *loggingT) exit(err error) { + fmt.Fprintf(os.Stderr, "log: exiting because of error: %s\n", err) + // If logExitFunc is set, we do that instead of exiting. + if logExitFunc != nil { + logExitFunc(err) + return + } + l.flushAll() + os.Exit(2) +} + +// syncBuffer joins a bufio.Writer to its underlying file, providing access to the +// file's Sync method and providing a wrapper for the Write method that provides log +// file rotation. There are conflicting methods, so the file cannot be embedded. +// l.mu is held for all its methods. +type syncBuffer struct { + logger *loggingT + *bufio.Writer + file *os.File + sev severity + nbytes uint64 // The number of bytes written to this file +} + +func (sb *syncBuffer) Sync() error { + return sb.file.Sync() +} + +func (sb *syncBuffer) Write(p []byte) (n int, err error) { + if sb.nbytes+uint64(len(p)) >= MaxSize { + if err := sb.rotateFile(time.Now()); err != nil { + sb.logger.exit(err) + } + } + n, err = sb.Writer.Write(p) + sb.nbytes += uint64(n) + if err != nil { + sb.logger.exit(err) + } + return +} + +// rotateFile closes the syncBuffer's file and starts a new one. +func (sb *syncBuffer) rotateFile(now time.Time) error { + if sb.file != nil { + sb.Flush() + sb.file.Close() + } + var err error + sb.file, _, err = create(severityName[sb.sev], now) + sb.nbytes = 0 + if err != nil { + return err + } + + sb.Writer = bufio.NewWriterSize(sb.file, bufferSize) + + // Write header. + var buf bytes.Buffer + fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05")) + fmt.Fprintf(&buf, "Running on machine: %s\n", host) + fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH) + fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n") + n, err := sb.file.Write(buf.Bytes()) + sb.nbytes += uint64(n) + return err +} + +// bufferSize sizes the buffer associated with each log file. It's large +// so that log records can accumulate without the logging thread blocking +// on disk I/O. The flushDaemon will block instead. +const bufferSize = 256 * 1024 + +// createFiles creates all the log files for severity from sev down to infoLog. +// l.mu is held. +func (l *loggingT) createFiles(sev severity) error { + now := time.Now() + // Files are created in decreasing severity order, so as soon as we find one + // has already been created, we can stop. + for s := sev; s >= infoLog && l.file[s] == nil; s-- { + sb := &syncBuffer{ + logger: l, + sev: s, + } + if err := sb.rotateFile(now); err != nil { + return err + } + l.file[s] = sb + } + return nil +} + +const flushInterval = 30 * time.Second + +// flushDaemon periodically flushes the log file buffers. +func (l *loggingT) flushDaemon() { + for _ = range time.NewTicker(flushInterval).C { + l.lockAndFlushAll() + } +} + +// lockAndFlushAll is like flushAll but locks l.mu first. +func (l *loggingT) lockAndFlushAll() { + l.mu.Lock() + l.flushAll() + l.mu.Unlock() +} + +// flushAll flushes all the logs and attempts to "sync" their data to disk. +// l.mu is held. +func (l *loggingT) flushAll() { + // Flush from fatal down, in case there's trouble flushing. + for s := fatalLog; s >= infoLog; s-- { + file := l.file[s] + if file != nil { + file.Flush() // ignore error + file.Sync() // ignore error + } + } +} + +// CopyStandardLogTo arranges for messages written to the Go "log" package's +// default logs to also appear in the Google logs for the named and lower +// severities. Subsequent changes to the standard log's default output location +// or format may break this behavior. +// +// Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not +// recognized, CopyStandardLogTo panics. +func CopyStandardLogTo(name string) { + sev, ok := severityByName(name) + if !ok { + panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name)) + } + // Set a log format that captures the user's file and line: + // d.go:23: message + stdLog.SetFlags(stdLog.Lshortfile) + stdLog.SetOutput(logBridge(sev)) +} + +// logBridge provides the Write method that enables CopyStandardLogTo to connect +// Go's standard logs to the logs provided by this package. +type logBridge severity + +// Write parses the standard logging line and passes its components to the +// logger for severity(lb). +func (lb logBridge) Write(b []byte) (n int, err error) { + var ( + file = "???" + line = 1 + text string + ) + // Split "d.go:23: message" into "d.go", "23", and "message". + if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 { + text = fmt.Sprintf("bad log format: %s", b) + } else { + file = string(parts[0]) + text = string(parts[2][1:]) // skip leading space + line, err = strconv.Atoi(string(parts[1])) + if err != nil { + text = fmt.Sprintf("bad line number: %s", b) + line = 1 + } + } + // printWithFileLine with alsoToStderr=true, so standard log messages + // always appear on standard error. + logging.printWithFileLine(severity(lb), file, line, true, text) + return len(b), nil +} + +// setV computes and remembers the V level for a given PC +// when vmodule is enabled. +// File pattern matching takes the basename of the file, stripped +// of its .go suffix, and uses filepath.Match, which is a little more +// general than the *? matching used in C++. +// l.mu is held. +func (l *loggingT) setV(pc uintptr) Level { + fn := runtime.FuncForPC(pc) + file, _ := fn.FileLine(pc) + // The file is something like /a/b/c/d.go. We want just the d. + if strings.HasSuffix(file, ".go") { + file = file[:len(file)-3] + } + if slash := strings.LastIndex(file, "/"); slash >= 0 { + file = file[slash+1:] + } + for _, filter := range l.vmodule.filter { + if filter.match(file) { + l.vmap[pc] = filter.level + return filter.level + } + } + l.vmap[pc] = 0 + return 0 +} + +// Verbose is a boolean type that implements Infof (like Printf) etc. +// See the documentation of V for more information. +type Verbose bool + +// V reports whether verbosity at the call site is at least the requested level. +// The returned value is a boolean of type Verbose, which implements Info, Infoln +// and Infof. These methods will write to the Info log if called. +// Thus, one may write either +// if glog.V(2) { glog.Info("log this") } +// or +// glog.V(2).Info("log this") +// The second form is shorter but the first is cheaper if logging is off because it does +// not evaluate its arguments. +// +// Whether an individual call to V generates a log record depends on the setting of +// the -v and --vmodule flags; both are off by default. If the level in the call to +// V is at least the value of -v, or of -vmodule for the source file containing the +// call, the V call will log. +func V(level Level) Verbose { + // This function tries hard to be cheap unless there's work to do. + // The fast path is two atomic loads and compares. + + // Here is a cheap but safe test to see if V logging is enabled globally. + if logging.verbosity.get() >= level { + return Verbose(true) + } + + // It's off globally but it vmodule may still be set. + // Here is another cheap but safe test to see if vmodule is enabled. + if atomic.LoadInt32(&logging.filterLength) > 0 { + // Now we need a proper lock to use the logging structure. The pcs field + // is shared so we must lock before accessing it. This is fairly expensive, + // but if V logging is enabled we're slow anyway. + logging.mu.Lock() + defer logging.mu.Unlock() + if runtime.Callers(2, logging.pcs[:]) == 0 { + return Verbose(false) + } + v, ok := logging.vmap[logging.pcs[0]] + if !ok { + v = logging.setV(logging.pcs[0]) + } + return Verbose(v >= level) + } + return Verbose(false) +} + +// Info is equivalent to the global Info function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Info(args ...interface{}) { + if v { + logging.print(infoLog, args...) + } +} + +// Infoln is equivalent to the global Infoln function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Infoln(args ...interface{}) { + if v { + logging.println(infoLog, args...) + } +} + +// Infof is equivalent to the global Infof function, guarded by the value of v. +// See the documentation of V for usage. +func (v Verbose) Infof(format string, args ...interface{}) { + if v { + logging.printf(infoLog, format, args...) + } +} + +// Info logs to the INFO log. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Info(args ...interface{}) { + logging.print(infoLog, args...) +} + +// InfoDepth acts as Info but uses depth to determine which call frame to log. +// InfoDepth(0, "msg") is the same as Info("msg"). +func InfoDepth(depth int, args ...interface{}) { + logging.printDepth(infoLog, depth, args...) +} + +// Infoln logs to the INFO log. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Infoln(args ...interface{}) { + logging.println(infoLog, args...) +} + +// Infof logs to the INFO log. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Infof(format string, args ...interface{}) { + logging.printf(infoLog, format, args...) +} + +// Warning logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Warning(args ...interface{}) { + logging.print(warningLog, args...) +} + +// WarningDepth acts as Warning but uses depth to determine which call frame to log. +// WarningDepth(0, "msg") is the same as Warning("msg"). +func WarningDepth(depth int, args ...interface{}) { + logging.printDepth(warningLog, depth, args...) +} + +// Warningln logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Warningln(args ...interface{}) { + logging.println(warningLog, args...) +} + +// Warningf logs to the WARNING and INFO logs. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Warningf(format string, args ...interface{}) { + logging.printf(warningLog, format, args...) +} + +// Error logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Error(args ...interface{}) { + logging.print(errorLog, args...) +} + +// ErrorDepth acts as Error but uses depth to determine which call frame to log. +// ErrorDepth(0, "msg") is the same as Error("msg"). +func ErrorDepth(depth int, args ...interface{}) { + logging.printDepth(errorLog, depth, args...) +} + +// Errorln logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Errorln(args ...interface{}) { + logging.println(errorLog, args...) +} + +// Errorf logs to the ERROR, WARNING, and INFO logs. +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Errorf(format string, args ...interface{}) { + logging.printf(errorLog, format, args...) +} + +// Fatal logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Fatal(args ...interface{}) { + logging.print(fatalLog, args...) +} + +// FatalDepth acts as Fatal but uses depth to determine which call frame to log. +// FatalDepth(0, "msg") is the same as Fatal("msg"). +func FatalDepth(depth int, args ...interface{}) { + logging.printDepth(fatalLog, depth, args...) +} + +// Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +func Fatalln(args ...interface{}) { + logging.println(fatalLog, args...) +} + +// Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs, +// including a stack trace of all running goroutines, then calls os.Exit(255). +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Fatalf(format string, args ...interface{}) { + logging.printf(fatalLog, format, args...) +} + +// fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks. +// It allows Exit and relatives to use the Fatal logs. +var fatalNoStacks uint32 + +// Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +// Arguments are handled in the manner of fmt.Print; a newline is appended if missing. +func Exit(args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.print(fatalLog, args...) +} + +// ExitDepth acts as Exit but uses depth to determine which call frame to log. +// ExitDepth(0, "msg") is the same as Exit("msg"). +func ExitDepth(depth int, args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.printDepth(fatalLog, depth, args...) +} + +// Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +func Exitln(args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.println(fatalLog, args...) +} + +// Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls os.Exit(1). +// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. +func Exitf(format string, args ...interface{}) { + atomic.StoreUint32(&fatalNoStacks, 1) + logging.printf(fatalLog, format, args...) +} diff --git a/vendor/github.com/golang/glog/glog_file.go b/vendor/github.com/golang/glog/glog_file.go new file mode 100644 index 00000000..65075d28 --- /dev/null +++ b/vendor/github.com/golang/glog/glog_file.go @@ -0,0 +1,124 @@ +// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ +// +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// File I/O for logs. + +package glog + +import ( + "errors" + "flag" + "fmt" + "os" + "os/user" + "path/filepath" + "strings" + "sync" + "time" +) + +// MaxSize is the maximum size of a log file in bytes. +var MaxSize uint64 = 1024 * 1024 * 1800 + +// logDirs lists the candidate directories for new log files. +var logDirs []string + +// If non-empty, overrides the choice of directory in which to write logs. +// See createLogDirs for the full list of possible destinations. +var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory") + +func createLogDirs() { + if *logDir != "" { + logDirs = append(logDirs, *logDir) + } + logDirs = append(logDirs, os.TempDir()) +} + +var ( + pid = os.Getpid() + program = filepath.Base(os.Args[0]) + host = "unknownhost" + userName = "unknownuser" +) + +func init() { + h, err := os.Hostname() + if err == nil { + host = shortHostname(h) + } + + current, err := user.Current() + if err == nil { + userName = current.Username + } + + // Sanitize userName since it may contain filepath separators on Windows. + userName = strings.Replace(userName, `\`, "_", -1) +} + +// shortHostname returns its argument, truncating at the first period. +// For instance, given "www.google.com" it returns "www". +func shortHostname(hostname string) string { + if i := strings.Index(hostname, "."); i >= 0 { + return hostname[:i] + } + return hostname +} + +// logName returns a new log file name containing tag, with start time t, and +// the name for the symlink for tag. +func logName(tag string, t time.Time) (name, link string) { + name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d", + program, + host, + userName, + tag, + t.Year(), + t.Month(), + t.Day(), + t.Hour(), + t.Minute(), + t.Second(), + pid) + return name, program + "." + tag +} + +var onceLogDirs sync.Once + +// create creates a new log file and returns the file and its filename, which +// contains tag ("INFO", "FATAL", etc.) and t. If the file is created +// successfully, create also attempts to update the symlink for that tag, ignoring +// errors. +func create(tag string, t time.Time) (f *os.File, filename string, err error) { + onceLogDirs.Do(createLogDirs) + if len(logDirs) == 0 { + return nil, "", errors.New("log: no log dirs") + } + name, link := logName(tag, t) + var lastErr error + for _, dir := range logDirs { + fname := filepath.Join(dir, name) + f, err := os.Create(fname) + if err == nil { + symlink := filepath.Join(dir, link) + os.Remove(symlink) // ignore err + os.Symlink(name, symlink) // ignore err + return f, fname, nil + } + lastErr = err + } + return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr) +} diff --git a/vendor/github.com/gomodule/redigo/LICENSE b/vendor/github.com/gomodule/redigo/LICENSE new file mode 100644 index 00000000..67db8588 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/vendor/github.com/gomodule/redigo/internal/commandinfo.go b/vendor/github.com/gomodule/redigo/internal/commandinfo.go new file mode 100644 index 00000000..b763efbd --- /dev/null +++ b/vendor/github.com/gomodule/redigo/internal/commandinfo.go @@ -0,0 +1,54 @@ +// Copyright 2014 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package internal // import "github.com/gomodule/redigo/internal" + +import ( + "strings" +) + +const ( + WatchState = 1 << iota + MultiState + SubscribeState + MonitorState +) + +type CommandInfo struct { + Set, Clear int +} + +var commandInfos = map[string]CommandInfo{ + "WATCH": {Set: WatchState}, + "UNWATCH": {Clear: WatchState}, + "MULTI": {Set: MultiState}, + "EXEC": {Clear: WatchState | MultiState}, + "DISCARD": {Clear: WatchState | MultiState}, + "PSUBSCRIBE": {Set: SubscribeState}, + "SUBSCRIBE": {Set: SubscribeState}, + "MONITOR": {Set: MonitorState}, +} + +func init() { + for n, ci := range commandInfos { + commandInfos[strings.ToLower(n)] = ci + } +} + +func LookupCommandInfo(commandName string) CommandInfo { + if ci, ok := commandInfos[commandName]; ok { + return ci + } + return commandInfos[strings.ToUpper(commandName)] +} diff --git a/vendor/github.com/gomodule/redigo/redis/conn.go b/vendor/github.com/gomodule/redigo/redis/conn.go new file mode 100644 index 00000000..5aa0f32f --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/conn.go @@ -0,0 +1,673 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bufio" + "bytes" + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "net/url" + "regexp" + "strconv" + "sync" + "time" +) + +var ( + _ ConnWithTimeout = (*conn)(nil) +) + +// conn is the low-level implementation of Conn +type conn struct { + // Shared + mu sync.Mutex + pending int + err error + conn net.Conn + + // Read + readTimeout time.Duration + br *bufio.Reader + + // Write + writeTimeout time.Duration + bw *bufio.Writer + + // Scratch space for formatting argument length. + // '*' or '$', length, "\r\n" + lenScratch [32]byte + + // Scratch space for formatting integers and floats. + numScratch [40]byte +} + +// DialTimeout acts like Dial but takes timeouts for establishing the +// connection to the server, writing a command and reading a reply. +// +// Deprecated: Use Dial with options instead. +func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { + return Dial(network, address, + DialConnectTimeout(connectTimeout), + DialReadTimeout(readTimeout), + DialWriteTimeout(writeTimeout)) +} + +// DialOption specifies an option for dialing a Redis server. +type DialOption struct { + f func(*dialOptions) +} + +type dialOptions struct { + readTimeout time.Duration + writeTimeout time.Duration + dialer *net.Dialer + dial func(network, addr string) (net.Conn, error) + db int + password string + useTLS bool + skipVerify bool + tlsConfig *tls.Config +} + +// DialReadTimeout specifies the timeout for reading a single command reply. +func DialReadTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.readTimeout = d + }} +} + +// DialWriteTimeout specifies the timeout for writing a single command. +func DialWriteTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.writeTimeout = d + }} +} + +// DialConnectTimeout specifies the timeout for connecting to the Redis server when +// no DialNetDial option is specified. +func DialConnectTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.dialer.Timeout = d + }} +} + +// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server +// when no DialNetDial option is specified. +// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then +// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected. +func DialKeepAlive(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.dialer.KeepAlive = d + }} +} + +// DialNetDial specifies a custom dial function for creating TCP +// connections, otherwise a net.Dialer customized via the other options is used. +// DialNetDial overrides DialConnectTimeout and DialKeepAlive. +func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { + return DialOption{func(do *dialOptions) { + do.dial = dial + }} +} + +// DialDatabase specifies the database to select when dialing a connection. +func DialDatabase(db int) DialOption { + return DialOption{func(do *dialOptions) { + do.db = db + }} +} + +// DialPassword specifies the password to use when connecting to +// the Redis server. +func DialPassword(password string) DialOption { + return DialOption{func(do *dialOptions) { + do.password = password + }} +} + +// DialTLSConfig specifies the config to use when a TLS connection is dialed. +// Has no effect when not dialing a TLS connection. +func DialTLSConfig(c *tls.Config) DialOption { + return DialOption{func(do *dialOptions) { + do.tlsConfig = c + }} +} + +// DialTLSSkipVerify disables server name verification when connecting over +// TLS. Has no effect when not dialing a TLS connection. +func DialTLSSkipVerify(skip bool) DialOption { + return DialOption{func(do *dialOptions) { + do.skipVerify = skip + }} +} + +// DialUseTLS specifies whether TLS should be used when connecting to the +// server. This option is ignore by DialURL. +func DialUseTLS(useTLS bool) DialOption { + return DialOption{func(do *dialOptions) { + do.useTLS = useTLS + }} +} + +// Dial connects to the Redis server at the given network and +// address using the specified options. +func Dial(network, address string, options ...DialOption) (Conn, error) { + do := dialOptions{ + dialer: &net.Dialer{ + KeepAlive: time.Minute * 5, + }, + } + for _, option := range options { + option.f(&do) + } + if do.dial == nil { + do.dial = do.dialer.Dial + } + + netConn, err := do.dial(network, address) + if err != nil { + return nil, err + } + + if do.useTLS { + var tlsConfig *tls.Config + if do.tlsConfig == nil { + tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify} + } else { + tlsConfig = cloneTLSConfig(do.tlsConfig) + } + if tlsConfig.ServerName == "" { + host, _, err := net.SplitHostPort(address) + if err != nil { + netConn.Close() + return nil, err + } + tlsConfig.ServerName = host + } + + tlsConn := tls.Client(netConn, tlsConfig) + if err := tlsConn.Handshake(); err != nil { + netConn.Close() + return nil, err + } + netConn = tlsConn + } + + c := &conn{ + conn: netConn, + bw: bufio.NewWriter(netConn), + br: bufio.NewReader(netConn), + readTimeout: do.readTimeout, + writeTimeout: do.writeTimeout, + } + + if do.password != "" { + if _, err := c.Do("AUTH", do.password); err != nil { + netConn.Close() + return nil, err + } + } + + if do.db != 0 { + if _, err := c.Do("SELECT", do.db); err != nil { + netConn.Close() + return nil, err + } + } + + return c, nil +} + +var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) + +// DialURL connects to a Redis server at the given URL using the Redis +// URI scheme. URLs should follow the draft IANA specification for the +// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). +func DialURL(rawurl string, options ...DialOption) (Conn, error) { + u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + + if u.Scheme != "redis" && u.Scheme != "rediss" { + return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) + } + + // As per the IANA draft spec, the host defaults to localhost and + // the port defaults to 6379. + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + // assume port is missing + host = u.Host + port = "6379" + } + if host == "" { + host = "localhost" + } + address := net.JoinHostPort(host, port) + + if u.User != nil { + password, isSet := u.User.Password() + if isSet { + options = append(options, DialPassword(password)) + } + } + + match := pathDBRegexp.FindStringSubmatch(u.Path) + if len(match) == 2 { + db := 0 + if len(match[1]) > 0 { + db, err = strconv.Atoi(match[1]) + if err != nil { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + } + if db != 0 { + options = append(options, DialDatabase(db)) + } + } else if u.Path != "" { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + + options = append(options, DialUseTLS(u.Scheme == "rediss")) + + return Dial("tcp", address, options...) +} + +// NewConn returns a new Redigo connection for the given net connection. +func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { + return &conn{ + conn: netConn, + bw: bufio.NewWriter(netConn), + br: bufio.NewReader(netConn), + readTimeout: readTimeout, + writeTimeout: writeTimeout, + } +} + +func (c *conn) Close() error { + c.mu.Lock() + err := c.err + if c.err == nil { + c.err = errors.New("redigo: closed") + err = c.conn.Close() + } + c.mu.Unlock() + return err +} + +func (c *conn) fatal(err error) error { + c.mu.Lock() + if c.err == nil { + c.err = err + // Close connection to force errors on subsequent calls and to unblock + // other reader or writer. + c.conn.Close() + } + c.mu.Unlock() + return err +} + +func (c *conn) Err() error { + c.mu.Lock() + err := c.err + c.mu.Unlock() + return err +} + +func (c *conn) writeLen(prefix byte, n int) error { + c.lenScratch[len(c.lenScratch)-1] = '\n' + c.lenScratch[len(c.lenScratch)-2] = '\r' + i := len(c.lenScratch) - 3 + for { + c.lenScratch[i] = byte('0' + n%10) + i -= 1 + n = n / 10 + if n == 0 { + break + } + } + c.lenScratch[i] = prefix + _, err := c.bw.Write(c.lenScratch[i:]) + return err +} + +func (c *conn) writeString(s string) error { + c.writeLen('$', len(s)) + c.bw.WriteString(s) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *conn) writeBytes(p []byte) error { + c.writeLen('$', len(p)) + c.bw.Write(p) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *conn) writeInt64(n int64) error { + return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) +} + +func (c *conn) writeFloat64(n float64) error { + return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) +} + +func (c *conn) writeCommand(cmd string, args []interface{}) error { + c.writeLen('*', 1+len(args)) + if err := c.writeString(cmd); err != nil { + return err + } + for _, arg := range args { + if err := c.writeArg(arg, true); err != nil { + return err + } + } + return nil +} + +func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) { + switch arg := arg.(type) { + case string: + return c.writeString(arg) + case []byte: + return c.writeBytes(arg) + case int: + return c.writeInt64(int64(arg)) + case int64: + return c.writeInt64(arg) + case float64: + return c.writeFloat64(arg) + case bool: + if arg { + return c.writeString("1") + } else { + return c.writeString("0") + } + case nil: + return c.writeString("") + case Argument: + if argumentTypeOK { + return c.writeArg(arg.RedisArg(), false) + } + // See comment in default clause below. + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + return c.writeBytes(buf.Bytes()) + default: + // This default clause is intended to handle builtin numeric types. + // The function should return an error for other types, but this is not + // done for compatibility with previous versions of the package. + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + return c.writeBytes(buf.Bytes()) + } +} + +type protocolError string + +func (pe protocolError) Error() string { + return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) +} + +func (c *conn) readLine() ([]byte, error) { + p, err := c.br.ReadSlice('\n') + if err == bufio.ErrBufferFull { + return nil, protocolError("long response line") + } + if err != nil { + return nil, err + } + i := len(p) - 2 + if i < 0 || p[i] != '\r' { + return nil, protocolError("bad response line terminator") + } + return p[:i], nil +} + +// parseLen parses bulk string and array lengths. +func parseLen(p []byte) (int, error) { + if len(p) == 0 { + return -1, protocolError("malformed length") + } + + if p[0] == '-' && len(p) == 2 && p[1] == '1' { + // handle $-1 and $-1 null replies. + return -1, nil + } + + var n int + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return -1, protocolError("illegal bytes in length") + } + n += int(b - '0') + } + + return n, nil +} + +// parseInt parses an integer reply. +func parseInt(p []byte) (interface{}, error) { + if len(p) == 0 { + return 0, protocolError("malformed integer") + } + + var negate bool + if p[0] == '-' { + negate = true + p = p[1:] + if len(p) == 0 { + return 0, protocolError("malformed integer") + } + } + + var n int64 + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return 0, protocolError("illegal bytes in length") + } + n += int64(b - '0') + } + + if negate { + n = -n + } + return n, nil +} + +var ( + okReply interface{} = "OK" + pongReply interface{} = "PONG" +) + +func (c *conn) readReply() (interface{}, error) { + line, err := c.readLine() + if err != nil { + return nil, err + } + if len(line) == 0 { + return nil, protocolError("short response line") + } + switch line[0] { + case '+': + switch { + case len(line) == 3 && line[1] == 'O' && line[2] == 'K': + // Avoid allocation for frequent "+OK" response. + return okReply, nil + case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': + // Avoid allocation in PING command benchmarks :) + return pongReply, nil + default: + return string(line[1:]), nil + } + case '-': + return Error(string(line[1:])), nil + case ':': + return parseInt(line[1:]) + case '$': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + p := make([]byte, n) + _, err = io.ReadFull(c.br, p) + if err != nil { + return nil, err + } + if line, err := c.readLine(); err != nil { + return nil, err + } else if len(line) != 0 { + return nil, protocolError("bad bulk string format") + } + return p, nil + case '*': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + r := make([]interface{}, n) + for i := range r { + r[i], err = c.readReply() + if err != nil { + return nil, err + } + } + return r, nil + } + return nil, protocolError("unexpected response line") +} + +func (c *conn) Send(cmd string, args ...interface{}) error { + c.mu.Lock() + c.pending += 1 + c.mu.Unlock() + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + if err := c.writeCommand(cmd, args); err != nil { + return c.fatal(err) + } + return nil +} + +func (c *conn) Flush() error { + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + if err := c.bw.Flush(); err != nil { + return c.fatal(err) + } + return nil +} + +func (c *conn) Receive() (interface{}, error) { + return c.ReceiveWithTimeout(c.readTimeout) +} + +func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { + var deadline time.Time + if timeout != 0 { + deadline = time.Now().Add(timeout) + } + c.conn.SetReadDeadline(deadline) + + if reply, err = c.readReply(); err != nil { + return nil, c.fatal(err) + } + // When using pub/sub, the number of receives can be greater than the + // number of sends. To enable normal use of the connection after + // unsubscribing from all channels, we do not decrement pending to a + // negative value. + // + // The pending field is decremented after the reply is read to handle the + // case where Receive is called before Send. + c.mu.Lock() + if c.pending > 0 { + c.pending -= 1 + } + c.mu.Unlock() + if err, ok := reply.(Error); ok { + return nil, err + } + return +} + +func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { + return c.DoWithTimeout(c.readTimeout, cmd, args...) +} + +func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { + c.mu.Lock() + pending := c.pending + c.pending = 0 + c.mu.Unlock() + + if cmd == "" && pending == 0 { + return nil, nil + } + + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + + if cmd != "" { + if err := c.writeCommand(cmd, args); err != nil { + return nil, c.fatal(err) + } + } + + if err := c.bw.Flush(); err != nil { + return nil, c.fatal(err) + } + + var deadline time.Time + if readTimeout != 0 { + deadline = time.Now().Add(readTimeout) + } + c.conn.SetReadDeadline(deadline) + + if cmd == "" { + reply := make([]interface{}, pending) + for i := range reply { + r, e := c.readReply() + if e != nil { + return nil, c.fatal(e) + } + reply[i] = r + } + return reply, nil + } + + var err error + var reply interface{} + for i := 0; i <= pending; i++ { + var e error + if reply, e = c.readReply(); e != nil { + return nil, c.fatal(e) + } + if e, ok := reply.(Error); ok && err == nil { + err = e + } + } + return reply, err +} diff --git a/vendor/github.com/gomodule/redigo/redis/doc.go b/vendor/github.com/gomodule/redigo/redis/doc.go new file mode 100644 index 00000000..70ec1ea6 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/doc.go @@ -0,0 +1,177 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// Package redis is a client for the Redis database. +// +// The Redigo FAQ (https://github.com/gomodule/redigo/wiki/FAQ) contains more +// documentation about this package. +// +// Connections +// +// The Conn interface is the primary interface for working with Redis. +// Applications create connections by calling the Dial, DialWithTimeout or +// NewConn functions. In the future, functions will be added for creating +// sharded and other types of connections. +// +// The application must call the connection Close method when the application +// is done with the connection. +// +// Executing Commands +// +// The Conn interface has a generic method for executing Redis commands: +// +// Do(commandName string, args ...interface{}) (reply interface{}, err error) +// +// The Redis command reference (http://redis.io/commands) lists the available +// commands. An example of using the Redis APPEND command is: +// +// n, err := conn.Do("APPEND", "key", "value") +// +// The Do method converts command arguments to bulk strings for transmission +// to the server as follows: +// +// Go Type Conversion +// []byte Sent as is +// string Sent as is +// int, int64 strconv.FormatInt(v) +// float64 strconv.FormatFloat(v, 'g', -1, 64) +// bool true -> "1", false -> "0" +// nil "" +// all other types fmt.Fprint(w, v) +// +// Redis command reply types are represented using the following Go types: +// +// Redis type Go type +// error redis.Error +// integer int64 +// simple string string +// bulk string []byte or nil if value not present. +// array []interface{} or nil if value not present. +// +// Use type assertions or the reply helper functions to convert from +// interface{} to the specific Go type for the command result. +// +// Pipelining +// +// Connections support pipelining using the Send, Flush and Receive methods. +// +// Send(commandName string, args ...interface{}) error +// Flush() error +// Receive() (reply interface{}, err error) +// +// Send writes the command to the connection's output buffer. Flush flushes the +// connection's output buffer to the server. Receive reads a single reply from +// the server. The following example shows a simple pipeline. +// +// c.Send("SET", "foo", "bar") +// c.Send("GET", "foo") +// c.Flush() +// c.Receive() // reply from SET +// v, err = c.Receive() // reply from GET +// +// The Do method combines the functionality of the Send, Flush and Receive +// methods. The Do method starts by writing the command and flushing the output +// buffer. Next, the Do method receives all pending replies including the reply +// for the command just sent by Do. If any of the received replies is an error, +// then Do returns the error. If there are no errors, then Do returns the last +// reply. If the command argument to the Do method is "", then the Do method +// will flush the output buffer and receive pending replies without sending a +// command. +// +// Use the Send and Do methods to implement pipelined transactions. +// +// c.Send("MULTI") +// c.Send("INCR", "foo") +// c.Send("INCR", "bar") +// r, err := c.Do("EXEC") +// fmt.Println(r) // prints [1, 1] +// +// Concurrency +// +// Connections support one concurrent caller to the Receive method and one +// concurrent caller to the Send and Flush methods. No other concurrency is +// supported including concurrent calls to the Do method. +// +// For full concurrent access to Redis, use the thread-safe Pool to get, use +// and release a connection from within a goroutine. Connections returned from +// a Pool have the concurrency restrictions described in the previous +// paragraph. +// +// Publish and Subscribe +// +// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. +// +// c.Send("SUBSCRIBE", "example") +// c.Flush() +// for { +// reply, err := c.Receive() +// if err != nil { +// return err +// } +// // process pushed message +// } +// +// The PubSubConn type wraps a Conn with convenience methods for implementing +// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods +// send and flush a subscription management command. The receive method +// converts a pushed message to convenient types for use in a type switch. +// +// psc := redis.PubSubConn{Conn: c} +// psc.Subscribe("example") +// for { +// switch v := psc.Receive().(type) { +// case redis.Message: +// fmt.Printf("%s: message: %s\n", v.Channel, v.Data) +// case redis.Subscription: +// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) +// case error: +// return v +// } +// } +// +// Reply Helpers +// +// The Bool, Int, Bytes, String, Strings and Values functions convert a reply +// to a value of a specific type. To allow convenient wrapping of calls to the +// connection Do and Receive methods, the functions take a second argument of +// type error. If the error is non-nil, then the helper function returns the +// error. If the error is nil, the function converts the reply to the specified +// type: +// +// exists, err := redis.Bool(c.Do("EXISTS", "foo")) +// if err != nil { +// // handle error return from c.Do or type conversion error. +// } +// +// The Scan function converts elements of a array reply to Go types: +// +// var value1 int +// var value2 string +// reply, err := redis.Values(c.Do("MGET", "key1", "key2")) +// if err != nil { +// // handle error +// } +// if _, err := redis.Scan(reply, &value1, &value2); err != nil { +// // handle error +// } +// +// Errors +// +// Connection methods return error replies from the server as type redis.Error. +// +// Call the connection Err() method to determine if the connection encountered +// non-recoverable error such as a network error or protocol parsing error. If +// Err() returns a non-nil value, then the connection is not usable and should +// be closed. +package redis // import "github.com/gomodule/redigo/redis" diff --git a/vendor/github.com/gomodule/redigo/redis/go16.go b/vendor/github.com/gomodule/redigo/redis/go16.go new file mode 100644 index 00000000..f6b1a7cc --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/go16.go @@ -0,0 +1,27 @@ +// +build !go1.7 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + } +} diff --git a/vendor/github.com/gomodule/redigo/redis/go17.go b/vendor/github.com/gomodule/redigo/redis/go17.go new file mode 100644 index 00000000..5f363791 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/go17.go @@ -0,0 +1,29 @@ +// +build go1.7,!go1.8 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return &tls.Config{ + Rand: cfg.Rand, + Time: cfg.Time, + Certificates: cfg.Certificates, + NameToCertificate: cfg.NameToCertificate, + GetCertificate: cfg.GetCertificate, + RootCAs: cfg.RootCAs, + NextProtos: cfg.NextProtos, + ServerName: cfg.ServerName, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + InsecureSkipVerify: cfg.InsecureSkipVerify, + CipherSuites: cfg.CipherSuites, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + ClientSessionCache: cfg.ClientSessionCache, + MinVersion: cfg.MinVersion, + MaxVersion: cfg.MaxVersion, + CurvePreferences: cfg.CurvePreferences, + DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, + Renegotiation: cfg.Renegotiation, + } +} diff --git a/vendor/github.com/gomodule/redigo/redis/go18.go b/vendor/github.com/gomodule/redigo/redis/go18.go new file mode 100644 index 00000000..558363be --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/go18.go @@ -0,0 +1,9 @@ +// +build go1.8 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return cfg.Clone() +} diff --git a/vendor/github.com/gomodule/redigo/redis/log.go b/vendor/github.com/gomodule/redigo/redis/log.go new file mode 100644 index 00000000..b2996611 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/log.go @@ -0,0 +1,134 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bytes" + "fmt" + "log" + "time" +) + +var ( + _ ConnWithTimeout = (*loggingConn)(nil) +) + +// NewLoggingConn returns a logging wrapper around a connection. +func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { + if prefix != "" { + prefix = prefix + "." + } + return &loggingConn{conn, logger, prefix} +} + +type loggingConn struct { + Conn + logger *log.Logger + prefix string +} + +func (c *loggingConn) Close() error { + err := c.Conn.Close() + var buf bytes.Buffer + fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) + c.logger.Output(2, buf.String()) + return err +} + +func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { + const chop = 32 + switch v := v.(type) { + case []byte: + if len(v) > chop { + fmt.Fprintf(buf, "%q...", v[:chop]) + } else { + fmt.Fprintf(buf, "%q", v) + } + case string: + if len(v) > chop { + fmt.Fprintf(buf, "%q...", v[:chop]) + } else { + fmt.Fprintf(buf, "%q", v) + } + case []interface{}: + if len(v) == 0 { + buf.WriteString("[]") + } else { + sep := "[" + fin := "]" + if len(v) > chop { + v = v[:chop] + fin = "...]" + } + for _, vv := range v { + buf.WriteString(sep) + c.printValue(buf, vv) + sep = ", " + } + buf.WriteString(fin) + } + default: + fmt.Fprint(buf, v) + } +} + +func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { + var buf bytes.Buffer + fmt.Fprintf(&buf, "%s%s(", c.prefix, method) + if method != "Receive" { + buf.WriteString(commandName) + for _, arg := range args { + buf.WriteString(", ") + c.printValue(&buf, arg) + } + } + buf.WriteString(") -> (") + if method != "Send" { + c.printValue(&buf, reply) + buf.WriteString(", ") + } + fmt.Fprintf(&buf, "%v)", err) + c.logger.Output(3, buf.String()) +} + +func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { + reply, err := c.Conn.Do(commandName, args...) + c.print("Do", commandName, args, reply, err) + return reply, err +} + +func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { + reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...) + c.print("DoWithTimeout", commandName, args, reply, err) + return reply, err +} + +func (c *loggingConn) Send(commandName string, args ...interface{}) error { + err := c.Conn.Send(commandName, args...) + c.print("Send", commandName, args, nil, err) + return err +} + +func (c *loggingConn) Receive() (interface{}, error) { + reply, err := c.Conn.Receive() + c.print("Receive", "", nil, reply, err) + return reply, err +} + +func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { + reply, err := ReceiveWithTimeout(c.Conn, timeout) + c.print("ReceiveWithTimeout", "", nil, reply, err) + return reply, err +} diff --git a/vendor/github.com/gomodule/redigo/redis/pool.go b/vendor/github.com/gomodule/redigo/redis/pool.go new file mode 100644 index 00000000..d77da325 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pool.go @@ -0,0 +1,562 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bytes" + "crypto/rand" + "crypto/sha1" + "errors" + "io" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/gomodule/redigo/internal" +) + +var ( + _ ConnWithTimeout = (*activeConn)(nil) + _ ConnWithTimeout = (*errorConn)(nil) +) + +var nowFunc = time.Now // for testing + +// ErrPoolExhausted is returned from a pool connection method (Do, Send, +// Receive, Flush, Err) when the maximum number of database connections in the +// pool has been reached. +var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") + +var ( + errPoolClosed = errors.New("redigo: connection pool closed") + errConnClosed = errors.New("redigo: connection closed") +) + +// Pool maintains a pool of connections. The application calls the Get method +// to get a connection from the pool and the connection's Close method to +// return the connection's resources to the pool. +// +// The following example shows how to use a pool in a web application. The +// application creates a pool at application startup and makes it available to +// request handlers using a package level variable. The pool configuration used +// here is an example, not a recommendation. +// +// func newPool(addr string) *redis.Pool { +// return &redis.Pool{ +// MaxIdle: 3, +// IdleTimeout: 240 * time.Second, +// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, +// } +// } +// +// var ( +// pool *redis.Pool +// redisServer = flag.String("redisServer", ":6379", "") +// ) +// +// func main() { +// flag.Parse() +// pool = newPool(*redisServer) +// ... +// } +// +// A request handler gets a connection from the pool and closes the connection +// when the handler is done: +// +// func serveHome(w http.ResponseWriter, r *http.Request) { +// conn := pool.Get() +// defer conn.Close() +// ... +// } +// +// Use the Dial function to authenticate connections with the AUTH command or +// select a database with the SELECT command: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// Dial: func () (redis.Conn, error) { +// c, err := redis.Dial("tcp", server) +// if err != nil { +// return nil, err +// } +// if _, err := c.Do("AUTH", password); err != nil { +// c.Close() +// return nil, err +// } +// if _, err := c.Do("SELECT", db); err != nil { +// c.Close() +// return nil, err +// } +// return c, nil +// }, +// } +// +// Use the TestOnBorrow function to check the health of an idle connection +// before the connection is returned to the application. This example PINGs +// connections that have been idle more than a minute: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// TestOnBorrow: func(c redis.Conn, t time.Time) error { +// if time.Since(t) < time.Minute { +// return nil +// } +// _, err := c.Do("PING") +// return err +// }, +// } +// +type Pool struct { + // Dial is an application supplied function for creating and configuring a + // connection. + // + // The connection returned from Dial must not be in a special state + // (subscribed to pubsub channel, transaction started, ...). + Dial func() (Conn, error) + + // TestOnBorrow is an optional application supplied function for checking + // the health of an idle connection before the connection is used again by + // the application. Argument t is the time that the connection was returned + // to the pool. If the function returns an error, then the connection is + // closed. + TestOnBorrow func(c Conn, t time.Time) error + + // Maximum number of idle connections in the pool. + MaxIdle int + + // Maximum number of connections allocated by the pool at a given time. + // When zero, there is no limit on the number of connections in the pool. + MaxActive int + + // Close connections after remaining idle for this duration. If the value + // is zero, then idle connections are not closed. Applications should set + // the timeout to a value less than the server's timeout. + IdleTimeout time.Duration + + // If Wait is true and the pool is at the MaxActive limit, then Get() waits + // for a connection to be returned to the pool before returning. + Wait bool + + // Close connections older than this duration. If the value is zero, then + // the pool does not close connections based on age. + MaxConnLifetime time.Duration + + chInitialized uint32 // set to 1 when field ch is initialized + + mu sync.Mutex // mu protects the following fields + closed bool // set to true when the pool is closed. + active int // the number of open connections in the pool + ch chan struct{} // limits open connections when p.Wait is true + idle idleList // idle connections +} + +// NewPool creates a new pool. +// +// Deprecated: Initialize the Pool directory as shown in the example. +func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { + return &Pool{Dial: newFn, MaxIdle: maxIdle} +} + +// Get gets a connection. The application must close the returned connection. +// This method always returns a valid connection so that applications can defer +// error handling to the first use of the connection. If there is an error +// getting an underlying connection, then the connection Err, Do, Send, Flush +// and Receive methods return that error. +func (p *Pool) Get() Conn { + pc, err := p.get(nil) + if err != nil { + return errorConn{err} + } + return &activeConn{p: p, pc: pc} +} + +// PoolStats contains pool statistics. +type PoolStats struct { + // ActiveCount is the number of connections in the pool. The count includes + // idle connections and connections in use. + ActiveCount int + // IdleCount is the number of idle connections in the pool. + IdleCount int +} + +// Stats returns pool's statistics. +func (p *Pool) Stats() PoolStats { + p.mu.Lock() + stats := PoolStats{ + ActiveCount: p.active, + IdleCount: p.idle.count, + } + p.mu.Unlock() + + return stats +} + +// ActiveCount returns the number of connections in the pool. The count +// includes idle connections and connections in use. +func (p *Pool) ActiveCount() int { + p.mu.Lock() + active := p.active + p.mu.Unlock() + return active +} + +// IdleCount returns the number of idle connections in the pool. +func (p *Pool) IdleCount() int { + p.mu.Lock() + idle := p.idle.count + p.mu.Unlock() + return idle +} + +// Close releases the resources used by the pool. +func (p *Pool) Close() error { + p.mu.Lock() + if p.closed { + p.mu.Unlock() + return nil + } + p.closed = true + p.active -= p.idle.count + pc := p.idle.front + p.idle.count = 0 + p.idle.front, p.idle.back = nil, nil + if p.ch != nil { + close(p.ch) + } + p.mu.Unlock() + for ; pc != nil; pc = pc.next { + pc.c.Close() + } + return nil +} + +func (p *Pool) lazyInit() { + // Fast path. + if atomic.LoadUint32(&p.chInitialized) == 1 { + return + } + // Slow path. + p.mu.Lock() + if p.chInitialized == 0 { + p.ch = make(chan struct{}, p.MaxActive) + if p.closed { + close(p.ch) + } else { + for i := 0; i < p.MaxActive; i++ { + p.ch <- struct{}{} + } + } + atomic.StoreUint32(&p.chInitialized, 1) + } + p.mu.Unlock() +} + +// get prunes stale connections and returns a connection from the idle list or +// creates a new connection. +func (p *Pool) get(ctx interface { + Done() <-chan struct{} + Err() error +}) (*poolConn, error) { + + // Handle limit for p.Wait == true. + if p.Wait && p.MaxActive > 0 { + p.lazyInit() + if ctx == nil { + <-p.ch + } else { + select { + case <-p.ch: + case <-ctx.Done(): + return nil, ctx.Err() + } + } + } + + p.mu.Lock() + + // Prune stale connections at the back of the idle list. + if p.IdleTimeout > 0 { + n := p.idle.count + for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ { + pc := p.idle.back + p.idle.popBack() + p.mu.Unlock() + pc.c.Close() + p.mu.Lock() + p.active-- + } + } + + // Get idle connection from the front of idle list. + for p.idle.front != nil { + pc := p.idle.front + p.idle.popFront() + p.mu.Unlock() + if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) && + (p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) { + return pc, nil + } + pc.c.Close() + p.mu.Lock() + p.active-- + } + + // Check for pool closed before dialing a new connection. + if p.closed { + p.mu.Unlock() + return nil, errors.New("redigo: get on closed pool") + } + + // Handle limit for p.Wait == false. + if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive { + p.mu.Unlock() + return nil, ErrPoolExhausted + } + + p.active++ + p.mu.Unlock() + c, err := p.Dial() + if err != nil { + c = nil + p.mu.Lock() + p.active-- + if p.ch != nil && !p.closed { + p.ch <- struct{}{} + } + p.mu.Unlock() + } + return &poolConn{c: c, created: nowFunc()}, err +} + +func (p *Pool) put(pc *poolConn, forceClose bool) error { + p.mu.Lock() + if !p.closed && !forceClose { + pc.t = nowFunc() + p.idle.pushFront(pc) + if p.idle.count > p.MaxIdle { + pc = p.idle.back + p.idle.popBack() + } else { + pc = nil + } + } + + if pc != nil { + p.mu.Unlock() + pc.c.Close() + p.mu.Lock() + p.active-- + } + + if p.ch != nil && !p.closed { + p.ch <- struct{}{} + } + p.mu.Unlock() + return nil +} + +type activeConn struct { + p *Pool + pc *poolConn + state int +} + +var ( + sentinel []byte + sentinelOnce sync.Once +) + +func initSentinel() { + p := make([]byte, 64) + if _, err := rand.Read(p); err == nil { + sentinel = p + } else { + h := sha1.New() + io.WriteString(h, "Oops, rand failed. Use time instead.") + io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) + sentinel = h.Sum(nil) + } +} + +func (ac *activeConn) Close() error { + pc := ac.pc + if pc == nil { + return nil + } + ac.pc = nil + + if ac.state&internal.MultiState != 0 { + pc.c.Send("DISCARD") + ac.state &^= (internal.MultiState | internal.WatchState) + } else if ac.state&internal.WatchState != 0 { + pc.c.Send("UNWATCH") + ac.state &^= internal.WatchState + } + if ac.state&internal.SubscribeState != 0 { + pc.c.Send("UNSUBSCRIBE") + pc.c.Send("PUNSUBSCRIBE") + // To detect the end of the message stream, ask the server to echo + // a sentinel value and read until we see that value. + sentinelOnce.Do(initSentinel) + pc.c.Send("ECHO", sentinel) + pc.c.Flush() + for { + p, err := pc.c.Receive() + if err != nil { + break + } + if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { + ac.state &^= internal.SubscribeState + break + } + } + } + pc.c.Do("") + ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil) + return nil +} + +func (ac *activeConn) Err() error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + return pc.c.Err() +} + +func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + ci := internal.LookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return pc.c.Do(commandName, args...) +} + +func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + cwt, ok := pc.c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + ci := internal.LookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return cwt.DoWithTimeout(timeout, commandName, args...) +} + +func (ac *activeConn) Send(commandName string, args ...interface{}) error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + ci := internal.LookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return pc.c.Send(commandName, args...) +} + +func (ac *activeConn) Flush() error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + return pc.c.Flush() +} + +func (ac *activeConn) Receive() (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + return pc.c.Receive() +} + +func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + cwt, ok := pc.c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.ReceiveWithTimeout(timeout) +} + +type errorConn struct{ err error } + +func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } +func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) { + return nil, ec.err +} +func (ec errorConn) Send(string, ...interface{}) error { return ec.err } +func (ec errorConn) Err() error { return ec.err } +func (ec errorConn) Close() error { return nil } +func (ec errorConn) Flush() error { return ec.err } +func (ec errorConn) Receive() (interface{}, error) { return nil, ec.err } +func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err } + +type idleList struct { + count int + front, back *poolConn +} + +type poolConn struct { + c Conn + t time.Time + created time.Time + next, prev *poolConn +} + +func (l *idleList) pushFront(pc *poolConn) { + pc.next = l.front + pc.prev = nil + if l.count == 0 { + l.back = pc + } else { + l.front.prev = pc + } + l.front = pc + l.count++ + return +} + +func (l *idleList) popFront() { + pc := l.front + l.count-- + if l.count == 0 { + l.front, l.back = nil, nil + } else { + pc.next.prev = nil + l.front = pc.next + } + pc.next, pc.prev = nil, nil +} + +func (l *idleList) popBack() { + pc := l.back + l.count-- + if l.count == 0 { + l.front, l.back = nil, nil + } else { + pc.prev.next = nil + l.back = pc.prev + } + pc.next, pc.prev = nil, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/pool17.go b/vendor/github.com/gomodule/redigo/redis/pool17.go new file mode 100644 index 00000000..c1ea18ee --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pool17.go @@ -0,0 +1,35 @@ +// Copyright 2018 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// +build go1.7 + +package redis + +import "context" + +// GetContext gets a connection using the provided context. +// +// The provided Context must be non-nil. If the context expires before the +// connection is complete, an error is returned. Any expiration on the context +// will not affect the returned connection. +// +// If the function completes without error, then the application must close the +// returned connection. +func (p *Pool) GetContext(ctx context.Context) (Conn, error) { + pc, err := p.get(ctx) + if err != nil { + return errorConn{err}, err + } + return &activeConn{p: p, pc: pc}, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/pubsub.go b/vendor/github.com/gomodule/redigo/redis/pubsub.go new file mode 100644 index 00000000..2da60211 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pubsub.go @@ -0,0 +1,148 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "time" +) + +// Subscription represents a subscribe or unsubscribe notification. +type Subscription struct { + // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" + Kind string + + // The channel that was changed. + Channel string + + // The current number of subscriptions for connection. + Count int +} + +// Message represents a message notification. +type Message struct { + // The originating channel. + Channel string + + // The matched pattern, if any + Pattern string + + // The message data. + Data []byte +} + +// Pong represents a pubsub pong notification. +type Pong struct { + Data string +} + +// PubSubConn wraps a Conn with convenience methods for subscribers. +type PubSubConn struct { + Conn Conn +} + +// Close closes the connection. +func (c PubSubConn) Close() error { + return c.Conn.Close() +} + +// Subscribe subscribes the connection to the specified channels. +func (c PubSubConn) Subscribe(channel ...interface{}) error { + c.Conn.Send("SUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// PSubscribe subscribes the connection to the given patterns. +func (c PubSubConn) PSubscribe(channel ...interface{}) error { + c.Conn.Send("PSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// Unsubscribe unsubscribes the connection from the given channels, or from all +// of them if none is given. +func (c PubSubConn) Unsubscribe(channel ...interface{}) error { + c.Conn.Send("UNSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// PUnsubscribe unsubscribes the connection from the given patterns, or from all +// of them if none is given. +func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { + c.Conn.Send("PUNSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// Ping sends a PING to the server with the specified data. +// +// The connection must be subscribed to at least one channel or pattern when +// calling this method. +func (c PubSubConn) Ping(data string) error { + c.Conn.Send("PING", data) + return c.Conn.Flush() +} + +// Receive returns a pushed message as a Subscription, Message, Pong or error. +// The return value is intended to be used directly in a type switch as +// illustrated in the PubSubConn example. +func (c PubSubConn) Receive() interface{} { + return c.receiveInternal(c.Conn.Receive()) +} + +// ReceiveWithTimeout is like Receive, but it allows the application to +// override the connection's default timeout. +func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} { + return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout)) +} + +func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} { + reply, err := Values(replyArg, errArg) + if err != nil { + return err + } + + var kind string + reply, err = Scan(reply, &kind) + if err != nil { + return err + } + + switch kind { + case "message": + var m Message + if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { + return err + } + return m + case "pmessage": + var m Message + if _, err := Scan(reply, &m.Pattern, &m.Channel, &m.Data); err != nil { + return err + } + return m + case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": + s := Subscription{Kind: kind} + if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { + return err + } + return s + case "pong": + var p Pong + if _, err := Scan(reply, &p.Data); err != nil { + return err + } + return p + } + return errors.New("redigo: unknown pubsub notification") +} diff --git a/vendor/github.com/gomodule/redigo/redis/redis.go b/vendor/github.com/gomodule/redigo/redis/redis.go new file mode 100644 index 00000000..141fa4a9 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/redis.go @@ -0,0 +1,117 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "time" +) + +// Error represents an error returned in a command reply. +type Error string + +func (err Error) Error() string { return string(err) } + +// Conn represents a connection to a Redis server. +type Conn interface { + // Close closes the connection. + Close() error + + // Err returns a non-nil value when the connection is not usable. + Err() error + + // Do sends a command to the server and returns the received reply. + Do(commandName string, args ...interface{}) (reply interface{}, err error) + + // Send writes the command to the client's output buffer. + Send(commandName string, args ...interface{}) error + + // Flush flushes the output buffer to the Redis server. + Flush() error + + // Receive receives a single reply from the Redis server + Receive() (reply interface{}, err error) +} + +// Argument is the interface implemented by an object which wants to control how +// the object is converted to Redis bulk strings. +type Argument interface { + // RedisArg returns a value to be encoded as a bulk string per the + // conversions listed in the section 'Executing Commands'. + // Implementations should typically return a []byte or string. + RedisArg() interface{} +} + +// Scanner is implemented by an object which wants to control its value is +// interpreted when read from Redis. +type Scanner interface { + // RedisScan assigns a value from a Redis value. The argument src is one of + // the reply types listed in the section `Executing Commands`. + // + // An error should be returned if the value cannot be stored without + // loss of information. + RedisScan(src interface{}) error +} + +// ConnWithTimeout is an optional interface that allows the caller to override +// a connection's default read timeout. This interface is useful for executing +// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the +// server. +// +// A connection's default read timeout is set with the DialReadTimeout dial +// option. Applications should rely on the default timeout for commands that do +// not block at the server. +// +// All of the Conn implementations in this package satisfy the ConnWithTimeout +// interface. +// +// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify +// use of this interface. +type ConnWithTimeout interface { + Conn + + // Do sends a command to the server and returns the received reply. + // The timeout overrides the read timeout set when dialing the + // connection. + DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) + + // Receive receives a single reply from the Redis server. The timeout + // overrides the read timeout set when dialing the connection. + ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) +} + +var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout") + +// DoWithTimeout executes a Redis command with the specified read timeout. If +// the connection does not satisfy the ConnWithTimeout interface, then an error +// is returned. +func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { + cwt, ok := c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.DoWithTimeout(timeout, cmd, args...) +} + +// ReceiveWithTimeout receives a reply with the specified read timeout. If the +// connection does not satisfy the ConnWithTimeout interface, then an error is +// returned. +func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) { + cwt, ok := c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.ReceiveWithTimeout(timeout) +} diff --git a/vendor/github.com/gomodule/redigo/redis/reply.go b/vendor/github.com/gomodule/redigo/redis/reply.go new file mode 100644 index 00000000..c2b3b2b6 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/reply.go @@ -0,0 +1,479 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "fmt" + "strconv" +) + +// ErrNil indicates that a reply value is nil. +var ErrNil = errors.New("redigo: nil returned") + +// Int is a helper that converts a command reply to an integer. If err is not +// equal to nil, then Int returns 0, err. Otherwise, Int converts the +// reply to an int as follows: +// +// Reply type Result +// integer int(reply), nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int(reply interface{}, err error) (int, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + x := int(reply) + if int64(x) != reply { + return 0, strconv.ErrRange + } + return x, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 0) + return int(n), err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) +} + +// Int64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int64(reply interface{}, err error) (int64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + return reply, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) +} + +var errNegativeInt = errors.New("redigo: unexpected value for Uint64") + +// Uint64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Uint64(reply interface{}, err error) (uint64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + if reply < 0 { + return 0, errNegativeInt + } + return uint64(reply), nil + case []byte: + n, err := strconv.ParseUint(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) +} + +// Float64 is a helper that converts a command reply to 64 bit float. If err is +// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts +// the reply to an int as follows: +// +// Reply type Result +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Float64(reply interface{}, err error) (float64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case []byte: + n, err := strconv.ParseFloat(string(reply), 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) +} + +// String is a helper that converts a command reply to a string. If err is not +// equal to nil, then String returns "", err. Otherwise String converts the +// reply to a string as follows: +// +// Reply type Result +// bulk string string(reply), nil +// simple string reply, nil +// nil "", ErrNil +// other "", error +func String(reply interface{}, err error) (string, error) { + if err != nil { + return "", err + } + switch reply := reply.(type) { + case []byte: + return string(reply), nil + case string: + return reply, nil + case nil: + return "", ErrNil + case Error: + return "", reply + } + return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) +} + +// Bytes is a helper that converts a command reply to a slice of bytes. If err +// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts +// the reply to a slice of bytes as follows: +// +// Reply type Result +// bulk string reply, nil +// simple string []byte(reply), nil +// nil nil, ErrNil +// other nil, error +func Bytes(reply interface{}, err error) ([]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []byte: + return reply, nil + case string: + return []byte(reply), nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) +} + +// Bool is a helper that converts a command reply to a boolean. If err is not +// equal to nil, then Bool returns false, err. Otherwise Bool converts the +// reply to boolean as follows: +// +// Reply type Result +// integer value != 0, nil +// bulk string strconv.ParseBool(reply) +// nil false, ErrNil +// other false, error +func Bool(reply interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + switch reply := reply.(type) { + case int64: + return reply != 0, nil + case []byte: + return strconv.ParseBool(string(reply)) + case nil: + return false, ErrNil + case Error: + return false, reply + } + return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) +} + +// MultiBulk is a helper that converts an array command reply to a []interface{}. +// +// Deprecated: Use Values instead. +func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } + +// Values is a helper that converts an array command reply to a []interface{}. +// If err is not equal to nil, then Values returns nil, err. Otherwise, Values +// converts the reply as follows: +// +// Reply type Result +// array reply, nil +// nil nil, ErrNil +// other nil, error +func Values(reply interface{}, err error) ([]interface{}, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + return reply, nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) +} + +func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error { + if err != nil { + return err + } + switch reply := reply.(type) { + case []interface{}: + makeSlice(len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + if err := assign(i, reply[i]); err != nil { + return err + } + } + return nil + case nil: + return ErrNil + case Error: + return reply + } + return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply) +} + +// Float64s is a helper that converts an array command reply to a []float64. If +// err is not equal to nil, then Float64s returns nil, err. Nil array items are +// converted to 0 in the output slice. Floats64 returns an error if an array +// item is not a bulk string or nil. +func Float64s(reply interface{}, err error) ([]float64, error) { + var result []float64 + err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error { + p, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v) + } + f, err := strconv.ParseFloat(string(p), 64) + result[i] = f + return err + }) + return result, err +} + +// Strings is a helper that converts an array command reply to a []string. If +// err is not equal to nil, then Strings returns nil, err. Nil array items are +// converted to "" in the output slice. Strings returns an error if an array +// item is not a bulk string or nil. +func Strings(reply interface{}, err error) ([]string, error) { + var result []string + err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case string: + result[i] = v + return nil + case []byte: + result[i] = string(v) + return nil + default: + return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v) + } + }) + return result, err +} + +// ByteSlices is a helper that converts an array command reply to a [][]byte. +// If err is not equal to nil, then ByteSlices returns nil, err. Nil array +// items are stay nil. ByteSlices returns an error if an array item is not a +// bulk string or nil. +func ByteSlices(reply interface{}, err error) ([][]byte, error) { + var result [][]byte + err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error { + p, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v) + } + result[i] = p + return nil + }) + return result, err +} + +// Int64s is a helper that converts an array command reply to a []int64. +// If err is not equal to nil, then Int64s returns nil, err. Nil array +// items are stay nil. Int64s returns an error if an array item is not a +// bulk string or nil. +func Int64s(reply interface{}, err error) ([]int64, error) { + var result []int64 + err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case int64: + result[i] = v + return nil + case []byte: + n, err := strconv.ParseInt(string(v), 10, 64) + result[i] = n + return err + default: + return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v) + } + }) + return result, err +} + +// Ints is a helper that converts an array command reply to a []in. +// If err is not equal to nil, then Ints returns nil, err. Nil array +// items are stay nil. Ints returns an error if an array item is not a +// bulk string or nil. +func Ints(reply interface{}, err error) ([]int, error) { + var result []int + err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case int64: + n := int(v) + if int64(n) != v { + return strconv.ErrRange + } + result[i] = n + return nil + case []byte: + n, err := strconv.Atoi(string(v)) + result[i] = n + return err + default: + return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v) + } + }) + return result, err +} + +// StringMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. +// Requires an even number of values in result. +func StringMap(result interface{}, err error) (map[string]string, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: StringMap expects even number of values result") + } + m := make(map[string]string, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, okKey := values[i].([]byte) + value, okValue := values[i+1].([]byte) + if !okKey || !okValue { + return nil, errors.New("redigo: StringMap key not a bulk string value") + } + m[string(key)] = string(value) + } + return m, nil +} + +// IntMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]int. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func IntMap(result interface{}, err error) (map[string]int, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: IntMap expects even number of values result") + } + m := make(map[string]int, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].([]byte) + if !ok { + return nil, errors.New("redigo: IntMap key not a bulk string value") + } + value, err := Int(values[i+1], nil) + if err != nil { + return nil, err + } + m[string(key)] = value + } + return m, nil +} + +// Int64Map is a helper that converts an array of strings (alternating key, value) +// into a map[string]int64. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func Int64Map(result interface{}, err error) (map[string]int64, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: Int64Map expects even number of values result") + } + m := make(map[string]int64, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].([]byte) + if !ok { + return nil, errors.New("redigo: Int64Map key not a bulk string value") + } + value, err := Int64(values[i+1], nil) + if err != nil { + return nil, err + } + m[string(key)] = value + } + return m, nil +} + +// Positions is a helper that converts an array of positions (lat, long) +// into a [][2]float64. The GEOPOS command returns replies in this format. +func Positions(result interface{}, err error) ([]*[2]float64, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + positions := make([]*[2]float64, len(values)) + for i := range values { + if values[i] == nil { + continue + } + p, ok := values[i].([]interface{}) + if !ok { + return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i]) + } + if len(p) != 2 { + return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p)) + } + lat, err := Float64(p[0], nil) + if err != nil { + return nil, err + } + long, err := Float64(p[1], nil) + if err != nil { + return nil, err + } + positions[i] = &[2]float64{lat, long} + } + return positions, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/scan.go b/vendor/github.com/gomodule/redigo/redis/scan.go new file mode 100644 index 00000000..ef9551bd --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/scan.go @@ -0,0 +1,585 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "sync" +) + +func ensureLen(d reflect.Value, n int) { + if n > d.Cap() { + d.Set(reflect.MakeSlice(d.Type(), n, n)) + } else { + d.SetLen(n) + } +} + +func cannotConvert(d reflect.Value, s interface{}) error { + var sname string + switch s.(type) { + case string: + sname = "Redis simple string" + case Error: + sname = "Redis error" + case int64: + sname = "Redis integer" + case []byte: + sname = "Redis bulk string" + case []interface{}: + sname = "Redis array" + default: + sname = reflect.TypeOf(s).String() + } + return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) +} + +func convertAssignBulkString(d reflect.Value, s []byte) (err error) { + switch d.Type().Kind() { + case reflect.Float32, reflect.Float64: + var x float64 + x, err = strconv.ParseFloat(string(s), d.Type().Bits()) + d.SetFloat(x) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + var x int64 + x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) + d.SetInt(x) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + var x uint64 + x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) + d.SetUint(x) + case reflect.Bool: + var x bool + x, err = strconv.ParseBool(string(s)) + d.SetBool(x) + case reflect.String: + d.SetString(string(s)) + case reflect.Slice: + if d.Type().Elem().Kind() != reflect.Uint8 { + err = cannotConvert(d, s) + } else { + d.SetBytes(s) + } + default: + err = cannotConvert(d, s) + } + return +} + +func convertAssignInt(d reflect.Value, s int64) (err error) { + switch d.Type().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + d.SetInt(s) + if d.Int() != s { + err = strconv.ErrRange + d.SetInt(0) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if s < 0 { + err = strconv.ErrRange + } else { + x := uint64(s) + d.SetUint(x) + if d.Uint() != x { + err = strconv.ErrRange + d.SetUint(0) + } + } + case reflect.Bool: + d.SetBool(s != 0) + default: + err = cannotConvert(d, s) + } + return +} + +func convertAssignValue(d reflect.Value, s interface{}) (err error) { + if d.Kind() != reflect.Ptr { + if d.CanAddr() { + d2 := d.Addr() + if d2.CanInterface() { + if scanner, ok := d2.Interface().(Scanner); ok { + return scanner.RedisScan(s) + } + } + } + } else if d.CanInterface() { + // Already a reflect.Ptr + if d.IsNil() { + d.Set(reflect.New(d.Type().Elem())) + } + if scanner, ok := d.Interface().(Scanner); ok { + return scanner.RedisScan(s) + } + } + + switch s := s.(type) { + case []byte: + err = convertAssignBulkString(d, s) + case int64: + err = convertAssignInt(d, s) + default: + err = cannotConvert(d, s) + } + return err +} + +func convertAssignArray(d reflect.Value, s []interface{}) error { + if d.Type().Kind() != reflect.Slice { + return cannotConvert(d, s) + } + ensureLen(d, len(s)) + for i := 0; i < len(s); i++ { + if err := convertAssignValue(d.Index(i), s[i]); err != nil { + return err + } + } + return nil +} + +func convertAssign(d interface{}, s interface{}) (err error) { + if scanner, ok := d.(Scanner); ok { + return scanner.RedisScan(s) + } + + // Handle the most common destination types using type switches and + // fall back to reflection for all other types. + switch s := s.(type) { + case nil: + // ignore + case []byte: + switch d := d.(type) { + case *string: + *d = string(s) + case *int: + *d, err = strconv.Atoi(string(s)) + case *bool: + *d, err = strconv.ParseBool(string(s)) + case *[]byte: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignBulkString(d.Elem(), s) + } + } + case int64: + switch d := d.(type) { + case *int: + x := int(s) + if int64(x) != s { + err = strconv.ErrRange + x = 0 + } + *d = x + case *bool: + *d = s != 0 + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignInt(d.Elem(), s) + } + } + case string: + switch d := d.(type) { + case *string: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + err = cannotConvert(reflect.ValueOf(d), s) + } + case []interface{}: + switch d := d.(type) { + case *[]interface{}: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignArray(d.Elem(), s) + } + } + case Error: + err = s + default: + err = cannotConvert(reflect.ValueOf(d), s) + } + return +} + +// Scan copies from src to the values pointed at by dest. +// +// Scan uses RedisScan if available otherwise: +// +// The values pointed at by dest must be an integer, float, boolean, string, +// []byte, interface{} or slices of these types. Scan uses the standard strconv +// package to convert bulk strings to numeric and boolean types. +// +// If a dest value is nil, then the corresponding src value is skipped. +// +// If a src element is nil, then the corresponding dest value is not modified. +// +// To enable easy use of Scan in a loop, Scan returns the slice of src +// following the copied values. +func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { + if len(src) < len(dest) { + return nil, errors.New("redigo.Scan: array short") + } + var err error + for i, d := range dest { + err = convertAssign(d, src[i]) + if err != nil { + err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) + break + } + } + return src[len(dest):], err +} + +type fieldSpec struct { + name string + index []int + omitEmpty bool +} + +type structSpec struct { + m map[string]*fieldSpec + l []*fieldSpec +} + +func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { + return ss.m[string(name)] +} + +func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + switch { + case f.PkgPath != "" && !f.Anonymous: + // Ignore unexported fields. + case f.Anonymous: + // TODO: Handle pointers. Requires change to decoder and + // protection against infinite recursion. + if f.Type.Kind() == reflect.Struct { + compileStructSpec(f.Type, depth, append(index, i), ss) + } + default: + fs := &fieldSpec{name: f.Name} + tag := f.Tag.Get("redis") + p := strings.Split(tag, ",") + if len(p) > 0 { + if p[0] == "-" { + continue + } + if len(p[0]) > 0 { + fs.name = p[0] + } + for _, s := range p[1:] { + switch s { + case "omitempty": + fs.omitEmpty = true + default: + panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) + } + } + } + d, found := depth[fs.name] + if !found { + d = 1 << 30 + } + switch { + case len(index) == d: + // At same depth, remove from result. + delete(ss.m, fs.name) + j := 0 + for i := 0; i < len(ss.l); i++ { + if fs.name != ss.l[i].name { + ss.l[j] = ss.l[i] + j += 1 + } + } + ss.l = ss.l[:j] + case len(index) < d: + fs.index = make([]int, len(index)+1) + copy(fs.index, index) + fs.index[len(index)] = i + depth[fs.name] = len(index) + ss.m[fs.name] = fs + ss.l = append(ss.l, fs) + } + } + } +} + +var ( + structSpecMutex sync.RWMutex + structSpecCache = make(map[reflect.Type]*structSpec) + defaultFieldSpec = &fieldSpec{} +) + +func structSpecForType(t reflect.Type) *structSpec { + + structSpecMutex.RLock() + ss, found := structSpecCache[t] + structSpecMutex.RUnlock() + if found { + return ss + } + + structSpecMutex.Lock() + defer structSpecMutex.Unlock() + ss, found = structSpecCache[t] + if found { + return ss + } + + ss = &structSpec{m: make(map[string]*fieldSpec)} + compileStructSpec(t, make(map[string]int), nil, ss) + structSpecCache[t] = ss + return ss +} + +var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") + +// ScanStruct scans alternating names and values from src to a struct. The +// HGETALL and CONFIG GET commands return replies in this format. +// +// ScanStruct uses exported field names to match values in the response. Use +// 'redis' field tag to override the name: +// +// Field int `redis:"myName"` +// +// Fields with the tag redis:"-" are ignored. +// +// Each field uses RedisScan if available otherwise: +// Integer, float, boolean, string and []byte fields are supported. Scan uses the +// standard strconv package to convert bulk string values to numeric and +// boolean types. +// +// If a src element is nil, then the corresponding field is not modified. +func ScanStruct(src []interface{}, dest interface{}) error { + d := reflect.ValueOf(dest) + if d.Kind() != reflect.Ptr || d.IsNil() { + return errScanStructValue + } + d = d.Elem() + if d.Kind() != reflect.Struct { + return errScanStructValue + } + ss := structSpecForType(d.Type()) + + if len(src)%2 != 0 { + return errors.New("redigo.ScanStruct: number of values not a multiple of 2") + } + + for i := 0; i < len(src); i += 2 { + s := src[i+1] + if s == nil { + continue + } + name, ok := src[i].([]byte) + if !ok { + return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) + } + fs := ss.fieldSpec(name) + if fs == nil { + continue + } + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) + } + } + return nil +} + +var ( + errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") +) + +// ScanSlice scans src to the slice pointed to by dest. The elements the dest +// slice must be integer, float, boolean, string, struct or pointer to struct +// values. +// +// Struct fields must be integer, float, boolean or string values. All struct +// fields are used unless a subset is specified using fieldNames. +func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { + d := reflect.ValueOf(dest) + if d.Kind() != reflect.Ptr || d.IsNil() { + return errScanSliceValue + } + d = d.Elem() + if d.Kind() != reflect.Slice { + return errScanSliceValue + } + + isPtr := false + t := d.Type().Elem() + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { + isPtr = true + t = t.Elem() + } + + if t.Kind() != reflect.Struct { + ensureLen(d, len(src)) + for i, s := range src { + if s == nil { + continue + } + if err := convertAssignValue(d.Index(i), s); err != nil { + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) + } + } + return nil + } + + ss := structSpecForType(t) + fss := ss.l + if len(fieldNames) > 0 { + fss = make([]*fieldSpec, len(fieldNames)) + for i, name := range fieldNames { + fss[i] = ss.m[name] + if fss[i] == nil { + return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) + } + } + } + + if len(fss) == 0 { + return errors.New("redigo.ScanSlice: no struct fields") + } + + n := len(src) / len(fss) + if n*len(fss) != len(src) { + return errors.New("redigo.ScanSlice: length not a multiple of struct field count") + } + + ensureLen(d, n) + for i := 0; i < n; i++ { + d := d.Index(i) + if isPtr { + if d.IsNil() { + d.Set(reflect.New(t)) + } + d = d.Elem() + } + for j, fs := range fss { + s := src[i*len(fss)+j] + if s == nil { + continue + } + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) + } + } + } + return nil +} + +// Args is a helper for constructing command arguments from structured values. +type Args []interface{} + +// Add returns the result of appending value to args. +func (args Args) Add(value ...interface{}) Args { + return append(args, value...) +} + +// AddFlat returns the result of appending the flattened value of v to args. +// +// Maps are flattened by appending the alternating keys and map values to args. +// +// Slices are flattened by appending the slice elements to args. +// +// Structs are flattened by appending the alternating names and values of +// exported fields to args. If v is a nil struct pointer, then nothing is +// appended. The 'redis' field tag overrides struct field names. See ScanStruct +// for more information on the use of the 'redis' field tag. +// +// Other types are appended to args as is. +func (args Args) AddFlat(v interface{}) Args { + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Struct: + args = flattenStruct(args, rv) + case reflect.Slice: + for i := 0; i < rv.Len(); i++ { + args = append(args, rv.Index(i).Interface()) + } + case reflect.Map: + for _, k := range rv.MapKeys() { + args = append(args, k.Interface(), rv.MapIndex(k).Interface()) + } + case reflect.Ptr: + if rv.Type().Elem().Kind() == reflect.Struct { + if !rv.IsNil() { + args = flattenStruct(args, rv.Elem()) + } + } else { + args = append(args, v) + } + default: + args = append(args, v) + } + return args +} + +func flattenStruct(args Args, v reflect.Value) Args { + ss := structSpecForType(v.Type()) + for _, fs := range ss.l { + fv := v.FieldByIndex(fs.index) + if fs.omitEmpty { + var empty = false + switch fv.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + empty = fv.Len() == 0 + case reflect.Bool: + empty = !fv.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + empty = fv.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + empty = fv.Uint() == 0 + case reflect.Float32, reflect.Float64: + empty = fv.Float() == 0 + case reflect.Interface, reflect.Ptr: + empty = fv.IsNil() + } + if empty { + continue + } + } + args = append(args, fs.name, fv.Interface()) + } + return args +} diff --git a/vendor/github.com/gomodule/redigo/redis/script.go b/vendor/github.com/gomodule/redigo/redis/script.go new file mode 100644 index 00000000..0ef1c821 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/script.go @@ -0,0 +1,91 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "crypto/sha1" + "encoding/hex" + "io" + "strings" +) + +// Script encapsulates the source, hash and key count for a Lua script. See +// http://redis.io/commands/eval for information on scripts in Redis. +type Script struct { + keyCount int + src string + hash string +} + +// NewScript returns a new script object. If keyCount is greater than or equal +// to zero, then the count is automatically inserted in the EVAL command +// argument list. If keyCount is less than zero, then the application supplies +// the count as the first value in the keysAndArgs argument to the Do, Send and +// SendHash methods. +func NewScript(keyCount int, src string) *Script { + h := sha1.New() + io.WriteString(h, src) + return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} +} + +func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { + var args []interface{} + if s.keyCount < 0 { + args = make([]interface{}, 1+len(keysAndArgs)) + args[0] = spec + copy(args[1:], keysAndArgs) + } else { + args = make([]interface{}, 2+len(keysAndArgs)) + args[0] = spec + args[1] = s.keyCount + copy(args[2:], keysAndArgs) + } + return args +} + +// Hash returns the script hash. +func (s *Script) Hash() string { + return s.hash +} + +// Do evaluates the script. Under the covers, Do optimistically evaluates the +// script using the EVALSHA command. If the command fails because the script is +// not loaded, then Do evaluates the script using the EVAL command (thus +// causing the script to load). +func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { + v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) + if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { + v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) + } + return v, err +} + +// SendHash evaluates the script without waiting for the reply. The script is +// evaluated with the EVALSHA command. The application must ensure that the +// script is loaded by a previous call to Send, Do or Load methods. +func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { + return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) +} + +// Send evaluates the script without waiting for the reply. +func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { + return c.Send("EVAL", s.args(s.src, keysAndArgs)...) +} + +// Load loads the script without evaluating it. +func (s *Script) Load(c Conn) error { + _, err := c.Do("SCRIPT", "LOAD", s.src) + return err +} diff --git a/vendor/github.com/jrallison/go-workers/.travis.yml b/vendor/github.com/jrallison/go-workers/.travis.yml new file mode 100644 index 00000000..141ea53c --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - 1.7 + +script: + - go get github.com/customerio/gospec + - go test -v + +services: + - redis-server diff --git a/vendor/github.com/jrallison/go-workers/LICENSE.txt b/vendor/github.com/jrallison/go-workers/LICENSE.txt new file mode 100644 index 00000000..5972762e --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 John Allison + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/jrallison/go-workers/README.md b/vendor/github.com/jrallison/go-workers/README.md new file mode 100644 index 00000000..0fffa36e --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/README.md @@ -0,0 +1,73 @@ +[![Build Status](https://travis-ci.org/jrallison/go-workers.png)](https://travis-ci.org/jrallison/go-workers) +[![GoDoc](https://godoc.org/github.com/jrallison/go-workers?status.png)](https://godoc.org/github.com/jrallison/go-workers) + +[Sidekiq](http://sidekiq.org/) compatible +background workers in [golang](http://golang.org/). + +* reliable queueing for all queues using [brpoplpush](http://redis.io/commands/brpoplpush) +* handles retries +* support custom middleware +* customize concurrency per queue +* responds to Unix signals to safely wait for jobs to finish before exiting. +* provides stats on what jobs are currently running +* well tested + +Example usage: + +```go +package main + +import ( + "github.com/jrallison/go-workers" +) + +func myJob(message *workers.Msg) { + // do something with your message + // message.Jid() + // message.Args() is a wrapper around go-simplejson (http://godoc.org/github.com/bitly/go-simplejson) +} + +type myMiddleware struct{} + +func (r *myMiddleware) Call(queue string, message *workers.Msg, next func() bool) (acknowledge bool) { + // do something before each message is processed + acknowledge = next() + // do something after each message is processed + return +} + +func main() { + workers.Configure(map[string]string{ + // location of redis instance + "server": "localhost:6379", + // instance of the database + "database": "0", + // number of connections to keep open with redis + "pool": "30", + // unique process id for this instance of workers (for proper recovery of inprogress jobs on crash) + "process": "1", + }) + + workers.Middleware.Append(&myMiddleware{}) + + // pull messages from "myqueue" with concurrency of 10 + workers.Process("myqueue", myJob, 10) + + // pull messages from "myqueue2" with concurrency of 20 + workers.Process("myqueue2", myJob, 20) + + // Add a job to a queue + workers.Enqueue("myqueue3", "Add", []int{1, 2}) + + // Add a job to a queue with retry + workers.EnqueueWithOptions("myqueue3", "Add", []int{1, 2}, workers.EnqueueOptions{Retry: true}) + + // stats will be available at http://localhost:8080/stats + go workers.StatsServer(8080) + + // Blocks until process is told to exit via unix signal + workers.Run() +} +``` + +Initial development sponsored by [Customer.io](http://customer.io) diff --git a/vendor/github.com/jrallison/go-workers/config.go b/vendor/github.com/jrallison/go-workers/config.go new file mode 100644 index 00000000..f93ac94c --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/config.go @@ -0,0 +1,80 @@ +package workers + +import ( + "strconv" + "time" + + "github.com/garyburd/redigo/redis" +) + +type config struct { + processId string + Namespace string + PollInterval int + Pool *redis.Pool + Fetch func(queue string) Fetcher +} + +var Config *config + +func Configure(options map[string]string) { + var poolSize int + var namespace string + var pollInterval int + + if options["server"] == "" { + panic("Configure requires a 'server' option, which identifies a Redis instance") + } + if options["process"] == "" { + panic("Configure requires a 'process' option, which uniquely identifies this instance") + } + if options["pool"] == "" { + options["pool"] = "1" + } + if options["namespace"] != "" { + namespace = options["namespace"] + ":" + } + if seconds, err := strconv.Atoi(options["poll_interval"]); err == nil { + pollInterval = seconds + } else { + pollInterval = 15 + } + + poolSize, _ = strconv.Atoi(options["pool"]) + + Config = &config{ + options["process"], + namespace, + pollInterval, + &redis.Pool{ + MaxIdle: poolSize, + IdleTimeout: 240 * time.Second, + Dial: func() (redis.Conn, error) { + c, err := redis.Dial("tcp", options["server"]) + if err != nil { + return nil, err + } + if options["password"] != "" { + if _, err := c.Do("AUTH", options["password"]); err != nil { + c.Close() + return nil, err + } + } + if options["database"] != "" { + if _, err := c.Do("SELECT", options["database"]); err != nil { + c.Close() + return nil, err + } + } + return c, err + }, + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err + }, + }, + func(queue string) Fetcher { + return NewFetch(queue, make(chan *Msg), make(chan bool)) + }, + } +} diff --git a/vendor/github.com/jrallison/go-workers/enqueue.go b/vendor/github.com/jrallison/go-workers/enqueue.go new file mode 100644 index 00000000..f9af7a88 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/enqueue.go @@ -0,0 +1,114 @@ +package workers + +import ( + "crypto/rand" + "encoding/json" + "fmt" + "io" + "time" +) + +const ( + NanoSecondPrecision = 1000000000.0 +) + +type EnqueueData struct { + Queue string `json:"queue,omitempty"` + Class string `json:"class"` + Args interface{} `json:"args"` + Jid string `json:"jid"` + EnqueuedAt float64 `json:"enqueued_at"` + EnqueueOptions +} + +type EnqueueOptions struct { + RetryCount int `json:"retry_count,omitempty"` + Retry bool `json:"retry,omitempty"` + At float64 `json:"at,omitempty"` +} + +func generateJid() string { + // Return 12 random bytes as 24 character hex + b := make([]byte, 12) + _, err := io.ReadFull(rand.Reader, b) + if err != nil { + return "" + } + return fmt.Sprintf("%x", b) +} + +func Enqueue(queue, class string, args interface{}) (string, error) { + return EnqueueWithOptions(queue, class, args, EnqueueOptions{At: nowToSecondsWithNanoPrecision()}) +} + +func EnqueueIn(queue, class string, in float64, args interface{}) (string, error) { + return EnqueueWithOptions(queue, class, args, EnqueueOptions{At: nowToSecondsWithNanoPrecision() + in}) +} + +func EnqueueAt(queue, class string, at time.Time, args interface{}) (string, error) { + return EnqueueWithOptions(queue, class, args, EnqueueOptions{At: timeToSecondsWithNanoPrecision(at)}) +} + +func EnqueueWithOptions(queue, class string, args interface{}, opts EnqueueOptions) (string, error) { + now := nowToSecondsWithNanoPrecision() + data := EnqueueData{ + Queue: queue, + Class: class, + Args: args, + Jid: generateJid(), + EnqueuedAt: now, + EnqueueOptions: opts, + } + + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + + if now < opts.At { + err := enqueueAt(data.At, bytes) + return data.Jid, err + } + + conn := Config.Pool.Get() + defer conn.Close() + + _, err = conn.Do("sadd", Config.Namespace+"queues", queue) + if err != nil { + return "", err + } + queue = Config.Namespace + "queue:" + queue + _, err = conn.Do("rpush", queue, bytes) + if err != nil { + return "", err + } + + return data.Jid, nil +} + +func enqueueAt(at float64, bytes []byte) error { + conn := Config.Pool.Get() + defer conn.Close() + + _, err := conn.Do( + "zadd", + Config.Namespace+SCHEDULED_JOBS_KEY, at, bytes, + ) + if err != nil { + return err + } + + return nil +} + +func timeToSecondsWithNanoPrecision(t time.Time) float64 { + return float64(t.UnixNano()) / NanoSecondPrecision +} + +func durationToSecondsWithNanoPrecision(d time.Duration) float64 { + return float64(d.Nanoseconds()) / NanoSecondPrecision +} + +func nowToSecondsWithNanoPrecision() float64 { + return timeToSecondsWithNanoPrecision(time.Now()) +} diff --git a/vendor/github.com/jrallison/go-workers/fetcher.go b/vendor/github.com/jrallison/go-workers/fetcher.go new file mode 100644 index 00000000..2745a800 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/fetcher.go @@ -0,0 +1,156 @@ +package workers + +import ( + "fmt" + "time" + + "github.com/garyburd/redigo/redis" +) + +type Fetcher interface { + Queue() string + Fetch() + Acknowledge(*Msg) + Ready() chan bool + FinishedWork() chan bool + Messages() chan *Msg + Close() + Closed() bool +} + +type fetch struct { + queue string + ready chan bool + finishedwork chan bool + messages chan *Msg + stop chan bool + exit chan bool + closed chan bool +} + +func NewFetch(queue string, messages chan *Msg, ready chan bool) Fetcher { + return &fetch{ + queue, + ready, + make(chan bool), + messages, + make(chan bool), + make(chan bool), + make(chan bool), + } +} + +func (f *fetch) Queue() string { + return f.queue +} + +func (f *fetch) processOldMessages() { + messages := f.inprogressMessages() + + for _, message := range messages { + <-f.Ready() + f.sendMessage(message) + } +} + +func (f *fetch) Fetch() { + f.processOldMessages() + + go func() { + for { + // f.Close() has been called + if f.Closed() { + break + } + <-f.Ready() + f.tryFetchMessage() + } + }() + + for { + select { + case <-f.stop: + // Stop the redis-polling goroutine + close(f.closed) + // Signal to Close() that the fetcher has stopped + close(f.exit) + break + } + } +} + +func (f *fetch) tryFetchMessage() { + conn := Config.Pool.Get() + defer conn.Close() + + message, err := redis.String(conn.Do("brpoplpush", f.queue, f.inprogressQueue(), 1)) + + if err != nil { + // If redis returns null, the queue is empty. Just ignore the error. + if err.Error() != "redigo: nil returned" { + Logger.Println("ERR: ", err) + time.Sleep(1 * time.Second) + } + } else { + f.sendMessage(message) + } +} + +func (f *fetch) sendMessage(message string) { + msg, err := NewMsg(message) + + if err != nil { + Logger.Println("ERR: Couldn't create message from", message, ":", err) + return + } + + f.Messages() <- msg +} + +func (f *fetch) Acknowledge(message *Msg) { + conn := Config.Pool.Get() + defer conn.Close() + conn.Do("lrem", f.inprogressQueue(), -1, message.OriginalJson()) +} + +func (f *fetch) Messages() chan *Msg { + return f.messages +} + +func (f *fetch) Ready() chan bool { + return f.ready +} + +func (f *fetch) FinishedWork() chan bool { + return f.finishedwork +} + +func (f *fetch) Close() { + f.stop <- true + <-f.exit +} + +func (f *fetch) Closed() bool { + select { + case <-f.closed: + return true + default: + return false + } +} + +func (f *fetch) inprogressMessages() []string { + conn := Config.Pool.Get() + defer conn.Close() + + messages, err := redis.Strings(conn.Do("lrange", f.inprogressQueue(), 0, -1)) + if err != nil { + Logger.Println("ERR: ", err) + } + + return messages +} + +func (f *fetch) inprogressQueue() string { + return fmt.Sprint(f.queue, ":", Config.processId, ":inprogress") +} diff --git a/vendor/github.com/jrallison/go-workers/hooks.go b/vendor/github.com/jrallison/go-workers/hooks.go new file mode 100644 index 00000000..aa01fa14 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/hooks.go @@ -0,0 +1,26 @@ +package workers + +var beforeStart []func() +var duringDrain []func() + +func BeforeStart(f func()) { + access.Lock() + defer access.Unlock() + beforeStart = append(beforeStart, f) +} + +// func AfterStart +// func BeforeQuit +// func AfterQuit + +func DuringDrain(f func()) { + access.Lock() + defer access.Unlock() + duringDrain = append(duringDrain, f) +} + +func runHooks(hooks []func()) { + for _, f := range hooks { + f() + } +} diff --git a/vendor/github.com/jrallison/go-workers/job.go b/vendor/github.com/jrallison/go-workers/job.go new file mode 100644 index 00000000..de8fd75f --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/job.go @@ -0,0 +1,3 @@ +package workers + +type jobFunc func(message *Msg) diff --git a/vendor/github.com/jrallison/go-workers/manager.go b/vendor/github.com/jrallison/go-workers/manager.go new file mode 100644 index 00000000..0e4ee6ff --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/manager.go @@ -0,0 +1,123 @@ +package workers + +import ( + "strings" + "sync" +) + +type manager struct { + queue string + fetch Fetcher + job jobFunc + concurrency int + workers []*worker + workersM *sync.Mutex + confirm chan *Msg + stop chan bool + exit chan bool + mids *Middlewares + *sync.WaitGroup +} + +func (m *manager) start() { + m.Add(1) + m.loadWorkers() + go m.manage() +} + +func (m *manager) prepare() { + if !m.fetch.Closed() { + m.fetch.Close() + } +} + +func (m *manager) quit() { + Logger.Println("quitting queue", m.queueName(), "(waiting for", m.processing(), "/", len(m.workers), "workers).") + m.prepare() + + m.workersM.Lock() + for _, worker := range m.workers { + worker.quit() + } + m.workersM.Unlock() + + m.stop <- true + <-m.exit + + m.reset() + + m.Done() +} + +func (m *manager) manage() { + Logger.Println("processing queue", m.queueName(), "with", m.concurrency, "workers.") + + go m.fetch.Fetch() + + for { + select { + case message := <-m.confirm: + m.fetch.Acknowledge(message) + case <-m.stop: + m.exit <- true + break + } + } +} + +func (m *manager) loadWorkers() { + m.workersM.Lock() + for i := 0; i < m.concurrency; i++ { + m.workers[i] = newWorker(m) + m.workers[i].start() + } + m.workersM.Unlock() +} + +func (m *manager) processing() (count int) { + m.workersM.Lock() + for _, worker := range m.workers { + if worker.processing() { + count++ + } + } + m.workersM.Unlock() + return +} + +func (m *manager) queueName() string { + return strings.Replace(m.queue, "queue:", "", 1) +} + +func (m *manager) reset() { + m.fetch = Config.Fetch(m.queue) +} + +func newManager(queue string, job jobFunc, concurrency int, mids ...Action) *manager { + var customMids *Middlewares + if len(mids) == 0 { + customMids = Middleware + } else { + customMids = NewMiddleware(Middleware.actions...) + for _, m := range mids { + customMids.Append(m) + } + } + m := &manager{ + Config.Namespace + "queue:" + queue, + nil, + job, + concurrency, + make([]*worker, concurrency), + &sync.Mutex{}, + make(chan *Msg), + make(chan bool), + make(chan bool), + customMids, + &sync.WaitGroup{}, + } + + m.fetch = Config.Fetch(m.queue) + + return m +} diff --git a/vendor/github.com/jrallison/go-workers/middleware.go b/vendor/github.com/jrallison/go-workers/middleware.go new file mode 100644 index 00000000..417d5754 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/middleware.go @@ -0,0 +1,48 @@ +package workers + +type Action interface { + Call(queue string, message *Msg, next func() bool) bool +} + +type Middlewares struct { + actions []Action +} + +func (m *Middlewares) Append(action Action) { + m.actions = append(m.actions, action) +} + +func (m *Middlewares) Prepend(action Action) { + actions := make([]Action, len(m.actions)+1) + actions[0] = action + copy(actions[1:], m.actions) + m.actions = actions +} + +func (m *Middlewares) call(queue string, message *Msg, final func()) bool { + return continuation(m.actions, queue, message, final)() +} + +func continuation(actions []Action, queue string, message *Msg, final func()) func() bool { + return func() (acknowledge bool) { + if len(actions) > 0 { + acknowledge = actions[0].Call( + queue, + message, + continuation(actions[1:], queue, message, final), + ) + + if !acknowledge { + return + } + } else { + final() + } + + return true + } +} + +func NewMiddleware(actions ...Action) *Middlewares { + return &Middlewares{actions} +} diff --git a/vendor/github.com/jrallison/go-workers/middleware_logging.go b/vendor/github.com/jrallison/go-workers/middleware_logging.go new file mode 100644 index 00000000..11eaa13c --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/middleware_logging.go @@ -0,0 +1,35 @@ +package workers + +import ( + "fmt" + "runtime" + "time" +) + +type MiddlewareLogging struct{} + +func (l *MiddlewareLogging) Call(queue string, message *Msg, next func() bool) (acknowledge bool) { + prefix := fmt.Sprint(queue, " JID-", message.Jid()) + + start := time.Now() + Logger.Println(prefix, "start") + Logger.Println(prefix, "args:", message.Args().ToJson()) + + defer func() { + if e := recover(); e != nil { + Logger.Println(prefix, "fail:", time.Since(start)) + + buf := make([]byte, 4096) + buf = buf[:runtime.Stack(buf, false)] + Logger.Printf("%s error: %v\n%s", prefix, e, buf) + + panic(e) + } + }() + + acknowledge = next() + + Logger.Println(prefix, "done:", time.Since(start)) + + return +} diff --git a/vendor/github.com/jrallison/go-workers/middleware_retry.go b/vendor/github.com/jrallison/go-workers/middleware_retry.go new file mode 100644 index 00000000..1bc1ccfd --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/middleware_retry.go @@ -0,0 +1,92 @@ +package workers + +import ( + "fmt" + "math" + "math/rand" + "time" +) + +const ( + DEFAULT_MAX_RETRY = 25 + LAYOUT = "2006-01-02 15:04:05 MST" +) + +type MiddlewareRetry struct{} + +func (r *MiddlewareRetry) Call(queue string, message *Msg, next func() bool) (acknowledge bool) { + defer func() { + if e := recover(); e != nil { + conn := Config.Pool.Get() + defer conn.Close() + + if retry(message) { + message.Set("queue", queue) + message.Set("error_message", fmt.Sprintf("%v", e)) + retryCount := incrementRetry(message) + + waitDuration := durationToSecondsWithNanoPrecision( + time.Duration( + secondsToDelay(retryCount), + ) * time.Second, + ) + + _, err := conn.Do( + "zadd", + Config.Namespace+RETRY_KEY, + nowToSecondsWithNanoPrecision()+waitDuration, + message.ToJson(), + ) + + // If we can't add the job to the retry queue, + // then we shouldn't acknowledge the job, otherwise + // it'll disappear into the void. + if err != nil { + acknowledge = false + } + } + + panic(e) + } + }() + + acknowledge = next() + + return +} + +func retry(message *Msg) bool { + retry := false + max := DEFAULT_MAX_RETRY + + if param, err := message.Get("retry").Bool(); err == nil { + retry = param + } else if param, err := message.Get("retry").Int(); err == nil { + max = param + retry = true + } + + count, _ := message.Get("retry_count").Int() + + return retry && count < max +} + +func incrementRetry(message *Msg) (retryCount int) { + retryCount = 0 + + if count, err := message.Get("retry_count").Int(); err != nil { + message.Set("failed_at", time.Now().UTC().Format(LAYOUT)) + } else { + message.Set("retried_at", time.Now().UTC().Format(LAYOUT)) + retryCount = count + 1 + } + + message.Set("retry_count", retryCount) + + return +} + +func secondsToDelay(count int) int { + power := math.Pow(float64(count), 4) + return int(power) + 15 + (rand.Intn(30) * (count + 1)) +} diff --git a/vendor/github.com/jrallison/go-workers/middleware_stats.go b/vendor/github.com/jrallison/go-workers/middleware_stats.go new file mode 100644 index 00000000..62a76635 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/middleware_stats.go @@ -0,0 +1,37 @@ +package workers + +import ( + "time" +) + +type MiddlewareStats struct{} + +func (l *MiddlewareStats) Call(queue string, message *Msg, next func() bool) (acknowledge bool) { + defer func() { + if e := recover(); e != nil { + incrementStats("failed") + panic(e) + } + }() + + acknowledge = next() + + incrementStats("processed") + + return +} + +func incrementStats(metric string) { + conn := Config.Pool.Get() + defer conn.Close() + + today := time.Now().UTC().Format("2006-01-02") + + conn.Send("multi") + conn.Send("incr", Config.Namespace+"stat:"+metric) + conn.Send("incr", Config.Namespace+"stat:"+metric+":"+today) + + if _, err := conn.Do("exec"); err != nil { + Logger.Println("couldn't save stats:", err) + } +} diff --git a/vendor/github.com/jrallison/go-workers/msg.go b/vendor/github.com/jrallison/go-workers/msg.go new file mode 100644 index 00000000..f922fa02 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/msg.go @@ -0,0 +1,67 @@ +package workers + +import ( + "github.com/bitly/go-simplejson" + "reflect" +) + +type data struct { + *simplejson.Json +} + +type Msg struct { + *data + original string +} + +type Args struct { + *data +} + +func (m *Msg) Jid() string { + return m.Get("jid").MustString() +} + +func (m *Msg) Args() *Args { + if args, ok := m.CheckGet("args"); ok { + return &Args{&data{args}} + } else { + d, _ := newData("[]") + return &Args{d} + } +} + +func (m *Msg) OriginalJson() string { + return m.original +} + +func (d *data) ToJson() string { + json, err := d.Encode() + + if err != nil { + Logger.Println("ERR: Couldn't generate json from", d, ":", err) + } + + return string(json) +} + +func (d *data) Equals(other interface{}) bool { + otherJson := reflect.ValueOf(other).MethodByName("ToJson").Call([]reflect.Value{}) + return d.ToJson() == otherJson[0].String() +} + +func NewMsg(content string) (*Msg, error) { + if d, err := newData(content); err != nil { + return nil, err + } else { + return &Msg{d, content}, nil + } +} + +func newData(content string) (*data, error) { + if json, err := simplejson.NewJson([]byte(content)); err != nil { + return nil, err + } else { + return &data{json}, nil + } +} diff --git a/vendor/github.com/jrallison/go-workers/scheduled.go b/vendor/github.com/jrallison/go-workers/scheduled.go new file mode 100644 index 00000000..a7b08fa4 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/scheduled.go @@ -0,0 +1,66 @@ +package workers + +import ( + "strings" + "time" + + "github.com/garyburd/redigo/redis" +) + +type scheduled struct { + keys []string + closed chan bool + exit chan bool +} + +func (s *scheduled) start() { + go (func() { + for { + select { + case <-s.closed: + return + default: + } + + s.poll() + + time.Sleep(time.Duration(Config.PollInterval) * time.Second) + } + })() +} + +func (s *scheduled) quit() { + close(s.closed) +} + +func (s *scheduled) poll() { + conn := Config.Pool.Get() + + now := nowToSecondsWithNanoPrecision() + + for _, key := range s.keys { + key = Config.Namespace + key + for { + messages, _ := redis.Strings(conn.Do("zrangebyscore", key, "-inf", now, "limit", 0, 1)) + + if len(messages) == 0 { + break + } + + message, _ := NewMsg(messages[0]) + + if removed, _ := redis.Bool(conn.Do("zrem", key, messages[0])); removed { + queue, _ := message.Get("queue").String() + queue = strings.TrimPrefix(queue, Config.Namespace) + message.Set("enqueued_at", nowToSecondsWithNanoPrecision()) + conn.Do("lpush", Config.Namespace+"queue:"+queue, message.ToJson()) + } + } + } + + conn.Close() +} + +func newScheduled(keys ...string) *scheduled { + return &scheduled{keys, make(chan bool), make(chan bool)} +} diff --git a/vendor/github.com/jrallison/go-workers/signals_posix.go b/vendor/github.com/jrallison/go-workers/signals_posix.go new file mode 100644 index 00000000..28fcb3d5 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/signals_posix.go @@ -0,0 +1,21 @@ +// +build !windows + +package workers + +import ( + "os" + "os/signal" + "syscall" +) + +func handleSignals() { + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGUSR1, syscall.SIGINT, syscall.SIGTERM) + + for sig := range signals { + switch sig { + case syscall.SIGINT, syscall.SIGUSR1, syscall.SIGTERM: + Quit() + } + } +} diff --git a/vendor/github.com/jrallison/go-workers/signals_windows.go b/vendor/github.com/jrallison/go-workers/signals_windows.go new file mode 100644 index 00000000..0b6c3980 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/signals_windows.go @@ -0,0 +1,19 @@ +package workers + +import ( + "os" + "os/signal" + "syscall" +) + +func handleSignals() { + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) + + for sig := range signals { + switch sig { + case syscall.SIGINT, syscall.SIGTERM: + Quit() + } + } +} diff --git a/vendor/github.com/jrallison/go-workers/stats.go b/vendor/github.com/jrallison/go-workers/stats.go new file mode 100644 index 00000000..d62864e2 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/stats.go @@ -0,0 +1,97 @@ +package workers + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +type stats struct { + Processed int `json:"processed"` + Failed int `json:"failed"` + Jobs interface{} `json:"jobs"` + Enqueued interface{} `json:"enqueued"` + Retries int64 `json:"retries"` +} + +func Stats(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("Access-Control-Allow-Origin", "*") + + jobs := make(map[string][]*map[string]interface{}) + enqueued := make(map[string]string) + + for _, m := range managers { + queue := m.queueName() + jobs[queue] = make([]*map[string]interface{}, 0) + enqueued[queue] = "" + for _, worker := range m.workers { + message := worker.currentMsg + startedAt := worker.startedAt + + if message != nil && startedAt > 0 { + jobs[queue] = append(jobs[queue], &map[string]interface{}{ + "message": message, + "started_at": startedAt, + }) + } + } + } + + stats := stats{ + 0, + 0, + jobs, + enqueued, + 0, + } + + conn := Config.Pool.Get() + defer conn.Close() + + conn.Send("multi") + conn.Send("get", Config.Namespace+"stat:processed") + conn.Send("get", Config.Namespace+"stat:failed") + conn.Send("zcard", Config.Namespace+RETRY_KEY) + + for key, _ := range enqueued { + conn.Send("llen", fmt.Sprintf("%squeue:%s", Config.Namespace, key)) + } + + r, err := conn.Do("exec") + + if err != nil { + Logger.Println("couldn't retrieve stats:", err) + } + + results := r.([]interface{}) + if len(results) == (3 + len(enqueued)) { + for index, result := range results { + if index == 0 && result != nil { + stats.Processed, _ = strconv.Atoi(string(result.([]byte))) + continue + } + if index == 1 && result != nil { + stats.Failed, _ = strconv.Atoi(string(result.([]byte))) + continue + } + + if index == 2 && result != nil { + stats.Retries = result.(int64) + continue + } + + queueIndex := 0 + for key, _ := range enqueued { + if queueIndex == (index - 3) { + enqueued[key] = fmt.Sprintf("%d", result.(int64)) + } + queueIndex++ + } + } + } + + body, _ := json.MarshalIndent(stats, "", " ") + fmt.Fprintln(w, string(body)) +} diff --git a/vendor/github.com/jrallison/go-workers/worker.go b/vendor/github.com/jrallison/go-workers/worker.go new file mode 100644 index 00000000..6dfc37b1 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/worker.go @@ -0,0 +1,75 @@ +package workers + +import ( + "sync/atomic" + "time" +) + +type worker struct { + manager *manager + stop chan bool + exit chan bool + currentMsg *Msg + startedAt int64 +} + +func (w *worker) start() { + go w.work(w.manager.fetch.Messages()) +} + +func (w *worker) quit() { + w.stop <- true + <-w.exit +} + +func (w *worker) work(messages chan *Msg) { + for { + select { + case message := <-messages: + atomic.StoreInt64(&w.startedAt, time.Now().UTC().Unix()) + w.currentMsg = message + + if w.process(message) { + w.manager.confirm <- message + } + + atomic.StoreInt64(&w.startedAt, 0) + w.currentMsg = nil + + // Attempt to tell fetcher we're finished. + // Can be used when the fetcher has slept due + // to detecting an empty queue to requery the + // queue immediately if we finish work. + select { + case w.manager.fetch.FinishedWork() <- true: + default: + } + case w.manager.fetch.Ready() <- true: + // Signaled to fetcher that we're + // ready to accept a message + case <-w.stop: + w.exit <- true + return + } + } +} + +func (w *worker) process(message *Msg) (acknowledge bool) { + acknowledge = true + + defer func() { + recover() + }() + + return w.manager.mids.call(w.manager.queueName(), message, func() { + w.manager.job(message) + }) +} + +func (w *worker) processing() bool { + return atomic.LoadInt64(&w.startedAt) > 0 +} + +func newWorker(m *manager) *worker { + return &worker{m, make(chan bool), make(chan bool), nil, 0} +} diff --git a/vendor/github.com/jrallison/go-workers/workers.go b/vendor/github.com/jrallison/go-workers/workers.go new file mode 100644 index 00000000..fc39be62 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/workers.go @@ -0,0 +1,129 @@ +package workers + +import ( + "errors" + "fmt" + "log" + "net/http" + "os" + "sync" +) + +const ( + RETRY_KEY = "goretry" + SCHEDULED_JOBS_KEY = "schedule" +) + +var Logger WorkersLogger = log.New(os.Stdout, "workers: ", log.Ldate|log.Lmicroseconds) + +var managers = make(map[string]*manager) +var schedule *scheduled +var control = make(map[string]chan string) +var access sync.Mutex +var started bool + +var Middleware = NewMiddleware( + &MiddlewareLogging{}, + &MiddlewareRetry{}, + &MiddlewareStats{}, +) + +func Process(queue string, job jobFunc, concurrency int, mids ...Action) { + access.Lock() + defer access.Unlock() + + managers[queue] = newManager(queue, job, concurrency, mids...) +} + +func Run() { + Start() + go handleSignals() + waitForExit() +} + +func ResetManagers() error { + access.Lock() + defer access.Unlock() + + if started { + return errors.New("Cannot reset worker managers while workers are running") + } + + managers = make(map[string]*manager) + + return nil +} + +func Start() { + access.Lock() + defer access.Unlock() + + if started { + return + } + + runHooks(beforeStart) + startSchedule() + startManagers() + + started = true +} + +func Quit() { + access.Lock() + defer access.Unlock() + + if !started { + return + } + + quitManagers() + quitSchedule() + runHooks(duringDrain) + waitForExit() + + started = false +} + +func StatsServer(port int) { + http.HandleFunc("/stats", Stats) + + Logger.Println("Stats are available at", fmt.Sprint("http://localhost:", port, "/stats")) + + if err := http.ListenAndServe(fmt.Sprint(":", port), nil); err != nil { + Logger.Println(err) + } +} + +func startSchedule() { + if schedule == nil { + schedule = newScheduled(RETRY_KEY, SCHEDULED_JOBS_KEY) + } + + schedule.start() +} + +func quitSchedule() { + if schedule != nil { + schedule.quit() + schedule = nil + } +} + +func startManagers() { + for _, manager := range managers { + manager.start() + } +} + +func quitManagers() { + for _, m := range managers { + go (func(m *manager) { m.quit() })(m) + } +} + +func waitForExit() { + for _, manager := range managers { + manager.Wait() + } +} diff --git a/vendor/github.com/jrallison/go-workers/workers_logger.go b/vendor/github.com/jrallison/go-workers/workers_logger.go new file mode 100644 index 00000000..cab91b16 --- /dev/null +++ b/vendor/github.com/jrallison/go-workers/workers_logger.go @@ -0,0 +1,6 @@ +package workers + +type WorkersLogger interface { + Println(...interface{}) + Printf(string, ...interface{}) +} diff --git a/vendor/github.com/pmezard/go-difflib/LICENSE b/vendor/github.com/pmezard/go-difflib/LICENSE new file mode 100644 index 00000000..c67dad61 --- /dev/null +++ b/vendor/github.com/pmezard/go-difflib/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013, Patrick Mezard +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + The names of its contributors may not be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pmezard/go-difflib/difflib/difflib.go b/vendor/github.com/pmezard/go-difflib/difflib/difflib.go new file mode 100644 index 00000000..003e99fa --- /dev/null +++ b/vendor/github.com/pmezard/go-difflib/difflib/difflib.go @@ -0,0 +1,772 @@ +// Package difflib is a partial port of Python difflib module. +// +// It provides tools to compare sequences of strings and generate textual diffs. +// +// The following class and functions have been ported: +// +// - SequenceMatcher +// +// - unified_diff +// +// - context_diff +// +// Getting unified diffs was the main goal of the port. Keep in mind this code +// is mostly suitable to output text differences in a human friendly way, there +// are no guarantees generated diffs are consumable by patch(1). +package difflib + +import ( + "bufio" + "bytes" + "fmt" + "io" + "strings" +) + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func calculateRatio(matches, length int) float64 { + if length > 0 { + return 2.0 * float64(matches) / float64(length) + } + return 1.0 +} + +type Match struct { + A int + B int + Size int +} + +type OpCode struct { + Tag byte + I1 int + I2 int + J1 int + J2 int +} + +// SequenceMatcher compares sequence of strings. The basic +// algorithm predates, and is a little fancier than, an algorithm +// published in the late 1980's by Ratcliff and Obershelp under the +// hyperbolic name "gestalt pattern matching". The basic idea is to find +// the longest contiguous matching subsequence that contains no "junk" +// elements (R-O doesn't address junk). The same idea is then applied +// recursively to the pieces of the sequences to the left and to the right +// of the matching subsequence. This does not yield minimal edit +// sequences, but does tend to yield matches that "look right" to people. +// +// SequenceMatcher tries to compute a "human-friendly diff" between two +// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the +// longest *contiguous* & junk-free matching subsequence. That's what +// catches peoples' eyes. The Windows(tm) windiff has another interesting +// notion, pairing up elements that appear uniquely in each sequence. +// That, and the method here, appear to yield more intuitive difference +// reports than does diff. This method appears to be the least vulnerable +// to synching up on blocks of "junk lines", though (like blank lines in +// ordinary text files, or maybe "

" lines in HTML files). That may be +// because this is the only method of the 3 that has a *concept* of +// "junk" . +// +// Timing: Basic R-O is cubic time worst case and quadratic time expected +// case. SequenceMatcher is quadratic time for the worst case and has +// expected-case behavior dependent in a complicated way on how many +// elements the sequences have in common; best case time is linear. +type SequenceMatcher struct { + a []string + b []string + b2j map[string][]int + IsJunk func(string) bool + autoJunk bool + bJunk map[string]struct{} + matchingBlocks []Match + fullBCount map[string]int + bPopular map[string]struct{} + opCodes []OpCode +} + +func NewMatcher(a, b []string) *SequenceMatcher { + m := SequenceMatcher{autoJunk: true} + m.SetSeqs(a, b) + return &m +} + +func NewMatcherWithJunk(a, b []string, autoJunk bool, + isJunk func(string) bool) *SequenceMatcher { + + m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk} + m.SetSeqs(a, b) + return &m +} + +// Set two sequences to be compared. +func (m *SequenceMatcher) SetSeqs(a, b []string) { + m.SetSeq1(a) + m.SetSeq2(b) +} + +// Set the first sequence to be compared. The second sequence to be compared is +// not changed. +// +// SequenceMatcher computes and caches detailed information about the second +// sequence, so if you want to compare one sequence S against many sequences, +// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other +// sequences. +// +// See also SetSeqs() and SetSeq2(). +func (m *SequenceMatcher) SetSeq1(a []string) { + if &a == &m.a { + return + } + m.a = a + m.matchingBlocks = nil + m.opCodes = nil +} + +// Set the second sequence to be compared. The first sequence to be compared is +// not changed. +func (m *SequenceMatcher) SetSeq2(b []string) { + if &b == &m.b { + return + } + m.b = b + m.matchingBlocks = nil + m.opCodes = nil + m.fullBCount = nil + m.chainB() +} + +func (m *SequenceMatcher) chainB() { + // Populate line -> index mapping + b2j := map[string][]int{} + for i, s := range m.b { + indices := b2j[s] + indices = append(indices, i) + b2j[s] = indices + } + + // Purge junk elements + m.bJunk = map[string]struct{}{} + if m.IsJunk != nil { + junk := m.bJunk + for s, _ := range b2j { + if m.IsJunk(s) { + junk[s] = struct{}{} + } + } + for s, _ := range junk { + delete(b2j, s) + } + } + + // Purge remaining popular elements + popular := map[string]struct{}{} + n := len(m.b) + if m.autoJunk && n >= 200 { + ntest := n/100 + 1 + for s, indices := range b2j { + if len(indices) > ntest { + popular[s] = struct{}{} + } + } + for s, _ := range popular { + delete(b2j, s) + } + } + m.bPopular = popular + m.b2j = b2j +} + +func (m *SequenceMatcher) isBJunk(s string) bool { + _, ok := m.bJunk[s] + return ok +} + +// Find longest matching block in a[alo:ahi] and b[blo:bhi]. +// +// If IsJunk is not defined: +// +// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where +// alo <= i <= i+k <= ahi +// blo <= j <= j+k <= bhi +// and for all (i',j',k') meeting those conditions, +// k >= k' +// i <= i' +// and if i == i', j <= j' +// +// In other words, of all maximal matching blocks, return one that +// starts earliest in a, and of all those maximal matching blocks that +// start earliest in a, return the one that starts earliest in b. +// +// If IsJunk is defined, first the longest matching block is +// determined as above, but with the additional restriction that no +// junk element appears in the block. Then that block is extended as +// far as possible by matching (only) junk elements on both sides. So +// the resulting block never matches on junk except as identical junk +// happens to be adjacent to an "interesting" match. +// +// If no blocks match, return (alo, blo, 0). +func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match { + // CAUTION: stripping common prefix or suffix would be incorrect. + // E.g., + // ab + // acab + // Longest matching block is "ab", but if common prefix is + // stripped, it's "a" (tied with "b"). UNIX(tm) diff does so + // strip, so ends up claiming that ab is changed to acab by + // inserting "ca" in the middle. That's minimal but unintuitive: + // "it's obvious" that someone inserted "ac" at the front. + // Windiff ends up at the same place as diff, but by pairing up + // the unique 'b's and then matching the first two 'a's. + besti, bestj, bestsize := alo, blo, 0 + + // find longest junk-free match + // during an iteration of the loop, j2len[j] = length of longest + // junk-free match ending with a[i-1] and b[j] + j2len := map[int]int{} + for i := alo; i != ahi; i++ { + // look at all instances of a[i] in b; note that because + // b2j has no junk keys, the loop is skipped if a[i] is junk + newj2len := map[int]int{} + for _, j := range m.b2j[m.a[i]] { + // a[i] matches b[j] + if j < blo { + continue + } + if j >= bhi { + break + } + k := j2len[j-1] + 1 + newj2len[j] = k + if k > bestsize { + besti, bestj, bestsize = i-k+1, j-k+1, k + } + } + j2len = newj2len + } + + // Extend the best by non-junk elements on each end. In particular, + // "popular" non-junk elements aren't in b2j, which greatly speeds + // the inner loop above, but also means "the best" match so far + // doesn't contain any junk *or* popular non-junk elements. + for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) && + m.a[besti-1] == m.b[bestj-1] { + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + } + for besti+bestsize < ahi && bestj+bestsize < bhi && + !m.isBJunk(m.b[bestj+bestsize]) && + m.a[besti+bestsize] == m.b[bestj+bestsize] { + bestsize += 1 + } + + // Now that we have a wholly interesting match (albeit possibly + // empty!), we may as well suck up the matching junk on each + // side of it too. Can't think of a good reason not to, and it + // saves post-processing the (possibly considerable) expense of + // figuring out what to do with it. In the case of an empty + // interesting match, this is clearly the right thing to do, + // because no other kind of match is possible in the regions. + for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) && + m.a[besti-1] == m.b[bestj-1] { + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + } + for besti+bestsize < ahi && bestj+bestsize < bhi && + m.isBJunk(m.b[bestj+bestsize]) && + m.a[besti+bestsize] == m.b[bestj+bestsize] { + bestsize += 1 + } + + return Match{A: besti, B: bestj, Size: bestsize} +} + +// Return list of triples describing matching subsequences. +// +// Each triple is of the form (i, j, n), and means that +// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in +// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are +// adjacent triples in the list, and the second is not the last triple in the +// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe +// adjacent equal blocks. +// +// The last triple is a dummy, (len(a), len(b), 0), and is the only +// triple with n==0. +func (m *SequenceMatcher) GetMatchingBlocks() []Match { + if m.matchingBlocks != nil { + return m.matchingBlocks + } + + var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match + matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match { + match := m.findLongestMatch(alo, ahi, blo, bhi) + i, j, k := match.A, match.B, match.Size + if match.Size > 0 { + if alo < i && blo < j { + matched = matchBlocks(alo, i, blo, j, matched) + } + matched = append(matched, match) + if i+k < ahi && j+k < bhi { + matched = matchBlocks(i+k, ahi, j+k, bhi, matched) + } + } + return matched + } + matched := matchBlocks(0, len(m.a), 0, len(m.b), nil) + + // It's possible that we have adjacent equal blocks in the + // matching_blocks list now. + nonAdjacent := []Match{} + i1, j1, k1 := 0, 0, 0 + for _, b := range matched { + // Is this block adjacent to i1, j1, k1? + i2, j2, k2 := b.A, b.B, b.Size + if i1+k1 == i2 && j1+k1 == j2 { + // Yes, so collapse them -- this just increases the length of + // the first block by the length of the second, and the first + // block so lengthened remains the block to compare against. + k1 += k2 + } else { + // Not adjacent. Remember the first block (k1==0 means it's + // the dummy we started with), and make the second block the + // new block to compare against. + if k1 > 0 { + nonAdjacent = append(nonAdjacent, Match{i1, j1, k1}) + } + i1, j1, k1 = i2, j2, k2 + } + } + if k1 > 0 { + nonAdjacent = append(nonAdjacent, Match{i1, j1, k1}) + } + + nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0}) + m.matchingBlocks = nonAdjacent + return m.matchingBlocks +} + +// Return list of 5-tuples describing how to turn a into b. +// +// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple +// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the +// tuple preceding it, and likewise for j1 == the previous j2. +// +// The tags are characters, with these meanings: +// +// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2] +// +// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case. +// +// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case. +// +// 'e' (equal): a[i1:i2] == b[j1:j2] +func (m *SequenceMatcher) GetOpCodes() []OpCode { + if m.opCodes != nil { + return m.opCodes + } + i, j := 0, 0 + matching := m.GetMatchingBlocks() + opCodes := make([]OpCode, 0, len(matching)) + for _, m := range matching { + // invariant: we've pumped out correct diffs to change + // a[:i] into b[:j], and the next matching block is + // a[ai:ai+size] == b[bj:bj+size]. So we need to pump + // out a diff to change a[i:ai] into b[j:bj], pump out + // the matching block, and move (i,j) beyond the match + ai, bj, size := m.A, m.B, m.Size + tag := byte(0) + if i < ai && j < bj { + tag = 'r' + } else if i < ai { + tag = 'd' + } else if j < bj { + tag = 'i' + } + if tag > 0 { + opCodes = append(opCodes, OpCode{tag, i, ai, j, bj}) + } + i, j = ai+size, bj+size + // the list of matching blocks is terminated by a + // sentinel with size 0 + if size > 0 { + opCodes = append(opCodes, OpCode{'e', ai, i, bj, j}) + } + } + m.opCodes = opCodes + return m.opCodes +} + +// Isolate change clusters by eliminating ranges with no changes. +// +// Return a generator of groups with up to n lines of context. +// Each group is in the same format as returned by GetOpCodes(). +func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode { + if n < 0 { + n = 3 + } + codes := m.GetOpCodes() + if len(codes) == 0 { + codes = []OpCode{OpCode{'e', 0, 1, 0, 1}} + } + // Fixup leading and trailing groups if they show no changes. + if codes[0].Tag == 'e' { + c := codes[0] + i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 + codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2} + } + if codes[len(codes)-1].Tag == 'e' { + c := codes[len(codes)-1] + i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 + codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)} + } + nn := n + n + groups := [][]OpCode{} + group := []OpCode{} + for _, c := range codes { + i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 + // End the current group and start a new one whenever + // there is a large range with no changes. + if c.Tag == 'e' && i2-i1 > nn { + group = append(group, OpCode{c.Tag, i1, min(i2, i1+n), + j1, min(j2, j1+n)}) + groups = append(groups, group) + group = []OpCode{} + i1, j1 = max(i1, i2-n), max(j1, j2-n) + } + group = append(group, OpCode{c.Tag, i1, i2, j1, j2}) + } + if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') { + groups = append(groups, group) + } + return groups +} + +// Return a measure of the sequences' similarity (float in [0,1]). +// +// Where T is the total number of elements in both sequences, and +// M is the number of matches, this is 2.0*M / T. +// Note that this is 1 if the sequences are identical, and 0 if +// they have nothing in common. +// +// .Ratio() is expensive to compute if you haven't already computed +// .GetMatchingBlocks() or .GetOpCodes(), in which case you may +// want to try .QuickRatio() or .RealQuickRation() first to get an +// upper bound. +func (m *SequenceMatcher) Ratio() float64 { + matches := 0 + for _, m := range m.GetMatchingBlocks() { + matches += m.Size + } + return calculateRatio(matches, len(m.a)+len(m.b)) +} + +// Return an upper bound on ratio() relatively quickly. +// +// This isn't defined beyond that it is an upper bound on .Ratio(), and +// is faster to compute. +func (m *SequenceMatcher) QuickRatio() float64 { + // viewing a and b as multisets, set matches to the cardinality + // of their intersection; this counts the number of matches + // without regard to order, so is clearly an upper bound + if m.fullBCount == nil { + m.fullBCount = map[string]int{} + for _, s := range m.b { + m.fullBCount[s] = m.fullBCount[s] + 1 + } + } + + // avail[x] is the number of times x appears in 'b' less the + // number of times we've seen it in 'a' so far ... kinda + avail := map[string]int{} + matches := 0 + for _, s := range m.a { + n, ok := avail[s] + if !ok { + n = m.fullBCount[s] + } + avail[s] = n - 1 + if n > 0 { + matches += 1 + } + } + return calculateRatio(matches, len(m.a)+len(m.b)) +} + +// Return an upper bound on ratio() very quickly. +// +// This isn't defined beyond that it is an upper bound on .Ratio(), and +// is faster to compute than either .Ratio() or .QuickRatio(). +func (m *SequenceMatcher) RealQuickRatio() float64 { + la, lb := len(m.a), len(m.b) + return calculateRatio(min(la, lb), la+lb) +} + +// Convert range to the "ed" format +func formatRangeUnified(start, stop int) string { + // Per the diff spec at http://www.unix.org/single_unix_specification/ + beginning := start + 1 // lines start numbering with one + length := stop - start + if length == 1 { + return fmt.Sprintf("%d", beginning) + } + if length == 0 { + beginning -= 1 // empty ranges begin at line just before the range + } + return fmt.Sprintf("%d,%d", beginning, length) +} + +// Unified diff parameters +type UnifiedDiff struct { + A []string // First sequence lines + FromFile string // First file name + FromDate string // First file time + B []string // Second sequence lines + ToFile string // Second file name + ToDate string // Second file time + Eol string // Headers end of line, defaults to LF + Context int // Number of context lines +} + +// Compare two sequences of lines; generate the delta as a unified diff. +// +// Unified diffs are a compact way of showing line changes and a few +// lines of context. The number of context lines is set by 'n' which +// defaults to three. +// +// By default, the diff control lines (those with ---, +++, or @@) are +// created with a trailing newline. This is helpful so that inputs +// created from file.readlines() result in diffs that are suitable for +// file.writelines() since both the inputs and outputs have trailing +// newlines. +// +// For inputs that do not have trailing newlines, set the lineterm +// argument to "" so that the output will be uniformly newline free. +// +// The unidiff format normally has a header for filenames and modification +// times. Any or all of these may be specified using strings for +// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. +// The modification times are normally expressed in the ISO 8601 format. +func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error { + buf := bufio.NewWriter(writer) + defer buf.Flush() + wf := func(format string, args ...interface{}) error { + _, err := buf.WriteString(fmt.Sprintf(format, args...)) + return err + } + ws := func(s string) error { + _, err := buf.WriteString(s) + return err + } + + if len(diff.Eol) == 0 { + diff.Eol = "\n" + } + + started := false + m := NewMatcher(diff.A, diff.B) + for _, g := range m.GetGroupedOpCodes(diff.Context) { + if !started { + started = true + fromDate := "" + if len(diff.FromDate) > 0 { + fromDate = "\t" + diff.FromDate + } + toDate := "" + if len(diff.ToDate) > 0 { + toDate = "\t" + diff.ToDate + } + if diff.FromFile != "" || diff.ToFile != "" { + err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol) + if err != nil { + return err + } + err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol) + if err != nil { + return err + } + } + } + first, last := g[0], g[len(g)-1] + range1 := formatRangeUnified(first.I1, last.I2) + range2 := formatRangeUnified(first.J1, last.J2) + if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil { + return err + } + for _, c := range g { + i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 + if c.Tag == 'e' { + for _, line := range diff.A[i1:i2] { + if err := ws(" " + line); err != nil { + return err + } + } + continue + } + if c.Tag == 'r' || c.Tag == 'd' { + for _, line := range diff.A[i1:i2] { + if err := ws("-" + line); err != nil { + return err + } + } + } + if c.Tag == 'r' || c.Tag == 'i' { + for _, line := range diff.B[j1:j2] { + if err := ws("+" + line); err != nil { + return err + } + } + } + } + } + return nil +} + +// Like WriteUnifiedDiff but returns the diff a string. +func GetUnifiedDiffString(diff UnifiedDiff) (string, error) { + w := &bytes.Buffer{} + err := WriteUnifiedDiff(w, diff) + return string(w.Bytes()), err +} + +// Convert range to the "ed" format. +func formatRangeContext(start, stop int) string { + // Per the diff spec at http://www.unix.org/single_unix_specification/ + beginning := start + 1 // lines start numbering with one + length := stop - start + if length == 0 { + beginning -= 1 // empty ranges begin at line just before the range + } + if length <= 1 { + return fmt.Sprintf("%d", beginning) + } + return fmt.Sprintf("%d,%d", beginning, beginning+length-1) +} + +type ContextDiff UnifiedDiff + +// Compare two sequences of lines; generate the delta as a context diff. +// +// Context diffs are a compact way of showing line changes and a few +// lines of context. The number of context lines is set by diff.Context +// which defaults to three. +// +// By default, the diff control lines (those with *** or ---) are +// created with a trailing newline. +// +// For inputs that do not have trailing newlines, set the diff.Eol +// argument to "" so that the output will be uniformly newline free. +// +// The context diff format normally has a header for filenames and +// modification times. Any or all of these may be specified using +// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate. +// The modification times are normally expressed in the ISO 8601 format. +// If not specified, the strings default to blanks. +func WriteContextDiff(writer io.Writer, diff ContextDiff) error { + buf := bufio.NewWriter(writer) + defer buf.Flush() + var diffErr error + wf := func(format string, args ...interface{}) { + _, err := buf.WriteString(fmt.Sprintf(format, args...)) + if diffErr == nil && err != nil { + diffErr = err + } + } + ws := func(s string) { + _, err := buf.WriteString(s) + if diffErr == nil && err != nil { + diffErr = err + } + } + + if len(diff.Eol) == 0 { + diff.Eol = "\n" + } + + prefix := map[byte]string{ + 'i': "+ ", + 'd': "- ", + 'r': "! ", + 'e': " ", + } + + started := false + m := NewMatcher(diff.A, diff.B) + for _, g := range m.GetGroupedOpCodes(diff.Context) { + if !started { + started = true + fromDate := "" + if len(diff.FromDate) > 0 { + fromDate = "\t" + diff.FromDate + } + toDate := "" + if len(diff.ToDate) > 0 { + toDate = "\t" + diff.ToDate + } + if diff.FromFile != "" || diff.ToFile != "" { + wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol) + wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol) + } + } + + first, last := g[0], g[len(g)-1] + ws("***************" + diff.Eol) + + range1 := formatRangeContext(first.I1, last.I2) + wf("*** %s ****%s", range1, diff.Eol) + for _, c := range g { + if c.Tag == 'r' || c.Tag == 'd' { + for _, cc := range g { + if cc.Tag == 'i' { + continue + } + for _, line := range diff.A[cc.I1:cc.I2] { + ws(prefix[cc.Tag] + line) + } + } + break + } + } + + range2 := formatRangeContext(first.J1, last.J2) + wf("--- %s ----%s", range2, diff.Eol) + for _, c := range g { + if c.Tag == 'r' || c.Tag == 'i' { + for _, cc := range g { + if cc.Tag == 'd' { + continue + } + for _, line := range diff.B[cc.J1:cc.J2] { + ws(prefix[cc.Tag] + line) + } + } + break + } + } + } + return diffErr +} + +// Like WriteContextDiff but returns the diff a string. +func GetContextDiffString(diff ContextDiff) (string, error) { + w := &bytes.Buffer{} + err := WriteContextDiff(w, diff) + return string(w.Bytes()), err +} + +// Split a string on "\n" while preserving them. The output can be used +// as input for UnifiedDiff and ContextDiff structures. +func SplitLines(s string) []string { + lines := strings.SplitAfter(s, "\n") + lines[len(lines)-1] += "\n" + return lines +} diff --git a/vendor/github.com/robfig/cron/v3/.gitignore b/vendor/github.com/robfig/cron/v3/.gitignore new file mode 100644 index 00000000..00268614 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/github.com/robfig/cron/v3/.travis.yml b/vendor/github.com/robfig/cron/v3/.travis.yml new file mode 100644 index 00000000..4f2ee4d9 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/.travis.yml @@ -0,0 +1 @@ +language: go diff --git a/vendor/github.com/robfig/cron/v3/LICENSE b/vendor/github.com/robfig/cron/v3/LICENSE new file mode 100644 index 00000000..3a0f627f --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/LICENSE @@ -0,0 +1,21 @@ +Copyright (C) 2012 Rob Figueiredo +All Rights Reserved. + +MIT LICENSE + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/robfig/cron/v3/README.md b/vendor/github.com/robfig/cron/v3/README.md new file mode 100644 index 00000000..984c537c --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/README.md @@ -0,0 +1,125 @@ +[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron) +[![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](https://travis-ci.org/robfig/cron) + +# cron + +Cron V3 has been released! + +To download the specific tagged release, run: + + go get github.com/robfig/cron/v3@v3.0.0 + +Import it in your program as: + + import "github.com/robfig/cron/v3" + +It requires Go 1.11 or later due to usage of Go Modules. + +Refer to the documentation here: +http://godoc.org/github.com/robfig/cron + +The rest of this document describes the the advances in v3 and a list of +breaking changes for users that wish to upgrade from an earlier version. + +## Upgrading to v3 (June 2019) + +cron v3 is a major upgrade to the library that addresses all outstanding bugs, +feature requests, and rough edges. It is based on a merge of master which +contains various fixes to issues found over the years and the v2 branch which +contains some backwards-incompatible features like the ability to remove cron +jobs. In addition, v3 adds support for Go Modules, cleans up rough edges like +the timezone support, and fixes a number of bugs. + +New features: + +- Support for Go modules. Callers must now import this library as + `github.com/robfig/cron/v3`, instead of `gopkg.in/...` + +- Fixed bugs: + - 0f01e6b parser: fix combining of Dow and Dom (#70) + - dbf3220 adjust times when rolling the clock forward to handle non-existent midnight (#157) + - eeecf15 spec_test.go: ensure an error is returned on 0 increment (#144) + - 70971dc cron.Entries(): update request for snapshot to include a reply channel (#97) + - 1cba5e6 cron: fix: removing a job causes the next scheduled job to run too late (#206) + +- Standard cron spec parsing by default (first field is "minute"), with an easy + way to opt into the seconds field (quartz-compatible). Although, note that the + year field (optional in Quartz) is not supported. + +- Extensible, key/value logging via an interface that complies with + the https://github.com/go-logr/logr project. + +- The new Chain & JobWrapper types allow you to install "interceptors" to add + cross-cutting behavior like the following: + - Recover any panics from jobs + - Delay a job's execution if the previous run hasn't completed yet + - Skip a job's execution if the previous run hasn't completed yet + - Log each job's invocations + - Notification when jobs are completed + +It is backwards incompatible with both v1 and v2. These updates are required: + +- The v1 branch accepted an optional seconds field at the beginning of the cron + spec. This is non-standard and has led to a lot of confusion. The new default + parser conforms to the standard as described by [the Cron wikipedia page]. + + UPDATING: To retain the old behavior, construct your Cron with a custom + parser: + + // Seconds field, required + cron.New(cron.WithSeconds()) + + // Seconds field, optional + cron.New( + cron.WithParser( + cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)) + +- The Cron type now accepts functional options on construction rather than the + previous ad-hoc behavior modification mechanisms (setting a field, calling a setter). + + UPDATING: Code that sets Cron.ErrorLogger or calls Cron.SetLocation must be + updated to provide those values on construction. + +- CRON_TZ is now the recommended way to specify the timezone of a single + schedule, which is sanctioned by the specification. The legacy "TZ=" prefix + will continue to be supported since it is unambiguous and easy to do so. + + UPDATING: No update is required. + +- By default, cron will no longer recover panics in jobs that it runs. + Recovering can be surprising (see issue #192) and seems to be at odds with + typical behavior of libraries. Relatedly, the `cron.WithPanicLogger` option + has been removed to accommodate the more general JobWrapper type. + + UPDATING: To opt into panic recovery and configure the panic logger: + + cron.New(cron.WithChain( + cron.Recover(logger), // or use cron.DefaultLogger + )) + +- In adding support for https://github.com/go-logr/logr, `cron.WithVerboseLogger` was + removed, since it is duplicative with the leveled logging. + + UPDATING: Callers should use `WithLogger` and specify a logger that does not + discard `Info` logs. For convenience, one is provided that wraps `*log.Logger`: + + cron.New( + cron.WithLogger(cron.VerbosePrintfLogger(logger))) + + +### Background - Cron spec format + +There are two cron spec formats in common usage: + +- The "standard" cron format, described on [the Cron wikipedia page] and used by + the cron Linux system utility. + +- The cron format used by [the Quartz Scheduler], commonly used for scheduled + jobs in Java software + +[the Cron wikipedia page]: https://en.wikipedia.org/wiki/Cron +[the Quartz Scheduler]: http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/tutorial-lesson-06.html + +The original version of this package included an optional "seconds" field, which +made it incompatible with both of these formats. Now, the "standard" format is +the default format accepted, and the Quartz format is opt-in. diff --git a/vendor/github.com/robfig/cron/v3/chain.go b/vendor/github.com/robfig/cron/v3/chain.go new file mode 100644 index 00000000..9565b418 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/chain.go @@ -0,0 +1,92 @@ +package cron + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +// JobWrapper decorates the given Job with some behavior. +type JobWrapper func(Job) Job + +// Chain is a sequence of JobWrappers that decorates submitted jobs with +// cross-cutting behaviors like logging or synchronization. +type Chain struct { + wrappers []JobWrapper +} + +// NewChain returns a Chain consisting of the given JobWrappers. +func NewChain(c ...JobWrapper) Chain { + return Chain{c} +} + +// Then decorates the given job with all JobWrappers in the chain. +// +// This: +// NewChain(m1, m2, m3).Then(job) +// is equivalent to: +// m1(m2(m3(job))) +func (c Chain) Then(j Job) Job { + for i := range c.wrappers { + j = c.wrappers[len(c.wrappers)-i-1](j) + } + return j +} + +// Recover panics in wrapped jobs and log them with the provided logger. +func Recover(logger Logger) JobWrapper { + return func(j Job) Job { + return FuncJob(func() { + defer func() { + if r := recover(); r != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + err, ok := r.(error) + if !ok { + err = fmt.Errorf("%v", r) + } + logger.Error(err, "panic", "stack", "...\n"+string(buf)) + } + }() + j.Run() + }) + } +} + +// DelayIfStillRunning serializes jobs, delaying subsequent runs until the +// previous one is complete. Jobs running after a delay of more than a minute +// have the delay logged at Info. +func DelayIfStillRunning(logger Logger) JobWrapper { + return func(j Job) Job { + var mu sync.Mutex + return FuncJob(func() { + start := time.Now() + mu.Lock() + defer mu.Unlock() + if dur := time.Since(start); dur > time.Minute { + logger.Info("delay", "duration", dur) + } + j.Run() + }) + } +} + +// SkipIfStillRunning skips an invocation of the Job if a previous invocation is +// still running. It logs skips to the given logger at Info level. +func SkipIfStillRunning(logger Logger) JobWrapper { + return func(j Job) Job { + var ch = make(chan struct{}, 1) + ch <- struct{}{} + return FuncJob(func() { + select { + case v := <-ch: + j.Run() + ch <- v + default: + logger.Info("skip") + } + }) + } +} diff --git a/vendor/github.com/robfig/cron/v3/constantdelay.go b/vendor/github.com/robfig/cron/v3/constantdelay.go new file mode 100644 index 00000000..cd6e7b1b --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/constantdelay.go @@ -0,0 +1,27 @@ +package cron + +import "time" + +// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes". +// It does not support jobs more frequent than once a second. +type ConstantDelaySchedule struct { + Delay time.Duration +} + +// Every returns a crontab Schedule that activates once every duration. +// Delays of less than a second are not supported (will round up to 1 second). +// Any fields less than a Second are truncated. +func Every(duration time.Duration) ConstantDelaySchedule { + if duration < time.Second { + duration = time.Second + } + return ConstantDelaySchedule{ + Delay: duration - time.Duration(duration.Nanoseconds())%time.Second, + } +} + +// Next returns the next time this should be run. +// This rounds so that the next activation time will be on the second. +func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time { + return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond) +} diff --git a/vendor/github.com/robfig/cron/v3/cron.go b/vendor/github.com/robfig/cron/v3/cron.go new file mode 100644 index 00000000..c7e91766 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/cron.go @@ -0,0 +1,355 @@ +package cron + +import ( + "context" + "sort" + "sync" + "time" +) + +// Cron keeps track of any number of entries, invoking the associated func as +// specified by the schedule. It may be started, stopped, and the entries may +// be inspected while running. +type Cron struct { + entries []*Entry + chain Chain + stop chan struct{} + add chan *Entry + remove chan EntryID + snapshot chan chan []Entry + running bool + logger Logger + runningMu sync.Mutex + location *time.Location + parser ScheduleParser + nextID EntryID + jobWaiter sync.WaitGroup +} + +// ScheduleParser is an interface for schedule spec parsers that return a Schedule +type ScheduleParser interface { + Parse(spec string) (Schedule, error) +} + +// Job is an interface for submitted cron jobs. +type Job interface { + Run() +} + +// Schedule describes a job's duty cycle. +type Schedule interface { + // Next returns the next activation time, later than the given time. + // Next is invoked initially, and then each time the job is run. + Next(time.Time) time.Time +} + +// EntryID identifies an entry within a Cron instance +type EntryID int + +// Entry consists of a schedule and the func to execute on that schedule. +type Entry struct { + // ID is the cron-assigned ID of this entry, which may be used to look up a + // snapshot or remove it. + ID EntryID + + // Schedule on which this job should be run. + Schedule Schedule + + // Next time the job will run, or the zero time if Cron has not been + // started or this entry's schedule is unsatisfiable + Next time.Time + + // Prev is the last time this job was run, or the zero time if never. + Prev time.Time + + // WrappedJob is the thing to run when the Schedule is activated. + WrappedJob Job + + // Job is the thing that was submitted to cron. + // It is kept around so that user code that needs to get at the job later, + // e.g. via Entries() can do so. + Job Job +} + +// Valid returns true if this is not the zero entry. +func (e Entry) Valid() bool { return e.ID != 0 } + +// byTime is a wrapper for sorting the entry array by time +// (with zero time at the end). +type byTime []*Entry + +func (s byTime) Len() int { return len(s) } +func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byTime) Less(i, j int) bool { + // Two zero times should return false. + // Otherwise, zero is "greater" than any other time. + // (To sort it at the end of the list.) + if s[i].Next.IsZero() { + return false + } + if s[j].Next.IsZero() { + return true + } + return s[i].Next.Before(s[j].Next) +} + +// New returns a new Cron job runner, modified by the given options. +// +// Available Settings +// +// Time Zone +// Description: The time zone in which schedules are interpreted +// Default: time.Local +// +// Parser +// Description: Parser converts cron spec strings into cron.Schedules. +// Default: Accepts this spec: https://en.wikipedia.org/wiki/Cron +// +// Chain +// Description: Wrap submitted jobs to customize behavior. +// Default: A chain that recovers panics and logs them to stderr. +// +// See "cron.With*" to modify the default behavior. +func New(opts ...Option) *Cron { + c := &Cron{ + entries: nil, + chain: NewChain(), + add: make(chan *Entry), + stop: make(chan struct{}), + snapshot: make(chan chan []Entry), + remove: make(chan EntryID), + running: false, + runningMu: sync.Mutex{}, + logger: DefaultLogger, + location: time.Local, + parser: standardParser, + } + for _, opt := range opts { + opt(c) + } + return c +} + +// FuncJob is a wrapper that turns a func() into a cron.Job +type FuncJob func() + +func (f FuncJob) Run() { f() } + +// AddFunc adds a func to the Cron to be run on the given schedule. +// The spec is parsed using the time zone of this Cron instance as the default. +// An opaque ID is returned that can be used to later remove it. +func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) { + return c.AddJob(spec, FuncJob(cmd)) +} + +// AddJob adds a Job to the Cron to be run on the given schedule. +// The spec is parsed using the time zone of this Cron instance as the default. +// An opaque ID is returned that can be used to later remove it. +func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error) { + schedule, err := c.parser.Parse(spec) + if err != nil { + return 0, err + } + return c.Schedule(schedule, cmd), nil +} + +// Schedule adds a Job to the Cron to be run on the given schedule. +// The job is wrapped with the configured Chain. +func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID { + c.runningMu.Lock() + defer c.runningMu.Unlock() + c.nextID++ + entry := &Entry{ + ID: c.nextID, + Schedule: schedule, + WrappedJob: c.chain.Then(cmd), + Job: cmd, + } + if !c.running { + c.entries = append(c.entries, entry) + } else { + c.add <- entry + } + return entry.ID +} + +// Entries returns a snapshot of the cron entries. +func (c *Cron) Entries() []Entry { + c.runningMu.Lock() + defer c.runningMu.Unlock() + if c.running { + replyChan := make(chan []Entry, 1) + c.snapshot <- replyChan + return <-replyChan + } + return c.entrySnapshot() +} + +// Location gets the time zone location +func (c *Cron) Location() *time.Location { + return c.location +} + +// Entry returns a snapshot of the given entry, or nil if it couldn't be found. +func (c *Cron) Entry(id EntryID) Entry { + for _, entry := range c.Entries() { + if id == entry.ID { + return entry + } + } + return Entry{} +} + +// Remove an entry from being run in the future. +func (c *Cron) Remove(id EntryID) { + c.runningMu.Lock() + defer c.runningMu.Unlock() + if c.running { + c.remove <- id + } else { + c.removeEntry(id) + } +} + +// Start the cron scheduler in its own goroutine, or no-op if already started. +func (c *Cron) Start() { + c.runningMu.Lock() + defer c.runningMu.Unlock() + if c.running { + return + } + c.running = true + go c.run() +} + +// Run the cron scheduler, or no-op if already running. +func (c *Cron) Run() { + c.runningMu.Lock() + if c.running { + c.runningMu.Unlock() + return + } + c.running = true + c.runningMu.Unlock() + c.run() +} + +// run the scheduler.. this is private just due to the need to synchronize +// access to the 'running' state variable. +func (c *Cron) run() { + c.logger.Info("start") + + // Figure out the next activation times for each entry. + now := c.now() + for _, entry := range c.entries { + entry.Next = entry.Schedule.Next(now) + c.logger.Info("schedule", "now", now, "entry", entry.ID, "next", entry.Next) + } + + for { + // Determine the next entry to run. + sort.Sort(byTime(c.entries)) + + var timer *time.Timer + if len(c.entries) == 0 || c.entries[0].Next.IsZero() { + // If there are no entries yet, just sleep - it still handles new entries + // and stop requests. + timer = time.NewTimer(100000 * time.Hour) + } else { + timer = time.NewTimer(c.entries[0].Next.Sub(now)) + } + + for { + select { + case now = <-timer.C: + now = now.In(c.location) + c.logger.Info("wake", "now", now) + + // Run every entry whose next time was less than now + for _, e := range c.entries { + if e.Next.After(now) || e.Next.IsZero() { + break + } + c.startJob(e.WrappedJob) + e.Prev = e.Next + e.Next = e.Schedule.Next(now) + c.logger.Info("run", "now", now, "entry", e.ID, "next", e.Next) + } + + case newEntry := <-c.add: + timer.Stop() + now = c.now() + newEntry.Next = newEntry.Schedule.Next(now) + c.entries = append(c.entries, newEntry) + c.logger.Info("added", "now", now, "entry", newEntry.ID, "next", newEntry.Next) + + case replyChan := <-c.snapshot: + replyChan <- c.entrySnapshot() + continue + + case <-c.stop: + timer.Stop() + c.logger.Info("stop") + return + + case id := <-c.remove: + timer.Stop() + now = c.now() + c.removeEntry(id) + c.logger.Info("removed", "entry", id) + } + + break + } + } +} + +// startJob runs the given job in a new goroutine. +func (c *Cron) startJob(j Job) { + c.jobWaiter.Add(1) + go func() { + defer c.jobWaiter.Done() + j.Run() + }() +} + +// now returns current time in c location +func (c *Cron) now() time.Time { + return time.Now().In(c.location) +} + +// Stop stops the cron scheduler if it is running; otherwise it does nothing. +// A context is returned so the caller can wait for running jobs to complete. +func (c *Cron) Stop() context.Context { + c.runningMu.Lock() + defer c.runningMu.Unlock() + if c.running { + c.stop <- struct{}{} + c.running = false + } + ctx, cancel := context.WithCancel(context.Background()) + go func() { + c.jobWaiter.Wait() + cancel() + }() + return ctx +} + +// entrySnapshot returns a copy of the current cron entry list. +func (c *Cron) entrySnapshot() []Entry { + var entries = make([]Entry, len(c.entries)) + for i, e := range c.entries { + entries[i] = *e + } + return entries +} + +func (c *Cron) removeEntry(id EntryID) { + var entries []*Entry + for _, e := range c.entries { + if e.ID != id { + entries = append(entries, e) + } + } + c.entries = entries +} diff --git a/vendor/github.com/robfig/cron/v3/doc.go b/vendor/github.com/robfig/cron/v3/doc.go new file mode 100644 index 00000000..fa5d08b4 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/doc.go @@ -0,0 +1,231 @@ +/* +Package cron implements a cron spec parser and job runner. + +Installation + +To download the specific tagged release, run: + + go get github.com/robfig/cron/v3@v3.0.0 + +Import it in your program as: + + import "github.com/robfig/cron/v3" + +It requires Go 1.11 or later due to usage of Go Modules. + +Usage + +Callers may register Funcs to be invoked on a given schedule. Cron will run +them in their own goroutines. + + c := cron.New() + c.AddFunc("30 * * * *", func() { fmt.Println("Every hour on the half hour") }) + c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println(".. in the range 3-6am, 8-11pm") }) + c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() { fmt.Println("Runs at 04:30 Tokyo time every day") }) + c.AddFunc("@hourly", func() { fmt.Println("Every hour, starting an hour from now") }) + c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") }) + c.Start() + .. + // Funcs are invoked in their own goroutine, asynchronously. + ... + // Funcs may also be added to a running Cron + c.AddFunc("@daily", func() { fmt.Println("Every day") }) + .. + // Inspect the cron job entries' next and previous run times. + inspect(c.Entries()) + .. + c.Stop() // Stop the scheduler (does not stop any jobs already running). + +CRON Expression Format + +A cron expression represents a set of times, using 5 space-separated fields. + + Field name | Mandatory? | Allowed values | Allowed special characters + ---------- | ---------- | -------------- | -------------------------- + Minutes | Yes | 0-59 | * / , - + Hours | Yes | 0-23 | * / , - + Day of month | Yes | 1-31 | * / , - ? + Month | Yes | 1-12 or JAN-DEC | * / , - + Day of week | Yes | 0-6 or SUN-SAT | * / , - ? + +Month and Day-of-week field values are case insensitive. "SUN", "Sun", and +"sun" are equally accepted. + +The specific interpretation of the format is based on the Cron Wikipedia page: +https://en.wikipedia.org/wiki/Cron + +Alternative Formats + +Alternative Cron expression formats support other fields like seconds. You can +implement that by creating a custom Parser as follows. + + cron.New( + cron.WithParser( + cron.NewParser( + cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor))) + +Since adding Seconds is the most common modification to the standard cron spec, +cron provides a builtin function to do that, which is equivalent to the custom +parser you saw earlier, except that its seconds field is REQUIRED: + + cron.New(cron.WithSeconds()) + +That emulates Quartz, the most popular alternative Cron schedule format: +http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html + +Special Characters + +Asterisk ( * ) + +The asterisk indicates that the cron expression will match for all values of the +field; e.g., using an asterisk in the 5th field (month) would indicate every +month. + +Slash ( / ) + +Slashes are used to describe increments of ranges. For example 3-59/15 in the +1st field (minutes) would indicate the 3rd minute of the hour and every 15 +minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...", +that is, an increment over the largest possible range of the field. The form +"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the +increment until the end of that specific range. It does not wrap around. + +Comma ( , ) + +Commas are used to separate items of a list. For example, using "MON,WED,FRI" in +the 5th field (day of week) would mean Mondays, Wednesdays and Fridays. + +Hyphen ( - ) + +Hyphens are used to define ranges. For example, 9-17 would indicate every +hour between 9am and 5pm inclusive. + +Question mark ( ? ) + +Question mark may be used instead of '*' for leaving either day-of-month or +day-of-week blank. + +Predefined schedules + +You may use one of several pre-defined schedules in place of a cron expression. + + Entry | Description | Equivalent To + ----- | ----------- | ------------- + @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 1 1 * + @monthly | Run once a month, midnight, first of month | 0 0 1 * * + @weekly | Run once a week, midnight between Sat/Sun | 0 0 * * 0 + @daily (or @midnight) | Run once a day, midnight | 0 0 * * * + @hourly | Run once an hour, beginning of hour | 0 * * * * + +Intervals + +You may also schedule a job to execute at fixed intervals, starting at the time it's added +or cron is run. This is supported by formatting the cron spec like this: + + @every + +where "duration" is a string accepted by time.ParseDuration +(http://golang.org/pkg/time/#ParseDuration). + +For example, "@every 1h30m10s" would indicate a schedule that activates after +1 hour, 30 minutes, 10 seconds, and then every interval after that. + +Note: The interval does not take the job runtime into account. For example, +if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes, +it will have only 2 minutes of idle time between each run. + +Time zones + +By default, all interpretation and scheduling is done in the machine's local +time zone (time.Local). You can specify a different time zone on construction: + + cron.New( + cron.WithLocation(time.UTC)) + +Individual cron schedules may also override the time zone they are to be +interpreted in by providing an additional space-separated field at the beginning +of the cron spec, of the form "CRON_TZ=Asia/Tokyo". + +For example: + + # Runs at 6am in time.Local + cron.New().AddFunc("0 6 * * ?", ...) + + # Runs at 6am in America/New_York + nyc, _ := time.LoadLocation("America/New_York") + c := cron.New(cron.WithLocation(nyc)) + c.AddFunc("0 6 * * ?", ...) + + # Runs at 6am in Asia/Tokyo + cron.New().AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...) + + # Runs at 6am in Asia/Tokyo + c := cron.New(cron.WithLocation(nyc)) + c.SetLocation("America/New_York") + c.AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...) + +The prefix "TZ=(TIME ZONE)" is also supported for legacy compatibility. + +Be aware that jobs scheduled during daylight-savings leap-ahead transitions will +not be run! + +Job Wrappers + +A Cron runner may be configured with a chain of job wrappers to add +cross-cutting functionality to all submitted jobs. For example, they may be used +to achieve the following effects: + + - Recover any panics from jobs (activated by default) + - Delay a job's execution if the previous run hasn't completed yet + - Skip a job's execution if the previous run hasn't completed yet + - Log each job's invocations + +Install wrappers for all jobs added to a cron using the `cron.WithChain` option: + + cron.New(cron.WithChain( + cron.SkipIfStillRunning(logger), + )) + +Install wrappers for individual jobs by explicitly wrapping them: + + job = cron.NewChain( + cron.SkipIfStillRunning(logger), + ).Then(job) + +Thread safety + +Since the Cron service runs concurrently with the calling code, some amount of +care must be taken to ensure proper synchronization. + +All cron methods are designed to be correctly synchronized as long as the caller +ensures that invocations have a clear happens-before ordering between them. + +Logging + +Cron defines a Logger interface that is a subset of the one defined in +github.com/go-logr/logr. It has two logging levels (Info and Error), and +parameters are key/value pairs. This makes it possible for cron logging to plug +into structured logging systems. An adapter, [Verbose]PrintfLogger, is provided +to wrap the standard library *log.Logger. + +For additional insight into Cron operations, verbose logging may be activated +which will record job runs, scheduling decisions, and added or removed jobs. +Activate it with a one-off logger as follows: + + cron.New( + cron.WithLogger( + cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags)))) + + +Implementation + +Cron entries are stored in an array, sorted by their next activation time. Cron +sleeps until the next job is due to be run. + +Upon waking: + - it runs each entry that is active on that second + - it calculates the next run times for the jobs that were run + - it re-sorts the array of entries by next activation time. + - it goes to sleep until the soonest job. +*/ +package cron diff --git a/vendor/github.com/robfig/cron/v3/go.mod b/vendor/github.com/robfig/cron/v3/go.mod new file mode 100644 index 00000000..8c95bf47 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/go.mod @@ -0,0 +1,3 @@ +module github.com/robfig/cron/v3 + +go 1.12 diff --git a/vendor/github.com/robfig/cron/v3/logger.go b/vendor/github.com/robfig/cron/v3/logger.go new file mode 100644 index 00000000..b4efcc05 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/logger.go @@ -0,0 +1,86 @@ +package cron + +import ( + "io/ioutil" + "log" + "os" + "strings" + "time" +) + +// DefaultLogger is used by Cron if none is specified. +var DefaultLogger Logger = PrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags)) + +// DiscardLogger can be used by callers to discard all log messages. +var DiscardLogger Logger = PrintfLogger(log.New(ioutil.Discard, "", 0)) + +// Logger is the interface used in this package for logging, so that any backend +// can be plugged in. It is a subset of the github.com/go-logr/logr interface. +type Logger interface { + // Info logs routine messages about cron's operation. + Info(msg string, keysAndValues ...interface{}) + // Error logs an error condition. + Error(err error, msg string, keysAndValues ...interface{}) +} + +// PrintfLogger wraps a Printf-based logger (such as the standard library "log") +// into an implementation of the Logger interface which logs errors only. +func PrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger { + return printfLogger{l, false} +} + +// VerbosePrintfLogger wraps a Printf-based logger (such as the standard library +// "log") into an implementation of the Logger interface which logs everything. +func VerbosePrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger { + return printfLogger{l, true} +} + +type printfLogger struct { + logger interface{ Printf(string, ...interface{}) } + logInfo bool +} + +func (pl printfLogger) Info(msg string, keysAndValues ...interface{}) { + if pl.logInfo { + keysAndValues = formatTimes(keysAndValues) + pl.logger.Printf( + formatString(len(keysAndValues)), + append([]interface{}{msg}, keysAndValues...)...) + } +} + +func (pl printfLogger) Error(err error, msg string, keysAndValues ...interface{}) { + keysAndValues = formatTimes(keysAndValues) + pl.logger.Printf( + formatString(len(keysAndValues)+2), + append([]interface{}{msg, "error", err}, keysAndValues...)...) +} + +// formatString returns a logfmt-like format string for the number of +// key/values. +func formatString(numKeysAndValues int) string { + var sb strings.Builder + sb.WriteString("%s") + if numKeysAndValues > 0 { + sb.WriteString(", ") + } + for i := 0; i < numKeysAndValues/2; i++ { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString("%v=%v") + } + return sb.String() +} + +// formatTimes formats any time.Time values as RFC3339. +func formatTimes(keysAndValues []interface{}) []interface{} { + var formattedArgs []interface{} + for _, arg := range keysAndValues { + if t, ok := arg.(time.Time); ok { + arg = t.Format(time.RFC3339) + } + formattedArgs = append(formattedArgs, arg) + } + return formattedArgs +} diff --git a/vendor/github.com/robfig/cron/v3/option.go b/vendor/github.com/robfig/cron/v3/option.go new file mode 100644 index 00000000..09e4278e --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/option.go @@ -0,0 +1,45 @@ +package cron + +import ( + "time" +) + +// Option represents a modification to the default behavior of a Cron. +type Option func(*Cron) + +// WithLocation overrides the timezone of the cron instance. +func WithLocation(loc *time.Location) Option { + return func(c *Cron) { + c.location = loc + } +} + +// WithSeconds overrides the parser used for interpreting job schedules to +// include a seconds field as the first one. +func WithSeconds() Option { + return WithParser(NewParser( + Second | Minute | Hour | Dom | Month | Dow | Descriptor, + )) +} + +// WithParser overrides the parser used for interpreting job schedules. +func WithParser(p ScheduleParser) Option { + return func(c *Cron) { + c.parser = p + } +} + +// WithChain specifies Job wrappers to apply to all jobs added to this cron. +// Refer to the Chain* functions in this package for provided wrappers. +func WithChain(wrappers ...JobWrapper) Option { + return func(c *Cron) { + c.chain = NewChain(wrappers...) + } +} + +// WithLogger uses the provided logger. +func WithLogger(logger Logger) Option { + return func(c *Cron) { + c.logger = logger + } +} diff --git a/vendor/github.com/robfig/cron/v3/parser.go b/vendor/github.com/robfig/cron/v3/parser.go new file mode 100644 index 00000000..3cf8879f --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/parser.go @@ -0,0 +1,434 @@ +package cron + +import ( + "fmt" + "math" + "strconv" + "strings" + "time" +) + +// Configuration options for creating a parser. Most options specify which +// fields should be included, while others enable features. If a field is not +// included the parser will assume a default value. These options do not change +// the order fields are parse in. +type ParseOption int + +const ( + Second ParseOption = 1 << iota // Seconds field, default 0 + SecondOptional // Optional seconds field, default 0 + Minute // Minutes field, default 0 + Hour // Hours field, default 0 + Dom // Day of month field, default * + Month // Month field, default * + Dow // Day of week field, default * + DowOptional // Optional day of week field, default * + Descriptor // Allow descriptors such as @monthly, @weekly, etc. +) + +var places = []ParseOption{ + Second, + Minute, + Hour, + Dom, + Month, + Dow, +} + +var defaults = []string{ + "0", + "0", + "0", + "*", + "*", + "*", +} + +// A custom Parser that can be configured. +type Parser struct { + options ParseOption +} + +// NewParser creates a Parser with custom options. +// +// It panics if more than one Optional is given, since it would be impossible to +// correctly infer which optional is provided or missing in general. +// +// Examples +// +// // Standard parser without descriptors +// specParser := NewParser(Minute | Hour | Dom | Month | Dow) +// sched, err := specParser.Parse("0 0 15 */3 *") +// +// // Same as above, just excludes time fields +// subsParser := NewParser(Dom | Month | Dow) +// sched, err := specParser.Parse("15 */3 *") +// +// // Same as above, just makes Dow optional +// subsParser := NewParser(Dom | Month | DowOptional) +// sched, err := specParser.Parse("15 */3") +// +func NewParser(options ParseOption) Parser { + optionals := 0 + if options&DowOptional > 0 { + optionals++ + } + if options&SecondOptional > 0 { + optionals++ + } + if optionals > 1 { + panic("multiple optionals may not be configured") + } + return Parser{options} +} + +// Parse returns a new crontab schedule representing the given spec. +// It returns a descriptive error if the spec is not valid. +// It accepts crontab specs and features configured by NewParser. +func (p Parser) Parse(spec string) (Schedule, error) { + if len(spec) == 0 { + return nil, fmt.Errorf("empty spec string") + } + + // Extract timezone if present + var loc = time.Local + if strings.HasPrefix(spec, "TZ=") || strings.HasPrefix(spec, "CRON_TZ=") { + var err error + i := strings.Index(spec, " ") + eq := strings.Index(spec, "=") + if loc, err = time.LoadLocation(spec[eq+1 : i]); err != nil { + return nil, fmt.Errorf("provided bad location %s: %v", spec[eq+1:i], err) + } + spec = strings.TrimSpace(spec[i:]) + } + + // Handle named schedules (descriptors), if configured + if strings.HasPrefix(spec, "@") { + if p.options&Descriptor == 0 { + return nil, fmt.Errorf("parser does not accept descriptors: %v", spec) + } + return parseDescriptor(spec, loc) + } + + // Split on whitespace. + fields := strings.Fields(spec) + + // Validate & fill in any omitted or optional fields + var err error + fields, err = normalizeFields(fields, p.options) + if err != nil { + return nil, err + } + + field := func(field string, r bounds) uint64 { + if err != nil { + return 0 + } + var bits uint64 + bits, err = getField(field, r) + return bits + } + + var ( + second = field(fields[0], seconds) + minute = field(fields[1], minutes) + hour = field(fields[2], hours) + dayofmonth = field(fields[3], dom) + month = field(fields[4], months) + dayofweek = field(fields[5], dow) + ) + if err != nil { + return nil, err + } + + return &SpecSchedule{ + Second: second, + Minute: minute, + Hour: hour, + Dom: dayofmonth, + Month: month, + Dow: dayofweek, + Location: loc, + }, nil +} + +// normalizeFields takes a subset set of the time fields and returns the full set +// with defaults (zeroes) populated for unset fields. +// +// As part of performing this function, it also validates that the provided +// fields are compatible with the configured options. +func normalizeFields(fields []string, options ParseOption) ([]string, error) { + // Validate optionals & add their field to options + optionals := 0 + if options&SecondOptional > 0 { + options |= Second + optionals++ + } + if options&DowOptional > 0 { + options |= Dow + optionals++ + } + if optionals > 1 { + return nil, fmt.Errorf("multiple optionals may not be configured") + } + + // Figure out how many fields we need + max := 0 + for _, place := range places { + if options&place > 0 { + max++ + } + } + min := max - optionals + + // Validate number of fields + if count := len(fields); count < min || count > max { + if min == max { + return nil, fmt.Errorf("expected exactly %d fields, found %d: %s", min, count, fields) + } + return nil, fmt.Errorf("expected %d to %d fields, found %d: %s", min, max, count, fields) + } + + // Populate the optional field if not provided + if min < max && len(fields) == min { + switch { + case options&DowOptional > 0: + fields = append(fields, defaults[5]) // TODO: improve access to default + case options&SecondOptional > 0: + fields = append([]string{defaults[0]}, fields...) + default: + return nil, fmt.Errorf("unknown optional field") + } + } + + // Populate all fields not part of options with their defaults + n := 0 + expandedFields := make([]string, len(places)) + copy(expandedFields, defaults) + for i, place := range places { + if options&place > 0 { + expandedFields[i] = fields[n] + n++ + } + } + return expandedFields, nil +} + +var standardParser = NewParser( + Minute | Hour | Dom | Month | Dow | Descriptor, +) + +// ParseStandard returns a new crontab schedule representing the given +// standardSpec (https://en.wikipedia.org/wiki/Cron). It requires 5 entries +// representing: minute, hour, day of month, month and day of week, in that +// order. It returns a descriptive error if the spec is not valid. +// +// It accepts +// - Standard crontab specs, e.g. "* * * * ?" +// - Descriptors, e.g. "@midnight", "@every 1h30m" +func ParseStandard(standardSpec string) (Schedule, error) { + return standardParser.Parse(standardSpec) +} + +// getField returns an Int with the bits set representing all of the times that +// the field represents or error parsing field value. A "field" is a comma-separated +// list of "ranges". +func getField(field string, r bounds) (uint64, error) { + var bits uint64 + ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' }) + for _, expr := range ranges { + bit, err := getRange(expr, r) + if err != nil { + return bits, err + } + bits |= bit + } + return bits, nil +} + +// getRange returns the bits indicated by the given expression: +// number | number "-" number [ "/" number ] +// or error parsing range. +func getRange(expr string, r bounds) (uint64, error) { + var ( + start, end, step uint + rangeAndStep = strings.Split(expr, "/") + lowAndHigh = strings.Split(rangeAndStep[0], "-") + singleDigit = len(lowAndHigh) == 1 + err error + ) + + var extra uint64 + if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" { + start = r.min + end = r.max + extra = starBit + } else { + start, err = parseIntOrName(lowAndHigh[0], r.names) + if err != nil { + return 0, err + } + switch len(lowAndHigh) { + case 1: + end = start + case 2: + end, err = parseIntOrName(lowAndHigh[1], r.names) + if err != nil { + return 0, err + } + default: + return 0, fmt.Errorf("too many hyphens: %s", expr) + } + } + + switch len(rangeAndStep) { + case 1: + step = 1 + case 2: + step, err = mustParseInt(rangeAndStep[1]) + if err != nil { + return 0, err + } + + // Special handling: "N/step" means "N-max/step". + if singleDigit { + end = r.max + } + if step > 1 { + extra = 0 + } + default: + return 0, fmt.Errorf("too many slashes: %s", expr) + } + + if start < r.min { + return 0, fmt.Errorf("beginning of range (%d) below minimum (%d): %s", start, r.min, expr) + } + if end > r.max { + return 0, fmt.Errorf("end of range (%d) above maximum (%d): %s", end, r.max, expr) + } + if start > end { + return 0, fmt.Errorf("beginning of range (%d) beyond end of range (%d): %s", start, end, expr) + } + if step == 0 { + return 0, fmt.Errorf("step of range should be a positive number: %s", expr) + } + + return getBits(start, end, step) | extra, nil +} + +// parseIntOrName returns the (possibly-named) integer contained in expr. +func parseIntOrName(expr string, names map[string]uint) (uint, error) { + if names != nil { + if namedInt, ok := names[strings.ToLower(expr)]; ok { + return namedInt, nil + } + } + return mustParseInt(expr) +} + +// mustParseInt parses the given expression as an int or returns an error. +func mustParseInt(expr string) (uint, error) { + num, err := strconv.Atoi(expr) + if err != nil { + return 0, fmt.Errorf("failed to parse int from %s: %s", expr, err) + } + if num < 0 { + return 0, fmt.Errorf("negative number (%d) not allowed: %s", num, expr) + } + + return uint(num), nil +} + +// getBits sets all bits in the range [min, max], modulo the given step size. +func getBits(min, max, step uint) uint64 { + var bits uint64 + + // If step is 1, use shifts. + if step == 1 { + return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min) + } + + // Else, use a simple loop. + for i := min; i <= max; i += step { + bits |= 1 << i + } + return bits +} + +// all returns all bits within the given bounds. (plus the star bit) +func all(r bounds) uint64 { + return getBits(r.min, r.max, 1) | starBit +} + +// parseDescriptor returns a predefined schedule for the expression, or error if none matches. +func parseDescriptor(descriptor string, loc *time.Location) (Schedule, error) { + switch descriptor { + case "@yearly", "@annually": + return &SpecSchedule{ + Second: 1 << seconds.min, + Minute: 1 << minutes.min, + Hour: 1 << hours.min, + Dom: 1 << dom.min, + Month: 1 << months.min, + Dow: all(dow), + Location: loc, + }, nil + + case "@monthly": + return &SpecSchedule{ + Second: 1 << seconds.min, + Minute: 1 << minutes.min, + Hour: 1 << hours.min, + Dom: 1 << dom.min, + Month: all(months), + Dow: all(dow), + Location: loc, + }, nil + + case "@weekly": + return &SpecSchedule{ + Second: 1 << seconds.min, + Minute: 1 << minutes.min, + Hour: 1 << hours.min, + Dom: all(dom), + Month: all(months), + Dow: 1 << dow.min, + Location: loc, + }, nil + + case "@daily", "@midnight": + return &SpecSchedule{ + Second: 1 << seconds.min, + Minute: 1 << minutes.min, + Hour: 1 << hours.min, + Dom: all(dom), + Month: all(months), + Dow: all(dow), + Location: loc, + }, nil + + case "@hourly": + return &SpecSchedule{ + Second: 1 << seconds.min, + Minute: 1 << minutes.min, + Hour: all(hours), + Dom: all(dom), + Month: all(months), + Dow: all(dow), + Location: loc, + }, nil + + } + + const every = "@every " + if strings.HasPrefix(descriptor, every) { + duration, err := time.ParseDuration(descriptor[len(every):]) + if err != nil { + return nil, fmt.Errorf("failed to parse duration %s: %s", descriptor, err) + } + return Every(duration), nil + } + + return nil, fmt.Errorf("unrecognized descriptor: %s", descriptor) +} diff --git a/vendor/github.com/robfig/cron/v3/spec.go b/vendor/github.com/robfig/cron/v3/spec.go new file mode 100644 index 00000000..fa1e241e --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/spec.go @@ -0,0 +1,188 @@ +package cron + +import "time" + +// SpecSchedule specifies a duty cycle (to the second granularity), based on a +// traditional crontab specification. It is computed initially and stored as bit sets. +type SpecSchedule struct { + Second, Minute, Hour, Dom, Month, Dow uint64 + + // Override location for this schedule. + Location *time.Location +} + +// bounds provides a range of acceptable values (plus a map of name to value). +type bounds struct { + min, max uint + names map[string]uint +} + +// The bounds for each field. +var ( + seconds = bounds{0, 59, nil} + minutes = bounds{0, 59, nil} + hours = bounds{0, 23, nil} + dom = bounds{1, 31, nil} + months = bounds{1, 12, map[string]uint{ + "jan": 1, + "feb": 2, + "mar": 3, + "apr": 4, + "may": 5, + "jun": 6, + "jul": 7, + "aug": 8, + "sep": 9, + "oct": 10, + "nov": 11, + "dec": 12, + }} + dow = bounds{0, 6, map[string]uint{ + "sun": 0, + "mon": 1, + "tue": 2, + "wed": 3, + "thu": 4, + "fri": 5, + "sat": 6, + }} +) + +const ( + // Set the top bit if a star was included in the expression. + starBit = 1 << 63 +) + +// Next returns the next time this schedule is activated, greater than the given +// time. If no time can be found to satisfy the schedule, return the zero time. +func (s *SpecSchedule) Next(t time.Time) time.Time { + // General approach + // + // For Month, Day, Hour, Minute, Second: + // Check if the time value matches. If yes, continue to the next field. + // If the field doesn't match the schedule, then increment the field until it matches. + // While incrementing the field, a wrap-around brings it back to the beginning + // of the field list (since it is necessary to re-verify previous field + // values) + + // Convert the given time into the schedule's timezone, if one is specified. + // Save the original timezone so we can convert back after we find a time. + // Note that schedules without a time zone specified (time.Local) are treated + // as local to the time provided. + origLocation := t.Location() + loc := s.Location + if loc == time.Local { + loc = t.Location() + } + if s.Location != time.Local { + t = t.In(s.Location) + } + + // Start at the earliest possible time (the upcoming second). + t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond) + + // This flag indicates whether a field has been incremented. + added := false + + // If no time is found within five years, return zero. + yearLimit := t.Year() + 5 + +WRAP: + if t.Year() > yearLimit { + return time.Time{} + } + + // Find the first applicable month. + // If it's this month, then do nothing. + for 1< 12 { + t = t.Add(time.Duration(24-t.Hour()) * time.Hour) + } else { + t = t.Add(time.Duration(-t.Hour()) * time.Hour) + } + } + + if t.Day() == 1 { + goto WRAP + } + } + + for 1< 0 + dowMatch bool = 1< 0 + ) + if s.Dom&starBit > 0 || s.Dow&starBit > 0 { + return domMatch && dowMatch + } + return domMatch || dowMatch +} diff --git a/vendor/github.com/stretchr/testify/LICENSE b/vendor/github.com/stretchr/testify/LICENSE new file mode 100644 index 00000000..f38ec595 --- /dev/null +++ b/vendor/github.com/stretchr/testify/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go new file mode 100644 index 00000000..bf89ecd2 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -0,0 +1,622 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package assert + +import ( + http "net/http" + url "net/url" + time "time" +) + +// Conditionf uses a Comparison to assert a complex condition. +func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Condition(t, comp, append([]interface{}{msg}, args...)...) +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Contains(t, s, contains, append([]interface{}{msg}, args...)...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return DirExists(t, path, append([]interface{}{msg}, args...)...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Empty(t, object, append([]interface{}{msg}, args...)...) +} + +// Equalf asserts that two objects are equal. +// +// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Equal(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...) +} + +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123)) +func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func Errorf(t TestingT, err error, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Error(t, err, append([]interface{}{msg}, args...)...) +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...) +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123)) +func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Failf reports a failure through +func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Fail(t, failureMessage, append([]interface{}{msg}, args...)...) +} + +// FailNowf fails test +func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...) +} + +// Falsef asserts that the specified value is false. +// +// assert.Falsef(t, myBool, "error message %s", "formatted") +func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return False(t, value, append([]interface{}{msg}, args...)...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return FileExists(t, path, append([]interface{}{msg}, args...)...) +} + +// Greaterf asserts that the first element is greater than the second +// +// assert.Greaterf(t, 2, 1, "error message %s", "formatted") +// assert.Greaterf(t, float64(2, "error message %s", "formatted"), float64(1)) +// assert.Greaterf(t, "b", "a", "error message %s", "formatted") +func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Greater(t, e1, e2, append([]interface{}{msg}, args...)...) +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") +func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...) +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...) +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...) +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...) +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...) +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...) +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) +func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) +} + +// IsTypef asserts that the specified objects are of the same type. +func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...) +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Len(t, object, length, append([]interface{}{msg}, args...)...) +} + +// Lessf asserts that the first element is less than the second +// +// assert.Lessf(t, 1, 2, "error message %s", "formatted") +// assert.Lessf(t, float64(1, "error message %s", "formatted"), float64(2)) +// assert.Lessf(t, "a", "b", "error message %s", "formatted") +func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Less(t, e1, e2, append([]interface{}{msg}, args...)...) +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") +// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") +func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...) +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Never(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...) +} + +// Nilf asserts that the specified object is nil. +// +// assert.Nilf(t, err, "error message %s", "formatted") +func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Nil(t, object, append([]interface{}{msg}, args...)...) +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NoDirExists(t, path, append([]interface{}{msg}, args...)...) +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoErrorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NoError(t, err, append([]interface{}{msg}, args...)...) +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NoFileExists(t, path, append([]interface{}{msg}, args...)...) +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotContains(t, s, contains, append([]interface{}{msg}, args...)...) +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotEmpty(t, object, append([]interface{}{msg}, args...)...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// NotNilf asserts that the specified object is not nil. +// +// assert.NotNilf(t, err, "error message %s", "formatted") +func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotNil(t, object, append([]interface{}{msg}, args...)...) +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotPanics(t, f, append([]interface{}{msg}, args...)...) +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...) +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...) +} + +// NotZerof asserts that i is not the zero value for its type. +func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotZero(t, i, append([]interface{}{msg}, args...)...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Panics(t, f, append([]interface{}{msg}, args...)...) +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return PanicsWithError(t, errString, f, append([]interface{}{msg}, args...)...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...) +} + +// Regexpf asserts that a specified regexp matches a string. +// +// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Regexp(t, rx, str, append([]interface{}{msg}, args...)...) +} + +// Samef asserts that two pointers reference the same object. +// +// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Same(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Subset(t, list, subset, append([]interface{}{msg}, args...)...) +} + +// Truef asserts that the specified value is true. +// +// assert.Truef(t, myBool, "error message %s", "formatted") +func Truef(t TestingT, value bool, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return True(t, value, append([]interface{}{msg}, args...)...) +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...) +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return YAMLEq(t, expected, actual, append([]interface{}{msg}, args...)...) +} + +// Zerof asserts that i is the zero value for its type. +func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Zero(t, i, append([]interface{}{msg}, args...)...) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl new file mode 100644 index 00000000..d2bb0b81 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl @@ -0,0 +1,5 @@ +{{.CommentFormat}} +func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool { + if h, ok := t.(tHelper); ok { h.Helper() } + return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}}) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go new file mode 100644 index 00000000..75ecdcaa --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -0,0 +1,1232 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package assert + +import ( + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Condition(a.t, comp, msgAndArgs...) +} + +// Conditionf uses a Comparison to assert a complex condition. +func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Conditionf(a.t, comp, msg, args...) +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Contains(a.t, s, contains, msgAndArgs...) +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Containsf(a.t, s, contains, msg, args...) +} + +// DirExists checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ElementsMatchf(a.t, listA, listB, msg, args...) +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Empty(obj) +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Empty(a.t, object, msgAndArgs...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Emptyf(obj, "error message %s", "formatted") +func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Emptyf(a.t, object, msg, args...) +} + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Equal(a.t, expected, actual, msgAndArgs...) +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualError(err, expectedErrorString) +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return EqualError(a.t, theError, errString, msgAndArgs...) +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return EqualErrorf(a.t, theError, errString, msg, args...) +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValues(uint32(123), int32(123)) +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return EqualValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123)) +func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return EqualValuesf(a.t, expected, actual, msg, args...) +} + +// Equalf asserts that two objects are equal. +// +// a.Equalf(123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Equalf(a.t, expected, actual, msg, args...) +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err) { +// assert.Equal(t, expectedError, err) +// } +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Error(a.t, err, msgAndArgs...) +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Errorf(a.t, err, msg, args...) +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Eventually(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Eventuallyf(a.t, condition, waitFor, tick, msg, args...) +} + +// Exactly asserts that two objects are equal in value and type. +// +// a.Exactly(int32(123), int64(123)) +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Exactly(a.t, expected, actual, msgAndArgs...) +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123)) +func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Exactlyf(a.t, expected, actual, msg, args...) +} + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Fail(a.t, failureMessage, msgAndArgs...) +} + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return FailNow(a.t, failureMessage, msgAndArgs...) +} + +// FailNowf fails test +func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return FailNowf(a.t, failureMessage, msg, args...) +} + +// Failf reports a failure through +func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Failf(a.t, failureMessage, msg, args...) +} + +// False asserts that the specified value is false. +// +// a.False(myBool) +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return False(a.t, value, msgAndArgs...) +} + +// Falsef asserts that the specified value is false. +// +// a.Falsef(myBool, "error message %s", "formatted") +func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Falsef(a.t, value, msg, args...) +} + +// FileExists checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return FileExistsf(a.t, path, msg, args...) +} + +// Greater asserts that the first element is greater than the second +// +// a.Greater(2, 1) +// a.Greater(float64(2), float64(1)) +// a.Greater("b", "a") +func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Greater(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqual(2, 1) +// a.GreaterOrEqual(2, 2) +// a.GreaterOrEqual("b", "a") +// a.GreaterOrEqual("b", "b") +func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return GreaterOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") +// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") +// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") +// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return GreaterOrEqualf(a.t, e1, e2, msg, args...) +} + +// Greaterf asserts that the first element is greater than the second +// +// a.Greaterf(2, 1, "error message %s", "formatted") +// a.Greaterf(float64(2, "error message %s", "formatted"), float64(1)) +// a.Greaterf("b", "a", "error message %s", "formatted") +func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Greaterf(a.t, e1, e2, msg, args...) +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPError(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPErrorf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPRedirectf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return HTTPSuccessf(a.t, handler, method, url, values, msg, args...) +} + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject)) +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Implements(a.t, interfaceObject, object, msgAndArgs...) +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) +func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Implementsf(a.t, interfaceObject, object, msg, args...) +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, 22/7.0, 0.01) +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaSlicef(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// a.InDeltaf(math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InDeltaf(a.t, expected, actual, delta, msg, args...) +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InEpsilonf(a.t, expected, actual, epsilon, msg, args...) +} + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsType(a.t, expectedType, object, msgAndArgs...) +} + +// IsTypef asserts that the specified objects are of the same type. +func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsTypef(a.t, expectedType, object, msg, args...) +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return JSONEq(a.t, expected, actual, msgAndArgs...) +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return JSONEqf(a.t, expected, actual, msg, args...) +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// a.Len(mySlice, 3) +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Len(a.t, object, length, msgAndArgs...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// a.Lenf(mySlice, 3, "error message %s", "formatted") +func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Lenf(a.t, object, length, msg, args...) +} + +// Less asserts that the first element is less than the second +// +// a.Less(1, 2) +// a.Less(float64(1), float64(2)) +// a.Less("a", "b") +func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Less(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// a.LessOrEqual(1, 2) +// a.LessOrEqual(2, 2) +// a.LessOrEqual("a", "b") +// a.LessOrEqual("b", "b") +func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return LessOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// a.LessOrEqualf(1, 2, "error message %s", "formatted") +// a.LessOrEqualf(2, 2, "error message %s", "formatted") +// a.LessOrEqualf("a", "b", "error message %s", "formatted") +// a.LessOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return LessOrEqualf(a.t, e1, e2, msg, args...) +} + +// Lessf asserts that the first element is less than the second +// +// a.Lessf(1, 2, "error message %s", "formatted") +// a.Lessf(float64(1, "error message %s", "formatted"), float64(2)) +// a.Lessf("a", "b", "error message %s", "formatted") +func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Lessf(a.t, e1, e2, msg, args...) +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Never(func() bool { return false; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Never(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Never(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Neverf(func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Neverf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Neverf(a.t, condition, waitFor, tick, msg, args...) +} + +// Nil asserts that the specified object is nil. +// +// a.Nil(err) +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Nil(a.t, object, msgAndArgs...) +} + +// Nilf asserts that the specified object is nil. +// +// a.Nilf(err, "error message %s", "formatted") +func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Nilf(a.t, object, msg, args...) +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExists(path string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NoDirExists(a.t, path, msgAndArgs...) +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExistsf(path string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NoDirExistsf(a.t, path, msg, args...) +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NoError(a.t, err, msgAndArgs...) +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NoErrorf(a.t, err, msg, args...) +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExists(path string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NoFileExists(a.t, path, msgAndArgs...) +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExistsf(path string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NoFileExistsf(a.t, path, msg, args...) +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotContains(a.t, s, contains, msgAndArgs...) +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotContainsf(a.t, s, contains, msg, args...) +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotEmpty(a.t, object, msgAndArgs...) +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotEmptyf(a.t, object, msg, args...) +} + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotEqual(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotEqualf(a.t, expected, actual, msg, args...) +} + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err) +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotNil(a.t, object, msgAndArgs...) +} + +// NotNilf asserts that the specified object is not nil. +// +// a.NotNilf(err, "error message %s", "formatted") +func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotNilf(a.t, object, msg, args...) +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ RemainCalm() }) +func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotPanics(a.t, f, msgAndArgs...) +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotPanicsf(a.t, f, msg, args...) +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotRegexp(a.t, rx, str, msgAndArgs...) +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotRegexpf(a.t, rx, str, msg, args...) +} + +// NotSame asserts that two pointers do not reference the same object. +// +// a.NotSame(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotSame(a.t, expected, actual, msgAndArgs...) +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// a.NotSamef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotSamef(a.t, expected, actual, msg, args...) +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotSubset(a.t, list, subset, msgAndArgs...) +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotSubsetf(a.t, list, subset, msg, args...) +} + +// NotZero asserts that i is not the zero value for its type. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotZero(a.t, i, msgAndArgs...) +} + +// NotZerof asserts that i is not the zero value for its type. +func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotZerof(a.t, i, msg, args...) +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ GoCrazy() }) +func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Panics(a.t, f, msgAndArgs...) +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithError("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithError(errString string, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return PanicsWithError(a.t, errString, f, msgAndArgs...) +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithErrorf(errString string, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return PanicsWithErrorf(a.t, errString, f, msg, args...) +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return PanicsWithValue(a.t, expected, f, msgAndArgs...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return PanicsWithValuef(a.t, expected, f, msg, args...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Panicsf(a.t, f, msg, args...) +} + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Regexp(a.t, rx, str, msgAndArgs...) +} + +// Regexpf asserts that a specified regexp matches a string. +// +// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Regexpf(a.t, rx, str, msg, args...) +} + +// Same asserts that two pointers reference the same object. +// +// a.Same(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Same(a.t, expected, actual, msgAndArgs...) +} + +// Samef asserts that two pointers reference the same object. +// +// a.Samef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Samef(a.t, expected, actual, msg, args...) +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Subset(a.t, list, subset, msgAndArgs...) +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Subsetf(a.t, list, subset, msg, args...) +} + +// True asserts that the specified value is true. +// +// a.True(myBool) +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return True(a.t, value, msgAndArgs...) +} + +// Truef asserts that the specified value is true. +// +// a.Truef(myBool, "error message %s", "formatted") +func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Truef(a.t, value, msg, args...) +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return WithinDurationf(a.t, expected, actual, delta, msg, args...) +} + +// YAMLEq asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return YAMLEq(a.t, expected, actual, msgAndArgs...) +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEqf(expected string, actual string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return YAMLEqf(a.t, expected, actual, msg, args...) +} + +// Zero asserts that i is the zero value for its type. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Zero(a.t, i, msgAndArgs...) +} + +// Zerof asserts that i is the zero value for its type. +func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Zerof(a.t, i, msg, args...) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl b/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl new file mode 100644 index 00000000..188bb9e1 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl @@ -0,0 +1,5 @@ +{{.CommentWithoutT "a"}} +func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool { + if h, ok := a.t.(tHelper); ok { h.Helper() } + return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_order.go b/vendor/github.com/stretchr/testify/assert/assertion_order.go new file mode 100644 index 00000000..15a486ca --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_order.go @@ -0,0 +1,309 @@ +package assert + +import ( + "fmt" + "reflect" +) + +func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) { + switch kind { + case reflect.Int: + { + intobj1 := obj1.(int) + intobj2 := obj2.(int) + if intobj1 > intobj2 { + return -1, true + } + if intobj1 == intobj2 { + return 0, true + } + if intobj1 < intobj2 { + return 1, true + } + } + case reflect.Int8: + { + int8obj1 := obj1.(int8) + int8obj2 := obj2.(int8) + if int8obj1 > int8obj2 { + return -1, true + } + if int8obj1 == int8obj2 { + return 0, true + } + if int8obj1 < int8obj2 { + return 1, true + } + } + case reflect.Int16: + { + int16obj1 := obj1.(int16) + int16obj2 := obj2.(int16) + if int16obj1 > int16obj2 { + return -1, true + } + if int16obj1 == int16obj2 { + return 0, true + } + if int16obj1 < int16obj2 { + return 1, true + } + } + case reflect.Int32: + { + int32obj1 := obj1.(int32) + int32obj2 := obj2.(int32) + if int32obj1 > int32obj2 { + return -1, true + } + if int32obj1 == int32obj2 { + return 0, true + } + if int32obj1 < int32obj2 { + return 1, true + } + } + case reflect.Int64: + { + int64obj1 := obj1.(int64) + int64obj2 := obj2.(int64) + if int64obj1 > int64obj2 { + return -1, true + } + if int64obj1 == int64obj2 { + return 0, true + } + if int64obj1 < int64obj2 { + return 1, true + } + } + case reflect.Uint: + { + uintobj1 := obj1.(uint) + uintobj2 := obj2.(uint) + if uintobj1 > uintobj2 { + return -1, true + } + if uintobj1 == uintobj2 { + return 0, true + } + if uintobj1 < uintobj2 { + return 1, true + } + } + case reflect.Uint8: + { + uint8obj1 := obj1.(uint8) + uint8obj2 := obj2.(uint8) + if uint8obj1 > uint8obj2 { + return -1, true + } + if uint8obj1 == uint8obj2 { + return 0, true + } + if uint8obj1 < uint8obj2 { + return 1, true + } + } + case reflect.Uint16: + { + uint16obj1 := obj1.(uint16) + uint16obj2 := obj2.(uint16) + if uint16obj1 > uint16obj2 { + return -1, true + } + if uint16obj1 == uint16obj2 { + return 0, true + } + if uint16obj1 < uint16obj2 { + return 1, true + } + } + case reflect.Uint32: + { + uint32obj1 := obj1.(uint32) + uint32obj2 := obj2.(uint32) + if uint32obj1 > uint32obj2 { + return -1, true + } + if uint32obj1 == uint32obj2 { + return 0, true + } + if uint32obj1 < uint32obj2 { + return 1, true + } + } + case reflect.Uint64: + { + uint64obj1 := obj1.(uint64) + uint64obj2 := obj2.(uint64) + if uint64obj1 > uint64obj2 { + return -1, true + } + if uint64obj1 == uint64obj2 { + return 0, true + } + if uint64obj1 < uint64obj2 { + return 1, true + } + } + case reflect.Float32: + { + float32obj1 := obj1.(float32) + float32obj2 := obj2.(float32) + if float32obj1 > float32obj2 { + return -1, true + } + if float32obj1 == float32obj2 { + return 0, true + } + if float32obj1 < float32obj2 { + return 1, true + } + } + case reflect.Float64: + { + float64obj1 := obj1.(float64) + float64obj2 := obj2.(float64) + if float64obj1 > float64obj2 { + return -1, true + } + if float64obj1 == float64obj2 { + return 0, true + } + if float64obj1 < float64obj2 { + return 1, true + } + } + case reflect.String: + { + stringobj1 := obj1.(string) + stringobj2 := obj2.(string) + if stringobj1 > stringobj2 { + return -1, true + } + if stringobj1 == stringobj2 { + return 0, true + } + if stringobj1 < stringobj2 { + return 1, true + } + } + } + + return 0, false +} + +// Greater asserts that the first element is greater than the second +// +// assert.Greater(t, 2, 1) +// assert.Greater(t, float64(2), float64(1)) +// assert.Greater(t, "b", "a") +func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + e1Kind := reflect.ValueOf(e1).Kind() + e2Kind := reflect.ValueOf(e2).Kind() + if e1Kind != e2Kind { + return Fail(t, "Elements should be the same type", msgAndArgs...) + } + + res, isComparable := compare(e1, e2, e1Kind) + if !isComparable { + return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...) + } + + if res != -1 { + return Fail(t, fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2), msgAndArgs...) + } + + return true +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// assert.GreaterOrEqual(t, 2, 1) +// assert.GreaterOrEqual(t, 2, 2) +// assert.GreaterOrEqual(t, "b", "a") +// assert.GreaterOrEqual(t, "b", "b") +func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + e1Kind := reflect.ValueOf(e1).Kind() + e2Kind := reflect.ValueOf(e2).Kind() + if e1Kind != e2Kind { + return Fail(t, "Elements should be the same type", msgAndArgs...) + } + + res, isComparable := compare(e1, e2, e1Kind) + if !isComparable { + return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...) + } + + if res != -1 && res != 0 { + return Fail(t, fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2), msgAndArgs...) + } + + return true +} + +// Less asserts that the first element is less than the second +// +// assert.Less(t, 1, 2) +// assert.Less(t, float64(1), float64(2)) +// assert.Less(t, "a", "b") +func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + e1Kind := reflect.ValueOf(e1).Kind() + e2Kind := reflect.ValueOf(e2).Kind() + if e1Kind != e2Kind { + return Fail(t, "Elements should be the same type", msgAndArgs...) + } + + res, isComparable := compare(e1, e2, e1Kind) + if !isComparable { + return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...) + } + + if res != 1 { + return Fail(t, fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2), msgAndArgs...) + } + + return true +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// assert.LessOrEqual(t, 1, 2) +// assert.LessOrEqual(t, 2, 2) +// assert.LessOrEqual(t, "a", "b") +// assert.LessOrEqual(t, "b", "b") +func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + e1Kind := reflect.ValueOf(e1).Kind() + e2Kind := reflect.ValueOf(e2).Kind() + if e1Kind != e2Kind { + return Fail(t, "Elements should be the same type", msgAndArgs...) + } + + res, isComparable := compare(e1, e2, e1Kind) + if !isComparable { + return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...) + } + + if res != 1 && res != 0 { + return Fail(t, fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2), msgAndArgs...) + } + + return true +} diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go new file mode 100644 index 00000000..bdd81389 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -0,0 +1,1626 @@ +package assert + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "math" + "os" + "reflect" + "regexp" + "runtime" + "runtime/debug" + "strings" + "time" + "unicode" + "unicode/utf8" + + "github.com/davecgh/go-spew/spew" + "github.com/pmezard/go-difflib/difflib" + yaml "gopkg.in/yaml.v2" +) + +//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl" + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Errorf(format string, args ...interface{}) +} + +// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful +// for table driven tests. +type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) bool + +// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful +// for table driven tests. +type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) bool + +// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful +// for table driven tests. +type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool + +// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful +// for table driven tests. +type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool + +// Comparison a custom function that returns true on success and false on failure +type Comparison func() (success bool) + +/* + Helper functions +*/ + +// ObjectsAreEqual determines if two objects are considered equal. +// +// This function does no assertion of any kind. +func ObjectsAreEqual(expected, actual interface{}) bool { + if expected == nil || actual == nil { + return expected == actual + } + + exp, ok := expected.([]byte) + if !ok { + return reflect.DeepEqual(expected, actual) + } + + act, ok := actual.([]byte) + if !ok { + return false + } + if exp == nil || act == nil { + return exp == nil && act == nil + } + return bytes.Equal(exp, act) +} + +// ObjectsAreEqualValues gets whether two objects are equal, or if their +// values are equal. +func ObjectsAreEqualValues(expected, actual interface{}) bool { + if ObjectsAreEqual(expected, actual) { + return true + } + + actualType := reflect.TypeOf(actual) + if actualType == nil { + return false + } + expectedValue := reflect.ValueOf(expected) + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + // Attempt comparison after type conversion + return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + + return false +} + +/* CallerInfo is necessary because the assert functions use the testing object +internally, causing it to print the file:line of the assert method, rather than where +the problem actually occurred in calling code.*/ + +// CallerInfo returns an array of strings containing the file and line number +// of each stack frame leading from the current test to the assert call that +// failed. +func CallerInfo() []string { + + pc := uintptr(0) + file := "" + line := 0 + ok := false + name := "" + + callers := []string{} + for i := 0; ; i++ { + pc, file, line, ok = runtime.Caller(i) + if !ok { + // The breaks below failed to terminate the loop, and we ran off the + // end of the call stack. + break + } + + // This is a huge edge case, but it will panic if this is the case, see #180 + if file == "" { + break + } + + f := runtime.FuncForPC(pc) + if f == nil { + break + } + name = f.Name() + + // testing.tRunner is the standard library function that calls + // tests. Subtests are called directly by tRunner, without going through + // the Test/Benchmark/Example function that contains the t.Run calls, so + // with subtests we should break when we hit tRunner, without adding it + // to the list of callers. + if name == "testing.tRunner" { + break + } + + parts := strings.Split(file, "/") + file = parts[len(parts)-1] + if len(parts) > 1 { + dir := parts[len(parts)-2] + if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { + callers = append(callers, fmt.Sprintf("%s:%d", file, line)) + } + } + + // Drop the package + segments := strings.Split(name, ".") + name = segments[len(segments)-1] + if isTest(name, "Test") || + isTest(name, "Benchmark") || + isTest(name, "Example") { + break + } + } + + return callers +} + +// Stolen from the `go test` tool. +// isTest tells whether name looks like a test (or benchmark, according to prefix). +// It is a Test (say) if there is a character after Test that is not a lower-case letter. +// We don't want TesticularCancer. +func isTest(name, prefix string) bool { + if !strings.HasPrefix(name, prefix) { + return false + } + if len(name) == len(prefix) { // "Test" is ok + return true + } + rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) + return !unicode.IsLower(rune) +} + +func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { + if len(msgAndArgs) == 0 || msgAndArgs == nil { + return "" + } + if len(msgAndArgs) == 1 { + msg := msgAndArgs[0] + if msgAsStr, ok := msg.(string); ok { + return msgAsStr + } + return fmt.Sprintf("%+v", msg) + } + if len(msgAndArgs) > 1 { + return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) + } + return "" +} + +// Aligns the provided message so that all lines after the first line start at the same location as the first line. +// Assumes that the first line starts at the correct location (after carriage return, tab, label, spacer and tab). +// The longestLabelLen parameter specifies the length of the longest label in the output (required becaues this is the +// basis on which the alignment occurs). +func indentMessageLines(message string, longestLabelLen int) string { + outBuf := new(bytes.Buffer) + + for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ { + // no need to align first line because it starts at the correct location (after the label) + if i != 0 { + // append alignLen+1 spaces to align with "{{longestLabel}}:" before adding tab + outBuf.WriteString("\n\t" + strings.Repeat(" ", longestLabelLen+1) + "\t") + } + outBuf.WriteString(scanner.Text()) + } + + return outBuf.String() +} + +type failNower interface { + FailNow() +} + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + Fail(t, failureMessage, msgAndArgs...) + + // We cannot extend TestingT with FailNow() and + // maintain backwards compatibility, so we fallback + // to panicking when FailNow is not available in + // TestingT. + // See issue #263 + + if t, ok := t.(failNower); ok { + t.FailNow() + } else { + panic("test failed and t is missing `FailNow()`") + } + return false +} + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + content := []labeledContent{ + {"Error Trace", strings.Join(CallerInfo(), "\n\t\t\t")}, + {"Error", failureMessage}, + } + + // Add test name if the Go version supports it + if n, ok := t.(interface { + Name() string + }); ok { + content = append(content, labeledContent{"Test", n.Name()}) + } + + message := messageFromMsgAndArgs(msgAndArgs...) + if len(message) > 0 { + content = append(content, labeledContent{"Messages", message}) + } + + t.Errorf("\n%s", ""+labeledOutput(content...)) + + return false +} + +type labeledContent struct { + label string + content string +} + +// labeledOutput returns a string consisting of the provided labeledContent. Each labeled output is appended in the following manner: +// +// \t{{label}}:{{align_spaces}}\t{{content}}\n +// +// The initial carriage return is required to undo/erase any padding added by testing.T.Errorf. The "\t{{label}}:" is for the label. +// If a label is shorter than the longest label provided, padding spaces are added to make all the labels match in length. Once this +// alignment is achieved, "\t{{content}}\n" is added for the output. +// +// If the content of the labeledOutput contains line breaks, the subsequent lines are aligned so that they start at the same location as the first line. +func labeledOutput(content ...labeledContent) string { + longestLabel := 0 + for _, v := range content { + if len(v.label) > longestLabel { + longestLabel = len(v.label) + } + } + var output string + for _, v := range content { + output += "\t" + v.label + ":" + strings.Repeat(" ", longestLabel-len(v.label)) + "\t" + indentMessageLines(v.content, longestLabel) + "\n" + } + return output +} + +// Implements asserts that an object is implemented by the specified interface. +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + interfaceType := reflect.TypeOf(interfaceObject).Elem() + + if object == nil { + return Fail(t, fmt.Sprintf("Cannot check if nil implements %v", interfaceType), msgAndArgs...) + } + if !reflect.TypeOf(object).Implements(interfaceType) { + return Fail(t, fmt.Sprintf("%T must implement %v", object, interfaceType), msgAndArgs...) + } + + return true +} + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + if !ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType)) { + return Fail(t, fmt.Sprintf("Object expected to be of type %v, but was %v", reflect.TypeOf(expectedType), reflect.TypeOf(object)), msgAndArgs...) + } + + return true +} + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)", + expected, actual, err), msgAndArgs...) + } + + if !ObjectsAreEqual(expected, actual) { + diff := diff(expected, actual) + expected, actual = formatUnequalValues(expected, actual) + return Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", expected, actual, diff), msgAndArgs...) + } + + return true + +} + +// validateEqualArgs checks whether provided arguments can be safely used in the +// Equal/NotEqual functions. +func validateEqualArgs(expected, actual interface{}) error { + if expected == nil && actual == nil { + return nil + } + + if isFunction(expected) || isFunction(actual) { + return errors.New("cannot take func type as argument") + } + return nil +} + +// Same asserts that two pointers reference the same object. +// +// assert.Same(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + if !samePointers(expected, actual) { + return Fail(t, fmt.Sprintf("Not same: \n"+ + "expected: %p %#v\n"+ + "actual : %p %#v", expected, expected, actual, actual), msgAndArgs...) + } + + return true +} + +// NotSame asserts that two pointers do not reference the same object. +// +// assert.NotSame(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + if samePointers(expected, actual) { + return Fail(t, fmt.Sprintf( + "Expected and actual point to the same object: %p %#v", + expected, expected), msgAndArgs...) + } + return true +} + +// samePointers compares two generic interface objects and returns whether +// they point to the same object +func samePointers(first, second interface{}) bool { + firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second) + if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr { + return false + } + + firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second) + if firstType != secondType { + return false + } + + // compare pointer addresses + return first == second +} + +// formatUnequalValues takes two values of arbitrary types and returns string +// representations appropriate to be presented to the user. +// +// If the values are not of like type, the returned strings will be prefixed +// with the type name, and the value will be enclosed in parenthesis similar +// to a type conversion in the Go grammar. +func formatUnequalValues(expected, actual interface{}) (e string, a string) { + if reflect.TypeOf(expected) != reflect.TypeOf(actual) { + return fmt.Sprintf("%T(%#v)", expected, expected), + fmt.Sprintf("%T(%#v)", actual, actual) + } + switch expected.(type) { + case time.Duration: + return fmt.Sprintf("%v", expected), fmt.Sprintf("%v", actual) + } + return fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual) +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValues(t, uint32(123), int32(123)) +func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + if !ObjectsAreEqualValues(expected, actual) { + diff := diff(expected, actual) + expected, actual = formatUnequalValues(expected, actual) + return Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", expected, actual, diff), msgAndArgs...) + } + + return true + +} + +// Exactly asserts that two objects are equal in value and type. +// +// assert.Exactly(t, int32(123), int64(123)) +func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + aType := reflect.TypeOf(expected) + bType := reflect.TypeOf(actual) + + if aType != bType { + return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...) + } + + return Equal(t, expected, actual, msgAndArgs...) + +} + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err) +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !isNil(object) { + return true + } + return Fail(t, "Expected value not to be nil.", msgAndArgs...) +} + +// containsKind checks if a specified kind in the slice of kinds. +func containsKind(kinds []reflect.Kind, kind reflect.Kind) bool { + for i := 0; i < len(kinds); i++ { + if kind == kinds[i] { + return true + } + } + + return false +} + +// isNil checks if a specified object is nil or not, without Failing. +func isNil(object interface{}) bool { + if object == nil { + return true + } + + value := reflect.ValueOf(object) + kind := value.Kind() + isNilableKind := containsKind( + []reflect.Kind{ + reflect.Chan, reflect.Func, + reflect.Interface, reflect.Map, + reflect.Ptr, reflect.Slice}, + kind) + + if isNilableKind && value.IsNil() { + return true + } + + return false +} + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err) +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if isNil(object) { + return true + } + return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...) +} + +// isEmpty gets whether the specified object is considered empty or not. +func isEmpty(object interface{}) bool { + + // get nil case out of the way + if object == nil { + return true + } + + objValue := reflect.ValueOf(object) + + switch objValue.Kind() { + // collection types are empty when they have no element + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + return objValue.Len() == 0 + // pointers are empty if nil or if the value they point to is empty + case reflect.Ptr: + if objValue.IsNil() { + return true + } + deref := objValue.Elem().Interface() + return isEmpty(deref) + // for all other types, compare against the zero value + default: + zero := reflect.Zero(objValue.Type()) + return reflect.DeepEqual(object, zero.Interface()) + } +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Empty(t, obj) +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + pass := isEmpty(object) + if !pass { + Fail(t, fmt.Sprintf("Should be empty, but was %v", object), msgAndArgs...) + } + + return pass + +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + pass := !isEmpty(object) + if !pass { + Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...) + } + + return pass + +} + +// getLen try to get length of object. +// return (false, 0) if impossible. +func getLen(x interface{}) (ok bool, length int) { + v := reflect.ValueOf(x) + defer func() { + if e := recover(); e != nil { + ok = false + } + }() + return true, v.Len() +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// assert.Len(t, mySlice, 3) +func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + ok, l := getLen(object) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", object), msgAndArgs...) + } + + if l != length { + return Fail(t, fmt.Sprintf("\"%s\" should have %d item(s), but has %d", object, length, l), msgAndArgs...) + } + return true +} + +// True asserts that the specified value is true. +// +// assert.True(t, myBool) +func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if h, ok := t.(interface { + Helper() + }); ok { + h.Helper() + } + + if value != true { + return Fail(t, "Should be true", msgAndArgs...) + } + + return true + +} + +// False asserts that the specified value is false. +// +// assert.False(t, myBool) +func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + if value != false { + return Fail(t, "Should be false", msgAndArgs...) + } + + return true + +} + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if err := validateEqualArgs(expected, actual); err != nil { + return Fail(t, fmt.Sprintf("Invalid operation: %#v != %#v (%s)", + expected, actual, err), msgAndArgs...) + } + + if ObjectsAreEqual(expected, actual) { + return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...) + } + + return true + +} + +// containsElement try loop over the list check if the list includes the element. +// return (false, false) if impossible. +// return (true, false) if element was not found. +// return (true, true) if element was found. +func includeElement(list interface{}, element interface{}) (ok, found bool) { + + listValue := reflect.ValueOf(list) + listKind := reflect.TypeOf(list).Kind() + defer func() { + if e := recover(); e != nil { + ok = false + found = false + } + }() + + if listKind == reflect.String { + elementValue := reflect.ValueOf(element) + return true, strings.Contains(listValue.String(), elementValue.String()) + } + + if listKind == reflect.Map { + mapKeys := listValue.MapKeys() + for i := 0; i < len(mapKeys); i++ { + if ObjectsAreEqual(mapKeys[i].Interface(), element) { + return true, true + } + } + return true, false + } + + for i := 0; i < listValue.Len(); i++ { + if ObjectsAreEqual(listValue.Index(i).Interface(), element) { + return true, true + } + } + return true, false + +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Contains(t, "Hello World", "World") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") +func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + ok, found := includeElement(s, contains) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) + } + if !found { + return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", s, contains), msgAndArgs...) + } + + return true + +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContains(t, "Hello World", "Earth") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") +func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + ok, found := includeElement(s, contains) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) + } + if found { + return Fail(t, fmt.Sprintf("\"%s\" should not contain \"%s\"", s, contains), msgAndArgs...) + } + + return true + +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if subset == nil { + return true // we consider nil to be equal to the nil set + } + + subsetValue := reflect.ValueOf(subset) + defer func() { + if e := recover(); e != nil { + ok = false + } + }() + + listKind := reflect.TypeOf(list).Kind() + subsetKind := reflect.TypeOf(subset).Kind() + + if listKind != reflect.Array && listKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) + } + + if subsetKind != reflect.Array && subsetKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) + } + + for i := 0; i < subsetValue.Len(); i++ { + element := subsetValue.Index(i).Interface() + ok, found := includeElement(list, element) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) + } + if !found { + return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, element), msgAndArgs...) + } + } + + return true +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if subset == nil { + return Fail(t, fmt.Sprintf("nil is the empty set which is a subset of every set"), msgAndArgs...) + } + + subsetValue := reflect.ValueOf(subset) + defer func() { + if e := recover(); e != nil { + ok = false + } + }() + + listKind := reflect.TypeOf(list).Kind() + subsetKind := reflect.TypeOf(subset).Kind() + + if listKind != reflect.Array && listKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) + } + + if subsetKind != reflect.Array && subsetKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) + } + + for i := 0; i < subsetValue.Len(); i++ { + element := subsetValue.Index(i).Interface() + ok, found := includeElement(list, element) + if !ok { + return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) + } + if !found { + return true + } + } + + return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if isEmpty(listA) && isEmpty(listB) { + return true + } + + aKind := reflect.TypeOf(listA).Kind() + bKind := reflect.TypeOf(listB).Kind() + + if aKind != reflect.Array && aKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...) + } + + if bKind != reflect.Array && bKind != reflect.Slice { + return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...) + } + + aValue := reflect.ValueOf(listA) + bValue := reflect.ValueOf(listB) + + aLen := aValue.Len() + bLen := bValue.Len() + + if aLen != bLen { + return Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...) + } + + // Mark indexes in bValue that we already used + visited := make([]bool, bLen) + for i := 0; i < aLen; i++ { + element := aValue.Index(i).Interface() + found := false + for j := 0; j < bLen; j++ { + if visited[j] { + continue + } + if ObjectsAreEqual(bValue.Index(j).Interface(), element) { + visited[j] = true + found = true + break + } + } + if !found { + return Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...) + } + } + + return true +} + +// Condition uses a Comparison to assert a complex condition. +func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + result := comp() + if !result { + Fail(t, "Condition failed!", msgAndArgs...) + } + return result +} + +// PanicTestFunc defines a func that should be passed to the assert.Panics and assert.NotPanics +// methods, and represents a simple func that takes no arguments, and returns nothing. +type PanicTestFunc func() + +// didPanic returns true if the function passed to it panics. Otherwise, it returns false. +func didPanic(f PanicTestFunc) (bool, interface{}, string) { + + didPanic := false + var message interface{} + var stack string + func() { + + defer func() { + if message = recover(); message != nil { + didPanic = true + stack = string(debug.Stack()) + } + }() + + // call the target function + f() + + }() + + return didPanic, message, stack + +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panics(t, func(){ GoCrazy() }) +func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + if funcDidPanic, panicValue, _ := didPanic(f); !funcDidPanic { + return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...) + } + + return true +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + funcDidPanic, panicValue, panickedStack := didPanic(f) + if !funcDidPanic { + return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...) + } + if panicValue != expected { + return Fail(t, fmt.Sprintf("func %#v should panic with value:\t%#v\n\tPanic value:\t%#v\n\tPanic stack:\t%s", f, expected, panicValue, panickedStack), msgAndArgs...) + } + + return true +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithError(t TestingT, errString string, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + funcDidPanic, panicValue, panickedStack := didPanic(f) + if !funcDidPanic { + return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...) + } + panicErr, ok := panicValue.(error) + if !ok || panicErr.Error() != errString { + return Fail(t, fmt.Sprintf("func %#v should panic with error message:\t%#v\n\tPanic value:\t%#v\n\tPanic stack:\t%s", f, errString, panicValue, panickedStack), msgAndArgs...) + } + + return true +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanics(t, func(){ RemainCalm() }) +func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + if funcDidPanic, panicValue, panickedStack := didPanic(f); funcDidPanic { + return Fail(t, fmt.Sprintf("func %#v should not panic\n\tPanic value:\t%v\n\tPanic stack:\t%s", f, panicValue, panickedStack), msgAndArgs...) + } + + return true +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) +func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + dt := expected.Sub(actual) + if dt < -delta || dt > delta { + return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, delta, dt), msgAndArgs...) + } + + return true +} + +func toFloat(x interface{}) (float64, bool) { + var xf float64 + xok := true + + switch xn := x.(type) { + case uint8: + xf = float64(xn) + case uint16: + xf = float64(xn) + case uint32: + xf = float64(xn) + case uint64: + xf = float64(xn) + case int: + xf = float64(xn) + case int8: + xf = float64(xn) + case int16: + xf = float64(xn) + case int32: + xf = float64(xn) + case int64: + xf = float64(xn) + case float32: + xf = float64(xn) + case float64: + xf = float64(xn) + case time.Duration: + xf = float64(xn) + default: + xok = false + } + + return xf, xok +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// assert.InDelta(t, math.Pi, 22/7.0, 0.01) +func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + af, aok := toFloat(expected) + bf, bok := toFloat(actual) + + if !aok || !bok { + return Fail(t, fmt.Sprintf("Parameters must be numerical"), msgAndArgs...) + } + + if math.IsNaN(af) { + return Fail(t, fmt.Sprintf("Expected must not be NaN"), msgAndArgs...) + } + + if math.IsNaN(bf) { + return Fail(t, fmt.Sprintf("Expected %v with delta %v, but was NaN", expected, delta), msgAndArgs...) + } + + dt := af - bf + if dt < -delta || dt > delta { + return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, delta, dt), msgAndArgs...) + } + + return true +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if expected == nil || actual == nil || + reflect.TypeOf(actual).Kind() != reflect.Slice || + reflect.TypeOf(expected).Kind() != reflect.Slice { + return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...) + } + + actualSlice := reflect.ValueOf(actual) + expectedSlice := reflect.ValueOf(expected) + + for i := 0; i < actualSlice.Len(); i++ { + result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta, msgAndArgs...) + if !result { + return result + } + } + + return true +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if expected == nil || actual == nil || + reflect.TypeOf(actual).Kind() != reflect.Map || + reflect.TypeOf(expected).Kind() != reflect.Map { + return Fail(t, "Arguments must be maps", msgAndArgs...) + } + + expectedMap := reflect.ValueOf(expected) + actualMap := reflect.ValueOf(actual) + + if expectedMap.Len() != actualMap.Len() { + return Fail(t, "Arguments must have the same number of keys", msgAndArgs...) + } + + for _, k := range expectedMap.MapKeys() { + ev := expectedMap.MapIndex(k) + av := actualMap.MapIndex(k) + + if !ev.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in expected map", k), msgAndArgs...) + } + + if !av.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in actual map", k), msgAndArgs...) + } + + if !InDelta( + t, + ev.Interface(), + av.Interface(), + delta, + msgAndArgs..., + ) { + return false + } + } + + return true +} + +func calcRelativeError(expected, actual interface{}) (float64, error) { + af, aok := toFloat(expected) + if !aok { + return 0, fmt.Errorf("expected value %q cannot be converted to float", expected) + } + if af == 0 { + return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error") + } + bf, bok := toFloat(actual) + if !bok { + return 0, fmt.Errorf("actual value %q cannot be converted to float", actual) + } + + return math.Abs(af-bf) / math.Abs(af), nil +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + actualEpsilon, err := calcRelativeError(expected, actual) + if err != nil { + return Fail(t, err.Error(), msgAndArgs...) + } + if actualEpsilon > epsilon { + return Fail(t, fmt.Sprintf("Relative error is too high: %#v (expected)\n"+ + " < %#v (actual)", epsilon, actualEpsilon), msgAndArgs...) + } + + return true +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if expected == nil || actual == nil || + reflect.TypeOf(actual).Kind() != reflect.Slice || + reflect.TypeOf(expected).Kind() != reflect.Slice { + return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...) + } + + actualSlice := reflect.ValueOf(actual) + expectedSlice := reflect.ValueOf(expected) + + for i := 0; i < actualSlice.Len(); i++ { + result := InEpsilon(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), epsilon) + if !result { + return result + } + } + + return true +} + +/* + Errors +*/ + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if err != nil { + return Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), msgAndArgs...) + } + + return true +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) +// } +func Error(t TestingT, err error, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + if err == nil { + return Fail(t, "An error is expected but got nil.", msgAndArgs...) + } + + return true +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualError(t, err, expectedErrorString) +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !Error(t, theError, msgAndArgs...) { + return false + } + expected := errString + actual := theError.Error() + // don't need to use deep equals here, we know they are both strings + if expected != actual { + return Fail(t, fmt.Sprintf("Error message not equal:\n"+ + "expected: %q\n"+ + "actual : %q", expected, actual), msgAndArgs...) + } + return true +} + +// matchRegexp return true if a specified regexp matches a string. +func matchRegexp(rx interface{}, str interface{}) bool { + + var r *regexp.Regexp + if rr, ok := rx.(*regexp.Regexp); ok { + r = rr + } else { + r = regexp.MustCompile(fmt.Sprint(rx)) + } + + return (r.FindStringIndex(fmt.Sprint(str)) != nil) + +} + +// Regexp asserts that a specified regexp matches a string. +// +// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") +// assert.Regexp(t, "start...$", "it's not starting") +func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + match := matchRegexp(rx, str) + + if !match { + Fail(t, fmt.Sprintf("Expect \"%v\" to match \"%v\"", str, rx), msgAndArgs...) + } + + return match +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// assert.NotRegexp(t, "^start", "it's not starting") +func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + match := matchRegexp(rx, str) + + if match { + Fail(t, fmt.Sprintf("Expect \"%v\" to NOT match \"%v\"", str, rx), msgAndArgs...) + } + + return !match + +} + +// Zero asserts that i is the zero value for its type. +func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { + return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...) + } + return true +} + +// NotZero asserts that i is not the zero value for its type. +func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if i == nil || reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) { + return Fail(t, fmt.Sprintf("Should not be zero, but was %v", i), msgAndArgs...) + } + return true +} + +// FileExists checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...) + } + return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...) + } + if info.IsDir() { + return Fail(t, fmt.Sprintf("%q is a directory", path), msgAndArgs...) + } + return true +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + info, err := os.Lstat(path) + if err != nil { + return true + } + if info.IsDir() { + return true + } + return Fail(t, fmt.Sprintf("file %q exists", path), msgAndArgs...) +} + +// DirExists checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...) + } + return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...) + } + if !info.IsDir() { + return Fail(t, fmt.Sprintf("%q is a file", path), msgAndArgs...) + } + return true +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + info, err := os.Lstat(path) + if err != nil { + if os.IsNotExist(err) { + return true + } + return true + } + if !info.IsDir() { + return true + } + return Fail(t, fmt.Sprintf("directory %q exists", path), msgAndArgs...) +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + var expectedJSONAsInterface, actualJSONAsInterface interface{} + + if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil { + return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...) + } + + if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil { + return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...) + } + + return Equal(t, expectedJSONAsInterface, actualJSONAsInterface, msgAndArgs...) +} + +// YAMLEq asserts that two YAML strings are equivalent. +func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + var expectedYAMLAsInterface, actualYAMLAsInterface interface{} + + if err := yaml.Unmarshal([]byte(expected), &expectedYAMLAsInterface); err != nil { + return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid yaml.\nYAML parsing error: '%s'", expected, err.Error()), msgAndArgs...) + } + + if err := yaml.Unmarshal([]byte(actual), &actualYAMLAsInterface); err != nil { + return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid yaml.\nYAML error: '%s'", actual, err.Error()), msgAndArgs...) + } + + return Equal(t, expectedYAMLAsInterface, actualYAMLAsInterface, msgAndArgs...) +} + +func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) { + t := reflect.TypeOf(v) + k := t.Kind() + + if k == reflect.Ptr { + t = t.Elem() + k = t.Kind() + } + return t, k +} + +// diff returns a diff of both values as long as both are of the same type and +// are a struct, map, slice, array or string. Otherwise it returns an empty string. +func diff(expected interface{}, actual interface{}) string { + if expected == nil || actual == nil { + return "" + } + + et, ek := typeAndKind(expected) + at, _ := typeAndKind(actual) + + if et != at { + return "" + } + + if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array && ek != reflect.String { + return "" + } + + var e, a string + if et != reflect.TypeOf("") { + e = spewConfig.Sdump(expected) + a = spewConfig.Sdump(actual) + } else { + e = reflect.ValueOf(expected).String() + a = reflect.ValueOf(actual).String() + } + + diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ + A: difflib.SplitLines(e), + B: difflib.SplitLines(a), + FromFile: "Expected", + FromDate: "", + ToFile: "Actual", + ToDate: "", + Context: 1, + }) + + return "\n\nDiff:\n" + diff +} + +func isFunction(arg interface{}) bool { + if arg == nil { + return false + } + return reflect.TypeOf(arg).Kind() == reflect.Func +} + +var spewConfig = spew.ConfigState{ + Indent: " ", + DisablePointerAddresses: true, + DisableCapacities: true, + SortKeys: true, +} + +type tHelper interface { + Helper() +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + ch := make(chan bool, 1) + + timer := time.NewTimer(waitFor) + defer timer.Stop() + + ticker := time.NewTicker(tick) + defer ticker.Stop() + + for tick := ticker.C; ; { + select { + case <-timer.C: + return Fail(t, "Condition never satisfied", msgAndArgs...) + case <-tick: + tick = nil + go func() { ch <- condition() }() + case v := <-ch: + if v { + return true + } + tick = ticker.C + } + } +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) +func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + ch := make(chan bool, 1) + + timer := time.NewTimer(waitFor) + defer timer.Stop() + + ticker := time.NewTicker(tick) + defer ticker.Stop() + + for tick := ticker.C; ; { + select { + case <-timer.C: + return true + case <-tick: + tick = nil + go func() { ch <- condition() }() + case v := <-ch: + if v { + return Fail(t, "Condition satisfied", msgAndArgs...) + } + tick = ticker.C + } + } +} diff --git a/vendor/github.com/stretchr/testify/assert/doc.go b/vendor/github.com/stretchr/testify/assert/doc.go new file mode 100644 index 00000000..c9dccc4d --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/doc.go @@ -0,0 +1,45 @@ +// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system. +// +// Example Usage +// +// The following is a complete example using assert in a standard test function: +// import ( +// "testing" +// "github.com/stretchr/testify/assert" +// ) +// +// func TestSomething(t *testing.T) { +// +// var a string = "Hello" +// var b string = "Hello" +// +// assert.Equal(t, a, b, "The two words should be the same.") +// +// } +// +// if you assert many times, use the format below: +// +// import ( +// "testing" +// "github.com/stretchr/testify/assert" +// ) +// +// func TestSomething(t *testing.T) { +// assert := assert.New(t) +// +// var a string = "Hello" +// var b string = "Hello" +// +// assert.Equal(a, b, "The two words should be the same.") +// } +// +// Assertions +// +// Assertions allow you to easily write test code, and are global funcs in the `assert` package. +// All assertion functions take, as the first argument, the `*testing.T` object provided by the +// testing framework. This allows the assertion funcs to write the failings and other details to +// the correct place. +// +// Every assertion function also takes an optional string message as the final argument, +// allowing custom error messages to be appended to the message the assertion method outputs. +package assert diff --git a/vendor/github.com/stretchr/testify/assert/errors.go b/vendor/github.com/stretchr/testify/assert/errors.go new file mode 100644 index 00000000..ac9dc9d1 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/errors.go @@ -0,0 +1,10 @@ +package assert + +import ( + "errors" +) + +// AnError is an error instance useful for testing. If the code does not care +// about error specifics, and only needs to return the error for example, this +// error should be used to make the test code more readable. +var AnError = errors.New("assert.AnError general error for testing") diff --git a/vendor/github.com/stretchr/testify/assert/forward_assertions.go b/vendor/github.com/stretchr/testify/assert/forward_assertions.go new file mode 100644 index 00000000..df189d23 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/forward_assertions.go @@ -0,0 +1,16 @@ +package assert + +// Assertions provides assertion methods around the +// TestingT interface. +type Assertions struct { + t TestingT +} + +// New makes a new Assertions object for the specified TestingT. +func New(t TestingT) *Assertions { + return &Assertions{ + t: t, + } +} + +//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs" diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go new file mode 100644 index 00000000..df46fa77 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go @@ -0,0 +1,143 @@ +package assert + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "strings" +) + +// httpCode is a helper that returns HTTP code of the response. It returns -1 and +// an error if building a new request fails. +func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) { + w := httptest.NewRecorder() + req, err := http.NewRequest(method, url, nil) + if err != nil { + return -1, err + } + req.URL.RawQuery = values.Encode() + handler(w, req) + return w.Code, nil +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + code, err := httpCode(handler, method, url, values) + if err != nil { + Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) + return false + } + + isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent + if !isSuccessCode { + Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code)) + } + + return isSuccessCode +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + code, err := httpCode(handler, method, url, values) + if err != nil { + Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) + return false + } + + isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect + if !isRedirectCode { + Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code)) + } + + return isRedirectCode +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + code, err := httpCode(handler, method, url, values) + if err != nil { + Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) + return false + } + + isErrorCode := code >= http.StatusBadRequest + if !isErrorCode { + Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code)) + } + + return isErrorCode +} + +// HTTPBody is a helper that returns HTTP body of the response. It returns +// empty string if building a new request fails. +func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string { + w := httptest.NewRecorder() + req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) + if err != nil { + return "" + } + handler(w, req) + return w.Body.String() +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + body := HTTPBody(handler, method, url, values) + + contains := strings.Contains(body, fmt.Sprint(str)) + if !contains { + Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) + } + + return contains +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + body := HTTPBody(handler, method, url, values) + + contains := strings.Contains(body, fmt.Sprint(str)) + if contains { + Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) + } + + return !contains +} diff --git a/vendor/github.com/youtube/vitess/LICENSE b/vendor/github.com/youtube/vitess/LICENSE new file mode 100644 index 00000000..989d02e4 --- /dev/null +++ b/vendor/github.com/youtube/vitess/LICENSE @@ -0,0 +1,28 @@ +Copyright 2012, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/youtube/vitess/go/acl/acl.go b/vendor/github.com/youtube/vitess/go/acl/acl.go new file mode 100644 index 00000000..bf50e2c5 --- /dev/null +++ b/vendor/github.com/youtube/vitess/go/acl/acl.go @@ -0,0 +1,95 @@ +// Copyright 2012, Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package acl contains functions to enforce access control lists. +// It allows you to register multiple security policies for enforcing +// ACLs for users or HTTP requests. The specific policy to use must be +// specified from a command line argument and cannot be changed on-the-fly. +package acl + +import ( + "flag" + "fmt" + "net/http" + "sync" + + log "github.com/golang/glog" +) + +// This is a list of predefined roles. Applications are free +// to invent more roles, as long as the acl policies they use can +// understand what they mean. +const ( + ADMIN = "admin" + DEBUGGING = "debugging" + MONITORING = "monitoring" +) + +var ( + securityPolicy = flag.String("security_policy", "", "security policy to enforce for URLs") + policies = make(map[string]Policy) + once sync.Once + currentPolicy Policy +) + +// Policy defines the interface that needs to be satisfied by +// ACL policy implementors. +type Policy interface { + // CheckAccessActor can be called to verify if an actor + // has access to the role. + CheckAccessActor(actor, role string) error + // CheckAccessHTTP can be called to verify if an actor in + // the http request has access to the role. + CheckAccessHTTP(req *http.Request, role string) error +} + +// RegisterPolicy registers a security policy. This function must be called +// before the first call to CheckAccess happens, preferably through an init. +// This will ensure that the requested policy can be found by other acl +// functions when needed. +func RegisterPolicy(name string, policy Policy) { + if _, ok := policies[name]; ok { + log.Fatalf("policy %s is already registered", name) + } + policies[name] = policy +} + +func savePolicy() { + if *securityPolicy == "" { + return + } + currentPolicy = policies[*securityPolicy] + if currentPolicy == nil { + log.Warningf("policy %s not found, using fallback policy", *securityPolicy) + currentPolicy = FallbackPolicy{} + } +} + +// CheckAccessActor uses the current security policy to +// verify if an actor has access to the role. +func CheckAccessActor(actor, role string) error { + once.Do(savePolicy) + if currentPolicy != nil { + return currentPolicy.CheckAccessActor(actor, role) + } + return nil +} + +// CheckAccessHTTP uses the current security policy to +// verify if an actor in an http request has access to +// the role. +func CheckAccessHTTP(req *http.Request, role string) error { + once.Do(savePolicy) + if currentPolicy != nil { + return currentPolicy.CheckAccessHTTP(req, role) + } + return nil +} + +// SendError is a convenience function that sends an ACL +// error as an HTTP response. +func SendError(w http.ResponseWriter, err error) { + w.WriteHeader(http.StatusForbidden) + fmt.Fprintf(w, "Access denied: %v\n", err) +} diff --git a/vendor/github.com/youtube/vitess/go/acl/fallback_policy.go b/vendor/github.com/youtube/vitess/go/acl/fallback_policy.go new file mode 100644 index 00000000..b04bb17d --- /dev/null +++ b/vendor/github.com/youtube/vitess/go/acl/fallback_policy.go @@ -0,0 +1,27 @@ +// Copyright 2012, Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package acl + +import ( + "errors" + "net/http" +) + +var fallbackError = errors.New("not allowed: fallback policy") + +// FallbackPolicy is the policy that's used if the +// requested policy cannot be found. It rejects all +// access. +type FallbackPolicy struct{} + +// CheckAccessActor disallows all actor access. +func (fp FallbackPolicy) CheckAccessActor(actor, role string) error { + return fallbackError +} + +// CheckAccessHTTP disallows all HTTP access. +func (fp FallbackPolicy) CheckAccessHTTP(req *http.Request, role string) error { + return fallbackError +} diff --git a/vendor/github.com/youtube/vitess/go/cache/lru_cache.go b/vendor/github.com/youtube/vitess/go/cache/lru_cache.go new file mode 100644 index 00000000..59a585d0 --- /dev/null +++ b/vendor/github.com/youtube/vitess/go/cache/lru_cache.go @@ -0,0 +1,267 @@ +// Copyright 2012, Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package cache implements a LRU cache. +// +// The implementation borrows heavily from SmallLRUCache +// (originally by Nathan Schrenk). The object maintains a doubly-linked list of +// elements. When an element is accessed, it is promoted to the head of the +// list. When space is needed, the element at the tail of the list +// (the least recently used element) is evicted. +package cache + +import ( + "container/list" + "fmt" + "sync" + "time" +) + +// LRUCache is a typical LRU cache implementation. If the cache +// reaches the capacity, the least recently used item is deleted from +// the cache. Note the capacity is not the number of items, but the +// total sum of the Size() of each item. +type LRUCache struct { + mu sync.Mutex + + // list & table of *entry objects + list *list.List + table map[string]*list.Element + + // Our current size. Obviously a gross simplification and + // low-grade approximation. + size int64 + + // How much we are limiting the cache to. + capacity int64 +} + +// Value is the interface values that go into LRUCache need to satisfy +type Value interface { + // Size returns how big this value is. If you want to just track + // the cache by number of objects, you may return the size as 1. + Size() int +} + +// Item is what is stored in the cache +type Item struct { + Key string + Value Value +} + +type entry struct { + key string + value Value + size int64 + timeAccessed time.Time +} + +// NewLRUCache creates a new empty cache with the given capacity. +func NewLRUCache(capacity int64) *LRUCache { + return &LRUCache{ + list: list.New(), + table: make(map[string]*list.Element), + capacity: capacity, + } +} + +// Get returns a value from the cache, and marks the entry as most +// recently used. +func (lru *LRUCache) Get(key string) (v Value, ok bool) { + lru.mu.Lock() + defer lru.mu.Unlock() + + element := lru.table[key] + if element == nil { + return nil, false + } + lru.moveToFront(element) + return element.Value.(*entry).value, true +} + +// Peek returns a value from the cache without changing the LRU order. +func (lru *LRUCache) Peek(key string) (v Value, ok bool) { + lru.mu.Lock() + defer lru.mu.Unlock() + + element := lru.table[key] + if element == nil { + return nil, false + } + return element.Value.(*entry).value, true +} + +// Set sets a value in the cache. +func (lru *LRUCache) Set(key string, value Value) { + lru.mu.Lock() + defer lru.mu.Unlock() + + if element := lru.table[key]; element != nil { + lru.updateInplace(element, value) + } else { + lru.addNew(key, value) + } +} + +// SetIfAbsent will set the value in the cache if not present. If the +// value exists in the cache, we don't set it. +func (lru *LRUCache) SetIfAbsent(key string, value Value) { + lru.mu.Lock() + defer lru.mu.Unlock() + + if element := lru.table[key]; element != nil { + lru.moveToFront(element) + } else { + lru.addNew(key, value) + } +} + +// Delete removes an entry from the cache, and returns if the entry existed. +func (lru *LRUCache) Delete(key string) bool { + lru.mu.Lock() + defer lru.mu.Unlock() + + element := lru.table[key] + if element == nil { + return false + } + + lru.list.Remove(element) + delete(lru.table, key) + lru.size -= element.Value.(*entry).size + return true +} + +// Clear will clear the entire cache. +func (lru *LRUCache) Clear() { + lru.mu.Lock() + defer lru.mu.Unlock() + + lru.list.Init() + lru.table = make(map[string]*list.Element) + lru.size = 0 +} + +// SetCapacity will set the capacity of the cache. If the capacity is +// smaller, and the current cache size exceed that capacity, the cache +// will be shrank. +func (lru *LRUCache) SetCapacity(capacity int64) { + lru.mu.Lock() + defer lru.mu.Unlock() + + lru.capacity = capacity + lru.checkCapacity() +} + +// Stats returns a few stats on the cache. +func (lru *LRUCache) Stats() (length, size, capacity int64, oldest time.Time) { + lru.mu.Lock() + defer lru.mu.Unlock() + if lastElem := lru.list.Back(); lastElem != nil { + oldest = lastElem.Value.(*entry).timeAccessed + } + return int64(lru.list.Len()), lru.size, lru.capacity, oldest +} + +// StatsJSON returns stats as a JSON object in a string. +func (lru *LRUCache) StatsJSON() string { + if lru == nil { + return "{}" + } + l, s, c, o := lru.Stats() + return fmt.Sprintf("{\"Length\": %v, \"Size\": %v, \"Capacity\": %v, \"OldestAccess\": \"%v\"}", l, s, c, o) +} + +// Length returns how many elements are in the cache +func (lru *LRUCache) Length() int64 { + lru.mu.Lock() + defer lru.mu.Unlock() + return int64(lru.list.Len()) +} + +// Size returns the sum of the objects' Size() method. +func (lru *LRUCache) Size() int64 { + lru.mu.Lock() + defer lru.mu.Unlock() + return lru.size +} + +// Capacity returns the cache maximum capacity. +func (lru *LRUCache) Capacity() int64 { + lru.mu.Lock() + defer lru.mu.Unlock() + return lru.capacity +} + +// Oldest returns the insertion time of the oldest element in the cache, +// or a IsZero() time if cache is empty. +func (lru *LRUCache) Oldest() (oldest time.Time) { + lru.mu.Lock() + defer lru.mu.Unlock() + if lastElem := lru.list.Back(); lastElem != nil { + oldest = lastElem.Value.(*entry).timeAccessed + } + return +} + +// Keys returns all the keys for the cache, ordered from most recently +// used to last recently used. +func (lru *LRUCache) Keys() []string { + lru.mu.Lock() + defer lru.mu.Unlock() + + keys := make([]string, 0, lru.list.Len()) + for e := lru.list.Front(); e != nil; e = e.Next() { + keys = append(keys, e.Value.(*entry).key) + } + return keys +} + +// Items returns all the values for the cache, ordered from most recently +// used to last recently used. +func (lru *LRUCache) Items() []Item { + lru.mu.Lock() + defer lru.mu.Unlock() + + items := make([]Item, 0, lru.list.Len()) + for e := lru.list.Front(); e != nil; e = e.Next() { + v := e.Value.(*entry) + items = append(items, Item{Key: v.key, Value: v.value}) + } + return items +} + +func (lru *LRUCache) updateInplace(element *list.Element, value Value) { + valueSize := int64(value.Size()) + sizeDiff := valueSize - element.Value.(*entry).size + element.Value.(*entry).value = value + element.Value.(*entry).size = valueSize + lru.size += sizeDiff + lru.moveToFront(element) + lru.checkCapacity() +} + +func (lru *LRUCache) moveToFront(element *list.Element) { + lru.list.MoveToFront(element) + element.Value.(*entry).timeAccessed = time.Now() +} + +func (lru *LRUCache) addNew(key string, value Value) { + newEntry := &entry{key, value, int64(value.Size()), time.Now()} + element := lru.list.PushFront(newEntry) + lru.table[key] = element + lru.size += newEntry.size + lru.checkCapacity() +} + +func (lru *LRUCache) checkCapacity() { + // Partially duplicated from Delete + for lru.size > lru.capacity { + delElem := lru.list.Back() + delValue := delElem.Value.(*entry) + lru.list.Remove(delElem) + delete(lru.table, delValue.key) + lru.size -= delValue.size + } +} diff --git a/vendor/github.com/youtube/vitess/go/pools/id_pool.go b/vendor/github.com/youtube/vitess/go/pools/id_pool.go new file mode 100644 index 00000000..d1f19c27 --- /dev/null +++ b/vendor/github.com/youtube/vitess/go/pools/id_pool.go @@ -0,0 +1,72 @@ +// Copyright 2014, Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pools + +import ( + "fmt" + "sync" +) + +// IDPool is used to ensure that the set of IDs in use concurrently never +// contains any duplicates. The IDs start at 1 and increase without bound, but +// will never be larger than the peak number of concurrent uses. +// +// IDPool's Get() and Set() methods can be used concurrently. +type IDPool struct { + sync.Mutex + + // used holds the set of values that have been returned to us with Put(). + used map[uint32]bool + // maxUsed remembers the largest value we've given out. + maxUsed uint32 +} + +// NewIDPool creates and initializes an IDPool. +func NewIDPool() *IDPool { + return &IDPool{ + used: make(map[uint32]bool), + } +} + +// Get returns an ID that is unique among currently active users of this pool. +func (pool *IDPool) Get() (id uint32) { + pool.Lock() + defer pool.Unlock() + + // Pick a value that's been returned, if any. + for key := range pool.used { + delete(pool.used, key) + return key + } + + // No recycled IDs are available, so increase the pool size. + pool.maxUsed += 1 + return pool.maxUsed +} + +// Put recycles an ID back into the pool for others to use. Putting back a value +// or 0, or a value that is not currently "checked out", will result in a panic +// because that should never happen except in the case of a programming error. +func (pool *IDPool) Put(id uint32) { + pool.Lock() + defer pool.Unlock() + + if id < 1 || id > pool.maxUsed { + panic(fmt.Errorf("IDPool.Put(%v): invalid value, must be in the range [1,%v]", id, pool.maxUsed)) + } + + if pool.used[id] { + panic(fmt.Errorf("IDPool.Put(%v): can't put value that was already recycled", id)) + } + + // If we're recycling maxUsed, just shrink the pool. + if id == pool.maxUsed { + pool.maxUsed = id - 1 + return + } + + // Add it to the set of recycled IDs. + pool.used[id] = true +} diff --git a/vendor/github.com/youtube/vitess/go/pools/numbered.go b/vendor/github.com/youtube/vitess/go/pools/numbered.go new file mode 100644 index 00000000..b3844eda --- /dev/null +++ b/vendor/github.com/youtube/vitess/go/pools/numbered.go @@ -0,0 +1,160 @@ +// Copyright 2012, Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pools + +import ( + "fmt" + "sync" + "time" +) + +// Numbered allows you to manage resources by tracking them with numbers. +// There are no interface restrictions on what you can track. +type Numbered struct { + mu sync.Mutex + empty *sync.Cond // Broadcast when pool becomes empty + resources map[int64]*numberedWrapper +} + +type numberedWrapper struct { + val interface{} + inUse bool + purpose string + timeCreated time.Time + timeUsed time.Time +} + +func NewNumbered() *Numbered { + n := &Numbered{resources: make(map[int64]*numberedWrapper)} + n.empty = sync.NewCond(&n.mu) + return n +} + +// Register starts tracking a resource by the supplied id. +// It does not lock the object. +// It returns an error if the id already exists. +func (nu *Numbered) Register(id int64, val interface{}) error { + nu.mu.Lock() + defer nu.mu.Unlock() + if _, ok := nu.resources[id]; ok { + return fmt.Errorf("already present") + } + now := time.Now() + nu.resources[id] = &numberedWrapper{ + val: val, + timeCreated: now, + timeUsed: now, + } + return nil +} + +// Unregiester forgets the specified resource. +// If the resource is not present, it's ignored. +func (nu *Numbered) Unregister(id int64) { + nu.mu.Lock() + defer nu.mu.Unlock() + delete(nu.resources, id) + if len(nu.resources) == 0 { + nu.empty.Broadcast() + } +} + +// Get locks the resource for use. It accepts a purpose as a string. +// If it cannot be found, it returns a "not found" error. If in use, +// it returns a "in use: purpose" error. +func (nu *Numbered) Get(id int64, purpose string) (val interface{}, err error) { + nu.mu.Lock() + defer nu.mu.Unlock() + nw, ok := nu.resources[id] + if !ok { + return nil, fmt.Errorf("not found") + } + if nw.inUse { + return nil, fmt.Errorf("in use: %s", nw.purpose) + } + nw.inUse = true + nw.purpose = purpose + return nw.val, nil +} + +// Put unlocks a resource for someone else to use. +func (nu *Numbered) Put(id int64) { + nu.mu.Lock() + defer nu.mu.Unlock() + if nw, ok := nu.resources[id]; ok { + nw.inUse = false + nw.purpose = "" + nw.timeUsed = time.Now() + } +} + +// GetAll returns the list of all resources in the pool. +func (nu *Numbered) GetAll() (vals []interface{}) { + nu.mu.Lock() + defer nu.mu.Unlock() + vals = make([]interface{}, 0, len(nu.resources)) + for _, nw := range nu.resources { + vals = append(vals, nw.val) + } + return vals +} + +// GetOutdated returns a list of resources that are older than age, and locks them. +// It does not return any resources that are already locked. +func (nu *Numbered) GetOutdated(age time.Duration, purpose string) (vals []interface{}) { + nu.mu.Lock() + defer nu.mu.Unlock() + now := time.Now() + for _, nw := range nu.resources { + if nw.inUse { + continue + } + if nw.timeCreated.Add(age).Sub(now) <= 0 { + nw.inUse = true + nw.purpose = purpose + vals = append(vals, nw.val) + } + } + return vals +} + +// GetIdle returns a list of resurces that have been idle for longer +// than timeout, and locks them. It does not return any resources that +// are already locked. +func (nu *Numbered) GetIdle(timeout time.Duration, purpose string) (vals []interface{}) { + nu.mu.Lock() + defer nu.mu.Unlock() + now := time.Now() + for _, nw := range nu.resources { + if nw.inUse { + continue + } + if nw.timeUsed.Add(timeout).Sub(now) <= 0 { + nw.inUse = true + nw.purpose = purpose + vals = append(vals, nw.val) + } + } + return vals +} + +// WaitForEmpty returns as soon as the pool becomes empty +func (nu *Numbered) WaitForEmpty() { + nu.mu.Lock() + defer nu.mu.Unlock() + for len(nu.resources) != 0 { + nu.empty.Wait() + } +} + +func (nu *Numbered) StatsJSON() string { + return fmt.Sprintf("{\"Size\": %v}", nu.Size()) +} + +func (nu *Numbered) Size() (size int64) { + nu.mu.Lock() + defer nu.mu.Unlock() + return int64(len(nu.resources)) +} diff --git a/vendor/github.com/youtube/vitess/go/pools/resource_pool.go b/vendor/github.com/youtube/vitess/go/pools/resource_pool.go new file mode 100644 index 00000000..1d549bdc --- /dev/null +++ b/vendor/github.com/youtube/vitess/go/pools/resource_pool.go @@ -0,0 +1,252 @@ +// Copyright 2012, Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package pools provides functionality to manage and reuse resources +// like connections. +package pools + +import ( + "errors" + "fmt" + "time" + + "github.com/youtube/vitess/go/sync2" + "golang.org/x/net/context" +) + +var ( + // ErrClosed is returned if ResourcePool is used when it's closed. + ErrClosed = errors.New("resource pool is closed") + + // ErrTimeout is returned if a resource get times out. + ErrTimeout = errors.New("resource pool timed out") +) + +// Factory is a function that can be used to create a resource. +type Factory func() (Resource, error) + +// Resource defines the interface that every resource must provide. +// Thread synchronization between Close() and IsClosed() +// is the responsibility of the caller. +type Resource interface { + Close() +} + +// ResourcePool allows you to use a pool of resources. +type ResourcePool struct { + resources chan resourceWrapper + factory Factory + capacity sync2.AtomicInt64 + idleTimeout sync2.AtomicDuration + + // stats + waitCount sync2.AtomicInt64 + waitTime sync2.AtomicDuration +} + +type resourceWrapper struct { + resource Resource + timeUsed time.Time +} + +// NewResourcePool creates a new ResourcePool pool. +// capacity is the number of active resources in the pool: +// there can be up to 'capacity' of these at a given time. +// maxCap specifies the extent to which the pool can be resized +// in the future through the SetCapacity function. +// You cannot resize the pool beyond maxCap. +// If a resource is unused beyond idleTimeout, it's discarded. +// An idleTimeout of 0 means that there is no timeout. +func NewResourcePool(factory Factory, capacity, maxCap int, idleTimeout time.Duration) *ResourcePool { + if capacity <= 0 || maxCap <= 0 || capacity > maxCap { + panic(errors.New("invalid/out of range capacity")) + } + rp := &ResourcePool{ + resources: make(chan resourceWrapper, maxCap), + factory: factory, + capacity: sync2.NewAtomicInt64(int64(capacity)), + idleTimeout: sync2.NewAtomicDuration(idleTimeout), + } + for i := 0; i < capacity; i++ { + rp.resources <- resourceWrapper{} + } + return rp +} + +// Close empties the pool calling Close on all its resources. +// You can call Close while there are outstanding resources. +// It waits for all resources to be returned (Put). +// After a Close, Get is not allowed. +func (rp *ResourcePool) Close() { + _ = rp.SetCapacity(0) +} + +// IsClosed returns true if the resource pool is closed. +func (rp *ResourcePool) IsClosed() (closed bool) { + return rp.capacity.Get() == 0 +} + +// Get will return the next available resource. If capacity +// has not been reached, it will create a new one using the factory. Otherwise, +// it will wait till the next resource becomes available or a timeout. +// A timeout of 0 is an indefinite wait. +func (rp *ResourcePool) Get(ctx context.Context) (resource Resource, err error) { + return rp.get(ctx, true) +} + +func (rp *ResourcePool) get(ctx context.Context, wait bool) (resource Resource, err error) { + // If ctx has already expired, avoid racing with rp's resource channel. + select { + case <-ctx.Done(): + return nil, ErrTimeout + default: + } + + // Fetch + var wrapper resourceWrapper + var ok bool + select { + case wrapper, ok = <-rp.resources: + default: + if !wait { + return nil, nil + } + startTime := time.Now() + select { + case wrapper, ok = <-rp.resources: + case <-ctx.Done(): + return nil, ErrTimeout + } + rp.recordWait(startTime) + } + if !ok { + return nil, ErrClosed + } + + // Unwrap + idleTimeout := rp.idleTimeout.Get() + if wrapper.resource != nil && idleTimeout > 0 && wrapper.timeUsed.Add(idleTimeout).Sub(time.Now()) < 0 { + wrapper.resource.Close() + wrapper.resource = nil + } + if wrapper.resource == nil { + wrapper.resource, err = rp.factory() + if err != nil { + rp.resources <- resourceWrapper{} + } + } + return wrapper.resource, err +} + +// Put will return a resource to the pool. For every successful Get, +// a corresponding Put is required. If you no longer need a resource, +// you will need to call Put(nil) instead of returning the closed resource. +// The will eventually cause a new resource to be created in its place. +func (rp *ResourcePool) Put(resource Resource) { + var wrapper resourceWrapper + if resource != nil { + wrapper = resourceWrapper{resource, time.Now()} + } + select { + case rp.resources <- wrapper: + default: + panic(errors.New("attempt to Put into a full ResourcePool")) + } +} + +// SetCapacity changes the capacity of the pool. +// You can use it to shrink or expand, but not beyond +// the max capacity. If the change requires the pool +// to be shrunk, SetCapacity waits till the necessary +// number of resources are returned to the pool. +// A SetCapacity of 0 is equivalent to closing the ResourcePool. +func (rp *ResourcePool) SetCapacity(capacity int) error { + if capacity < 0 || capacity > cap(rp.resources) { + return fmt.Errorf("capacity %d is out of range", capacity) + } + + // Atomically swap new capacity with old, but only + // if old capacity is non-zero. + var oldcap int + for { + oldcap = int(rp.capacity.Get()) + if oldcap == 0 { + return ErrClosed + } + if oldcap == capacity { + return nil + } + if rp.capacity.CompareAndSwap(int64(oldcap), int64(capacity)) { + break + } + } + + if capacity < oldcap { + for i := 0; i < oldcap-capacity; i++ { + wrapper := <-rp.resources + if wrapper.resource != nil { + wrapper.resource.Close() + } + } + } else { + for i := 0; i < capacity-oldcap; i++ { + rp.resources <- resourceWrapper{} + } + } + if capacity == 0 { + close(rp.resources) + } + return nil +} + +func (rp *ResourcePool) recordWait(start time.Time) { + rp.waitCount.Add(1) + rp.waitTime.Add(time.Now().Sub(start)) +} + +// SetIdleTimeout sets the idle timeout. +func (rp *ResourcePool) SetIdleTimeout(idleTimeout time.Duration) { + rp.idleTimeout.Set(idleTimeout) +} + +// StatsJSON returns the stats in JSON format. +func (rp *ResourcePool) StatsJSON() string { + c, a, mx, wc, wt, it := rp.Stats() + return fmt.Sprintf(`{"Capacity": %v, "Available": %v, "MaxCapacity": %v, "WaitCount": %v, "WaitTime": %v, "IdleTimeout": %v}`, c, a, mx, wc, int64(wt), int64(it)) +} + +// Stats returns the stats. +func (rp *ResourcePool) Stats() (capacity, available, maxCap, waitCount int64, waitTime, idleTimeout time.Duration) { + return rp.Capacity(), rp.Available(), rp.MaxCap(), rp.WaitCount(), rp.WaitTime(), rp.IdleTimeout() +} + +// Capacity returns the capacity. +func (rp *ResourcePool) Capacity() int64 { + return rp.capacity.Get() +} + +// Available returns the number of currently unused resources. +func (rp *ResourcePool) Available() int64 { + return int64(len(rp.resources)) +} + +// MaxCap returns the max capacity. +func (rp *ResourcePool) MaxCap() int64 { + return int64(cap(rp.resources)) +} + +// WaitCount returns the total number of waits. +func (rp *ResourcePool) WaitCount() int64 { + return rp.waitCount.Get() +} + +// WaitTime returns the total wait time. +func (rp *ResourcePool) WaitTime() time.Duration { + return rp.waitTime.Get() +} + +// IdleTimeout returns the idle timeout. +func (rp *ResourcePool) IdleTimeout() time.Duration { + return rp.idleTimeout.Get() +} diff --git a/vendor/github.com/youtube/vitess/go/sync2/atomic.go b/vendor/github.com/youtube/vitess/go/sync2/atomic.go new file mode 100644 index 00000000..4ea2271a --- /dev/null +++ b/vendor/github.com/youtube/vitess/go/sync2/atomic.go @@ -0,0 +1,162 @@ +// Copyright 2013, Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sync2 + +import ( + "sync" + "sync/atomic" + "time" +) + +// AtomicInt32 is a wrapper with a simpler interface around atomic.(Add|Store|Load|CompareAndSwap)Int32 functions. +type AtomicInt32 struct { + int32 +} + +// NewAtomicInt32 initializes a new AtomicInt32 with a given value. +func NewAtomicInt32(n int32) AtomicInt32 { + return AtomicInt32{n} +} + +// Add atomically adds n to the value. +func (i *AtomicInt32) Add(n int32) int32 { + return atomic.AddInt32(&i.int32, n) +} + +// Set atomically sets n as new value. +func (i *AtomicInt32) Set(n int32) { + atomic.StoreInt32(&i.int32, n) +} + +// Get atomically returns the current value. +func (i *AtomicInt32) Get() int32 { + return atomic.LoadInt32(&i.int32) +} + +// CompareAndSwap atomatically swaps the old with the new value. +func (i *AtomicInt32) CompareAndSwap(oldval, newval int32) (swapped bool) { + return atomic.CompareAndSwapInt32(&i.int32, oldval, newval) +} + +// AtomicInt64 is a wrapper with a simpler interface around atomic.(Add|Store|Load|CompareAndSwap)Int64 functions. +type AtomicInt64 struct { + int64 +} + +// NewAtomicInt64 initializes a new AtomicInt64 with a given value. +func NewAtomicInt64(n int64) AtomicInt64 { + return AtomicInt64{n} +} + +// Add atomically adds n to the value. +func (i *AtomicInt64) Add(n int64) int64 { + return atomic.AddInt64(&i.int64, n) +} + +// Set atomically sets n as new value. +func (i *AtomicInt64) Set(n int64) { + atomic.StoreInt64(&i.int64, n) +} + +// Get atomically returns the current value. +func (i *AtomicInt64) Get() int64 { + return atomic.LoadInt64(&i.int64) +} + +// CompareAndSwap atomatically swaps the old with the new value. +func (i *AtomicInt64) CompareAndSwap(oldval, newval int64) (swapped bool) { + return atomic.CompareAndSwapInt64(&i.int64, oldval, newval) +} + +// AtomicDuration is a wrapper with a simpler interface around atomic.(Add|Store|Load|CompareAndSwap)Int64 functions. +type AtomicDuration struct { + int64 +} + +// NewAtomicDuration initializes a new AtomicDuration with a given value. +func NewAtomicDuration(duration time.Duration) AtomicDuration { + return AtomicDuration{int64(duration)} +} + +// Add atomically adds duration to the value. +func (d *AtomicDuration) Add(duration time.Duration) time.Duration { + return time.Duration(atomic.AddInt64(&d.int64, int64(duration))) +} + +// Set atomically sets duration as new value. +func (d *AtomicDuration) Set(duration time.Duration) { + atomic.StoreInt64(&d.int64, int64(duration)) +} + +// Get atomically returns the current value. +func (d *AtomicDuration) Get() time.Duration { + return time.Duration(atomic.LoadInt64(&d.int64)) +} + +// CompareAndSwap atomatically swaps the old with the new value. +func (d *AtomicDuration) CompareAndSwap(oldval, newval time.Duration) (swapped bool) { + return atomic.CompareAndSwapInt64(&d.int64, int64(oldval), int64(newval)) +} + +// AtomicBool gives an atomic boolean variable. +type AtomicBool struct { + int32 +} + +// NewAtomicBool initializes a new AtomicBool with a given value. +func NewAtomicBool(n bool) AtomicBool { + if n { + return AtomicBool{1} + } + return AtomicBool{0} +} + +// Set atomically sets n as new value. +func (i *AtomicBool) Set(n bool) { + if n { + atomic.StoreInt32(&i.int32, 1) + } else { + atomic.StoreInt32(&i.int32, 0) + } +} + +// Get atomically returns the current value. +func (i *AtomicBool) Get() bool { + return atomic.LoadInt32(&i.int32) != 0 +} + +// AtomicString gives you atomic-style APIs for string, but +// it's only a convenience wrapper that uses a mutex. So, it's +// not as efficient as the rest of the atomic types. +type AtomicString struct { + mu sync.Mutex + str string +} + +// Set atomically sets str as new value. +func (s *AtomicString) Set(str string) { + s.mu.Lock() + s.str = str + s.mu.Unlock() +} + +// Get atomically returns the current value. +func (s *AtomicString) Get() string { + s.mu.Lock() + str := s.str + s.mu.Unlock() + return str +} + +// CompareAndSwap atomatically swaps the old with the new value. +func (s *AtomicString) CompareAndSwap(oldval, newval string) (swqpped bool) { + s.mu.Lock() + defer s.mu.Unlock() + if s.str == oldval { + s.str = newval + return true + } + return false +} diff --git a/vendor/github.com/youtube/vitess/go/sync2/consolidator.go b/vendor/github.com/youtube/vitess/go/sync2/consolidator.go new file mode 100644 index 00000000..52de3105 --- /dev/null +++ b/vendor/github.com/youtube/vitess/go/sync2/consolidator.go @@ -0,0 +1,137 @@ +// Copyright 2012, Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sync2 + +import ( + "fmt" + "net/http" + "sync" + "sync/atomic" + + "github.com/youtube/vitess/go/acl" + "github.com/youtube/vitess/go/cache" +) + +// Consolidator consolidates duplicate queries from executing simulaneously +// and shares results between them. +type Consolidator struct { + *ConsolidatorCache + + mu sync.Mutex + queries map[string]*Result +} + +// NewConsolidator creates a new Consolidator +func NewConsolidator() *Consolidator { + return &Consolidator{ + queries: make(map[string]*Result), + ConsolidatorCache: NewConsolidatorCache(1000), + } +} + +// Result is a wrapper for result of a query. +type Result struct { + // executing is used to block additional requests. + // The original request holds a write lock while additional ones are blocked + // on acquiring a read lock (see Wait() below.) + executing sync.RWMutex + consolidator *Consolidator + query string + Result interface{} + Err error +} + +// Create adds a query to currently executing queries and acquires a +// lock on its Result if it is not already present. If the query is +// a duplicate, Create returns false. +func (co *Consolidator) Create(query string) (r *Result, created bool) { + co.mu.Lock() + defer co.mu.Unlock() + if r, ok := co.queries[query]; ok { + return r, false + } + r = &Result{consolidator: co, query: query} + r.executing.Lock() + co.queries[query] = r + return r, true +} + +// Broadcast removes the entry from current queries and releases the +// lock on its Result. Broadcast should be invoked when original +// query completes execution. +func (rs *Result) Broadcast() { + rs.consolidator.mu.Lock() + defer rs.consolidator.mu.Unlock() + delete(rs.consolidator.queries, rs.query) + rs.executing.Unlock() +} + +// Wait waits for the original query to complete execution. Wait should +// be invoked for duplicate queries. +func (rs *Result) Wait() { + rs.consolidator.Record(rs.query) + rs.executing.RLock() +} + +// ConsolidatorCache is a thread-safe object used for counting how often recent +// queries have been consolidated. +// It is also used by the txserializer package to count how often transactions +// have been queued and had to wait because they targeted the same row (range). +type ConsolidatorCache struct { + *cache.LRUCache +} + +// NewConsolidatorCache creates a new cache with the given capacity. +func NewConsolidatorCache(capacity int64) *ConsolidatorCache { + return &ConsolidatorCache{cache.NewLRUCache(capacity)} +} + +// ServeHTTP lists the most recent, cached queries and their count. +func (cc *ConsolidatorCache) ServeHTTP(response http.ResponseWriter, request *http.Request) { + if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { + acl.SendError(response, err) + return + } + items := cc.Items() + response.Header().Set("Content-Type", "text/plain") + if items == nil { + response.Write([]byte("empty\n")) + return + } + response.Write([]byte(fmt.Sprintf("Length: %d\n", len(items)))) + for _, v := range items { + response.Write([]byte(fmt.Sprintf("%v: %s\n", v.Value.(*ccount).get(), v.Key))) + } +} + +// Record increments the count for "query" by 1. +// If it's not in the cache yet, it will be added. +func (cc *ConsolidatorCache) Record(query string) { + if v, ok := cc.Get(query); ok { + v.(*ccount).add(1) + } else { + c := ccount(1) + cc.Set(query, &c) + } +} + +// ccount elements are used with a cache.LRUCache object to track if another +// request for the same query is already in progress. +type ccount int64 + +// Size always returns 1 because we use the cache only to track queries, +// independent of the number of requests waiting for them. +// This implements the cache.Value interface. +func (cc *ccount) Size() int { + return 1 +} + +func (cc *ccount) add(n int64) int64 { + return atomic.AddInt64((*int64)(cc), n) +} + +func (cc *ccount) get() int64 { + return atomic.LoadInt64((*int64)(cc)) +} diff --git a/vendor/github.com/youtube/vitess/go/sync2/doc.go b/vendor/github.com/youtube/vitess/go/sync2/doc.go new file mode 100644 index 00000000..2580e414 --- /dev/null +++ b/vendor/github.com/youtube/vitess/go/sync2/doc.go @@ -0,0 +1,6 @@ +// Copyright 2014, Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sync2 provides extra functionality along the same lines as sync. +package sync2 diff --git a/vendor/github.com/youtube/vitess/go/sync2/semaphore.go b/vendor/github.com/youtube/vitess/go/sync2/semaphore.go new file mode 100644 index 00000000..cd75e72d --- /dev/null +++ b/vendor/github.com/youtube/vitess/go/sync2/semaphore.go @@ -0,0 +1,73 @@ +// Copyright 2012, Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sync2 + +// What's in a name? Channels have all you need to emulate a counting +// semaphore with a boatload of extra functionality. However, in some +// cases, you just want a familiar API. + +import ( + "time" +) + +// Semaphore is a counting semaphore with the option to +// specify a timeout. +type Semaphore struct { + slots chan struct{} + timeout time.Duration +} + +// NewSemaphore creates a Semaphore. The count parameter must be a positive +// number. A timeout of zero means that there is no timeout. +func NewSemaphore(count int, timeout time.Duration) *Semaphore { + sem := &Semaphore{ + slots: make(chan struct{}, count), + timeout: timeout, + } + for i := 0; i < count; i++ { + sem.slots <- struct{}{} + } + return sem +} + +// Acquire returns true on successful acquisition, and +// false on a timeout. +func (sem *Semaphore) Acquire() bool { + if sem.timeout == 0 { + <-sem.slots + return true + } + tm := time.NewTimer(sem.timeout) + defer tm.Stop() + select { + case <-sem.slots: + return true + case <-tm.C: + return false + } +} + +// TryAcquire acquires a semaphore if it's immediately available. +// It returns false otherwise. +func (sem *Semaphore) TryAcquire() bool { + select { + case <-sem.slots: + return true + default: + return false + } +} + +// Release releases the acquired semaphore. You must +// not release more than the number of semaphores you've +// acquired. +func (sem *Semaphore) Release() { + sem.slots <- struct{}{} +} + +// Size returns the current number of available slots. +func (sem *Semaphore) Size() int { + return len(sem.slots) +} diff --git a/vendor/golang.org/x/net/AUTHORS b/vendor/golang.org/x/net/AUTHORS new file mode 100644 index 00000000..15167cd7 --- /dev/null +++ b/vendor/golang.org/x/net/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/net/CONTRIBUTORS b/vendor/golang.org/x/net/CONTRIBUTORS new file mode 100644 index 00000000..1c4577e9 --- /dev/null +++ b/vendor/golang.org/x/net/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/net/LICENSE b/vendor/golang.org/x/net/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/vendor/golang.org/x/net/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/net/PATENTS b/vendor/golang.org/x/net/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/vendor/golang.org/x/net/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/net/context/context.go b/vendor/golang.org/x/net/context/context.go new file mode 100644 index 00000000..a3c021d3 --- /dev/null +++ b/vendor/golang.org/x/net/context/context.go @@ -0,0 +1,56 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package context defines the Context type, which carries deadlines, +// cancelation signals, and other request-scoped values across API boundaries +// and between processes. +// As of Go 1.7 this package is available in the standard library under the +// name context. https://golang.org/pkg/context. +// +// Incoming requests to a server should create a Context, and outgoing calls to +// servers should accept a Context. The chain of function calls between must +// propagate the Context, optionally replacing it with a modified copy created +// using WithDeadline, WithTimeout, WithCancel, or WithValue. +// +// Programs that use Contexts should follow these rules to keep interfaces +// consistent across packages and enable static analysis tools to check context +// propagation: +// +// Do not store Contexts inside a struct type; instead, pass a Context +// explicitly to each function that needs it. The Context should be the first +// parameter, typically named ctx: +// +// func DoSomething(ctx context.Context, arg Arg) error { +// // ... use ctx ... +// } +// +// Do not pass a nil Context, even if a function permits it. Pass context.TODO +// if you are unsure about which Context to use. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The same Context may be passed to functions running in different goroutines; +// Contexts are safe for simultaneous use by multiple goroutines. +// +// See http://blog.golang.org/context for example code for a server that uses +// Contexts. +package context // import "golang.org/x/net/context" + +// Background returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +func Background() Context { + return background +} + +// TODO returns a non-nil, empty Context. Code should use context.TODO when +// it's unclear which Context to use or it is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). TODO is recognized by static analysis tools that determine +// whether Contexts are propagated correctly in a program. +func TODO() Context { + return todo +} diff --git a/vendor/golang.org/x/net/context/go17.go b/vendor/golang.org/x/net/context/go17.go new file mode 100644 index 00000000..d20f52b7 --- /dev/null +++ b/vendor/golang.org/x/net/context/go17.go @@ -0,0 +1,72 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package context + +import ( + "context" // standard library's context, as of Go 1.7 + "time" +) + +var ( + todo = context.TODO() + background = context.Background() +) + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = context.Canceled + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = context.DeadlineExceeded + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + ctx, f := context.WithCancel(parent) + return ctx, CancelFunc(f) +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + ctx, f := context.WithDeadline(parent, deadline) + return ctx, CancelFunc(f) +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return context.WithValue(parent, key, val) +} diff --git a/vendor/golang.org/x/net/context/go19.go b/vendor/golang.org/x/net/context/go19.go new file mode 100644 index 00000000..d88bd1db --- /dev/null +++ b/vendor/golang.org/x/net/context/go19.go @@ -0,0 +1,20 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.9 + +package context + +import "context" // standard library's context, as of Go 1.7 + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context = context.Context + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc = context.CancelFunc diff --git a/vendor/golang.org/x/net/context/pre_go17.go b/vendor/golang.org/x/net/context/pre_go17.go new file mode 100644 index 00000000..0f35592d --- /dev/null +++ b/vendor/golang.org/x/net/context/pre_go17.go @@ -0,0 +1,300 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.7 + +package context + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// An emptyCtx is never canceled, has no values, and has no deadline. It is not +// struct{}, since vars of this type must have distinct addresses. +type emptyCtx int + +func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { + return +} + +func (*emptyCtx) Done() <-chan struct{} { + return nil +} + +func (*emptyCtx) Err() error { + return nil +} + +func (*emptyCtx) Value(key interface{}) interface{} { + return nil +} + +func (e *emptyCtx) String() string { + switch e { + case background: + return "context.Background" + case todo: + return "context.TODO" + } + return "unknown empty Context" +} + +var ( + background = new(emptyCtx) + todo = new(emptyCtx) +) + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = errors.New("context canceled") + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = errors.New("context deadline exceeded") + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + c := newCancelCtx(parent) + propagateCancel(parent, c) + return c, func() { c.cancel(true, Canceled) } +} + +// newCancelCtx returns an initialized cancelCtx. +func newCancelCtx(parent Context) *cancelCtx { + return &cancelCtx{ + Context: parent, + done: make(chan struct{}), + } +} + +// propagateCancel arranges for child to be canceled when parent is. +func propagateCancel(parent Context, child canceler) { + if parent.Done() == nil { + return // parent is never canceled + } + if p, ok := parentCancelCtx(parent); ok { + p.mu.Lock() + if p.err != nil { + // parent has already been canceled + child.cancel(false, p.err) + } else { + if p.children == nil { + p.children = make(map[canceler]bool) + } + p.children[child] = true + } + p.mu.Unlock() + } else { + go func() { + select { + case <-parent.Done(): + child.cancel(false, parent.Err()) + case <-child.Done(): + } + }() + } +} + +// parentCancelCtx follows a chain of parent references until it finds a +// *cancelCtx. This function understands how each of the concrete types in this +// package represents its parent. +func parentCancelCtx(parent Context) (*cancelCtx, bool) { + for { + switch c := parent.(type) { + case *cancelCtx: + return c, true + case *timerCtx: + return c.cancelCtx, true + case *valueCtx: + parent = c.Context + default: + return nil, false + } + } +} + +// removeChild removes a context from its parent. +func removeChild(parent Context, child canceler) { + p, ok := parentCancelCtx(parent) + if !ok { + return + } + p.mu.Lock() + if p.children != nil { + delete(p.children, child) + } + p.mu.Unlock() +} + +// A canceler is a context type that can be canceled directly. The +// implementations are *cancelCtx and *timerCtx. +type canceler interface { + cancel(removeFromParent bool, err error) + Done() <-chan struct{} +} + +// A cancelCtx can be canceled. When canceled, it also cancels any children +// that implement canceler. +type cancelCtx struct { + Context + + done chan struct{} // closed by the first cancel call. + + mu sync.Mutex + children map[canceler]bool // set to nil by the first cancel call + err error // set to non-nil by the first cancel call +} + +func (c *cancelCtx) Done() <-chan struct{} { + return c.done +} + +func (c *cancelCtx) Err() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.err +} + +func (c *cancelCtx) String() string { + return fmt.Sprintf("%v.WithCancel", c.Context) +} + +// cancel closes c.done, cancels each of c's children, and, if +// removeFromParent is true, removes c from its parent's children. +func (c *cancelCtx) cancel(removeFromParent bool, err error) { + if err == nil { + panic("context: internal error: missing cancel error") + } + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return // already canceled + } + c.err = err + close(c.done) + for child := range c.children { + // NOTE: acquiring the child's lock while holding parent's lock. + child.cancel(false, err) + } + c.children = nil + c.mu.Unlock() + + if removeFromParent { + removeChild(c.Context, c) + } +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { + // The current deadline is already sooner than the new one. + return WithCancel(parent) + } + c := &timerCtx{ + cancelCtx: newCancelCtx(parent), + deadline: deadline, + } + propagateCancel(parent, c) + d := deadline.Sub(time.Now()) + if d <= 0 { + c.cancel(true, DeadlineExceeded) // deadline has already passed + return c, func() { c.cancel(true, Canceled) } + } + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil { + c.timer = time.AfterFunc(d, func() { + c.cancel(true, DeadlineExceeded) + }) + } + return c, func() { c.cancel(true, Canceled) } +} + +// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to +// implement Done and Err. It implements cancel by stopping its timer then +// delegating to cancelCtx.cancel. +type timerCtx struct { + *cancelCtx + timer *time.Timer // Under cancelCtx.mu. + + deadline time.Time +} + +func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { + return c.deadline, true +} + +func (c *timerCtx) String() string { + return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) +} + +func (c *timerCtx) cancel(removeFromParent bool, err error) { + c.cancelCtx.cancel(false, err) + if removeFromParent { + // Remove this timerCtx from its parent cancelCtx's children. + removeChild(c.cancelCtx.Context, c) + } + c.mu.Lock() + if c.timer != nil { + c.timer.Stop() + c.timer = nil + } + c.mu.Unlock() +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return &valueCtx{parent, key, val} +} + +// A valueCtx carries a key-value pair. It implements Value for that key and +// delegates all other calls to the embedded Context. +type valueCtx struct { + Context + key, val interface{} +} + +func (c *valueCtx) String() string { + return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) +} + +func (c *valueCtx) Value(key interface{}) interface{} { + if c.key == key { + return c.val + } + return c.Context.Value(key) +} diff --git a/vendor/golang.org/x/net/context/pre_go19.go b/vendor/golang.org/x/net/context/pre_go19.go new file mode 100644 index 00000000..b105f80b --- /dev/null +++ b/vendor/golang.org/x/net/context/pre_go19.go @@ -0,0 +1,109 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.9 + +package context + +import "time" + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context interface { + // Deadline returns the time when work done on behalf of this context + // should be canceled. Deadline returns ok==false when no deadline is + // set. Successive calls to Deadline return the same results. + Deadline() (deadline time.Time, ok bool) + + // Done returns a channel that's closed when work done on behalf of this + // context should be canceled. Done may return nil if this context can + // never be canceled. Successive calls to Done return the same value. + // + // WithCancel arranges for Done to be closed when cancel is called; + // WithDeadline arranges for Done to be closed when the deadline + // expires; WithTimeout arranges for Done to be closed when the timeout + // elapses. + // + // Done is provided for use in select statements: + // + // // Stream generates values with DoSomething and sends them to out + // // until DoSomething returns an error or ctx.Done is closed. + // func Stream(ctx context.Context, out chan<- Value) error { + // for { + // v, err := DoSomething(ctx) + // if err != nil { + // return err + // } + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case out <- v: + // } + // } + // } + // + // See http://blog.golang.org/pipelines for more examples of how to use + // a Done channel for cancelation. + Done() <-chan struct{} + + // Err returns a non-nil error value after Done is closed. Err returns + // Canceled if the context was canceled or DeadlineExceeded if the + // context's deadline passed. No other values for Err are defined. + // After Done is closed, successive calls to Err return the same value. + Err() error + + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.WithValue and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // collisions. + // + // Packages that define a Context key should provide type-safe accessors + // for the values stores using that key: + // + // // Package user defines a User type that's stored in Contexts. + // package user + // + // import "golang.org/x/net/context" + // + // // User is the type of value stored in the Contexts. + // type User struct {...} + // + // // key is an unexported type for keys defined in this package. + // // This prevents collisions with keys defined in other packages. + // type key int + // + // // userKey is the key for user.User values in Contexts. It is + // // unexported; clients use user.NewContext and user.FromContext + // // instead of using this key directly. + // var userKey key = 0 + // + // // NewContext returns a new Context that carries value u. + // func NewContext(ctx context.Context, u *User) context.Context { + // return context.WithValue(ctx, userKey, u) + // } + // + // // FromContext returns the User value stored in ctx, if any. + // func FromContext(ctx context.Context) (*User, bool) { + // u, ok := ctx.Value(userKey).(*User) + // return u, ok + // } + Value(key interface{}) interface{} +} + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc func() diff --git a/vendor/gopkg.in/yaml.v2/.travis.yml b/vendor/gopkg.in/yaml.v2/.travis.yml new file mode 100644 index 00000000..9f556934 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/.travis.yml @@ -0,0 +1,12 @@ +language: go + +go: + - 1.4 + - 1.5 + - 1.6 + - 1.7 + - 1.8 + - 1.9 + - tip + +go_import_path: gopkg.in/yaml.v2 diff --git a/vendor/gopkg.in/yaml.v2/LICENSE b/vendor/gopkg.in/yaml.v2/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml new file mode 100644 index 00000000..8da58fbf --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml @@ -0,0 +1,31 @@ +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original copyright and license: + + apic.go + emitterc.go + parserc.go + readerc.go + scannerc.go + writerc.go + yamlh.go + yamlprivateh.go + +Copyright (c) 2006 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/gopkg.in/yaml.v2/NOTICE b/vendor/gopkg.in/yaml.v2/NOTICE new file mode 100644 index 00000000..866d74a7 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/NOTICE @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/README.md b/vendor/gopkg.in/yaml.v2/README.md new file mode 100644 index 00000000..b50c6e87 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/README.md @@ -0,0 +1,133 @@ +# YAML support for the Go language + +Introduction +------------ + +The yaml package enables Go programs to comfortably encode and decode YAML +values. It was developed within [Canonical](https://www.canonical.com) as +part of the [juju](https://juju.ubuntu.com) project, and is based on a +pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML) +C library to parse and generate YAML data quickly and reliably. + +Compatibility +------------- + +The yaml package supports most of YAML 1.1 and 1.2, including support for +anchors, tags, map merging, etc. Multi-document unmarshalling is not yet +implemented, and base-60 floats from YAML 1.1 are purposefully not +supported since they're a poor design and are gone in YAML 1.2. + +Installation and usage +---------------------- + +The import path for the package is *gopkg.in/yaml.v2*. + +To install it, run: + + go get gopkg.in/yaml.v2 + +API documentation +----------------- + +If opened in a browser, the import path itself leads to the API documentation: + + * [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2) + +API stability +------------- + +The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in). + + +License +------- + +The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details. + + +Example +------- + +```Go +package main + +import ( + "fmt" + "log" + + "gopkg.in/yaml.v2" +) + +var data = ` +a: Easy! +b: + c: 2 + d: [3, 4] +` + +// Note: struct fields must be public in order for unmarshal to +// correctly populate the data. +type T struct { + A string + B struct { + RenamedC int `yaml:"c"` + D []int `yaml:",flow"` + } +} + +func main() { + t := T{} + + err := yaml.Unmarshal([]byte(data), &t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t:\n%v\n\n", t) + + d, err := yaml.Marshal(&t) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- t dump:\n%s\n\n", string(d)) + + m := make(map[interface{}]interface{}) + + err = yaml.Unmarshal([]byte(data), &m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m:\n%v\n\n", m) + + d, err = yaml.Marshal(&m) + if err != nil { + log.Fatalf("error: %v", err) + } + fmt.Printf("--- m dump:\n%s\n\n", string(d)) +} +``` + +This example will generate the following output: + +``` +--- t: +{Easy! {2 [3 4]}} + +--- t dump: +a: Easy! +b: + c: 2 + d: [3, 4] + + +--- m: +map[a:Easy! b:map[c:2 d:[3 4]]] + +--- m dump: +a: Easy! +b: + c: 2 + d: + - 3 + - 4 +``` + diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v2/apic.go new file mode 100644 index 00000000..1f7e87e6 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/apic.go @@ -0,0 +1,739 @@ +package yaml + +import ( + "io" +) + +func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) { + //fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens)) + + // Check if we can move the queue at the beginning of the buffer. + if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) { + if parser.tokens_head != len(parser.tokens) { + copy(parser.tokens, parser.tokens[parser.tokens_head:]) + } + parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head] + parser.tokens_head = 0 + } + parser.tokens = append(parser.tokens, *token) + if pos < 0 { + return + } + copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:]) + parser.tokens[parser.tokens_head+pos] = *token +} + +// Create a new parser object. +func yaml_parser_initialize(parser *yaml_parser_t) bool { + *parser = yaml_parser_t{ + raw_buffer: make([]byte, 0, input_raw_buffer_size), + buffer: make([]byte, 0, input_buffer_size), + } + return true +} + +// Destroy a parser object. +func yaml_parser_delete(parser *yaml_parser_t) { + *parser = yaml_parser_t{} +} + +// String read handler. +func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + if parser.input_pos == len(parser.input) { + return 0, io.EOF + } + n = copy(buffer, parser.input[parser.input_pos:]) + parser.input_pos += n + return n, nil +} + +// Reader read handler. +func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + return parser.input_reader.Read(buffer) +} + +// Set a string input. +func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_string_read_handler + parser.input = input + parser.input_pos = 0 +} + +// Set a file input. +func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) { + if parser.read_handler != nil { + panic("must set the input source only once") + } + parser.read_handler = yaml_reader_read_handler + parser.input_reader = r +} + +// Set the source encoding. +func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) { + if parser.encoding != yaml_ANY_ENCODING { + panic("must set the encoding only once") + } + parser.encoding = encoding +} + +// Create a new emitter object. +func yaml_emitter_initialize(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{ + buffer: make([]byte, output_buffer_size), + raw_buffer: make([]byte, 0, output_raw_buffer_size), + states: make([]yaml_emitter_state_t, 0, initial_stack_size), + events: make([]yaml_event_t, 0, initial_queue_size), + } +} + +// Destroy an emitter object. +func yaml_emitter_delete(emitter *yaml_emitter_t) { + *emitter = yaml_emitter_t{} +} + +// String write handler. +func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + *emitter.output_buffer = append(*emitter.output_buffer, buffer...) + return nil +} + +// yaml_writer_write_handler uses emitter.output_writer to write the +// emitted text. +func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + _, err := emitter.output_writer.Write(buffer) + return err +} + +// Set a string output. +func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_string_write_handler + emitter.output_buffer = output_buffer +} + +// Set a file output. +func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) { + if emitter.write_handler != nil { + panic("must set the output target only once") + } + emitter.write_handler = yaml_writer_write_handler + emitter.output_writer = w +} + +// Set the output encoding. +func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) { + if emitter.encoding != yaml_ANY_ENCODING { + panic("must set the output encoding only once") + } + emitter.encoding = encoding +} + +// Set the canonical output style. +func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) { + emitter.canonical = canonical +} + +//// Set the indentation increment. +func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) { + if indent < 2 || indent > 9 { + indent = 2 + } + emitter.best_indent = indent +} + +// Set the preferred line width. +func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) { + if width < 0 { + width = -1 + } + emitter.best_width = width +} + +// Set if unescaped non-ASCII characters are allowed. +func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) { + emitter.unicode = unicode +} + +// Set the preferred line break character. +func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) { + emitter.line_break = line_break +} + +///* +// * Destroy a token object. +// */ +// +//YAML_DECLARE(void) +//yaml_token_delete(yaml_token_t *token) +//{ +// assert(token); // Non-NULL token object expected. +// +// switch (token.type) +// { +// case YAML_TAG_DIRECTIVE_TOKEN: +// yaml_free(token.data.tag_directive.handle); +// yaml_free(token.data.tag_directive.prefix); +// break; +// +// case YAML_ALIAS_TOKEN: +// yaml_free(token.data.alias.value); +// break; +// +// case YAML_ANCHOR_TOKEN: +// yaml_free(token.data.anchor.value); +// break; +// +// case YAML_TAG_TOKEN: +// yaml_free(token.data.tag.handle); +// yaml_free(token.data.tag.suffix); +// break; +// +// case YAML_SCALAR_TOKEN: +// yaml_free(token.data.scalar.value); +// break; +// +// default: +// break; +// } +// +// memset(token, 0, sizeof(yaml_token_t)); +//} +// +///* +// * Check if a string is a valid UTF-8 sequence. +// * +// * Check 'reader.c' for more details on UTF-8 encoding. +// */ +// +//static int +//yaml_check_utf8(yaml_char_t *start, size_t length) +//{ +// yaml_char_t *end = start+length; +// yaml_char_t *pointer = start; +// +// while (pointer < end) { +// unsigned char octet; +// unsigned int width; +// unsigned int value; +// size_t k; +// +// octet = pointer[0]; +// width = (octet & 0x80) == 0x00 ? 1 : +// (octet & 0xE0) == 0xC0 ? 2 : +// (octet & 0xF0) == 0xE0 ? 3 : +// (octet & 0xF8) == 0xF0 ? 4 : 0; +// value = (octet & 0x80) == 0x00 ? octet & 0x7F : +// (octet & 0xE0) == 0xC0 ? octet & 0x1F : +// (octet & 0xF0) == 0xE0 ? octet & 0x0F : +// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; +// if (!width) return 0; +// if (pointer+width > end) return 0; +// for (k = 1; k < width; k ++) { +// octet = pointer[k]; +// if ((octet & 0xC0) != 0x80) return 0; +// value = (value << 6) + (octet & 0x3F); +// } +// if (!((width == 1) || +// (width == 2 && value >= 0x80) || +// (width == 3 && value >= 0x800) || +// (width == 4 && value >= 0x10000))) return 0; +// +// pointer += width; +// } +// +// return 1; +//} +// + +// Create STREAM-START. +func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) { + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + encoding: encoding, + } +} + +// Create STREAM-END. +func yaml_stream_end_event_initialize(event *yaml_event_t) { + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + } +} + +// Create DOCUMENT-START. +func yaml_document_start_event_initialize( + event *yaml_event_t, + version_directive *yaml_version_directive_t, + tag_directives []yaml_tag_directive_t, + implicit bool, +) { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: implicit, + } +} + +// Create DOCUMENT-END. +func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) { + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + implicit: implicit, + } +} + +///* +// * Create ALIAS. +// */ +// +//YAML_DECLARE(int) +//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t) +//{ +// mark yaml_mark_t = { 0, 0, 0 } +// anchor_copy *yaml_char_t = NULL +// +// assert(event) // Non-NULL event object is expected. +// assert(anchor) // Non-NULL anchor is expected. +// +// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0 +// +// anchor_copy = yaml_strdup(anchor) +// if (!anchor_copy) +// return 0 +// +// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark) +// +// return 1 +//} + +// Create SCALAR. +func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + anchor: anchor, + tag: tag, + value: value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-START. +func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } + return true +} + +// Create SEQUENCE-END. +func yaml_sequence_end_event_initialize(event *yaml_event_t) bool { + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + } + return true +} + +// Create MAPPING-START. +func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) { + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(style), + } +} + +// Create MAPPING-END. +func yaml_mapping_end_event_initialize(event *yaml_event_t) { + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + } +} + +// Destroy an event object. +func yaml_event_delete(event *yaml_event_t) { + *event = yaml_event_t{} +} + +///* +// * Create a document object. +// */ +// +//YAML_DECLARE(int) +//yaml_document_initialize(document *yaml_document_t, +// version_directive *yaml_version_directive_t, +// tag_directives_start *yaml_tag_directive_t, +// tag_directives_end *yaml_tag_directive_t, +// start_implicit int, end_implicit int) +//{ +// struct { +// error yaml_error_type_t +// } context +// struct { +// start *yaml_node_t +// end *yaml_node_t +// top *yaml_node_t +// } nodes = { NULL, NULL, NULL } +// version_directive_copy *yaml_version_directive_t = NULL +// struct { +// start *yaml_tag_directive_t +// end *yaml_tag_directive_t +// top *yaml_tag_directive_t +// } tag_directives_copy = { NULL, NULL, NULL } +// value yaml_tag_directive_t = { NULL, NULL } +// mark yaml_mark_t = { 0, 0, 0 } +// +// assert(document) // Non-NULL document object is expected. +// assert((tag_directives_start && tag_directives_end) || +// (tag_directives_start == tag_directives_end)) +// // Valid tag directives are expected. +// +// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error +// +// if (version_directive) { +// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t)) +// if (!version_directive_copy) goto error +// version_directive_copy.major = version_directive.major +// version_directive_copy.minor = version_directive.minor +// } +// +// if (tag_directives_start != tag_directives_end) { +// tag_directive *yaml_tag_directive_t +// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE)) +// goto error +// for (tag_directive = tag_directives_start +// tag_directive != tag_directives_end; tag_directive ++) { +// assert(tag_directive.handle) +// assert(tag_directive.prefix) +// if (!yaml_check_utf8(tag_directive.handle, +// strlen((char *)tag_directive.handle))) +// goto error +// if (!yaml_check_utf8(tag_directive.prefix, +// strlen((char *)tag_directive.prefix))) +// goto error +// value.handle = yaml_strdup(tag_directive.handle) +// value.prefix = yaml_strdup(tag_directive.prefix) +// if (!value.handle || !value.prefix) goto error +// if (!PUSH(&context, tag_directives_copy, value)) +// goto error +// value.handle = NULL +// value.prefix = NULL +// } +// } +// +// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, +// tag_directives_copy.start, tag_directives_copy.top, +// start_implicit, end_implicit, mark, mark) +// +// return 1 +// +//error: +// STACK_DEL(&context, nodes) +// yaml_free(version_directive_copy) +// while (!STACK_EMPTY(&context, tag_directives_copy)) { +// value yaml_tag_directive_t = POP(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// } +// STACK_DEL(&context, tag_directives_copy) +// yaml_free(value.handle) +// yaml_free(value.prefix) +// +// return 0 +//} +// +///* +// * Destroy a document object. +// */ +// +//YAML_DECLARE(void) +//yaml_document_delete(document *yaml_document_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// tag_directive *yaml_tag_directive_t +// +// context.error = YAML_NO_ERROR // Eliminate a compiler warning. +// +// assert(document) // Non-NULL document object is expected. +// +// while (!STACK_EMPTY(&context, document.nodes)) { +// node yaml_node_t = POP(&context, document.nodes) +// yaml_free(node.tag) +// switch (node.type) { +// case YAML_SCALAR_NODE: +// yaml_free(node.data.scalar.value) +// break +// case YAML_SEQUENCE_NODE: +// STACK_DEL(&context, node.data.sequence.items) +// break +// case YAML_MAPPING_NODE: +// STACK_DEL(&context, node.data.mapping.pairs) +// break +// default: +// assert(0) // Should not happen. +// } +// } +// STACK_DEL(&context, document.nodes) +// +// yaml_free(document.version_directive) +// for (tag_directive = document.tag_directives.start +// tag_directive != document.tag_directives.end +// tag_directive++) { +// yaml_free(tag_directive.handle) +// yaml_free(tag_directive.prefix) +// } +// yaml_free(document.tag_directives.start) +// +// memset(document, 0, sizeof(yaml_document_t)) +//} +// +///** +// * Get a document node. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_node(document *yaml_document_t, index int) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (index > 0 && document.nodes.start + index <= document.nodes.top) { +// return document.nodes.start + index - 1 +// } +// return NULL +//} +// +///** +// * Get the root object. +// */ +// +//YAML_DECLARE(yaml_node_t *) +//yaml_document_get_root_node(document *yaml_document_t) +//{ +// assert(document) // Non-NULL document object is expected. +// +// if (document.nodes.top != document.nodes.start) { +// return document.nodes.start +// } +// return NULL +//} +// +///* +// * Add a scalar node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_scalar(document *yaml_document_t, +// tag *yaml_char_t, value *yaml_char_t, length int, +// style yaml_scalar_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// value_copy *yaml_char_t = NULL +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// assert(value) // Non-NULL value is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (length < 0) { +// length = strlen((char *)value) +// } +// +// if (!yaml_check_utf8(value, length)) goto error +// value_copy = yaml_malloc(length+1) +// if (!value_copy) goto error +// memcpy(value_copy, value, length) +// value_copy[length] = '\0' +// +// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// yaml_free(tag_copy) +// yaml_free(value_copy) +// +// return 0 +//} +// +///* +// * Add a sequence node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_sequence(document *yaml_document_t, +// tag *yaml_char_t, style yaml_sequence_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_item_t +// end *yaml_node_item_t +// top *yaml_node_item_t +// } items = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error +// +// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, items) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Add a mapping node to a document. +// */ +// +//YAML_DECLARE(int) +//yaml_document_add_mapping(document *yaml_document_t, +// tag *yaml_char_t, style yaml_mapping_style_t) +//{ +// struct { +// error yaml_error_type_t +// } context +// mark yaml_mark_t = { 0, 0, 0 } +// tag_copy *yaml_char_t = NULL +// struct { +// start *yaml_node_pair_t +// end *yaml_node_pair_t +// top *yaml_node_pair_t +// } pairs = { NULL, NULL, NULL } +// node yaml_node_t +// +// assert(document) // Non-NULL document object is expected. +// +// if (!tag) { +// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG +// } +// +// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error +// tag_copy = yaml_strdup(tag) +// if (!tag_copy) goto error +// +// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error +// +// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, +// style, mark, mark) +// if (!PUSH(&context, document.nodes, node)) goto error +// +// return document.nodes.top - document.nodes.start +// +//error: +// STACK_DEL(&context, pairs) +// yaml_free(tag_copy) +// +// return 0 +//} +// +///* +// * Append an item to a sequence node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_sequence_item(document *yaml_document_t, +// sequence int, item int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// assert(document) // Non-NULL document is required. +// assert(sequence > 0 +// && document.nodes.start + sequence <= document.nodes.top) +// // Valid sequence id is required. +// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE) +// // A sequence node is required. +// assert(item > 0 && document.nodes.start + item <= document.nodes.top) +// // Valid item id is required. +// +// if (!PUSH(&context, +// document.nodes.start[sequence-1].data.sequence.items, item)) +// return 0 +// +// return 1 +//} +// +///* +// * Append a pair of a key and a value to a mapping node. +// */ +// +//YAML_DECLARE(int) +//yaml_document_append_mapping_pair(document *yaml_document_t, +// mapping int, key int, value int) +//{ +// struct { +// error yaml_error_type_t +// } context +// +// pair yaml_node_pair_t +// +// assert(document) // Non-NULL document is required. +// assert(mapping > 0 +// && document.nodes.start + mapping <= document.nodes.top) +// // Valid mapping id is required. +// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE) +// // A mapping node is required. +// assert(key > 0 && document.nodes.start + key <= document.nodes.top) +// // Valid key id is required. +// assert(value > 0 && document.nodes.start + value <= document.nodes.top) +// // Valid value id is required. +// +// pair.key = key +// pair.value = value +// +// if (!PUSH(&context, +// document.nodes.start[mapping-1].data.mapping.pairs, pair)) +// return 0 +// +// return 1 +//} +// +// diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go new file mode 100644 index 00000000..e4e56e28 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -0,0 +1,775 @@ +package yaml + +import ( + "encoding" + "encoding/base64" + "fmt" + "io" + "math" + "reflect" + "strconv" + "time" +) + +const ( + documentNode = 1 << iota + mappingNode + sequenceNode + scalarNode + aliasNode +) + +type node struct { + kind int + line, column int + tag string + // For an alias node, alias holds the resolved alias. + alias *node + value string + implicit bool + children []*node + anchors map[string]*node +} + +// ---------------------------------------------------------------------------- +// Parser, produces a node tree out of a libyaml event stream. + +type parser struct { + parser yaml_parser_t + event yaml_event_t + doc *node + doneInit bool +} + +func newParser(b []byte) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + if len(b) == 0 { + b = []byte{'\n'} + } + yaml_parser_set_input_string(&p.parser, b) + return &p +} + +func newParserFromReader(r io.Reader) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") + } + yaml_parser_set_input_reader(&p.parser, r) + return &p +} + +func (p *parser) init() { + if p.doneInit { + return + } + p.expect(yaml_STREAM_START_EVENT) + p.doneInit = true +} + +func (p *parser) destroy() { + if p.event.typ != yaml_NO_EVENT { + yaml_event_delete(&p.event) + } + yaml_parser_delete(&p.parser) +} + +// expect consumes an event from the event stream and +// checks that it's of the expected type. +func (p *parser) expect(e yaml_event_type_t) { + if p.event.typ == yaml_NO_EVENT { + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() + } + } + if p.event.typ == yaml_STREAM_END_EVENT { + failf("attempted to go past the end of stream; corrupted value?") + } + if p.event.typ != e { + p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ) + p.fail() + } + yaml_event_delete(&p.event) + p.event.typ = yaml_NO_EVENT +} + +// peek peeks at the next event in the event stream, +// puts the results into p.event and returns the event type. +func (p *parser) peek() yaml_event_type_t { + if p.event.typ != yaml_NO_EVENT { + return p.event.typ + } + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() + } + return p.event.typ +} + +func (p *parser) fail() { + var where string + var line int + if p.parser.problem_mark.line != 0 { + line = p.parser.problem_mark.line + // Scanner errors don't iterate line before returning error + if p.parser.error == yaml_SCANNER_ERROR { + line++ + } + } else if p.parser.context_mark.line != 0 { + line = p.parser.context_mark.line + } + if line != 0 { + where = "line " + strconv.Itoa(line) + ": " + } + var msg string + if len(p.parser.problem) > 0 { + msg = p.parser.problem + } else { + msg = "unknown problem parsing YAML content" + } + failf("%s%s", where, msg) +} + +func (p *parser) anchor(n *node, anchor []byte) { + if anchor != nil { + p.doc.anchors[string(anchor)] = n + } +} + +func (p *parser) parse() *node { + p.init() + switch p.peek() { + case yaml_SCALAR_EVENT: + return p.scalar() + case yaml_ALIAS_EVENT: + return p.alias() + case yaml_MAPPING_START_EVENT: + return p.mapping() + case yaml_SEQUENCE_START_EVENT: + return p.sequence() + case yaml_DOCUMENT_START_EVENT: + return p.document() + case yaml_STREAM_END_EVENT: + // Happens when attempting to decode an empty buffer. + return nil + default: + panic("attempted to parse unknown event: " + p.event.typ.String()) + } +} + +func (p *parser) node(kind int) *node { + return &node{ + kind: kind, + line: p.event.start_mark.line, + column: p.event.start_mark.column, + } +} + +func (p *parser) document() *node { + n := p.node(documentNode) + n.anchors = make(map[string]*node) + p.doc = n + p.expect(yaml_DOCUMENT_START_EVENT) + n.children = append(n.children, p.parse()) + p.expect(yaml_DOCUMENT_END_EVENT) + return n +} + +func (p *parser) alias() *node { + n := p.node(aliasNode) + n.value = string(p.event.anchor) + n.alias = p.doc.anchors[n.value] + if n.alias == nil { + failf("unknown anchor '%s' referenced", n.value) + } + p.expect(yaml_ALIAS_EVENT) + return n +} + +func (p *parser) scalar() *node { + n := p.node(scalarNode) + n.value = string(p.event.value) + n.tag = string(p.event.tag) + n.implicit = p.event.implicit + p.anchor(n, p.event.anchor) + p.expect(yaml_SCALAR_EVENT) + return n +} + +func (p *parser) sequence() *node { + n := p.node(sequenceNode) + p.anchor(n, p.event.anchor) + p.expect(yaml_SEQUENCE_START_EVENT) + for p.peek() != yaml_SEQUENCE_END_EVENT { + n.children = append(n.children, p.parse()) + } + p.expect(yaml_SEQUENCE_END_EVENT) + return n +} + +func (p *parser) mapping() *node { + n := p.node(mappingNode) + p.anchor(n, p.event.anchor) + p.expect(yaml_MAPPING_START_EVENT) + for p.peek() != yaml_MAPPING_END_EVENT { + n.children = append(n.children, p.parse(), p.parse()) + } + p.expect(yaml_MAPPING_END_EVENT) + return n +} + +// ---------------------------------------------------------------------------- +// Decoder, unmarshals a node into a provided value. + +type decoder struct { + doc *node + aliases map[*node]bool + mapType reflect.Type + terrors []string + strict bool +} + +var ( + mapItemType = reflect.TypeOf(MapItem{}) + durationType = reflect.TypeOf(time.Duration(0)) + defaultMapType = reflect.TypeOf(map[interface{}]interface{}{}) + ifaceType = defaultMapType.Elem() + timeType = reflect.TypeOf(time.Time{}) + ptrTimeType = reflect.TypeOf(&time.Time{}) +) + +func newDecoder(strict bool) *decoder { + d := &decoder{mapType: defaultMapType, strict: strict} + d.aliases = make(map[*node]bool) + return d +} + +func (d *decoder) terror(n *node, tag string, out reflect.Value) { + if n.tag != "" { + tag = n.tag + } + value := n.value + if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG { + if len(value) > 10 { + value = " `" + value[:7] + "...`" + } else { + value = " `" + value + "`" + } + } + d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type())) +} + +func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) { + terrlen := len(d.terrors) + err := u.UnmarshalYAML(func(v interface{}) (err error) { + defer handleErr(&err) + d.unmarshal(n, reflect.ValueOf(v)) + if len(d.terrors) > terrlen { + issues := d.terrors[terrlen:] + d.terrors = d.terrors[:terrlen] + return &TypeError{issues} + } + return nil + }) + if e, ok := err.(*TypeError); ok { + d.terrors = append(d.terrors, e.Errors...) + return false + } + if err != nil { + fail(err) + } + return true +} + +// d.prepare initializes and dereferences pointers and calls UnmarshalYAML +// if a value is found to implement it. +// It returns the initialized and dereferenced out value, whether +// unmarshalling was already done by UnmarshalYAML, and if so whether +// its types unmarshalled appropriately. +// +// If n holds a null value, prepare returns before doing anything. +func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) { + if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) { + return out, false, false + } + again := true + for again { + again = false + if out.Kind() == reflect.Ptr { + if out.IsNil() { + out.Set(reflect.New(out.Type().Elem())) + } + out = out.Elem() + again = true + } + if out.CanAddr() { + if u, ok := out.Addr().Interface().(Unmarshaler); ok { + good = d.callUnmarshaler(n, u) + return out, true, good + } + } + } + return out, false, false +} + +func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { + switch n.kind { + case documentNode: + return d.document(n, out) + case aliasNode: + return d.alias(n, out) + } + out, unmarshaled, good := d.prepare(n, out) + if unmarshaled { + return good + } + switch n.kind { + case scalarNode: + good = d.scalar(n, out) + case mappingNode: + good = d.mapping(n, out) + case sequenceNode: + good = d.sequence(n, out) + default: + panic("internal error: unknown node kind: " + strconv.Itoa(n.kind)) + } + return good +} + +func (d *decoder) document(n *node, out reflect.Value) (good bool) { + if len(n.children) == 1 { + d.doc = n + d.unmarshal(n.children[0], out) + return true + } + return false +} + +func (d *decoder) alias(n *node, out reflect.Value) (good bool) { + if d.aliases[n] { + // TODO this could actually be allowed in some circumstances. + failf("anchor '%s' value contains itself", n.value) + } + d.aliases[n] = true + good = d.unmarshal(n.alias, out) + delete(d.aliases, n) + return good +} + +var zeroValue reflect.Value + +func resetMap(out reflect.Value) { + for _, k := range out.MapKeys() { + out.SetMapIndex(k, zeroValue) + } +} + +func (d *decoder) scalar(n *node, out reflect.Value) bool { + var tag string + var resolved interface{} + if n.tag == "" && !n.implicit { + tag = yaml_STR_TAG + resolved = n.value + } else { + tag, resolved = resolve(n.tag, n.value) + if tag == yaml_BINARY_TAG { + data, err := base64.StdEncoding.DecodeString(resolved.(string)) + if err != nil { + failf("!!binary value contains invalid base64 data") + } + resolved = string(data) + } + } + if resolved == nil { + if out.Kind() == reflect.Map && !out.CanAddr() { + resetMap(out) + } else { + out.Set(reflect.Zero(out.Type())) + } + return true + } + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + // We've resolved to exactly the type we want, so use that. + out.Set(resolvedv) + return true + } + // Perhaps we can use the value as a TextUnmarshaler to + // set its value. + if out.CanAddr() { + u, ok := out.Addr().Interface().(encoding.TextUnmarshaler) + if ok { + var text []byte + if tag == yaml_BINARY_TAG { + text = []byte(resolved.(string)) + } else { + // We let any value be unmarshaled into TextUnmarshaler. + // That might be more lax than we'd like, but the + // TextUnmarshaler itself should bowl out any dubious values. + text = []byte(n.value) + } + err := u.UnmarshalText(text) + if err != nil { + fail(err) + } + return true + } + } + switch out.Kind() { + case reflect.String: + if tag == yaml_BINARY_TAG { + out.SetString(resolved.(string)) + return true + } + if resolved != nil { + out.SetString(n.value) + return true + } + case reflect.Interface: + if resolved == nil { + out.Set(reflect.Zero(out.Type())) + } else if tag == yaml_TIMESTAMP_TAG { + // It looks like a timestamp but for backward compatibility + // reasons we set it as a string, so that code that unmarshals + // timestamp-like values into interface{} will continue to + // see a string and not a time.Time. + // TODO(v3) Drop this. + out.Set(reflect.ValueOf(n.value)) + } else { + out.Set(reflect.ValueOf(resolved)) + } + return true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch resolved := resolved.(type) { + case int: + if !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case int64: + if !out.OverflowInt(resolved) { + out.SetInt(resolved) + return true + } + case uint64: + if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case float64: + if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { + out.SetInt(int64(resolved)) + return true + } + case string: + if out.Type() == durationType { + d, err := time.ParseDuration(resolved) + if err == nil { + out.SetInt(int64(d)) + return true + } + } + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch resolved := resolved.(type) { + case int: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case int64: + if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case uint64: + if !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + case float64: + if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) { + out.SetUint(uint64(resolved)) + return true + } + } + case reflect.Bool: + switch resolved := resolved.(type) { + case bool: + out.SetBool(resolved) + return true + } + case reflect.Float32, reflect.Float64: + switch resolved := resolved.(type) { + case int: + out.SetFloat(float64(resolved)) + return true + case int64: + out.SetFloat(float64(resolved)) + return true + case uint64: + out.SetFloat(float64(resolved)) + return true + case float64: + out.SetFloat(resolved) + return true + } + case reflect.Struct: + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + out.Set(resolvedv) + return true + } + case reflect.Ptr: + if out.Type().Elem() == reflect.TypeOf(resolved) { + // TODO DOes this make sense? When is out a Ptr except when decoding a nil value? + elem := reflect.New(out.Type().Elem()) + elem.Elem().Set(reflect.ValueOf(resolved)) + out.Set(elem) + return true + } + } + d.terror(n, tag, out) + return false +} + +func settableValueOf(i interface{}) reflect.Value { + v := reflect.ValueOf(i) + sv := reflect.New(v.Type()).Elem() + sv.Set(v) + return sv +} + +func (d *decoder) sequence(n *node, out reflect.Value) (good bool) { + l := len(n.children) + + var iface reflect.Value + switch out.Kind() { + case reflect.Slice: + out.Set(reflect.MakeSlice(out.Type(), l, l)) + case reflect.Array: + if l != out.Len() { + failf("invalid array: want %d elements but got %d", out.Len(), l) + } + case reflect.Interface: + // No type hints. Will have to use a generic sequence. + iface = out + out = settableValueOf(make([]interface{}, l)) + default: + d.terror(n, yaml_SEQ_TAG, out) + return false + } + et := out.Type().Elem() + + j := 0 + for i := 0; i < l; i++ { + e := reflect.New(et).Elem() + if ok := d.unmarshal(n.children[i], e); ok { + out.Index(j).Set(e) + j++ + } + } + if out.Kind() != reflect.Array { + out.Set(out.Slice(0, j)) + } + if iface.IsValid() { + iface.Set(out) + } + return true +} + +func (d *decoder) mapping(n *node, out reflect.Value) (good bool) { + switch out.Kind() { + case reflect.Struct: + return d.mappingStruct(n, out) + case reflect.Slice: + return d.mappingSlice(n, out) + case reflect.Map: + // okay + case reflect.Interface: + if d.mapType.Kind() == reflect.Map { + iface := out + out = reflect.MakeMap(d.mapType) + iface.Set(out) + } else { + slicev := reflect.New(d.mapType).Elem() + if !d.mappingSlice(n, slicev) { + return false + } + out.Set(slicev) + return true + } + default: + d.terror(n, yaml_MAP_TAG, out) + return false + } + outt := out.Type() + kt := outt.Key() + et := outt.Elem() + + mapType := d.mapType + if outt.Key() == ifaceType && outt.Elem() == ifaceType { + d.mapType = outt + } + + if out.IsNil() { + out.Set(reflect.MakeMap(outt)) + } + l := len(n.children) + for i := 0; i < l; i += 2 { + if isMerge(n.children[i]) { + d.merge(n.children[i+1], out) + continue + } + k := reflect.New(kt).Elem() + if d.unmarshal(n.children[i], k) { + kkind := k.Kind() + if kkind == reflect.Interface { + kkind = k.Elem().Kind() + } + if kkind == reflect.Map || kkind == reflect.Slice { + failf("invalid map key: %#v", k.Interface()) + } + e := reflect.New(et).Elem() + if d.unmarshal(n.children[i+1], e) { + d.setMapIndex(n.children[i+1], out, k, e) + } + } + } + d.mapType = mapType + return true +} + +func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) { + if d.strict && out.MapIndex(k) != zeroValue { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface())) + return + } + out.SetMapIndex(k, v) +} + +func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) { + outt := out.Type() + if outt.Elem() != mapItemType { + d.terror(n, yaml_MAP_TAG, out) + return false + } + + mapType := d.mapType + d.mapType = outt + + var slice []MapItem + var l = len(n.children) + for i := 0; i < l; i += 2 { + if isMerge(n.children[i]) { + d.merge(n.children[i+1], out) + continue + } + item := MapItem{} + k := reflect.ValueOf(&item.Key).Elem() + if d.unmarshal(n.children[i], k) { + v := reflect.ValueOf(&item.Value).Elem() + if d.unmarshal(n.children[i+1], v) { + slice = append(slice, item) + } + } + } + out.Set(reflect.ValueOf(slice)) + d.mapType = mapType + return true +} + +func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { + sinfo, err := getStructInfo(out.Type()) + if err != nil { + panic(err) + } + name := settableValueOf("") + l := len(n.children) + + var inlineMap reflect.Value + var elemType reflect.Type + if sinfo.InlineMap != -1 { + inlineMap = out.Field(sinfo.InlineMap) + inlineMap.Set(reflect.New(inlineMap.Type()).Elem()) + elemType = inlineMap.Type().Elem() + } + + var doneFields []bool + if d.strict { + doneFields = make([]bool, len(sinfo.FieldsList)) + } + for i := 0; i < l; i += 2 { + ni := n.children[i] + if isMerge(ni) { + d.merge(n.children[i+1], out) + continue + } + if !d.unmarshal(ni, name) { + continue + } + if info, ok := sinfo.FieldsMap[name.String()]; ok { + if d.strict { + if doneFields[info.Id] { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type())) + continue + } + doneFields[info.Id] = true + } + var field reflect.Value + if info.Inline == nil { + field = out.Field(info.Num) + } else { + field = out.FieldByIndex(info.Inline) + } + d.unmarshal(n.children[i+1], field) + } else if sinfo.InlineMap != -1 { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + value := reflect.New(elemType).Elem() + d.unmarshal(n.children[i+1], value) + d.setMapIndex(n.children[i+1], inlineMap, name, value) + } else if d.strict { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type())) + } + } + return true +} + +func failWantMap() { + failf("map merge requires map or sequence of maps as the value") +} + +func (d *decoder) merge(n *node, out reflect.Value) { + switch n.kind { + case mappingNode: + d.unmarshal(n, out) + case aliasNode: + an, ok := d.doc.anchors[n.value] + if ok && an.kind != mappingNode { + failWantMap() + } + d.unmarshal(n, out) + case sequenceNode: + // Step backwards as earlier nodes take precedence. + for i := len(n.children) - 1; i >= 0; i-- { + ni := n.children[i] + if ni.kind == aliasNode { + an, ok := d.doc.anchors[ni.value] + if ok && an.kind != mappingNode { + failWantMap() + } + } else if ni.kind != mappingNode { + failWantMap() + } + d.unmarshal(ni, out) + } + default: + failWantMap() + } +} + +func isMerge(n *node) bool { + return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG) +} diff --git a/vendor/gopkg.in/yaml.v2/emitterc.go b/vendor/gopkg.in/yaml.v2/emitterc.go new file mode 100644 index 00000000..a1c2cc52 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/emitterc.go @@ -0,0 +1,1685 @@ +package yaml + +import ( + "bytes" + "fmt" +) + +// Flush the buffer if needed. +func flush(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) { + return yaml_emitter_flush(emitter) + } + return true +} + +// Put a character to the output buffer. +func put(emitter *yaml_emitter_t, value byte) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + emitter.buffer[emitter.buffer_pos] = value + emitter.buffer_pos++ + emitter.column++ + return true +} + +// Put a line break to the output buffer. +func put_break(emitter *yaml_emitter_t) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + switch emitter.line_break { + case yaml_CR_BREAK: + emitter.buffer[emitter.buffer_pos] = '\r' + emitter.buffer_pos += 1 + case yaml_LN_BREAK: + emitter.buffer[emitter.buffer_pos] = '\n' + emitter.buffer_pos += 1 + case yaml_CRLN_BREAK: + emitter.buffer[emitter.buffer_pos+0] = '\r' + emitter.buffer[emitter.buffer_pos+1] = '\n' + emitter.buffer_pos += 2 + default: + panic("unknown line break setting") + } + emitter.column = 0 + emitter.line++ + return true +} + +// Copy a character from a string into buffer. +func write(emitter *yaml_emitter_t, s []byte, i *int) bool { + if emitter.buffer_pos+5 >= len(emitter.buffer) && !yaml_emitter_flush(emitter) { + return false + } + p := emitter.buffer_pos + w := width(s[*i]) + switch w { + case 4: + emitter.buffer[p+3] = s[*i+3] + fallthrough + case 3: + emitter.buffer[p+2] = s[*i+2] + fallthrough + case 2: + emitter.buffer[p+1] = s[*i+1] + fallthrough + case 1: + emitter.buffer[p+0] = s[*i+0] + default: + panic("unknown character width") + } + emitter.column++ + emitter.buffer_pos += w + *i += w + return true +} + +// Write a whole string into buffer. +func write_all(emitter *yaml_emitter_t, s []byte) bool { + for i := 0; i < len(s); { + if !write(emitter, s, &i) { + return false + } + } + return true +} + +// Copy a line break character from a string into buffer. +func write_break(emitter *yaml_emitter_t, s []byte, i *int) bool { + if s[*i] == '\n' { + if !put_break(emitter) { + return false + } + *i++ + } else { + if !write(emitter, s, i) { + return false + } + emitter.column = 0 + emitter.line++ + } + return true +} + +// Set an emitter error and return false. +func yaml_emitter_set_emitter_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_EMITTER_ERROR + emitter.problem = problem + return false +} + +// Emit an event. +func yaml_emitter_emit(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.events = append(emitter.events, *event) + for !yaml_emitter_need_more_events(emitter) { + event := &emitter.events[emitter.events_head] + if !yaml_emitter_analyze_event(emitter, event) { + return false + } + if !yaml_emitter_state_machine(emitter, event) { + return false + } + yaml_event_delete(event) + emitter.events_head++ + } + return true +} + +// Check if we need to accumulate more events before emitting. +// +// We accumulate extra +// - 1 event for DOCUMENT-START +// - 2 events for SEQUENCE-START +// - 3 events for MAPPING-START +// +func yaml_emitter_need_more_events(emitter *yaml_emitter_t) bool { + if emitter.events_head == len(emitter.events) { + return true + } + var accumulate int + switch emitter.events[emitter.events_head].typ { + case yaml_DOCUMENT_START_EVENT: + accumulate = 1 + break + case yaml_SEQUENCE_START_EVENT: + accumulate = 2 + break + case yaml_MAPPING_START_EVENT: + accumulate = 3 + break + default: + return false + } + if len(emitter.events)-emitter.events_head > accumulate { + return false + } + var level int + for i := emitter.events_head; i < len(emitter.events); i++ { + switch emitter.events[i].typ { + case yaml_STREAM_START_EVENT, yaml_DOCUMENT_START_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT: + level++ + case yaml_STREAM_END_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_END_EVENT, yaml_MAPPING_END_EVENT: + level-- + } + if level == 0 { + return false + } + } + return true +} + +// Append a directive to the directives stack. +func yaml_emitter_append_tag_directive(emitter *yaml_emitter_t, value *yaml_tag_directive_t, allow_duplicates bool) bool { + for i := 0; i < len(emitter.tag_directives); i++ { + if bytes.Equal(value.handle, emitter.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_emitter_set_emitter_error(emitter, "duplicate %TAG directive") + } + } + + // [Go] Do we actually need to copy this given garbage collection + // and the lack of deallocating destructors? + tag_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(tag_copy.handle, value.handle) + copy(tag_copy.prefix, value.prefix) + emitter.tag_directives = append(emitter.tag_directives, tag_copy) + return true +} + +// Increase the indentation level. +func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool) bool { + emitter.indents = append(emitter.indents, emitter.indent) + if emitter.indent < 0 { + if flow { + emitter.indent = emitter.best_indent + } else { + emitter.indent = 0 + } + } else if !indentless { + emitter.indent += emitter.best_indent + } + return true +} + +// State dispatcher. +func yaml_emitter_state_machine(emitter *yaml_emitter_t, event *yaml_event_t) bool { + switch emitter.state { + default: + case yaml_EMIT_STREAM_START_STATE: + return yaml_emitter_emit_stream_start(emitter, event) + + case yaml_EMIT_FIRST_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, true) + + case yaml_EMIT_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, false) + + case yaml_EMIT_DOCUMENT_CONTENT_STATE: + return yaml_emitter_emit_document_content(emitter, event) + + case yaml_EMIT_DOCUMENT_END_STATE: + return yaml_emitter_emit_document_end(emitter, event) + + case yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, true) + + case yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, false) + + case yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, false) + + case yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, true) + + case yaml_EMIT_FLOW_MAPPING_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, false) + + case yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, true) + + case yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, false) + + case yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, true) + + case yaml_EMIT_BLOCK_MAPPING_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, false) + + case yaml_EMIT_END_STATE: + return yaml_emitter_set_emitter_error(emitter, "expected nothing after STREAM-END") + } + panic("invalid emitter state") +} + +// Expect STREAM-START. +func yaml_emitter_emit_stream_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_STREAM_START_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected STREAM-START") + } + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = event.encoding + if emitter.encoding == yaml_ANY_ENCODING { + emitter.encoding = yaml_UTF8_ENCODING + } + } + if emitter.best_indent < 2 || emitter.best_indent > 9 { + emitter.best_indent = 2 + } + if emitter.best_width >= 0 && emitter.best_width <= emitter.best_indent*2 { + emitter.best_width = 80 + } + if emitter.best_width < 0 { + emitter.best_width = 1<<31 - 1 + } + if emitter.line_break == yaml_ANY_BREAK { + emitter.line_break = yaml_LN_BREAK + } + + emitter.indent = -1 + emitter.line = 0 + emitter.column = 0 + emitter.whitespace = true + emitter.indention = true + + if emitter.encoding != yaml_UTF8_ENCODING { + if !yaml_emitter_write_bom(emitter) { + return false + } + } + emitter.state = yaml_EMIT_FIRST_DOCUMENT_START_STATE + return true +} + +// Expect DOCUMENT-START or STREAM-END. +func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + + if event.typ == yaml_DOCUMENT_START_EVENT { + + if event.version_directive != nil { + if !yaml_emitter_analyze_version_directive(emitter, event.version_directive) { + return false + } + } + + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_analyze_tag_directive(emitter, tag_directive) { + return false + } + if !yaml_emitter_append_tag_directive(emitter, tag_directive, false) { + return false + } + } + + for i := 0; i < len(default_tag_directives); i++ { + tag_directive := &default_tag_directives[i] + if !yaml_emitter_append_tag_directive(emitter, tag_directive, true) { + return false + } + } + + implicit := event.implicit + if !first || emitter.canonical { + implicit = false + } + + if emitter.open_ended && (event.version_directive != nil || len(event.tag_directives) > 0) { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if event.version_directive != nil { + implicit = false + if !yaml_emitter_write_indicator(emitter, []byte("%YAML"), true, false, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("1.1"), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if len(event.tag_directives) > 0 { + implicit = false + for i := 0; i < len(event.tag_directives); i++ { + tag_directive := &event.tag_directives[i] + if !yaml_emitter_write_indicator(emitter, []byte("%TAG"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_handle(emitter, tag_directive.handle) { + return false + } + if !yaml_emitter_write_tag_content(emitter, tag_directive.prefix, true) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + if yaml_emitter_check_empty_document(emitter) { + implicit = false + } + if !implicit { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte("---"), true, false, false) { + return false + } + if emitter.canonical { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + } + + emitter.state = yaml_EMIT_DOCUMENT_CONTENT_STATE + return true + } + + if event.typ == yaml_STREAM_END_EVENT { + if emitter.open_ended { + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_END_STATE + return true + } + + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-START or STREAM-END") +} + +// Expect the root node. +func yaml_emitter_emit_document_content(emitter *yaml_emitter_t, event *yaml_event_t) bool { + emitter.states = append(emitter.states, yaml_EMIT_DOCUMENT_END_STATE) + return yaml_emitter_emit_node(emitter, event, true, false, false, false) +} + +// Expect DOCUMENT-END. +func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if event.typ != yaml_DOCUMENT_END_EVENT { + return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-END") + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !event.implicit { + // [Go] Allocate the slice elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("..."), true, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_flush(emitter) { + return false + } + emitter.state = yaml_EMIT_DOCUMENT_START_STATE + emitter.tag_directives = emitter.tag_directives[:0] + return true +} + +// Expect a flow item node. +func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'['}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{']'}, false, false, false) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + + return true + } + + if !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE) + return yaml_emitter_emit_node(emitter, event, false, true, false, false) +} + +// Expect a flow key node. +func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_write_indicator(emitter, []byte{'{'}, true, true, false) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + emitter.flow_level++ + } + + if event.typ == yaml_MAPPING_END_EVENT { + emitter.flow_level-- + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + if emitter.canonical && !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'}'}, false, false, false) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + + if !first { + if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) { + return false + } + } + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + + if !emitter.canonical && yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, false) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a flow value node. +func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if emitter.canonical || emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, false) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block item node. +func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, emitter.mapping_context && !emitter.indention) { + return false + } + } + if event.typ == yaml_SEQUENCE_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'-'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE) + return yaml_emitter_emit_node(emitter, event, false, true, false, false) +} + +// Expect a block key node. +func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool { + if first { + if !yaml_emitter_increase_indent(emitter, false, false) { + return false + } + } + if event.typ == yaml_MAPPING_END_EVENT { + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true + } + if !yaml_emitter_write_indent(emitter) { + return false + } + if yaml_emitter_check_simple_key(emitter) { + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, true) + } + if !yaml_emitter_write_indicator(emitter, []byte{'?'}, true, false, true) { + return false + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_VALUE_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a block value node. +func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_event_t, simple bool) bool { + if simple { + if !yaml_emitter_write_indicator(emitter, []byte{':'}, false, false, false) { + return false + } + } else { + if !yaml_emitter_write_indent(emitter) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{':'}, true, false, true) { + return false + } + } + emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE) + return yaml_emitter_emit_node(emitter, event, false, false, true, false) +} + +// Expect a node. +func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t, + root bool, sequence bool, mapping bool, simple_key bool) bool { + + emitter.root_context = root + emitter.sequence_context = sequence + emitter.mapping_context = mapping + emitter.simple_key_context = simple_key + + switch event.typ { + case yaml_ALIAS_EVENT: + return yaml_emitter_emit_alias(emitter, event) + case yaml_SCALAR_EVENT: + return yaml_emitter_emit_scalar(emitter, event) + case yaml_SEQUENCE_START_EVENT: + return yaml_emitter_emit_sequence_start(emitter, event) + case yaml_MAPPING_START_EVENT: + return yaml_emitter_emit_mapping_start(emitter, event) + default: + return yaml_emitter_set_emitter_error(emitter, + fmt.Sprintf("expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, but got %v", event.typ)) + } +} + +// Expect ALIAS. +func yaml_emitter_emit_alias(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SCALAR. +func yaml_emitter_emit_scalar(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_select_scalar_style(emitter, event) { + return false + } + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if !yaml_emitter_increase_indent(emitter, true, false) { + return false + } + if !yaml_emitter_process_scalar(emitter) { + return false + } + emitter.indent = emitter.indents[len(emitter.indents)-1] + emitter.indents = emitter.indents[:len(emitter.indents)-1] + emitter.state = emitter.states[len(emitter.states)-1] + emitter.states = emitter.states[:len(emitter.states)-1] + return true +} + +// Expect SEQUENCE-START. +func yaml_emitter_emit_sequence_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.sequence_style() == yaml_FLOW_SEQUENCE_STYLE || + yaml_emitter_check_empty_sequence(emitter) { + emitter.state = yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE + } + return true +} + +// Expect MAPPING-START. +func yaml_emitter_emit_mapping_start(emitter *yaml_emitter_t, event *yaml_event_t) bool { + if !yaml_emitter_process_anchor(emitter) { + return false + } + if !yaml_emitter_process_tag(emitter) { + return false + } + if emitter.flow_level > 0 || emitter.canonical || event.mapping_style() == yaml_FLOW_MAPPING_STYLE || + yaml_emitter_check_empty_mapping(emitter) { + emitter.state = yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE + } else { + emitter.state = yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE + } + return true +} + +// Check if the document content is an empty scalar. +func yaml_emitter_check_empty_document(emitter *yaml_emitter_t) bool { + return false // [Go] Huh? +} + +// Check if the next events represent an empty sequence. +func yaml_emitter_check_empty_sequence(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_SEQUENCE_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_SEQUENCE_END_EVENT +} + +// Check if the next events represent an empty mapping. +func yaml_emitter_check_empty_mapping(emitter *yaml_emitter_t) bool { + if len(emitter.events)-emitter.events_head < 2 { + return false + } + return emitter.events[emitter.events_head].typ == yaml_MAPPING_START_EVENT && + emitter.events[emitter.events_head+1].typ == yaml_MAPPING_END_EVENT +} + +// Check if the next node can be expressed as a simple key. +func yaml_emitter_check_simple_key(emitter *yaml_emitter_t) bool { + length := 0 + switch emitter.events[emitter.events_head].typ { + case yaml_ALIAS_EVENT: + length += len(emitter.anchor_data.anchor) + case yaml_SCALAR_EVENT: + if emitter.scalar_data.multiline { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + + len(emitter.scalar_data.value) + case yaml_SEQUENCE_START_EVENT: + if !yaml_emitter_check_empty_sequence(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + case yaml_MAPPING_START_EVENT: + if !yaml_emitter_check_empty_mapping(emitter) { + return false + } + length += len(emitter.anchor_data.anchor) + + len(emitter.tag_data.handle) + + len(emitter.tag_data.suffix) + default: + return false + } + return length <= 128 +} + +// Determine an acceptable scalar style. +func yaml_emitter_select_scalar_style(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + no_tag := len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 + if no_tag && !event.implicit && !event.quoted_implicit { + return yaml_emitter_set_emitter_error(emitter, "neither tag nor implicit flags are specified") + } + + style := event.scalar_style() + if style == yaml_ANY_SCALAR_STYLE { + style = yaml_PLAIN_SCALAR_STYLE + } + if emitter.canonical { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + if emitter.simple_key_context && emitter.scalar_data.multiline { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + + if style == yaml_PLAIN_SCALAR_STYLE { + if emitter.flow_level > 0 && !emitter.scalar_data.flow_plain_allowed || + emitter.flow_level == 0 && !emitter.scalar_data.block_plain_allowed { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if len(emitter.scalar_data.value) == 0 && (emitter.flow_level > 0 || emitter.simple_key_context) { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + if no_tag && !event.implicit { + style = yaml_SINGLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_SINGLE_QUOTED_SCALAR_STYLE { + if !emitter.scalar_data.single_quoted_allowed { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + if style == yaml_LITERAL_SCALAR_STYLE || style == yaml_FOLDED_SCALAR_STYLE { + if !emitter.scalar_data.block_allowed || emitter.flow_level > 0 || emitter.simple_key_context { + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + } + + if no_tag && !event.quoted_implicit && style != yaml_PLAIN_SCALAR_STYLE { + emitter.tag_data.handle = []byte{'!'} + } + emitter.scalar_data.style = style + return true +} + +// Write an anchor. +func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool { + if emitter.anchor_data.anchor == nil { + return true + } + c := []byte{'&'} + if emitter.anchor_data.alias { + c[0] = '*' + } + if !yaml_emitter_write_indicator(emitter, c, true, false, false) { + return false + } + return yaml_emitter_write_anchor(emitter, emitter.anchor_data.anchor) +} + +// Write a tag. +func yaml_emitter_process_tag(emitter *yaml_emitter_t) bool { + if len(emitter.tag_data.handle) == 0 && len(emitter.tag_data.suffix) == 0 { + return true + } + if len(emitter.tag_data.handle) > 0 { + if !yaml_emitter_write_tag_handle(emitter, emitter.tag_data.handle) { + return false + } + if len(emitter.tag_data.suffix) > 0 { + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + } + } else { + // [Go] Allocate these slices elsewhere. + if !yaml_emitter_write_indicator(emitter, []byte("!<"), true, false, false) { + return false + } + if !yaml_emitter_write_tag_content(emitter, emitter.tag_data.suffix, false) { + return false + } + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, false, false, false) { + return false + } + } + return true +} + +// Write a scalar. +func yaml_emitter_process_scalar(emitter *yaml_emitter_t) bool { + switch emitter.scalar_data.style { + case yaml_PLAIN_SCALAR_STYLE: + return yaml_emitter_write_plain_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_SINGLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_single_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_DOUBLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_double_quoted_scalar(emitter, emitter.scalar_data.value, !emitter.simple_key_context) + + case yaml_LITERAL_SCALAR_STYLE: + return yaml_emitter_write_literal_scalar(emitter, emitter.scalar_data.value) + + case yaml_FOLDED_SCALAR_STYLE: + return yaml_emitter_write_folded_scalar(emitter, emitter.scalar_data.value) + } + panic("unknown scalar style") +} + +// Check if a %YAML directive is valid. +func yaml_emitter_analyze_version_directive(emitter *yaml_emitter_t, version_directive *yaml_version_directive_t) bool { + if version_directive.major != 1 || version_directive.minor != 1 { + return yaml_emitter_set_emitter_error(emitter, "incompatible %YAML directive") + } + return true +} + +// Check if a %TAG directive is valid. +func yaml_emitter_analyze_tag_directive(emitter *yaml_emitter_t, tag_directive *yaml_tag_directive_t) bool { + handle := tag_directive.handle + prefix := tag_directive.prefix + if len(handle) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag handle must not be empty") + } + if handle[0] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must start with '!'") + } + if handle[len(handle)-1] != '!' { + return yaml_emitter_set_emitter_error(emitter, "tag handle must end with '!'") + } + for i := 1; i < len(handle)-1; i += width(handle[i]) { + if !is_alpha(handle, i) { + return yaml_emitter_set_emitter_error(emitter, "tag handle must contain alphanumerical characters only") + } + } + if len(prefix) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag prefix must not be empty") + } + return true +} + +// Check if an anchor is valid. +func yaml_emitter_analyze_anchor(emitter *yaml_emitter_t, anchor []byte, alias bool) bool { + if len(anchor) == 0 { + problem := "anchor value must not be empty" + if alias { + problem = "alias value must not be empty" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + for i := 0; i < len(anchor); i += width(anchor[i]) { + if !is_alpha(anchor, i) { + problem := "anchor value must contain alphanumerical characters only" + if alias { + problem = "alias value must contain alphanumerical characters only" + } + return yaml_emitter_set_emitter_error(emitter, problem) + } + } + emitter.anchor_data.anchor = anchor + emitter.anchor_data.alias = alias + return true +} + +// Check if a tag is valid. +func yaml_emitter_analyze_tag(emitter *yaml_emitter_t, tag []byte) bool { + if len(tag) == 0 { + return yaml_emitter_set_emitter_error(emitter, "tag value must not be empty") + } + for i := 0; i < len(emitter.tag_directives); i++ { + tag_directive := &emitter.tag_directives[i] + if bytes.HasPrefix(tag, tag_directive.prefix) { + emitter.tag_data.handle = tag_directive.handle + emitter.tag_data.suffix = tag[len(tag_directive.prefix):] + return true + } + } + emitter.tag_data.suffix = tag + return true +} + +// Check if a scalar is valid. +func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool { + var ( + block_indicators = false + flow_indicators = false + line_breaks = false + special_characters = false + + leading_space = false + leading_break = false + trailing_space = false + trailing_break = false + break_space = false + space_break = false + + preceded_by_whitespace = false + followed_by_whitespace = false + previous_space = false + previous_break = false + ) + + emitter.scalar_data.value = value + + if len(value) == 0 { + emitter.scalar_data.multiline = false + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = false + return true + } + + if len(value) >= 3 && ((value[0] == '-' && value[1] == '-' && value[2] == '-') || (value[0] == '.' && value[1] == '.' && value[2] == '.')) { + block_indicators = true + flow_indicators = true + } + + preceded_by_whitespace = true + for i, w := 0, 0; i < len(value); i += w { + w = width(value[i]) + followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w) + + if i == 0 { + switch value[i] { + case '#', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': + flow_indicators = true + block_indicators = true + case '?', ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '-': + if followed_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } else { + switch value[i] { + case ',', '?', '[', ']', '{', '}': + flow_indicators = true + case ':': + flow_indicators = true + if followed_by_whitespace { + block_indicators = true + } + case '#': + if preceded_by_whitespace { + flow_indicators = true + block_indicators = true + } + } + } + + if !is_printable(value, i) || !is_ascii(value, i) && !emitter.unicode { + special_characters = true + } + if is_space(value, i) { + if i == 0 { + leading_space = true + } + if i+width(value[i]) == len(value) { + trailing_space = true + } + if previous_break { + break_space = true + } + previous_space = true + previous_break = false + } else if is_break(value, i) { + line_breaks = true + if i == 0 { + leading_break = true + } + if i+width(value[i]) == len(value) { + trailing_break = true + } + if previous_space { + space_break = true + } + previous_space = false + previous_break = true + } else { + previous_space = false + previous_break = false + } + + // [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition. + preceded_by_whitespace = is_blankz(value, i) + } + + emitter.scalar_data.multiline = line_breaks + emitter.scalar_data.flow_plain_allowed = true + emitter.scalar_data.block_plain_allowed = true + emitter.scalar_data.single_quoted_allowed = true + emitter.scalar_data.block_allowed = true + + if leading_space || leading_break || trailing_space || trailing_break { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if trailing_space { + emitter.scalar_data.block_allowed = false + } + if break_space { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + } + if space_break || special_characters { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + emitter.scalar_data.single_quoted_allowed = false + emitter.scalar_data.block_allowed = false + } + if line_breaks { + emitter.scalar_data.flow_plain_allowed = false + emitter.scalar_data.block_plain_allowed = false + } + if flow_indicators { + emitter.scalar_data.flow_plain_allowed = false + } + if block_indicators { + emitter.scalar_data.block_plain_allowed = false + } + return true +} + +// Check if the event data is valid. +func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bool { + + emitter.anchor_data.anchor = nil + emitter.tag_data.handle = nil + emitter.tag_data.suffix = nil + emitter.scalar_data.value = nil + + switch event.typ { + case yaml_ALIAS_EVENT: + if !yaml_emitter_analyze_anchor(emitter, event.anchor, true) { + return false + } + + case yaml_SCALAR_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || (!event.implicit && !event.quoted_implicit)) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + if !yaml_emitter_analyze_scalar(emitter, event.value) { + return false + } + + case yaml_SEQUENCE_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + + case yaml_MAPPING_START_EVENT: + if len(event.anchor) > 0 { + if !yaml_emitter_analyze_anchor(emitter, event.anchor, false) { + return false + } + } + if len(event.tag) > 0 && (emitter.canonical || !event.implicit) { + if !yaml_emitter_analyze_tag(emitter, event.tag) { + return false + } + } + } + return true +} + +// Write the BOM character. +func yaml_emitter_write_bom(emitter *yaml_emitter_t) bool { + if !flush(emitter) { + return false + } + pos := emitter.buffer_pos + emitter.buffer[pos+0] = '\xEF' + emitter.buffer[pos+1] = '\xBB' + emitter.buffer[pos+2] = '\xBF' + emitter.buffer_pos += 3 + return true +} + +func yaml_emitter_write_indent(emitter *yaml_emitter_t) bool { + indent := emitter.indent + if indent < 0 { + indent = 0 + } + if !emitter.indention || emitter.column > indent || (emitter.column == indent && !emitter.whitespace) { + if !put_break(emitter) { + return false + } + } + for emitter.column < indent { + if !put(emitter, ' ') { + return false + } + } + emitter.whitespace = true + emitter.indention = true + return true +} + +func yaml_emitter_write_indicator(emitter *yaml_emitter_t, indicator []byte, need_whitespace, is_whitespace, is_indention bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, indicator) { + return false + } + emitter.whitespace = is_whitespace + emitter.indention = (emitter.indention && is_indention) + emitter.open_ended = false + return true +} + +func yaml_emitter_write_anchor(emitter *yaml_emitter_t, value []byte) bool { + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_handle(emitter *yaml_emitter_t, value []byte) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + if !write_all(emitter, value) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_tag_content(emitter *yaml_emitter_t, value []byte, need_whitespace bool) bool { + if need_whitespace && !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + for i := 0; i < len(value); { + var must_write bool + switch value[i] { + case ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '_', '.', '~', '*', '\'', '(', ')', '[', ']': + must_write = true + default: + must_write = is_alpha(value, i) + } + if must_write { + if !write(emitter, value, &i) { + return false + } + } else { + w := width(value[i]) + for k := 0; k < w; k++ { + octet := value[i] + i++ + if !put(emitter, '%') { + return false + } + + c := octet >> 4 + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + + c = octet & 0x0f + if c < 10 { + c += '0' + } else { + c += 'A' - 10 + } + if !put(emitter, c) { + return false + } + } + } + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + if !emitter.whitespace { + if !put(emitter, ' ') { + return false + } + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + + emitter.whitespace = false + emitter.indention = false + if emitter.root_context { + emitter.open_ended = true + } + + return true +} + +func yaml_emitter_write_single_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, true, false, false) { + return false + } + + spaces := false + breaks := false + for i := 0; i < len(value); { + if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 && !is_space(value, i+1) { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + spaces = true + } else if is_break(value, i) { + if !breaks && value[i] == '\n' { + if !put_break(emitter) { + return false + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if value[i] == '\'' { + if !put(emitter, '\'') { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + spaces = false + breaks = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'\''}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_double_quoted_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool { + spaces := false + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, true, false, false) { + return false + } + + for i := 0; i < len(value); { + if !is_printable(value, i) || (!emitter.unicode && !is_ascii(value, i)) || + is_bom(value, i) || is_break(value, i) || + value[i] == '"' || value[i] == '\\' { + + octet := value[i] + + var w int + var v rune + switch { + case octet&0x80 == 0x00: + w, v = 1, rune(octet&0x7F) + case octet&0xE0 == 0xC0: + w, v = 2, rune(octet&0x1F) + case octet&0xF0 == 0xE0: + w, v = 3, rune(octet&0x0F) + case octet&0xF8 == 0xF0: + w, v = 4, rune(octet&0x07) + } + for k := 1; k < w; k++ { + octet = value[i+k] + v = (v << 6) + (rune(octet) & 0x3F) + } + i += w + + if !put(emitter, '\\') { + return false + } + + var ok bool + switch v { + case 0x00: + ok = put(emitter, '0') + case 0x07: + ok = put(emitter, 'a') + case 0x08: + ok = put(emitter, 'b') + case 0x09: + ok = put(emitter, 't') + case 0x0A: + ok = put(emitter, 'n') + case 0x0b: + ok = put(emitter, 'v') + case 0x0c: + ok = put(emitter, 'f') + case 0x0d: + ok = put(emitter, 'r') + case 0x1b: + ok = put(emitter, 'e') + case 0x22: + ok = put(emitter, '"') + case 0x5c: + ok = put(emitter, '\\') + case 0x85: + ok = put(emitter, 'N') + case 0xA0: + ok = put(emitter, '_') + case 0x2028: + ok = put(emitter, 'L') + case 0x2029: + ok = put(emitter, 'P') + default: + if v <= 0xFF { + ok = put(emitter, 'x') + w = 2 + } else if v <= 0xFFFF { + ok = put(emitter, 'u') + w = 4 + } else { + ok = put(emitter, 'U') + w = 8 + } + for k := (w - 1) * 4; ok && k >= 0; k -= 4 { + digit := byte((v >> uint(k)) & 0x0F) + if digit < 10 { + ok = put(emitter, digit+'0') + } else { + ok = put(emitter, digit+'A'-10) + } + } + } + if !ok { + return false + } + spaces = false + } else if is_space(value, i) { + if allow_breaks && !spaces && emitter.column > emitter.best_width && i > 0 && i < len(value)-1 { + if !yaml_emitter_write_indent(emitter) { + return false + } + if is_space(value, i+1) { + if !put(emitter, '\\') { + return false + } + } + i += width(value[i]) + } else if !write(emitter, value, &i) { + return false + } + spaces = true + } else { + if !write(emitter, value, &i) { + return false + } + spaces = false + } + } + if !yaml_emitter_write_indicator(emitter, []byte{'"'}, false, false, false) { + return false + } + emitter.whitespace = false + emitter.indention = false + return true +} + +func yaml_emitter_write_block_scalar_hints(emitter *yaml_emitter_t, value []byte) bool { + if is_space(value, 0) || is_break(value, 0) { + indent_hint := []byte{'0' + byte(emitter.best_indent)} + if !yaml_emitter_write_indicator(emitter, indent_hint, false, false, false) { + return false + } + } + + emitter.open_ended = false + + var chomp_hint [1]byte + if len(value) == 0 { + chomp_hint[0] = '-' + } else { + i := len(value) - 1 + for value[i]&0xC0 == 0x80 { + i-- + } + if !is_break(value, i) { + chomp_hint[0] = '-' + } else if i == 0 { + chomp_hint[0] = '+' + emitter.open_ended = true + } else { + i-- + for value[i]&0xC0 == 0x80 { + i-- + } + if is_break(value, i) { + chomp_hint[0] = '+' + emitter.open_ended = true + } + } + } + if chomp_hint[0] != 0 { + if !yaml_emitter_write_indicator(emitter, chomp_hint[:], false, false, false) { + return false + } + } + return true +} + +func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'|'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + if !put_break(emitter) { + return false + } + emitter.indention = true + emitter.whitespace = true + breaks := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + } + if !write(emitter, value, &i) { + return false + } + emitter.indention = false + breaks = false + } + } + + return true +} + +func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) bool { + if !yaml_emitter_write_indicator(emitter, []byte{'>'}, true, false, false) { + return false + } + if !yaml_emitter_write_block_scalar_hints(emitter, value) { + return false + } + + if !put_break(emitter) { + return false + } + emitter.indention = true + emitter.whitespace = true + + breaks := true + leading_spaces := true + for i := 0; i < len(value); { + if is_break(value, i) { + if !breaks && !leading_spaces && value[i] == '\n' { + k := 0 + for is_break(value, k) { + k += width(value[k]) + } + if !is_blankz(value, k) { + if !put_break(emitter) { + return false + } + } + } + if !write_break(emitter, value, &i) { + return false + } + emitter.indention = true + breaks = true + } else { + if breaks { + if !yaml_emitter_write_indent(emitter) { + return false + } + leading_spaces = is_blank(value, i) + } + if !breaks && is_space(value, i) && !is_space(value, i+1) && emitter.column > emitter.best_width { + if !yaml_emitter_write_indent(emitter) { + return false + } + i += width(value[i]) + } else { + if !write(emitter, value, &i) { + return false + } + } + emitter.indention = false + breaks = false + } + } + return true +} diff --git a/vendor/gopkg.in/yaml.v2/encode.go b/vendor/gopkg.in/yaml.v2/encode.go new file mode 100644 index 00000000..0ee738e1 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/encode.go @@ -0,0 +1,390 @@ +package yaml + +import ( + "encoding" + "fmt" + "io" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +// jsonNumber is the interface of the encoding/json.Number datatype. +// Repeating the interface here avoids a dependency on encoding/json, and also +// supports other libraries like jsoniter, which use a similar datatype with +// the same interface. Detecting this interface is useful when dealing with +// structures containing json.Number, which is a string under the hood. The +// encoder should prefer the use of Int64(), Float64() and string(), in that +// order, when encoding this type. +type jsonNumber interface { + Float64() (float64, error) + Int64() (int64, error) + String() string +} + +type encoder struct { + emitter yaml_emitter_t + event yaml_event_t + out []byte + flow bool + // doneInit holds whether the initial stream_start_event has been + // emitted. + doneInit bool +} + +func newEncoder() *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_string(&e.emitter, &e.out) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func newEncoderWithWriter(w io.Writer) *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_writer(&e.emitter, w) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func (e *encoder) init() { + if e.doneInit { + return + } + yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING) + e.emit() + e.doneInit = true +} + +func (e *encoder) finish() { + e.emitter.open_ended = false + yaml_stream_end_event_initialize(&e.event) + e.emit() +} + +func (e *encoder) destroy() { + yaml_emitter_delete(&e.emitter) +} + +func (e *encoder) emit() { + // This will internally delete the e.event value. + e.must(yaml_emitter_emit(&e.emitter, &e.event)) +} + +func (e *encoder) must(ok bool) { + if !ok { + msg := e.emitter.problem + if msg == "" { + msg = "unknown problem generating YAML content" + } + failf("%s", msg) + } +} + +func (e *encoder) marshalDoc(tag string, in reflect.Value) { + e.init() + yaml_document_start_event_initialize(&e.event, nil, nil, true) + e.emit() + e.marshal(tag, in) + yaml_document_end_event_initialize(&e.event, true) + e.emit() +} + +func (e *encoder) marshal(tag string, in reflect.Value) { + if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() { + e.nilv() + return + } + iface := in.Interface() + switch m := iface.(type) { + case jsonNumber: + integer, err := m.Int64() + if err == nil { + // In this case the json.Number is a valid int64 + in = reflect.ValueOf(integer) + break + } + float, err := m.Float64() + if err == nil { + // In this case the json.Number is a valid float64 + in = reflect.ValueOf(float) + break + } + // fallback case - no number could be obtained + in = reflect.ValueOf(m.String()) + case time.Time, *time.Time: + // Although time.Time implements TextMarshaler, + // we don't want to treat it as a string for YAML + // purposes because YAML has special support for + // timestamps. + case Marshaler: + v, err := m.MarshalYAML() + if err != nil { + fail(err) + } + if v == nil { + e.nilv() + return + } + in = reflect.ValueOf(v) + case encoding.TextMarshaler: + text, err := m.MarshalText() + if err != nil { + fail(err) + } + in = reflect.ValueOf(string(text)) + case nil: + e.nilv() + return + } + switch in.Kind() { + case reflect.Interface: + e.marshal(tag, in.Elem()) + case reflect.Map: + e.mapv(tag, in) + case reflect.Ptr: + if in.Type() == ptrTimeType { + e.timev(tag, in.Elem()) + } else { + e.marshal(tag, in.Elem()) + } + case reflect.Struct: + if in.Type() == timeType { + e.timev(tag, in) + } else { + e.structv(tag, in) + } + case reflect.Slice, reflect.Array: + if in.Type().Elem() == mapItemType { + e.itemsv(tag, in) + } else { + e.slicev(tag, in) + } + case reflect.String: + e.stringv(tag, in) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if in.Type() == durationType { + e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String())) + } else { + e.intv(tag, in) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + e.uintv(tag, in) + case reflect.Float32, reflect.Float64: + e.floatv(tag, in) + case reflect.Bool: + e.boolv(tag, in) + default: + panic("cannot marshal type: " + in.Type().String()) + } +} + +func (e *encoder) mapv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + keys := keyList(in.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + e.marshal("", k) + e.marshal("", in.MapIndex(k)) + } + }) +} + +func (e *encoder) itemsv(tag string, in reflect.Value) { + e.mappingv(tag, func() { + slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem) + for _, item := range slice { + e.marshal("", reflect.ValueOf(item.Key)) + e.marshal("", reflect.ValueOf(item.Value)) + } + }) +} + +func (e *encoder) structv(tag string, in reflect.Value) { + sinfo, err := getStructInfo(in.Type()) + if err != nil { + panic(err) + } + e.mappingv(tag, func() { + for _, info := range sinfo.FieldsList { + var value reflect.Value + if info.Inline == nil { + value = in.Field(info.Num) + } else { + value = in.FieldByIndex(info.Inline) + } + if info.OmitEmpty && isZero(value) { + continue + } + e.marshal("", reflect.ValueOf(info.Key)) + e.flow = info.Flow + e.marshal("", value) + } + if sinfo.InlineMap >= 0 { + m := in.Field(sinfo.InlineMap) + if m.Len() > 0 { + e.flow = false + keys := keyList(m.MapKeys()) + sort.Sort(keys) + for _, k := range keys { + if _, found := sinfo.FieldsMap[k.String()]; found { + panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String())) + } + e.marshal("", k) + e.flow = false + e.marshal("", m.MapIndex(k)) + } + } + } + }) +} + +func (e *encoder) mappingv(tag string, f func()) { + implicit := tag == "" + style := yaml_BLOCK_MAPPING_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_MAPPING_STYLE + } + yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style) + e.emit() + f() + yaml_mapping_end_event_initialize(&e.event) + e.emit() +} + +func (e *encoder) slicev(tag string, in reflect.Value) { + implicit := tag == "" + style := yaml_BLOCK_SEQUENCE_STYLE + if e.flow { + e.flow = false + style = yaml_FLOW_SEQUENCE_STYLE + } + e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) + e.emit() + n := in.Len() + for i := 0; i < n; i++ { + e.marshal("", in.Index(i)) + } + e.must(yaml_sequence_end_event_initialize(&e.event)) + e.emit() +} + +// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1. +// +// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported +// in YAML 1.2 and by this package, but these should be marshalled quoted for +// the time being for compatibility with other parsers. +func isBase60Float(s string) (result bool) { + // Fast path. + if s == "" { + return false + } + c := s[0] + if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 { + return false + } + // Do the full match. + return base60float.MatchString(s) +} + +// From http://yaml.org/type/float.html, except the regular expression there +// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix. +var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`) + +func (e *encoder) stringv(tag string, in reflect.Value) { + var style yaml_scalar_style_t + s := in.String() + canUsePlain := true + switch { + case !utf8.ValidString(s): + if tag == yaml_BINARY_TAG { + failf("explicitly tagged !!binary data must be base64-encoded") + } + if tag != "" { + failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) + } + // It can't be encoded directly as YAML so use a binary tag + // and encode it as base64. + tag = yaml_BINARY_TAG + s = encodeBase64(s) + case tag == "": + // Check to see if it would resolve to a specific + // tag when encoded unquoted. If it doesn't, + // there's no need to quote it. + rtag, _ := resolve("", s) + canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s) + } + // Note: it's possible for user code to emit invalid YAML + // if they explicitly specify a tag and a string containing + // text that's incompatible with that tag. + switch { + case strings.Contains(s, "\n"): + style = yaml_LITERAL_SCALAR_STYLE + case canUsePlain: + style = yaml_PLAIN_SCALAR_STYLE + default: + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + e.emitScalar(s, "", tag, style) +} + +func (e *encoder) boolv(tag string, in reflect.Value) { + var s string + if in.Bool() { + s = "true" + } else { + s = "false" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) intv(tag string, in reflect.Value) { + s := strconv.FormatInt(in.Int(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) uintv(tag string, in reflect.Value) { + s := strconv.FormatUint(in.Uint(), 10) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) timev(tag string, in reflect.Value) { + t := in.Interface().(time.Time) + s := t.Format(time.RFC3339Nano) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) floatv(tag string, in reflect.Value) { + // Issue #352: When formatting, use the precision of the underlying value + precision := 64 + if in.Kind() == reflect.Float32 { + precision = 32 + } + + s := strconv.FormatFloat(in.Float(), 'g', -1, precision) + switch s { + case "+Inf": + s = ".inf" + case "-Inf": + s = "-.inf" + case "NaN": + s = ".nan" + } + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) nilv() { + e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE) +} + +func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) { + implicit := tag == "" + e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style)) + e.emit() +} diff --git a/vendor/gopkg.in/yaml.v2/go.mod b/vendor/gopkg.in/yaml.v2/go.mod new file mode 100644 index 00000000..1934e876 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/go.mod @@ -0,0 +1,5 @@ +module "gopkg.in/yaml.v2" + +require ( + "gopkg.in/check.v1" v0.0.0-20161208181325-20d25e280405 +) diff --git a/vendor/gopkg.in/yaml.v2/parserc.go b/vendor/gopkg.in/yaml.v2/parserc.go new file mode 100644 index 00000000..81d05dfe --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/parserc.go @@ -0,0 +1,1095 @@ +package yaml + +import ( + "bytes" +) + +// The parser implements the following grammar: +// +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// implicit_document ::= block_node DOCUMENT-END* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// block_node_or_indentless_sequence ::= +// ALIAS +// | properties (block_content | indentless_block_sequence)? +// | block_content +// | indentless_block_sequence +// block_node ::= ALIAS +// | properties block_content? +// | block_content +// flow_node ::= ALIAS +// | properties flow_content? +// | flow_content +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// block_content ::= block_collection | flow_collection | SCALAR +// flow_content ::= flow_collection | SCALAR +// block_collection ::= block_sequence | block_mapping +// flow_collection ::= flow_sequence | flow_mapping +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// block_mapping ::= BLOCK-MAPPING_START +// ((KEY block_node_or_indentless_sequence?)? +// (VALUE block_node_or_indentless_sequence?)?)* +// BLOCK-END +// flow_sequence ::= FLOW-SEQUENCE-START +// (flow_sequence_entry FLOW-ENTRY)* +// flow_sequence_entry? +// FLOW-SEQUENCE-END +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// flow_mapping ::= FLOW-MAPPING-START +// (flow_mapping_entry FLOW-ENTRY)* +// flow_mapping_entry? +// FLOW-MAPPING-END +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + +// Peek the next token in the token queue. +func peek_token(parser *yaml_parser_t) *yaml_token_t { + if parser.token_available || yaml_parser_fetch_more_tokens(parser) { + return &parser.tokens[parser.tokens_head] + } + return nil +} + +// Remove the next token from the queue (must be called after peek_token). +func skip_token(parser *yaml_parser_t) { + parser.token_available = false + parser.tokens_parsed++ + parser.stream_end_produced = parser.tokens[parser.tokens_head].typ == yaml_STREAM_END_TOKEN + parser.tokens_head++ +} + +// Get the next event. +func yaml_parser_parse(parser *yaml_parser_t, event *yaml_event_t) bool { + // Erase the event object. + *event = yaml_event_t{} + + // No events after the end of the stream or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR || parser.state == yaml_PARSE_END_STATE { + return true + } + + // Generate the next event. + return yaml_parser_state_machine(parser, event) +} + +// Set parser error. +func yaml_parser_set_parser_error(parser *yaml_parser_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +func yaml_parser_set_parser_error_context(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string, problem_mark yaml_mark_t) bool { + parser.error = yaml_PARSER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = problem_mark + return false +} + +// State dispatcher. +func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool { + //trace("yaml_parser_state_machine", "state:", parser.state.String()) + + switch parser.state { + case yaml_PARSE_STREAM_START_STATE: + return yaml_parser_parse_stream_start(parser, event) + + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, true) + + case yaml_PARSE_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, false) + + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return yaml_parser_parse_document_content(parser, event) + + case yaml_PARSE_DOCUMENT_END_STATE: + return yaml_parser_parse_document_end(parser, event) + + case yaml_PARSE_BLOCK_NODE_STATE: + return yaml_parser_parse_node(parser, event, true, false) + + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return yaml_parser_parse_node(parser, event, true, true) + + case yaml_PARSE_FLOW_NODE_STATE: + return yaml_parser_parse_node(parser, event, false, false) + + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, true) + + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, false) + + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_indentless_sequence_entry(parser, event) + + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, true) + + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, false) + + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return yaml_parser_parse_block_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, true) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, false) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event) + + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event) + + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, true) + + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, false) + + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, true) + + default: + panic("invalid parser state") + } +} + +// Parse the production: +// stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +// ************ +func yaml_parser_parse_stream_start(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_STREAM_START_TOKEN { + return yaml_parser_set_parser_error(parser, "did not find expected ", token.start_mark) + } + parser.state = yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + encoding: token.encoding, + } + skip_token(parser) + return true +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// * +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// ************************* +func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t, implicit bool) bool { + + token := peek_token(parser) + if token == nil { + return false + } + + // Parse extra document end indicators. + if !implicit { + for token.typ == yaml_DOCUMENT_END_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + if implicit && token.typ != yaml_VERSION_DIRECTIVE_TOKEN && + token.typ != yaml_TAG_DIRECTIVE_TOKEN && + token.typ != yaml_DOCUMENT_START_TOKEN && + token.typ != yaml_STREAM_END_TOKEN { + // Parse an implicit document. + if !yaml_parser_process_directives(parser, nil, nil) { + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_BLOCK_NODE_STATE + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + } else if token.typ != yaml_STREAM_END_TOKEN { + // Parse an explicit document. + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + start_mark := token.start_mark + if !yaml_parser_process_directives(parser, &version_directive, &tag_directives) { + return false + } + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_DOCUMENT_START_TOKEN { + yaml_parser_set_parser_error(parser, + "did not find expected ", token.start_mark) + return false + } + parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE) + parser.state = yaml_PARSE_DOCUMENT_CONTENT_STATE + end_mark := token.end_mark + + *event = yaml_event_t{ + typ: yaml_DOCUMENT_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + version_directive: version_directive, + tag_directives: tag_directives, + implicit: false, + } + skip_token(parser) + + } else { + // Parse the stream end. + parser.state = yaml_PARSE_END_STATE + *event = yaml_event_t{ + typ: yaml_STREAM_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + } + + return true +} + +// Parse the productions: +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// *********** +// +func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN || + token.typ == yaml_TAG_DIRECTIVE_TOKEN || + token.typ == yaml_DOCUMENT_START_TOKEN || + token.typ == yaml_DOCUMENT_END_TOKEN || + token.typ == yaml_STREAM_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + return yaml_parser_process_empty_scalar(parser, event, + token.start_mark) + } + return yaml_parser_parse_node(parser, event, true, false) +} + +// Parse the productions: +// implicit_document ::= block_node DOCUMENT-END* +// ************* +// explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +// +func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + start_mark := token.start_mark + end_mark := token.start_mark + + implicit := true + if token.typ == yaml_DOCUMENT_END_TOKEN { + end_mark = token.end_mark + skip_token(parser) + implicit = false + } + + parser.tag_directives = parser.tag_directives[:0] + + parser.state = yaml_PARSE_DOCUMENT_START_STATE + *event = yaml_event_t{ + typ: yaml_DOCUMENT_END_EVENT, + start_mark: start_mark, + end_mark: end_mark, + implicit: implicit, + } + return true +} + +// Parse the productions: +// block_node_or_indentless_sequence ::= +// ALIAS +// ***** +// | properties (block_content | indentless_block_sequence)? +// ********** * +// | block_content | indentless_block_sequence +// * +// block_node ::= ALIAS +// ***** +// | properties block_content? +// ********** * +// | block_content +// * +// flow_node ::= ALIAS +// ***** +// | properties flow_content? +// ********** * +// | flow_content +// * +// properties ::= TAG ANCHOR? | ANCHOR TAG? +// ************************* +// block_content ::= block_collection | flow_collection | SCALAR +// ****** +// flow_content ::= flow_collection | SCALAR +// ****** +func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, indentless_sequence bool) bool { + //defer trace("yaml_parser_parse_node", "block:", block, "indentless_sequence:", indentless_sequence)() + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_ALIAS_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + *event = yaml_event_t{ + typ: yaml_ALIAS_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + anchor: token.value, + } + skip_token(parser) + return true + } + + start_mark := token.start_mark + end_mark := token.start_mark + + var tag_token bool + var tag_handle, tag_suffix, anchor []byte + var tag_mark yaml_mark_t + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + start_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } else if token.typ == yaml_TAG_TOKEN { + tag_token = true + tag_handle = token.value + tag_suffix = token.suffix + start_mark = token.start_mark + tag_mark = token.start_mark + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_ANCHOR_TOKEN { + anchor = token.value + end_mark = token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + } + + var tag []byte + if tag_token { + if len(tag_handle) == 0 { + tag = tag_suffix + tag_suffix = nil + } else { + for i := range parser.tag_directives { + if bytes.Equal(parser.tag_directives[i].handle, tag_handle) { + tag = append([]byte(nil), parser.tag_directives[i].prefix...) + tag = append(tag, tag_suffix...) + break + } + } + if len(tag) == 0 { + yaml_parser_set_parser_error_context(parser, + "while parsing a node", start_mark, + "found undefined tag handle", tag_mark) + return false + } + } + } + + implicit := len(tag) == 0 + if indentless_sequence && token.typ == yaml_BLOCK_ENTRY_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_SCALAR_TOKEN { + var plain_implicit, quoted_implicit bool + end_mark = token.end_mark + if (len(tag) == 0 && token.style == yaml_PLAIN_SCALAR_STYLE) || (len(tag) == 1 && tag[0] == '!') { + plain_implicit = true + } else if len(tag) == 0 { + quoted_implicit = true + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + value: token.value, + implicit: plain_implicit, + quoted_implicit: quoted_implicit, + style: yaml_style_t(token.style), + } + skip_token(parser) + return true + } + if token.typ == yaml_FLOW_SEQUENCE_START_TOKEN { + // [Go] Some of the events below can be merged as they differ only on style. + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_SEQUENCE_STYLE), + } + return true + } + if token.typ == yaml_FLOW_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + return true + } + if block && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_SEQUENCE_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE), + } + return true + } + if block && token.typ == yaml_BLOCK_MAPPING_START_TOKEN { + end_mark = token.end_mark + parser.state = yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + style: yaml_style_t(yaml_BLOCK_MAPPING_STYLE), + } + return true + } + if len(anchor) > 0 || len(tag) > 0 { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: start_mark, + end_mark: end_mark, + anchor: anchor, + tag: tag, + implicit: implicit, + quoted_implicit: false, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true + } + + context := "while parsing a flow node" + if block { + context = "while parsing a block node" + } + yaml_parser_set_parser_error_context(parser, context, start_mark, + "did not find expected node content", token.start_mark) + return false +} + +// Parse the productions: +// block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +// ******************** *********** * ********* +// +func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } else { + parser.state = yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } + if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block collection", context_mark, + "did not find expected '-' indicator", token.start_mark) +} + +// Parse the productions: +// indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +// *********** * +func yaml_parser_parse_indentless_sequence_entry(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_BLOCK_ENTRY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_BLOCK_ENTRY_TOKEN && + token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, true, false) + } + parser.state = yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be token.end_mark? + } + return true +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// ******************* +// ((KEY block_node_or_indentless_sequence?)? +// *** * +// (VALUE block_node_or_indentless_sequence?)?)* +// +// BLOCK-END +// ********* +// +func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ == yaml_KEY_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } else { + parser.state = yaml_PARSE_BLOCK_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + } else if token.typ == yaml_BLOCK_END_TOKEN { + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + return true + } + + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a block mapping", context_mark, + "did not find expected key", token.start_mark) +} + +// Parse the productions: +// block_mapping ::= BLOCK-MAPPING_START +// +// ((KEY block_node_or_indentless_sequence?)? +// +// (VALUE block_node_or_indentless_sequence?)?)* +// ***** * +// BLOCK-END +// +// +func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + mark := token.end_mark + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_KEY_TOKEN && + token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_BLOCK_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_BLOCK_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, true, true) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) + } + parser.state = yaml_PARSE_BLOCK_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence ::= FLOW-SEQUENCE-START +// ******************* +// (flow_sequence_entry FLOW-ENTRY)* +// * ********** +// flow_sequence_entry? +// * +// FLOW-SEQUENCE-END +// ***************** +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow sequence", context_mark, + "did not find expected ',' or ']'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_START_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + implicit: true, + style: yaml_style_t(yaml_FLOW_MAPPING_STYLE), + } + skip_token(parser) + return true + } else if token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + + *event = yaml_event_t{ + typ: yaml_SEQUENCE_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + + skip_token(parser) + return true +} + +// +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// *** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_key(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + mark := token.end_mark + skip_token(parser) + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// ***** * +// +func yaml_parser_parse_flow_sequence_entry_mapping_value(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token := peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_SEQUENCE_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Parse the productions: +// flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * +// +func yaml_parser_parse_flow_sequence_entry_mapping_end(parser *yaml_parser_t, event *yaml_event_t) bool { + token := peek_token(parser) + if token == nil { + return false + } + parser.state = yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.start_mark, // [Go] Shouldn't this be end_mark? + } + return true +} + +// Parse the productions: +// flow_mapping ::= FLOW-MAPPING-START +// ****************** +// (flow_mapping_entry FLOW-ENTRY)* +// * ********** +// flow_mapping_entry? +// ****************** +// FLOW-MAPPING-END +// **************** +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * *** * +// +func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { + if first { + token := peek_token(parser) + parser.marks = append(parser.marks, token.start_mark) + skip_token(parser) + } + + token := peek_token(parser) + if token == nil { + return false + } + + if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + if !first { + if token.typ == yaml_FLOW_ENTRY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } else { + context_mark := parser.marks[len(parser.marks)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow mapping", context_mark, + "did not find expected ',' or '}'", token.start_mark) + } + } + + if token.typ == yaml_KEY_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_VALUE_TOKEN && + token.typ != yaml_FLOW_ENTRY_TOKEN && + token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } else { + parser.state = yaml_PARSE_FLOW_MAPPING_VALUE_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + } else if token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + + parser.state = parser.states[len(parser.states)-1] + parser.states = parser.states[:len(parser.states)-1] + parser.marks = parser.marks[:len(parser.marks)-1] + *event = yaml_event_t{ + typ: yaml_MAPPING_END_EVENT, + start_mark: token.start_mark, + end_mark: token.end_mark, + } + skip_token(parser) + return true +} + +// Parse the productions: +// flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +// * ***** * +// +func yaml_parser_parse_flow_mapping_value(parser *yaml_parser_t, event *yaml_event_t, empty bool) bool { + token := peek_token(parser) + if token == nil { + return false + } + if empty { + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) + } + if token.typ == yaml_VALUE_TOKEN { + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + if token.typ != yaml_FLOW_ENTRY_TOKEN && token.typ != yaml_FLOW_MAPPING_END_TOKEN { + parser.states = append(parser.states, yaml_PARSE_FLOW_MAPPING_KEY_STATE) + return yaml_parser_parse_node(parser, event, false, false) + } + } + parser.state = yaml_PARSE_FLOW_MAPPING_KEY_STATE + return yaml_parser_process_empty_scalar(parser, event, token.start_mark) +} + +// Generate an empty scalar event. +func yaml_parser_process_empty_scalar(parser *yaml_parser_t, event *yaml_event_t, mark yaml_mark_t) bool { + *event = yaml_event_t{ + typ: yaml_SCALAR_EVENT, + start_mark: mark, + end_mark: mark, + value: nil, // Empty + implicit: true, + style: yaml_style_t(yaml_PLAIN_SCALAR_STYLE), + } + return true +} + +var default_tag_directives = []yaml_tag_directive_t{ + {[]byte("!"), []byte("!")}, + {[]byte("!!"), []byte("tag:yaml.org,2002:")}, +} + +// Parse directives. +func yaml_parser_process_directives(parser *yaml_parser_t, + version_directive_ref **yaml_version_directive_t, + tag_directives_ref *[]yaml_tag_directive_t) bool { + + var version_directive *yaml_version_directive_t + var tag_directives []yaml_tag_directive_t + + token := peek_token(parser) + if token == nil { + return false + } + + for token.typ == yaml_VERSION_DIRECTIVE_TOKEN || token.typ == yaml_TAG_DIRECTIVE_TOKEN { + if token.typ == yaml_VERSION_DIRECTIVE_TOKEN { + if version_directive != nil { + yaml_parser_set_parser_error(parser, + "found duplicate %YAML directive", token.start_mark) + return false + } + if token.major != 1 || token.minor != 1 { + yaml_parser_set_parser_error(parser, + "found incompatible YAML document", token.start_mark) + return false + } + version_directive = &yaml_version_directive_t{ + major: token.major, + minor: token.minor, + } + } else if token.typ == yaml_TAG_DIRECTIVE_TOKEN { + value := yaml_tag_directive_t{ + handle: token.value, + prefix: token.prefix, + } + if !yaml_parser_append_tag_directive(parser, value, false, token.start_mark) { + return false + } + tag_directives = append(tag_directives, value) + } + + skip_token(parser) + token = peek_token(parser) + if token == nil { + return false + } + } + + for i := range default_tag_directives { + if !yaml_parser_append_tag_directive(parser, default_tag_directives[i], true, token.start_mark) { + return false + } + } + + if version_directive_ref != nil { + *version_directive_ref = version_directive + } + if tag_directives_ref != nil { + *tag_directives_ref = tag_directives + } + return true +} + +// Append a tag directive to the directives stack. +func yaml_parser_append_tag_directive(parser *yaml_parser_t, value yaml_tag_directive_t, allow_duplicates bool, mark yaml_mark_t) bool { + for i := range parser.tag_directives { + if bytes.Equal(value.handle, parser.tag_directives[i].handle) { + if allow_duplicates { + return true + } + return yaml_parser_set_parser_error(parser, "found duplicate %TAG directive", mark) + } + } + + // [Go] I suspect the copy is unnecessary. This was likely done + // because there was no way to track ownership of the data. + value_copy := yaml_tag_directive_t{ + handle: make([]byte, len(value.handle)), + prefix: make([]byte, len(value.prefix)), + } + copy(value_copy.handle, value.handle) + copy(value_copy.prefix, value.prefix) + parser.tag_directives = append(parser.tag_directives, value_copy) + return true +} diff --git a/vendor/gopkg.in/yaml.v2/readerc.go b/vendor/gopkg.in/yaml.v2/readerc.go new file mode 100644 index 00000000..7c1f5fac --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/readerc.go @@ -0,0 +1,412 @@ +package yaml + +import ( + "io" +) + +// Set the reader error and return 0. +func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool { + parser.error = yaml_READER_ERROR + parser.problem = problem + parser.problem_offset = offset + parser.problem_value = value + return false +} + +// Byte order marks. +const ( + bom_UTF8 = "\xef\xbb\xbf" + bom_UTF16LE = "\xff\xfe" + bom_UTF16BE = "\xfe\xff" +) + +// Determine the input stream encoding by checking the BOM symbol. If no BOM is +// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. +func yaml_parser_determine_encoding(parser *yaml_parser_t) bool { + // Ensure that we had enough bytes in the raw buffer. + for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 { + if !yaml_parser_update_raw_buffer(parser) { + return false + } + } + + // Determine the encoding. + buf := parser.raw_buffer + pos := parser.raw_buffer_pos + avail := len(buf) - pos + if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] { + parser.encoding = yaml_UTF16LE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] { + parser.encoding = yaml_UTF16BE_ENCODING + parser.raw_buffer_pos += 2 + parser.offset += 2 + } else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] { + parser.encoding = yaml_UTF8_ENCODING + parser.raw_buffer_pos += 3 + parser.offset += 3 + } else { + parser.encoding = yaml_UTF8_ENCODING + } + return true +} + +// Update the raw buffer. +func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool { + size_read := 0 + + // Return if the raw buffer is full. + if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) { + return true + } + + // Return on EOF. + if parser.eof { + return true + } + + // Move the remaining bytes in the raw buffer to the beginning. + if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) { + copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:]) + } + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos] + parser.raw_buffer_pos = 0 + + // Call the read handler to fill the buffer. + size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)]) + parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read] + if err == io.EOF { + parser.eof = true + } else if err != nil { + return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1) + } + return true +} + +// Ensure that the buffer contains at least `length` characters. +// Return true on success, false on failure. +// +// The length is supposed to be significantly less that the buffer size. +func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { + if parser.read_handler == nil { + panic("read handler must be set") + } + + // [Go] This function was changed to guarantee the requested length size at EOF. + // The fact we need to do this is pretty awful, but the description above implies + // for that to be the case, and there are tests + + // If the EOF flag is set and the raw buffer is empty, do nothing. + if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { + // [Go] ACTUALLY! Read the documentation of this function above. + // This is just broken. To return true, we need to have the + // given length in the buffer. Not doing that means every single + // check that calls this function to make sure the buffer has a + // given length is Go) panicking; or C) accessing invalid memory. + //return true + } + + // Return if the buffer contains enough characters. + if parser.unread >= length { + return true + } + + // Determine the input encoding if it is not known yet. + if parser.encoding == yaml_ANY_ENCODING { + if !yaml_parser_determine_encoding(parser) { + return false + } + } + + // Move the unread characters to the beginning of the buffer. + buffer_len := len(parser.buffer) + if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len { + copy(parser.buffer, parser.buffer[parser.buffer_pos:]) + buffer_len -= parser.buffer_pos + parser.buffer_pos = 0 + } else if parser.buffer_pos == buffer_len { + buffer_len = 0 + parser.buffer_pos = 0 + } + + // Open the whole buffer for writing, and cut it before returning. + parser.buffer = parser.buffer[:cap(parser.buffer)] + + // Fill the buffer until it has enough characters. + first := true + for parser.unread < length { + + // Fill the raw buffer if necessary. + if !first || parser.raw_buffer_pos == len(parser.raw_buffer) { + if !yaml_parser_update_raw_buffer(parser) { + parser.buffer = parser.buffer[:buffer_len] + return false + } + } + first = false + + // Decode the raw buffer. + inner: + for parser.raw_buffer_pos != len(parser.raw_buffer) { + var value rune + var width int + + raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos + + // Decode the next character. + switch parser.encoding { + case yaml_UTF8_ENCODING: + // Decode a UTF-8 character. Check RFC 3629 + // (http://www.ietf.org/rfc/rfc3629.txt) for more details. + // + // The following table (taken from the RFC) is used for + // decoding. + // + // Char. number range | UTF-8 octet sequence + // (hexadecimal) | (binary) + // --------------------+------------------------------------ + // 0000 0000-0000 007F | 0xxxxxxx + // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // + // Additionally, the characters in the range 0xD800-0xDFFF + // are prohibited as they are reserved for use with UTF-16 + // surrogate pairs. + + // Determine the length of the UTF-8 sequence. + octet := parser.raw_buffer[parser.raw_buffer_pos] + switch { + case octet&0x80 == 0x00: + width = 1 + case octet&0xE0 == 0xC0: + width = 2 + case octet&0xF0 == 0xE0: + width = 3 + case octet&0xF8 == 0xF0: + width = 4 + default: + // The leading octet is invalid. + return yaml_parser_set_reader_error(parser, + "invalid leading UTF-8 octet", + parser.offset, int(octet)) + } + + // Check if the raw buffer contains an incomplete character. + if width > raw_unread { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-8 octet sequence", + parser.offset, -1) + } + break inner + } + + // Decode the leading octet. + switch { + case octet&0x80 == 0x00: + value = rune(octet & 0x7F) + case octet&0xE0 == 0xC0: + value = rune(octet & 0x1F) + case octet&0xF0 == 0xE0: + value = rune(octet & 0x0F) + case octet&0xF8 == 0xF0: + value = rune(octet & 0x07) + default: + value = 0 + } + + // Check and decode the trailing octets. + for k := 1; k < width; k++ { + octet = parser.raw_buffer[parser.raw_buffer_pos+k] + + // Check if the octet is valid. + if (octet & 0xC0) != 0x80 { + return yaml_parser_set_reader_error(parser, + "invalid trailing UTF-8 octet", + parser.offset+k, int(octet)) + } + + // Decode the octet. + value = (value << 6) + rune(octet&0x3F) + } + + // Check the length of the sequence against the value. + switch { + case width == 1: + case width == 2 && value >= 0x80: + case width == 3 && value >= 0x800: + case width == 4 && value >= 0x10000: + default: + return yaml_parser_set_reader_error(parser, + "invalid length of a UTF-8 sequence", + parser.offset, -1) + } + + // Check the range of the value. + if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF { + return yaml_parser_set_reader_error(parser, + "invalid Unicode character", + parser.offset, int(value)) + } + + case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING: + var low, high int + if parser.encoding == yaml_UTF16LE_ENCODING { + low, high = 0, 1 + } else { + low, high = 1, 0 + } + + // The UTF-16 encoding is not as simple as one might + // naively think. Check RFC 2781 + // (http://www.ietf.org/rfc/rfc2781.txt). + // + // Normally, two subsequent bytes describe a Unicode + // character. However a special technique (called a + // surrogate pair) is used for specifying character + // values larger than 0xFFFF. + // + // A surrogate pair consists of two pseudo-characters: + // high surrogate area (0xD800-0xDBFF) + // low surrogate area (0xDC00-0xDFFF) + // + // The following formulas are used for decoding + // and encoding characters using surrogate pairs: + // + // U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) + // U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) + // W1 = 110110yyyyyyyyyy + // W2 = 110111xxxxxxxxxx + // + // where U is the character value, W1 is the high surrogate + // area, W2 is the low surrogate area. + + // Check for incomplete UTF-16 character. + if raw_unread < 2 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 character", + parser.offset, -1) + } + break inner + } + + // Get the character. + value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8) + + // Check for unexpected low surrogate area. + if value&0xFC00 == 0xDC00 { + return yaml_parser_set_reader_error(parser, + "unexpected low surrogate area", + parser.offset, int(value)) + } + + // Check for a high surrogate area. + if value&0xFC00 == 0xD800 { + width = 4 + + // Check for incomplete surrogate pair. + if raw_unread < 4 { + if parser.eof { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 surrogate pair", + parser.offset, -1) + } + break inner + } + + // Get the next character. + value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) + + (rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8) + + // Check for a low surrogate area. + if value2&0xFC00 != 0xDC00 { + return yaml_parser_set_reader_error(parser, + "expected low surrogate area", + parser.offset+2, int(value2)) + } + + // Generate the value of the surrogate pair. + value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF) + } else { + width = 2 + } + + default: + panic("impossible") + } + + // Check if the character is in the allowed range: + // #x9 | #xA | #xD | [#x20-#x7E] (8 bit) + // | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) + // | [#x10000-#x10FFFF] (32 bit) + switch { + case value == 0x09: + case value == 0x0A: + case value == 0x0D: + case value >= 0x20 && value <= 0x7E: + case value == 0x85: + case value >= 0xA0 && value <= 0xD7FF: + case value >= 0xE000 && value <= 0xFFFD: + case value >= 0x10000 && value <= 0x10FFFF: + default: + return yaml_parser_set_reader_error(parser, + "control characters are not allowed", + parser.offset, int(value)) + } + + // Move the raw pointers. + parser.raw_buffer_pos += width + parser.offset += width + + // Finally put the character into the buffer. + if value <= 0x7F { + // 0000 0000-0000 007F . 0xxxxxxx + parser.buffer[buffer_len+0] = byte(value) + buffer_len += 1 + } else if value <= 0x7FF { + // 0000 0080-0000 07FF . 110xxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6)) + parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F)) + buffer_len += 2 + } else if value <= 0xFFFF { + // 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F)) + buffer_len += 3 + } else { + // 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18)) + parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F)) + parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F)) + parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F)) + buffer_len += 4 + } + + parser.unread++ + } + + // On EOF, put NUL into the buffer and return. + if parser.eof { + parser.buffer[buffer_len] = 0 + buffer_len++ + parser.unread++ + break + } + } + // [Go] Read the documentation of this function above. To return true, + // we need to have the given length in the buffer. Not doing that means + // every single check that calls this function to make sure the buffer + // has a given length is Go) panicking; or C) accessing invalid memory. + // This happens here due to the EOF above breaking early. + for buffer_len < length { + parser.buffer[buffer_len] = 0 + buffer_len++ + } + parser.buffer = parser.buffer[:buffer_len] + return true +} diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go new file mode 100644 index 00000000..6c151db6 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -0,0 +1,258 @@ +package yaml + +import ( + "encoding/base64" + "math" + "regexp" + "strconv" + "strings" + "time" +) + +type resolveMapItem struct { + value interface{} + tag string +} + +var resolveTable = make([]byte, 256) +var resolveMap = make(map[string]resolveMapItem) + +func init() { + t := resolveTable + t[int('+')] = 'S' // Sign + t[int('-')] = 'S' + for _, c := range "0123456789" { + t[int(c)] = 'D' // Digit + } + for _, c := range "yYnNtTfFoO~" { + t[int(c)] = 'M' // In map + } + t[int('.')] = '.' // Float (potentially in map) + + var resolveMapList = []struct { + v interface{} + tag string + l []string + }{ + {true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}}, + {true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}}, + {true, yaml_BOOL_TAG, []string{"on", "On", "ON"}}, + {false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}}, + {false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}}, + {false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}}, + {nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}}, + {math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}}, + {math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}}, + {math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}}, + {math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}}, + {"<<", yaml_MERGE_TAG, []string{"<<"}}, + } + + m := resolveMap + for _, item := range resolveMapList { + for _, s := range item.l { + m[s] = resolveMapItem{item.v, item.tag} + } + } +} + +const longTagPrefix = "tag:yaml.org,2002:" + +func shortTag(tag string) string { + // TODO This can easily be made faster and produce less garbage. + if strings.HasPrefix(tag, longTagPrefix) { + return "!!" + tag[len(longTagPrefix):] + } + return tag +} + +func longTag(tag string) string { + if strings.HasPrefix(tag, "!!") { + return longTagPrefix + tag[2:] + } + return tag +} + +func resolvableTag(tag string) bool { + switch tag { + case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG: + return true + } + return false +} + +var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`) + +func resolve(tag string, in string) (rtag string, out interface{}) { + if !resolvableTag(tag) { + return tag, in + } + + defer func() { + switch tag { + case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG: + return + case yaml_FLOAT_TAG: + if rtag == yaml_INT_TAG { + switch v := out.(type) { + case int64: + rtag = yaml_FLOAT_TAG + out = float64(v) + return + case int: + rtag = yaml_FLOAT_TAG + out = float64(v) + return + } + } + } + failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)) + }() + + // Any data is accepted as a !!str or !!binary. + // Otherwise, the prefix is enough of a hint about what it might be. + hint := byte('N') + if in != "" { + hint = resolveTable[in[0]] + } + if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG { + // Handle things we can lookup in a map. + if item, ok := resolveMap[in]; ok { + return item.tag, item.value + } + + // Base 60 floats are a bad idea, were dropped in YAML 1.2, and + // are purposefully unsupported here. They're still quoted on + // the way out for compatibility with other parser, though. + + switch hint { + case 'M': + // We've already checked the map above. + + case '.': + // Not in the map, so maybe a normal float. + floatv, err := strconv.ParseFloat(in, 64) + if err == nil { + return yaml_FLOAT_TAG, floatv + } + + case 'D', 'S': + // Int, float, or timestamp. + // Only try values as a timestamp if the value is unquoted or there's an explicit + // !!timestamp tag. + if tag == "" || tag == yaml_TIMESTAMP_TAG { + t, ok := parseTimestamp(in) + if ok { + return yaml_TIMESTAMP_TAG, t + } + } + + plain := strings.Replace(in, "_", "", -1) + intv, err := strconv.ParseInt(plain, 0, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + uintv, err := strconv.ParseUint(plain, 0, 64) + if err == nil { + return yaml_INT_TAG, uintv + } + if yamlStyleFloat.MatchString(plain) { + floatv, err := strconv.ParseFloat(plain, 64) + if err == nil { + return yaml_FLOAT_TAG, floatv + } + } + if strings.HasPrefix(plain, "0b") { + intv, err := strconv.ParseInt(plain[2:], 2, 64) + if err == nil { + if intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + uintv, err := strconv.ParseUint(plain[2:], 2, 64) + if err == nil { + return yaml_INT_TAG, uintv + } + } else if strings.HasPrefix(plain, "-0b") { + intv, err := strconv.ParseInt("-" + plain[3:], 2, 64) + if err == nil { + if true || intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) + } else { + return yaml_INT_TAG, intv + } + } + } + default: + panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")") + } + } + return yaml_STR_TAG, in +} + +// encodeBase64 encodes s as base64 that is broken up into multiple lines +// as appropriate for the resulting length. +func encodeBase64(s string) string { + const lineLen = 70 + encLen := base64.StdEncoding.EncodedLen(len(s)) + lines := encLen/lineLen + 1 + buf := make([]byte, encLen*2+lines) + in := buf[0:encLen] + out := buf[encLen:] + base64.StdEncoding.Encode(in, []byte(s)) + k := 0 + for i := 0; i < len(in); i += lineLen { + j := i + lineLen + if j > len(in) { + j = len(in) + } + k += copy(out[k:], in[i:j]) + if lines > 1 { + out[k] = '\n' + k++ + } + } + return string(out[:k]) +} + +// This is a subset of the formats allowed by the regular expression +// defined at http://yaml.org/type/timestamp.html. +var allowedTimestampFormats = []string{ + "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. + "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". + "2006-1-2 15:4:5.999999999", // space separated with no time zone + "2006-1-2", // date only + // Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5" + // from the set of examples. +} + +// parseTimestamp parses s as a timestamp string and +// returns the timestamp and reports whether it succeeded. +// Timestamp formats are defined at http://yaml.org/type/timestamp.html +func parseTimestamp(s string) (time.Time, bool) { + // TODO write code to check all the formats supported by + // http://yaml.org/type/timestamp.html instead of using time.Parse. + + // Quick check: all date formats start with YYYY-. + i := 0 + for ; i < len(s); i++ { + if c := s[i]; c < '0' || c > '9' { + break + } + } + if i != 4 || i == len(s) || s[i] != '-' { + return time.Time{}, false + } + for _, format := range allowedTimestampFormats { + if t, err := time.Parse(format, s); err == nil { + return t, true + } + } + return time.Time{}, false +} diff --git a/vendor/gopkg.in/yaml.v2/scannerc.go b/vendor/gopkg.in/yaml.v2/scannerc.go new file mode 100644 index 00000000..077fd1dd --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/scannerc.go @@ -0,0 +1,2696 @@ +package yaml + +import ( + "bytes" + "fmt" +) + +// Introduction +// ************ +// +// The following notes assume that you are familiar with the YAML specification +// (http://yaml.org/spec/1.2/spec.html). We mostly follow it, although in +// some cases we are less restrictive that it requires. +// +// The process of transforming a YAML stream into a sequence of events is +// divided on two steps: Scanning and Parsing. +// +// The Scanner transforms the input stream into a sequence of tokens, while the +// parser transform the sequence of tokens produced by the Scanner into a +// sequence of parsing events. +// +// The Scanner is rather clever and complicated. The Parser, on the contrary, +// is a straightforward implementation of a recursive-descendant parser (or, +// LL(1) parser, as it is usually called). +// +// Actually there are two issues of Scanning that might be called "clever", the +// rest is quite straightforward. The issues are "block collection start" and +// "simple keys". Both issues are explained below in details. +// +// Here the Scanning step is explained and implemented. We start with the list +// of all the tokens produced by the Scanner together with short descriptions. +// +// Now, tokens: +// +// STREAM-START(encoding) # The stream start. +// STREAM-END # The stream end. +// VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. +// TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. +// DOCUMENT-START # '---' +// DOCUMENT-END # '...' +// BLOCK-SEQUENCE-START # Indentation increase denoting a block +// BLOCK-MAPPING-START # sequence or a block mapping. +// BLOCK-END # Indentation decrease. +// FLOW-SEQUENCE-START # '[' +// FLOW-SEQUENCE-END # ']' +// BLOCK-SEQUENCE-START # '{' +// BLOCK-SEQUENCE-END # '}' +// BLOCK-ENTRY # '-' +// FLOW-ENTRY # ',' +// KEY # '?' or nothing (simple keys). +// VALUE # ':' +// ALIAS(anchor) # '*anchor' +// ANCHOR(anchor) # '&anchor' +// TAG(handle,suffix) # '!handle!suffix' +// SCALAR(value,style) # A scalar. +// +// The following two tokens are "virtual" tokens denoting the beginning and the +// end of the stream: +// +// STREAM-START(encoding) +// STREAM-END +// +// We pass the information about the input stream encoding with the +// STREAM-START token. +// +// The next two tokens are responsible for tags: +// +// VERSION-DIRECTIVE(major,minor) +// TAG-DIRECTIVE(handle,prefix) +// +// Example: +// +// %YAML 1.1 +// %TAG ! !foo +// %TAG !yaml! tag:yaml.org,2002: +// --- +// +// The correspoding sequence of tokens: +// +// STREAM-START(utf-8) +// VERSION-DIRECTIVE(1,1) +// TAG-DIRECTIVE("!","!foo") +// TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") +// DOCUMENT-START +// STREAM-END +// +// Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole +// line. +// +// The document start and end indicators are represented by: +// +// DOCUMENT-START +// DOCUMENT-END +// +// Note that if a YAML stream contains an implicit document (without '---' +// and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be +// produced. +// +// In the following examples, we present whole documents together with the +// produced tokens. +// +// 1. An implicit document: +// +// 'a scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// STREAM-END +// +// 2. An explicit document: +// +// --- +// 'a scalar' +// ... +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// SCALAR("a scalar",single-quoted) +// DOCUMENT-END +// STREAM-END +// +// 3. Several documents in a stream: +// +// 'a scalar' +// --- +// 'another scalar' +// --- +// 'yet another scalar' +// +// Tokens: +// +// STREAM-START(utf-8) +// SCALAR("a scalar",single-quoted) +// DOCUMENT-START +// SCALAR("another scalar",single-quoted) +// DOCUMENT-START +// SCALAR("yet another scalar",single-quoted) +// STREAM-END +// +// We have already introduced the SCALAR token above. The following tokens are +// used to describe aliases, anchors, tag, and scalars: +// +// ALIAS(anchor) +// ANCHOR(anchor) +// TAG(handle,suffix) +// SCALAR(value,style) +// +// The following series of examples illustrate the usage of these tokens: +// +// 1. A recursive sequence: +// +// &A [ *A ] +// +// Tokens: +// +// STREAM-START(utf-8) +// ANCHOR("A") +// FLOW-SEQUENCE-START +// ALIAS("A") +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A tagged scalar: +// +// !!float "3.14" # A good approximation. +// +// Tokens: +// +// STREAM-START(utf-8) +// TAG("!!","float") +// SCALAR("3.14",double-quoted) +// STREAM-END +// +// 3. Various scalar styles: +// +// --- # Implicit empty plain scalars do not produce tokens. +// --- a plain scalar +// --- 'a single-quoted scalar' +// --- "a double-quoted scalar" +// --- |- +// a literal scalar +// --- >- +// a folded +// scalar +// +// Tokens: +// +// STREAM-START(utf-8) +// DOCUMENT-START +// DOCUMENT-START +// SCALAR("a plain scalar",plain) +// DOCUMENT-START +// SCALAR("a single-quoted scalar",single-quoted) +// DOCUMENT-START +// SCALAR("a double-quoted scalar",double-quoted) +// DOCUMENT-START +// SCALAR("a literal scalar",literal) +// DOCUMENT-START +// SCALAR("a folded scalar",folded) +// STREAM-END +// +// Now it's time to review collection-related tokens. We will start with +// flow collections: +// +// FLOW-SEQUENCE-START +// FLOW-SEQUENCE-END +// FLOW-MAPPING-START +// FLOW-MAPPING-END +// FLOW-ENTRY +// KEY +// VALUE +// +// The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and +// FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' +// correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the +// indicators '?' and ':', which are used for denoting mapping keys and values, +// are represented by the KEY and VALUE tokens. +// +// The following examples show flow collections: +// +// 1. A flow sequence: +// +// [item 1, item 2, item 3] +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-SEQUENCE-START +// SCALAR("item 1",plain) +// FLOW-ENTRY +// SCALAR("item 2",plain) +// FLOW-ENTRY +// SCALAR("item 3",plain) +// FLOW-SEQUENCE-END +// STREAM-END +// +// 2. A flow mapping: +// +// { +// a simple key: a value, # Note that the KEY token is produced. +// ? a complex key: another value, +// } +// +// Tokens: +// +// STREAM-START(utf-8) +// FLOW-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// FLOW-ENTRY +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// FLOW-ENTRY +// FLOW-MAPPING-END +// STREAM-END +// +// A simple key is a key which is not denoted by the '?' indicator. Note that +// the Scanner still produce the KEY token whenever it encounters a simple key. +// +// For scanning block collections, the following tokens are used (note that we +// repeat KEY and VALUE here): +// +// BLOCK-SEQUENCE-START +// BLOCK-MAPPING-START +// BLOCK-END +// BLOCK-ENTRY +// KEY +// VALUE +// +// The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation +// increase that precedes a block collection (cf. the INDENT token in Python). +// The token BLOCK-END denote indentation decrease that ends a block collection +// (cf. the DEDENT token in Python). However YAML has some syntax pecularities +// that makes detections of these tokens more complex. +// +// The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators +// '-', '?', and ':' correspondingly. +// +// The following examples show how the tokens BLOCK-SEQUENCE-START, +// BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: +// +// 1. Block sequences: +// +// - item 1 +// - item 2 +// - +// - item 3.1 +// - item 3.2 +// - +// key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 3.1",plain) +// BLOCK-ENTRY +// SCALAR("item 3.2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Block mappings: +// +// a simple key: a value # The KEY token is produced here. +// ? a complex key +// : another value +// a mapping: +// key 1: value 1 +// key 2: value 2 +// a sequence: +// - item 1 +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a simple key",plain) +// VALUE +// SCALAR("a value",plain) +// KEY +// SCALAR("a complex key",plain) +// VALUE +// SCALAR("another value",plain) +// KEY +// SCALAR("a mapping",plain) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML does not always require to start a new block collection from a new +// line. If the current line contains only '-', '?', and ':' indicators, a new +// block collection may start at the current line. The following examples +// illustrate this case: +// +// 1. Collections in a sequence: +// +// - - item 1 +// - item 2 +// - key 1: value 1 +// key 2: value 2 +// - ? complex key +// : complex value +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-ENTRY +// BLOCK-MAPPING-START +// KEY +// SCALAR("complex key") +// VALUE +// SCALAR("complex value") +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// 2. Collections in a mapping: +// +// ? a sequence +// : - item 1 +// - item 2 +// ? a mapping +// : key 1: value 1 +// key 2: value 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("a sequence",plain) +// VALUE +// BLOCK-SEQUENCE-START +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// KEY +// SCALAR("a mapping",plain) +// VALUE +// BLOCK-MAPPING-START +// KEY +// SCALAR("key 1",plain) +// VALUE +// SCALAR("value 1",plain) +// KEY +// SCALAR("key 2",plain) +// VALUE +// SCALAR("value 2",plain) +// BLOCK-END +// BLOCK-END +// STREAM-END +// +// YAML also permits non-indented sequences if they are included into a block +// mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: +// +// key: +// - item 1 # BLOCK-SEQUENCE-START is NOT produced here. +// - item 2 +// +// Tokens: +// +// STREAM-START(utf-8) +// BLOCK-MAPPING-START +// KEY +// SCALAR("key",plain) +// VALUE +// BLOCK-ENTRY +// SCALAR("item 1",plain) +// BLOCK-ENTRY +// SCALAR("item 2",plain) +// BLOCK-END +// + +// Ensure that the buffer contains the required number of characters. +// Return true on success, false on failure (reader error or memory error). +func cache(parser *yaml_parser_t, length int) bool { + // [Go] This was inlined: !cache(A, B) -> unread < B && !update(A, B) + return parser.unread >= length || yaml_parser_update_buffer(parser, length) +} + +// Advance the buffer pointer. +func skip(parser *yaml_parser_t) { + parser.mark.index++ + parser.mark.column++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) +} + +func skip_line(parser *yaml_parser_t) { + if is_crlf(parser.buffer, parser.buffer_pos) { + parser.mark.index += 2 + parser.mark.column = 0 + parser.mark.line++ + parser.unread -= 2 + parser.buffer_pos += 2 + } else if is_break(parser.buffer, parser.buffer_pos) { + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + parser.buffer_pos += width(parser.buffer[parser.buffer_pos]) + } +} + +// Copy a character to a string buffer and advance pointers. +func read(parser *yaml_parser_t, s []byte) []byte { + w := width(parser.buffer[parser.buffer_pos]) + if w == 0 { + panic("invalid character sequence") + } + if len(s) == 0 { + s = make([]byte, 0, 32) + } + if w == 1 && len(s)+w <= cap(s) { + s = s[:len(s)+1] + s[len(s)-1] = parser.buffer[parser.buffer_pos] + parser.buffer_pos++ + } else { + s = append(s, parser.buffer[parser.buffer_pos:parser.buffer_pos+w]...) + parser.buffer_pos += w + } + parser.mark.index++ + parser.mark.column++ + parser.unread-- + return s +} + +// Copy a line break character to a string buffer and advance pointers. +func read_line(parser *yaml_parser_t, s []byte) []byte { + buf := parser.buffer + pos := parser.buffer_pos + switch { + case buf[pos] == '\r' && buf[pos+1] == '\n': + // CR LF . LF + s = append(s, '\n') + parser.buffer_pos += 2 + parser.mark.index++ + parser.unread-- + case buf[pos] == '\r' || buf[pos] == '\n': + // CR|LF . LF + s = append(s, '\n') + parser.buffer_pos += 1 + case buf[pos] == '\xC2' && buf[pos+1] == '\x85': + // NEL . LF + s = append(s, '\n') + parser.buffer_pos += 2 + case buf[pos] == '\xE2' && buf[pos+1] == '\x80' && (buf[pos+2] == '\xA8' || buf[pos+2] == '\xA9'): + // LS|PS . LS|PS + s = append(s, buf[parser.buffer_pos:pos+3]...) + parser.buffer_pos += 3 + default: + return s + } + parser.mark.index++ + parser.mark.column = 0 + parser.mark.line++ + parser.unread-- + return s +} + +// Get the next token. +func yaml_parser_scan(parser *yaml_parser_t, token *yaml_token_t) bool { + // Erase the token object. + *token = yaml_token_t{} // [Go] Is this necessary? + + // No tokens after STREAM-END or error. + if parser.stream_end_produced || parser.error != yaml_NO_ERROR { + return true + } + + // Ensure that the tokens queue contains enough tokens. + if !parser.token_available { + if !yaml_parser_fetch_more_tokens(parser) { + return false + } + } + + // Fetch the next token from the queue. + *token = parser.tokens[parser.tokens_head] + parser.tokens_head++ + parser.tokens_parsed++ + parser.token_available = false + + if token.typ == yaml_STREAM_END_TOKEN { + parser.stream_end_produced = true + } + return true +} + +// Set the scanner error and return false. +func yaml_parser_set_scanner_error(parser *yaml_parser_t, context string, context_mark yaml_mark_t, problem string) bool { + parser.error = yaml_SCANNER_ERROR + parser.context = context + parser.context_mark = context_mark + parser.problem = problem + parser.problem_mark = parser.mark + return false +} + +func yaml_parser_set_scanner_tag_error(parser *yaml_parser_t, directive bool, context_mark yaml_mark_t, problem string) bool { + context := "while parsing a tag" + if directive { + context = "while parsing a %TAG directive" + } + return yaml_parser_set_scanner_error(parser, context, context_mark, problem) +} + +func trace(args ...interface{}) func() { + pargs := append([]interface{}{"+++"}, args...) + fmt.Println(pargs...) + pargs = append([]interface{}{"---"}, args...) + return func() { fmt.Println(pargs...) } +} + +// Ensure that the tokens queue contains at least one token which can be +// returned to the Parser. +func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { + // While we need more tokens to fetch, do it. + for { + // Check if we really need to fetch more tokens. + need_more_tokens := false + + if parser.tokens_head == len(parser.tokens) { + // Queue is empty. + need_more_tokens = true + } else { + // Check if any potential simple key may occupy the head position. + if !yaml_parser_stale_simple_keys(parser) { + return false + } + + for i := range parser.simple_keys { + simple_key := &parser.simple_keys[i] + if simple_key.possible && simple_key.token_number == parser.tokens_parsed { + need_more_tokens = true + break + } + } + } + + // We are finished. + if !need_more_tokens { + break + } + // Fetch the next token. + if !yaml_parser_fetch_next_token(parser) { + return false + } + } + + parser.token_available = true + return true +} + +// The dispatcher for token fetchers. +func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool { + // Ensure that the buffer is initialized. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we just started scanning. Fetch STREAM-START then. + if !parser.stream_start_produced { + return yaml_parser_fetch_stream_start(parser) + } + + // Eat whitespaces and comments until we reach the next token. + if !yaml_parser_scan_to_next_token(parser) { + return false + } + + // Remove obsolete potential simple keys. + if !yaml_parser_stale_simple_keys(parser) { + return false + } + + // Check the indentation level against the current column. + if !yaml_parser_unroll_indent(parser, parser.mark.column) { + return false + } + + // Ensure that the buffer contains at least 4 characters. 4 is the length + // of the longest indicators ('--- ' and '... '). + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + // Is it the end of the stream? + if is_z(parser.buffer, parser.buffer_pos) { + return yaml_parser_fetch_stream_end(parser) + } + + // Is it a directive? + if parser.mark.column == 0 && parser.buffer[parser.buffer_pos] == '%' { + return yaml_parser_fetch_directive(parser) + } + + buf := parser.buffer + pos := parser.buffer_pos + + // Is it the document start indicator? + if parser.mark.column == 0 && buf[pos] == '-' && buf[pos+1] == '-' && buf[pos+2] == '-' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_START_TOKEN) + } + + // Is it the document end indicator? + if parser.mark.column == 0 && buf[pos] == '.' && buf[pos+1] == '.' && buf[pos+2] == '.' && is_blankz(buf, pos+3) { + return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_END_TOKEN) + } + + // Is it the flow sequence start indicator? + if buf[pos] == '[' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_SEQUENCE_START_TOKEN) + } + + // Is it the flow mapping start indicator? + if parser.buffer[parser.buffer_pos] == '{' { + return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_MAPPING_START_TOKEN) + } + + // Is it the flow sequence end indicator? + if parser.buffer[parser.buffer_pos] == ']' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_SEQUENCE_END_TOKEN) + } + + // Is it the flow mapping end indicator? + if parser.buffer[parser.buffer_pos] == '}' { + return yaml_parser_fetch_flow_collection_end(parser, + yaml_FLOW_MAPPING_END_TOKEN) + } + + // Is it the flow entry indicator? + if parser.buffer[parser.buffer_pos] == ',' { + return yaml_parser_fetch_flow_entry(parser) + } + + // Is it the block entry indicator? + if parser.buffer[parser.buffer_pos] == '-' && is_blankz(parser.buffer, parser.buffer_pos+1) { + return yaml_parser_fetch_block_entry(parser) + } + + // Is it the key indicator? + if parser.buffer[parser.buffer_pos] == '?' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_key(parser) + } + + // Is it the value indicator? + if parser.buffer[parser.buffer_pos] == ':' && (parser.flow_level > 0 || is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_value(parser) + } + + // Is it an alias? + if parser.buffer[parser.buffer_pos] == '*' { + return yaml_parser_fetch_anchor(parser, yaml_ALIAS_TOKEN) + } + + // Is it an anchor? + if parser.buffer[parser.buffer_pos] == '&' { + return yaml_parser_fetch_anchor(parser, yaml_ANCHOR_TOKEN) + } + + // Is it a tag? + if parser.buffer[parser.buffer_pos] == '!' { + return yaml_parser_fetch_tag(parser) + } + + // Is it a literal scalar? + if parser.buffer[parser.buffer_pos] == '|' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, true) + } + + // Is it a folded scalar? + if parser.buffer[parser.buffer_pos] == '>' && parser.flow_level == 0 { + return yaml_parser_fetch_block_scalar(parser, false) + } + + // Is it a single-quoted scalar? + if parser.buffer[parser.buffer_pos] == '\'' { + return yaml_parser_fetch_flow_scalar(parser, true) + } + + // Is it a double-quoted scalar? + if parser.buffer[parser.buffer_pos] == '"' { + return yaml_parser_fetch_flow_scalar(parser, false) + } + + // Is it a plain scalar? + // + // A plain scalar may start with any non-blank characters except + // + // '-', '?', ':', ',', '[', ']', '{', '}', + // '#', '&', '*', '!', '|', '>', '\'', '\"', + // '%', '@', '`'. + // + // In the block context (and, for the '-' indicator, in the flow context + // too), it may also start with the characters + // + // '-', '?', ':' + // + // if it is followed by a non-space character. + // + // The last rule is more restrictive than the specification requires. + // [Go] Make this logic more reasonable. + //switch parser.buffer[parser.buffer_pos] { + //case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`': + //} + if !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '-' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}' || parser.buffer[parser.buffer_pos] == '#' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '*' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '|' || + parser.buffer[parser.buffer_pos] == '>' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '"' || parser.buffer[parser.buffer_pos] == '%' || + parser.buffer[parser.buffer_pos] == '@' || parser.buffer[parser.buffer_pos] == '`') || + (parser.buffer[parser.buffer_pos] == '-' && !is_blank(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level == 0 && + (parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == ':') && + !is_blankz(parser.buffer, parser.buffer_pos+1)) { + return yaml_parser_fetch_plain_scalar(parser) + } + + // If we don't determine the token type so far, it is an error. + return yaml_parser_set_scanner_error(parser, + "while scanning for the next token", parser.mark, + "found character that cannot start any token") +} + +// Check the list of potential simple keys and remove the positions that +// cannot contain simple keys anymore. +func yaml_parser_stale_simple_keys(parser *yaml_parser_t) bool { + // Check for a potential simple key for each flow level. + for i := range parser.simple_keys { + simple_key := &parser.simple_keys[i] + + // The specification requires that a simple key + // + // - is limited to a single line, + // - is shorter than 1024 characters. + if simple_key.possible && (simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index) { + + // Check if the potential simple key to be removed is required. + if simple_key.required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", simple_key.mark, + "could not find expected ':'") + } + simple_key.possible = false + } + } + return true +} + +// Check if a simple key may start at the current position and add it if +// needed. +func yaml_parser_save_simple_key(parser *yaml_parser_t) bool { + // A simple key is required at the current position if the scanner is in + // the block context and the current column coincides with the indentation + // level. + + required := parser.flow_level == 0 && parser.indent == parser.mark.column + + // + // If the current position may start a simple key, save it. + // + if parser.simple_key_allowed { + simple_key := yaml_simple_key_t{ + possible: true, + required: required, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + } + simple_key.mark = parser.mark + + if !yaml_parser_remove_simple_key(parser) { + return false + } + parser.simple_keys[len(parser.simple_keys)-1] = simple_key + } + return true +} + +// Remove a potential simple key at the current flow level. +func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { + i := len(parser.simple_keys) - 1 + if parser.simple_keys[i].possible { + // If the key is required, it is an error. + if parser.simple_keys[i].required { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", parser.simple_keys[i].mark, + "could not find expected ':'") + } + } + // Remove the key from the stack. + parser.simple_keys[i].possible = false + return true +} + +// Increase the flow level and resize the simple key list if needed. +func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { + // Reset the simple key on the next level. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + // Increase the flow level. + parser.flow_level++ + return true +} + +// Decrease the flow level. +func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { + if parser.flow_level > 0 { + parser.flow_level-- + parser.simple_keys = parser.simple_keys[:len(parser.simple_keys)-1] + } + return true +} + +// Push the current indentation level to the stack and set the new level +// the current column is greater than the indentation level. In this case, +// append or insert the specified token into the token queue. +func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml_token_type_t, mark yaml_mark_t) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + if parser.indent < column { + // Push the current indentation level to the stack and set the new + // indentation level. + parser.indents = append(parser.indents, parser.indent) + parser.indent = column + + // Create a token and insert it into the queue. + token := yaml_token_t{ + typ: typ, + start_mark: mark, + end_mark: mark, + } + if number > -1 { + number -= parser.tokens_parsed + } + yaml_insert_token(parser, number, &token) + } + return true +} + +// Pop indentation levels from the indents stack until the current level +// becomes less or equal to the column. For each indentation level, append +// the BLOCK-END token. +func yaml_parser_unroll_indent(parser *yaml_parser_t, column int) bool { + // In the flow context, do nothing. + if parser.flow_level > 0 { + return true + } + + // Loop through the indentation levels in the stack. + for parser.indent > column { + // Create a token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + + // Pop the indentation level. + parser.indent = parser.indents[len(parser.indents)-1] + parser.indents = parser.indents[:len(parser.indents)-1] + } + return true +} + +// Initialize the scanner and produce the STREAM-START token. +func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool { + + // Set the initial indentation. + parser.indent = -1 + + // Initialize the simple key stack. + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + + // A simple key is allowed at the beginning of the stream. + parser.simple_key_allowed = true + + // We have started. + parser.stream_start_produced = true + + // Create the STREAM-START token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_START_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + encoding: parser.encoding, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the STREAM-END token and shut down the scanner. +func yaml_parser_fetch_stream_end(parser *yaml_parser_t) bool { + + // Force new line. + if parser.mark.column != 0 { + parser.mark.column = 0 + parser.mark.line++ + } + + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the STREAM-END token and append it to the queue. + token := yaml_token_t{ + typ: yaml_STREAM_END_TOKEN, + start_mark: parser.mark, + end_mark: parser.mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. +func yaml_parser_fetch_directive(parser *yaml_parser_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. + token := yaml_token_t{} + if !yaml_parser_scan_directive(parser, &token) { + return false + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the DOCUMENT-START or DOCUMENT-END token. +func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset the indentation level. + if !yaml_parser_unroll_indent(parser, -1) { + return false + } + + // Reset simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + parser.simple_key_allowed = false + + // Consume the token. + start_mark := parser.mark + + skip(parser) + skip(parser) + skip(parser) + + end_mark := parser.mark + + // Create the DOCUMENT-START or DOCUMENT-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. +func yaml_parser_fetch_flow_collection_start(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // The indicators '[' and '{' may start a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // Increase the flow level. + if !yaml_parser_increase_flow_level(parser) { + return false + } + + // A simple key may follow the indicators '[' and '{'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. +func yaml_parser_fetch_flow_collection_end(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // Reset any potential simple key on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Decrease the flow level. + if !yaml_parser_decrease_flow_level(parser) { + return false + } + + // No simple keys after the indicators ']' and '}'. + parser.simple_key_allowed = false + + // Consume the token. + + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. + token := yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + } + // Append the token to the queue. + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the FLOW-ENTRY token. +func yaml_parser_fetch_flow_entry(parser *yaml_parser_t) bool { + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after ','. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the FLOW-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_FLOW_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the BLOCK-ENTRY token. +func yaml_parser_fetch_block_entry(parser *yaml_parser_t) bool { + // Check if the scanner is in the block context. + if parser.flow_level == 0 { + // Check if we are allowed to start a new entry. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "block sequence entries are not allowed in this context") + } + // Add the BLOCK-SEQUENCE-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_SEQUENCE_START_TOKEN, parser.mark) { + return false + } + } else { + // It is an error for the '-' indicator to occur in the flow context, + // but we let the Parser detect and report about it because the Parser + // is able to point to the context. + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '-'. + parser.simple_key_allowed = true + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the BLOCK-ENTRY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_BLOCK_ENTRY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the KEY token. +func yaml_parser_fetch_key(parser *yaml_parser_t) bool { + + // In the block context, additional checks are required. + if parser.flow_level == 0 { + // Check if we are allowed to start a new key (not nessesary simple). + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping keys are not allowed in this context") + } + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Reset any potential simple keys on the current flow level. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // Simple keys are allowed after '?' in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the KEY token and append it to the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the VALUE token. +func yaml_parser_fetch_value(parser *yaml_parser_t) bool { + + simple_key := &parser.simple_keys[len(parser.simple_keys)-1] + + // Have we found a simple key? + if simple_key.possible { + // Create the KEY token and insert it into the queue. + token := yaml_token_t{ + typ: yaml_KEY_TOKEN, + start_mark: simple_key.mark, + end_mark: simple_key.mark, + } + yaml_insert_token(parser, simple_key.token_number-parser.tokens_parsed, &token) + + // In the block context, we may need to add the BLOCK-MAPPING-START token. + if !yaml_parser_roll_indent(parser, simple_key.mark.column, + simple_key.token_number, + yaml_BLOCK_MAPPING_START_TOKEN, simple_key.mark) { + return false + } + + // Remove the simple key. + simple_key.possible = false + + // A simple key cannot follow another simple key. + parser.simple_key_allowed = false + + } else { + // The ':' indicator follows a complex key. + + // In the block context, extra checks are required. + if parser.flow_level == 0 { + + // Check if we are allowed to start a complex value. + if !parser.simple_key_allowed { + return yaml_parser_set_scanner_error(parser, "", parser.mark, + "mapping values are not allowed in this context") + } + + // Add the BLOCK-MAPPING-START token if needed. + if !yaml_parser_roll_indent(parser, parser.mark.column, -1, yaml_BLOCK_MAPPING_START_TOKEN, parser.mark) { + return false + } + } + + // Simple keys after ':' are allowed in the block context. + parser.simple_key_allowed = parser.flow_level == 0 + } + + // Consume the token. + start_mark := parser.mark + skip(parser) + end_mark := parser.mark + + // Create the VALUE token and append it to the queue. + token := yaml_token_t{ + typ: yaml_VALUE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the ALIAS or ANCHOR token. +func yaml_parser_fetch_anchor(parser *yaml_parser_t, typ yaml_token_type_t) bool { + // An anchor or an alias could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow an anchor or an alias. + parser.simple_key_allowed = false + + // Create the ALIAS or ANCHOR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_anchor(parser, &token, typ) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the TAG token. +func yaml_parser_fetch_tag(parser *yaml_parser_t) bool { + // A tag could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a tag. + parser.simple_key_allowed = false + + // Create the TAG token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_tag(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. +func yaml_parser_fetch_block_scalar(parser *yaml_parser_t, literal bool) bool { + // Remove any potential simple keys. + if !yaml_parser_remove_simple_key(parser) { + return false + } + + // A simple key may follow a block scalar. + parser.simple_key_allowed = true + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_block_scalar(parser, &token, literal) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. +func yaml_parser_fetch_flow_scalar(parser *yaml_parser_t, single bool) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_flow_scalar(parser, &token, single) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Produce the SCALAR(...,plain) token. +func yaml_parser_fetch_plain_scalar(parser *yaml_parser_t) bool { + // A plain scalar could be a simple key. + if !yaml_parser_save_simple_key(parser) { + return false + } + + // A simple key cannot follow a flow scalar. + parser.simple_key_allowed = false + + // Create the SCALAR token and append it to the queue. + var token yaml_token_t + if !yaml_parser_scan_plain_scalar(parser, &token) { + return false + } + yaml_insert_token(parser, -1, &token) + return true +} + +// Eat whitespaces and comments until the next token is found. +func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool { + + // Until the next token is not found. + for { + // Allow the BOM mark to start a line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.mark.column == 0 && is_bom(parser.buffer, parser.buffer_pos) { + skip(parser) + } + + // Eat whitespaces. + // Tabs are allowed: + // - in the flow context + // - in the block context, but not at the beginning of the line or + // after '-', '?', or ':' (complex value). + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for parser.buffer[parser.buffer_pos] == ' ' || ((parser.flow_level > 0 || !parser.simple_key_allowed) && parser.buffer[parser.buffer_pos] == '\t') { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Eat a comment until a line break. + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // If it is a line break, eat it. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + + // In the block context, a new line may start a simple key. + if parser.flow_level == 0 { + parser.simple_key_allowed = true + } + } else { + break // We have found a token. + } + } + + return true +} + +// Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool { + // Eat '%'. + start_mark := parser.mark + skip(parser) + + // Scan the directive name. + var name []byte + if !yaml_parser_scan_directive_name(parser, start_mark, &name) { + return false + } + + // Is it a YAML directive? + if bytes.Equal(name, []byte("YAML")) { + // Scan the VERSION directive value. + var major, minor int8 + if !yaml_parser_scan_version_directive_value(parser, start_mark, &major, &minor) { + return false + } + end_mark := parser.mark + + // Create a VERSION-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_VERSION_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + major: major, + minor: minor, + } + + // Is it a TAG directive? + } else if bytes.Equal(name, []byte("TAG")) { + // Scan the TAG directive value. + var handle, prefix []byte + if !yaml_parser_scan_tag_directive_value(parser, start_mark, &handle, &prefix) { + return false + } + end_mark := parser.mark + + // Create a TAG-DIRECTIVE token. + *token = yaml_token_t{ + typ: yaml_TAG_DIRECTIVE_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + prefix: prefix, + } + + // Unknown directive. + } else { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unknown directive name") + return false + } + + // Eat the rest of the line including any comments. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + return true +} + +// Scan the directive name. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^ +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^ +// +func yaml_parser_scan_directive_name(parser *yaml_parser_t, start_mark yaml_mark_t, name *[]byte) bool { + // Consume the directive name. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + var s []byte + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the name is empty. + if len(s) == 0 { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "could not find expected directive name") + return false + } + + // Check for an blank character after the name. + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unexpected non-alphabetical character") + return false + } + *name = s + return true +} + +// Scan the value of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^^^^^^ +func yaml_parser_scan_version_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, major, minor *int8) bool { + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the major version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, major) { + return false + } + + // Eat '.'. + if parser.buffer[parser.buffer_pos] != '.' { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected digit or '.' character") + } + + skip(parser) + + // Consume the minor version number. + if !yaml_parser_scan_version_directive_number(parser, start_mark, minor) { + return false + } + return true +} + +const max_number_length = 2 + +// Scan the version number of VERSION-DIRECTIVE. +// +// Scope: +// %YAML 1.1 # a comment \n +// ^ +// %YAML 1.1 # a comment \n +// ^ +func yaml_parser_scan_version_directive_number(parser *yaml_parser_t, start_mark yaml_mark_t, number *int8) bool { + + // Repeat while the next character is digit. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var value, length int8 + for is_digit(parser.buffer, parser.buffer_pos) { + // Check if the number is too long. + length++ + if length > max_number_length { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "found extremely long version number") + } + value = value*10 + int8(as_digit(parser.buffer, parser.buffer_pos)) + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the number was present. + if length == 0 { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected version number") + } + *number = value + return true +} + +// Scan the value of a TAG-DIRECTIVE token. +// +// Scope: +// %TAG !yaml! tag:yaml.org,2002: \n +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// +func yaml_parser_scan_tag_directive_value(parser *yaml_parser_t, start_mark yaml_mark_t, handle, prefix *[]byte) bool { + var handle_value, prefix_value []byte + + // Eat whitespaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a handle. + if !yaml_parser_scan_tag_handle(parser, true, start_mark, &handle_value) { + return false + } + + // Expect a whitespace. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blank(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace") + return false + } + + // Eat whitespaces. + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Scan a prefix. + if !yaml_parser_scan_tag_uri(parser, true, nil, start_mark, &prefix_value) { + return false + } + + // Expect a whitespace or line break. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace or line break") + return false + } + + *handle = handle_value + *prefix = prefix_value + return true +} + +func yaml_parser_scan_anchor(parser *yaml_parser_t, token *yaml_token_t, typ yaml_token_type_t) bool { + var s []byte + + // Eat the indicator character. + start_mark := parser.mark + skip(parser) + + // Consume the value. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + end_mark := parser.mark + + /* + * Check if length of the anchor is greater than 0 and it is followed by + * a whitespace character or one of the indicators: + * + * '?', ':', ',', ']', '}', '%', '@', '`'. + */ + + if len(s) == 0 || + !(is_blankz(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '}' || + parser.buffer[parser.buffer_pos] == '%' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '`') { + context := "while scanning an alias" + if typ == yaml_ANCHOR_TOKEN { + context = "while scanning an anchor" + } + yaml_parser_set_scanner_error(parser, context, start_mark, + "did not find expected alphabetic or numeric character") + return false + } + + // Create a token. + *token = yaml_token_t{ + typ: typ, + start_mark: start_mark, + end_mark: end_mark, + value: s, + } + + return true +} + +/* + * Scan a TAG token. + */ + +func yaml_parser_scan_tag(parser *yaml_parser_t, token *yaml_token_t) bool { + var handle, suffix []byte + + start_mark := parser.mark + + // Check if the tag is in the canonical form. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + if parser.buffer[parser.buffer_pos+1] == '<' { + // Keep the handle as '' + + // Eat '!<' + skip(parser) + skip(parser) + + // Consume the tag value. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + + // Check for '>' and eat it. + if parser.buffer[parser.buffer_pos] != '>' { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find the expected '>'") + return false + } + + skip(parser) + } else { + // The tag has either the '!suffix' or the '!handle!suffix' form. + + // First, try to scan a handle. + if !yaml_parser_scan_tag_handle(parser, false, start_mark, &handle) { + return false + } + + // Check if it is, indeed, handle. + if handle[0] == '!' && len(handle) > 1 && handle[len(handle)-1] == '!' { + // Scan the suffix now. + if !yaml_parser_scan_tag_uri(parser, false, nil, start_mark, &suffix) { + return false + } + } else { + // It wasn't a handle after all. Scan the rest of the tag. + if !yaml_parser_scan_tag_uri(parser, false, handle, start_mark, &suffix) { + return false + } + + // Set the handle to '!'. + handle = []byte{'!'} + + // A special case: the '!' tag. Set the handle to '' and the + // suffix to '!'. + if len(suffix) == 0 { + handle, suffix = suffix, handle + } + } + } + + // Check the character which ends the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if !is_blankz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find expected whitespace or line break") + return false + } + + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_TAG_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: handle, + suffix: suffix, + } + return true +} + +// Scan a tag handle. +func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, handle *[]byte) bool { + // Check the initial '!' character. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] != '!' { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + + var s []byte + + // Copy the '!' character. + s = read(parser, s) + + // Copy all subsequent alphabetical and numerical characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_alpha(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check if the trailing character is '!' and copy it. + if parser.buffer[parser.buffer_pos] == '!' { + s = read(parser, s) + } else { + // It's either the '!' tag or not really a tag handle. If it's a %TAG + // directive, it's an error. If it's a tag token, it must be a part of URI. + if directive && string(s) != "!" { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected '!'") + return false + } + } + + *handle = s + return true +} + +// Scan a tag. +func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte, start_mark yaml_mark_t, uri *[]byte) bool { + //size_t length = head ? strlen((char *)head) : 0 + var s []byte + hasTag := len(head) > 0 + + // Copy the head if needed. + // + // Note that we don't copy the leading '!' character. + if len(head) > 1 { + s = append(s, head[1:]...) + } + + // Scan the tag. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // The set of characters that may appear in URI is as follows: + // + // '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', + // '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']', + // '%'. + // [Go] Convert this into more reasonable logic. + for is_alpha(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == ';' || + parser.buffer[parser.buffer_pos] == '/' || parser.buffer[parser.buffer_pos] == '?' || + parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == '@' || + parser.buffer[parser.buffer_pos] == '&' || parser.buffer[parser.buffer_pos] == '=' || + parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '$' || + parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '.' || + parser.buffer[parser.buffer_pos] == '!' || parser.buffer[parser.buffer_pos] == '~' || + parser.buffer[parser.buffer_pos] == '*' || parser.buffer[parser.buffer_pos] == '\'' || + parser.buffer[parser.buffer_pos] == '(' || parser.buffer[parser.buffer_pos] == ')' || + parser.buffer[parser.buffer_pos] == '[' || parser.buffer[parser.buffer_pos] == ']' || + parser.buffer[parser.buffer_pos] == '%' { + // Check if it is a URI-escape sequence. + if parser.buffer[parser.buffer_pos] == '%' { + if !yaml_parser_scan_uri_escapes(parser, directive, start_mark, &s) { + return false + } + } else { + s = read(parser, s) + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + hasTag = true + } + + if !hasTag { + yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find expected tag URI") + return false + } + *uri = s + return true +} + +// Decode an URI-escape sequence corresponding to a single UTF-8 character. +func yaml_parser_scan_uri_escapes(parser *yaml_parser_t, directive bool, start_mark yaml_mark_t, s *[]byte) bool { + + // Decode the required number of characters. + w := 1024 + for w > 0 { + // Check for a URI-escaped octet. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + + if !(parser.buffer[parser.buffer_pos] == '%' && + is_hex(parser.buffer, parser.buffer_pos+1) && + is_hex(parser.buffer, parser.buffer_pos+2)) { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "did not find URI escaped octet") + } + + // Get the octet. + octet := byte((as_hex(parser.buffer, parser.buffer_pos+1) << 4) + as_hex(parser.buffer, parser.buffer_pos+2)) + + // If it is the leading octet, determine the length of the UTF-8 sequence. + if w == 1024 { + w = width(octet) + if w == 0 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect leading UTF-8 octet") + } + } else { + // Check if the trailing octet is correct. + if octet&0xC0 != 0x80 { + return yaml_parser_set_scanner_tag_error(parser, directive, + start_mark, "found an incorrect trailing UTF-8 octet") + } + } + + // Copy the octet and move the pointers. + *s = append(*s, octet) + skip(parser) + skip(parser) + skip(parser) + w-- + } + return true +} + +// Scan a block scalar. +func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, literal bool) bool { + // Eat the indicator '|' or '>'. + start_mark := parser.mark + skip(parser) + + // Scan the additional block scalar indicators. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check for a chomping indicator. + var chomping, increment int + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + // Set the chomping method and eat the indicator. + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + + // Check for an indentation indicator. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if is_digit(parser.buffer, parser.buffer_pos) { + // Check that the indentation is greater than 0. + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + + // Get the indentation level and eat the indicator. + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + } + + } else if is_digit(parser.buffer, parser.buffer_pos) { + // Do the same as above, but in the opposite order. + + if parser.buffer[parser.buffer_pos] == '0' { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0") + return false + } + increment = as_digit(parser.buffer, parser.buffer_pos) + skip(parser) + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + if parser.buffer[parser.buffer_pos] == '+' || parser.buffer[parser.buffer_pos] == '-' { + if parser.buffer[parser.buffer_pos] == '+' { + chomping = +1 + } else { + chomping = -1 + } + skip(parser) + } + } + + // Eat whitespaces and comments to the end of the line. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for is_blank(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.buffer[parser.buffer_pos] == '#' { + for !is_breakz(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + } + + // Check if we are at the end of the line. + if !is_breakz(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "did not find expected comment or line break") + return false + } + + // Eat a line break. + if is_break(parser.buffer, parser.buffer_pos) { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + skip_line(parser) + } + + end_mark := parser.mark + + // Set the indentation level if it was specified. + var indent int + if increment > 0 { + if parser.indent >= 0 { + indent = parser.indent + increment + } else { + indent = increment + } + } + + // Scan the leading line breaks and determine the indentation level if needed. + var s, leading_break, trailing_breaks []byte + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + + // Scan the block scalar content. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + var leading_blank, trailing_blank bool + for parser.mark.column == indent && !is_z(parser.buffer, parser.buffer_pos) { + // We are at the beginning of a non-empty line. + + // Is it a trailing whitespace? + trailing_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Check if we need to fold the leading line break. + if !literal && !leading_blank && !trailing_blank && len(leading_break) > 0 && leading_break[0] == '\n' { + // Do we need to join the lines by space? + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } + } else { + s = append(s, leading_break...) + } + leading_break = leading_break[:0] + + // Append the remaining line breaks. + s = append(s, trailing_breaks...) + trailing_breaks = trailing_breaks[:0] + + // Is it a leading whitespace? + leading_blank = is_blank(parser.buffer, parser.buffer_pos) + + // Consume the current line. + for !is_breakz(parser.buffer, parser.buffer_pos) { + s = read(parser, s) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + leading_break = read_line(parser, leading_break) + + // Eat the following indentation spaces and line breaks. + if !yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, start_mark, &end_mark) { + return false + } + } + + // Chomp the tail. + if chomping != -1 { + s = append(s, leading_break...) + } + if chomping == 1 { + s = append(s, trailing_breaks...) + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_LITERAL_SCALAR_STYLE, + } + if !literal { + token.style = yaml_FOLDED_SCALAR_STYLE + } + return true +} + +// Scan indentation spaces and line breaks for a block scalar. Determine the +// indentation level if needed. +func yaml_parser_scan_block_scalar_breaks(parser *yaml_parser_t, indent *int, breaks *[]byte, start_mark yaml_mark_t, end_mark *yaml_mark_t) bool { + *end_mark = parser.mark + + // Eat the indentation spaces and line breaks. + max_indent := 0 + for { + // Eat the indentation spaces. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + for (*indent == 0 || parser.mark.column < *indent) && is_space(parser.buffer, parser.buffer_pos) { + skip(parser) + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + if parser.mark.column > max_indent { + max_indent = parser.mark.column + } + + // Check for a tab character messing the indentation. + if (*indent == 0 || parser.mark.column < *indent) && is_tab(parser.buffer, parser.buffer_pos) { + return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found a tab character where an indentation space is expected") + } + + // Have we found a non-empty line? + if !is_break(parser.buffer, parser.buffer_pos) { + break + } + + // Consume the line break. + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + // [Go] Should really be returning breaks instead. + *breaks = read_line(parser, *breaks) + *end_mark = parser.mark + } + + // Determine the indentation level if needed. + if *indent == 0 { + *indent = max_indent + if *indent < parser.indent+1 { + *indent = parser.indent + 1 + } + if *indent < 1 { + *indent = 1 + } + } + return true +} + +// Scan a quoted scalar. +func yaml_parser_scan_flow_scalar(parser *yaml_parser_t, token *yaml_token_t, single bool) bool { + // Eat the left quote. + start_mark := parser.mark + skip(parser) + + // Consume the content of the quoted scalar. + var s, leading_break, trailing_breaks, whitespaces []byte + for { + // Check that there are no document indicators at the beginning of the line. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected document indicator") + return false + } + + // Check for EOF. + if is_z(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected end of stream") + return false + } + + // Consume non-blank characters. + leading_blanks := false + for !is_blankz(parser.buffer, parser.buffer_pos) { + if single && parser.buffer[parser.buffer_pos] == '\'' && parser.buffer[parser.buffer_pos+1] == '\'' { + // Is is an escaped single quote. + s = append(s, '\'') + skip(parser) + skip(parser) + + } else if single && parser.buffer[parser.buffer_pos] == '\'' { + // It is a right single quote. + break + } else if !single && parser.buffer[parser.buffer_pos] == '"' { + // It is a right double quote. + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' && is_break(parser.buffer, parser.buffer_pos+1) { + // It is an escaped line break. + if parser.unread < 3 && !yaml_parser_update_buffer(parser, 3) { + return false + } + skip(parser) + skip_line(parser) + leading_blanks = true + break + + } else if !single && parser.buffer[parser.buffer_pos] == '\\' { + // It is an escape sequence. + code_length := 0 + + // Check the escape character. + switch parser.buffer[parser.buffer_pos+1] { + case '0': + s = append(s, 0) + case 'a': + s = append(s, '\x07') + case 'b': + s = append(s, '\x08') + case 't', '\t': + s = append(s, '\x09') + case 'n': + s = append(s, '\x0A') + case 'v': + s = append(s, '\x0B') + case 'f': + s = append(s, '\x0C') + case 'r': + s = append(s, '\x0D') + case 'e': + s = append(s, '\x1B') + case ' ': + s = append(s, '\x20') + case '"': + s = append(s, '"') + case '\'': + s = append(s, '\'') + case '\\': + s = append(s, '\\') + case 'N': // NEL (#x85) + s = append(s, '\xC2') + s = append(s, '\x85') + case '_': // #xA0 + s = append(s, '\xC2') + s = append(s, '\xA0') + case 'L': // LS (#x2028) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA8') + case 'P': // PS (#x2029) + s = append(s, '\xE2') + s = append(s, '\x80') + s = append(s, '\xA9') + case 'x': + code_length = 2 + case 'u': + code_length = 4 + case 'U': + code_length = 8 + default: + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found unknown escape character") + return false + } + + skip(parser) + skip(parser) + + // Consume an arbitrary escape code. + if code_length > 0 { + var value int + + // Scan the character value. + if parser.unread < code_length && !yaml_parser_update_buffer(parser, code_length) { + return false + } + for k := 0; k < code_length; k++ { + if !is_hex(parser.buffer, parser.buffer_pos+k) { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "did not find expected hexdecimal number") + return false + } + value = (value << 4) + as_hex(parser.buffer, parser.buffer_pos+k) + } + + // Check the value and write the character. + if (value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found invalid Unicode character escape code") + return false + } + if value <= 0x7F { + s = append(s, byte(value)) + } else if value <= 0x7FF { + s = append(s, byte(0xC0+(value>>6))) + s = append(s, byte(0x80+(value&0x3F))) + } else if value <= 0xFFFF { + s = append(s, byte(0xE0+(value>>12))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } else { + s = append(s, byte(0xF0+(value>>18))) + s = append(s, byte(0x80+((value>>12)&0x3F))) + s = append(s, byte(0x80+((value>>6)&0x3F))) + s = append(s, byte(0x80+(value&0x3F))) + } + + // Advance the pointer. + for k := 0; k < code_length; k++ { + skip(parser) + } + } + } else { + // It is a non-escaped non-blank character. + s = read(parser, s) + } + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + // Check if we are at the end of the scalar. + if single { + if parser.buffer[parser.buffer_pos] == '\'' { + break + } + } else { + if parser.buffer[parser.buffer_pos] == '"' { + break + } + } + + // Consume blank characters. + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Join the whitespaces or fold line breaks. + if leading_blanks { + // Do we need to fold line breaks? + if len(leading_break) > 0 && leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Eat the right quote. + skip(parser) + end_mark := parser.mark + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_SINGLE_QUOTED_SCALAR_STYLE, + } + if !single { + token.style = yaml_DOUBLE_QUOTED_SCALAR_STYLE + } + return true +} + +// Scan a plain scalar. +func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) bool { + + var s, leading_break, trailing_breaks, whitespaces []byte + var leading_blanks bool + var indent = parser.indent + 1 + + start_mark := parser.mark + end_mark := parser.mark + + // Consume the content of the plain scalar. + for { + // Check for a document indicator. + if parser.unread < 4 && !yaml_parser_update_buffer(parser, 4) { + return false + } + if parser.mark.column == 0 && + ((parser.buffer[parser.buffer_pos+0] == '-' && + parser.buffer[parser.buffer_pos+1] == '-' && + parser.buffer[parser.buffer_pos+2] == '-') || + (parser.buffer[parser.buffer_pos+0] == '.' && + parser.buffer[parser.buffer_pos+1] == '.' && + parser.buffer[parser.buffer_pos+2] == '.')) && + is_blankz(parser.buffer, parser.buffer_pos+3) { + break + } + + // Check for a comment. + if parser.buffer[parser.buffer_pos] == '#' { + break + } + + // Consume non-blank characters. + for !is_blankz(parser.buffer, parser.buffer_pos) { + + // Check for indicators that may end a plain scalar. + if (parser.buffer[parser.buffer_pos] == ':' && is_blankz(parser.buffer, parser.buffer_pos+1)) || + (parser.flow_level > 0 && + (parser.buffer[parser.buffer_pos] == ',' || + parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == '[' || + parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || + parser.buffer[parser.buffer_pos] == '}')) { + break + } + + // Check if we need to join whitespaces and breaks. + if leading_blanks || len(whitespaces) > 0 { + if leading_blanks { + // Do we need to fold line breaks? + if leading_break[0] == '\n' { + if len(trailing_breaks) == 0 { + s = append(s, ' ') + } else { + s = append(s, trailing_breaks...) + } + } else { + s = append(s, leading_break...) + s = append(s, trailing_breaks...) + } + trailing_breaks = trailing_breaks[:0] + leading_break = leading_break[:0] + leading_blanks = false + } else { + s = append(s, whitespaces...) + whitespaces = whitespaces[:0] + } + } + + // Copy the character. + s = read(parser, s) + + end_mark = parser.mark + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + } + + // Is it the end? + if !(is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos)) { + break + } + + // Consume blank characters. + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + + for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { + if is_blank(parser.buffer, parser.buffer_pos) { + + // Check for tab characters that abuse indentation. + if leading_blanks && parser.mark.column < indent && is_tab(parser.buffer, parser.buffer_pos) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found a tab character that violates indentation") + return false + } + + // Consume a space or a tab character. + if !leading_blanks { + whitespaces = read(parser, whitespaces) + } else { + skip(parser) + } + } else { + if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) { + return false + } + + // Check if it is a first line break. + if !leading_blanks { + whitespaces = whitespaces[:0] + leading_break = read_line(parser, leading_break) + leading_blanks = true + } else { + trailing_breaks = read_line(parser, trailing_breaks) + } + } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + } + + // Check indentation level. + if parser.flow_level == 0 && parser.mark.column < indent { + break + } + } + + // Create a token. + *token = yaml_token_t{ + typ: yaml_SCALAR_TOKEN, + start_mark: start_mark, + end_mark: end_mark, + value: s, + style: yaml_PLAIN_SCALAR_STYLE, + } + + // Note that we change the 'simple_key_allowed' flag. + if leading_blanks { + parser.simple_key_allowed = true + } + return true +} diff --git a/vendor/gopkg.in/yaml.v2/sorter.go b/vendor/gopkg.in/yaml.v2/sorter.go new file mode 100644 index 00000000..4c45e660 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/sorter.go @@ -0,0 +1,113 @@ +package yaml + +import ( + "reflect" + "unicode" +) + +type keyList []reflect.Value + +func (l keyList) Len() int { return len(l) } +func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l keyList) Less(i, j int) bool { + a := l[i] + b := l[j] + ak := a.Kind() + bk := b.Kind() + for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() { + a = a.Elem() + ak = a.Kind() + } + for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() { + b = b.Elem() + bk = b.Kind() + } + af, aok := keyFloat(a) + bf, bok := keyFloat(b) + if aok && bok { + if af != bf { + return af < bf + } + if ak != bk { + return ak < bk + } + return numLess(a, b) + } + if ak != reflect.String || bk != reflect.String { + return ak < bk + } + ar, br := []rune(a.String()), []rune(b.String()) + for i := 0; i < len(ar) && i < len(br); i++ { + if ar[i] == br[i] { + continue + } + al := unicode.IsLetter(ar[i]) + bl := unicode.IsLetter(br[i]) + if al && bl { + return ar[i] < br[i] + } + if al || bl { + return bl + } + var ai, bi int + var an, bn int64 + if ar[i] == '0' || br[i] == '0' { + for j := i-1; j >= 0 && unicode.IsDigit(ar[j]); j-- { + if ar[j] != '0' { + an = 1 + bn = 1 + break + } + } + } + for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ { + an = an*10 + int64(ar[ai]-'0') + } + for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ { + bn = bn*10 + int64(br[bi]-'0') + } + if an != bn { + return an < bn + } + if ai != bi { + return ai < bi + } + return ar[i] < br[i] + } + return len(ar) < len(br) +} + +// keyFloat returns a float value for v if it is a number/bool +// and whether it is a number/bool or not. +func keyFloat(v reflect.Value) (f float64, ok bool) { + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return float64(v.Int()), true + case reflect.Float32, reflect.Float64: + return v.Float(), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return float64(v.Uint()), true + case reflect.Bool: + if v.Bool() { + return 1, true + } + return 0, true + } + return 0, false +} + +// numLess returns whether a < b. +// a and b must necessarily have the same kind. +func numLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return a.Int() < b.Int() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Bool: + return !a.Bool() && b.Bool() + } + panic("not a number") +} diff --git a/vendor/gopkg.in/yaml.v2/writerc.go b/vendor/gopkg.in/yaml.v2/writerc.go new file mode 100644 index 00000000..a2dde608 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/writerc.go @@ -0,0 +1,26 @@ +package yaml + +// Set the writer error and return false. +func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool { + emitter.error = yaml_WRITER_ERROR + emitter.problem = problem + return false +} + +// Flush the output buffer. +func yaml_emitter_flush(emitter *yaml_emitter_t) bool { + if emitter.write_handler == nil { + panic("write handler not set") + } + + // Check if the buffer is empty. + if emitter.buffer_pos == 0 { + return true + } + + if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { + return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) + } + emitter.buffer_pos = 0 + return true +} diff --git a/vendor/gopkg.in/yaml.v2/yaml.go b/vendor/gopkg.in/yaml.v2/yaml.go new file mode 100644 index 00000000..de85aa4c --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yaml.go @@ -0,0 +1,466 @@ +// Package yaml implements YAML support for the Go language. +// +// Source code and other details for the project are available at GitHub: +// +// https://github.com/go-yaml/yaml +// +package yaml + +import ( + "errors" + "fmt" + "io" + "reflect" + "strings" + "sync" +) + +// MapSlice encodes and decodes as a YAML map. +// The order of keys is preserved when encoding and decoding. +type MapSlice []MapItem + +// MapItem is an item in a MapSlice. +type MapItem struct { + Key, Value interface{} +} + +// The Unmarshaler interface may be implemented by types to customize their +// behavior when being unmarshaled from a YAML document. The UnmarshalYAML +// method receives a function that may be called to unmarshal the original +// YAML value into a field or variable. It is safe to call the unmarshal +// function parameter more than once if necessary. +type Unmarshaler interface { + UnmarshalYAML(unmarshal func(interface{}) error) error +} + +// The Marshaler interface may be implemented by types to customize their +// behavior when being marshaled into a YAML document. The returned value +// is marshaled in place of the original value implementing Marshaler. +// +// If an error is returned by MarshalYAML, the marshaling procedure stops +// and returns with the provided error. +type Marshaler interface { + MarshalYAML() (interface{}, error) +} + +// Unmarshal decodes the first document found within the in byte slice +// and assigns decoded values into the out value. +// +// Maps and pointers (to a struct, string, int, etc) are accepted as out +// values. If an internal pointer within a struct is not initialized, +// the yaml package will initialize it if necessary for unmarshalling +// the provided data. The out parameter must not be nil. +// +// The type of the decoded values should be compatible with the respective +// values in out. If one or more values cannot be decoded due to a type +// mismatches, decoding continues partially until the end of the YAML +// content, and a *yaml.TypeError is returned with details for all +// missed values. +// +// Struct fields are only unmarshalled if they are exported (have an +// upper case first letter), and are unmarshalled using the field name +// lowercased as the default key. Custom keys may be defined via the +// "yaml" name in the field tag: the content preceding the first comma +// is used as the key, and the following comma-separated options are +// used to tweak the marshalling process (see Marshal). +// Conflicting names result in a runtime error. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// var t T +// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t) +// +// See the documentation of Marshal for the format of tags and a list of +// supported tag options. +// +func Unmarshal(in []byte, out interface{}) (err error) { + return unmarshal(in, out, false) +} + +// UnmarshalStrict is like Unmarshal except that any fields that are found +// in the data that do not have corresponding struct members, or mapping +// keys that are duplicates, will result in +// an error. +func UnmarshalStrict(in []byte, out interface{}) (err error) { + return unmarshal(in, out, true) +} + +// A Decorder reads and decodes YAML values from an input stream. +type Decoder struct { + strict bool + parser *parser +} + +// NewDecoder returns a new decoder that reads from r. +// +// The decoder introduces its own buffering and may read +// data from r beyond the YAML values requested. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{ + parser: newParserFromReader(r), + } +} + +// SetStrict sets whether strict decoding behaviour is enabled when +// decoding items in the data (see UnmarshalStrict). By default, decoding is not strict. +func (dec *Decoder) SetStrict(strict bool) { + dec.strict = strict +} + +// Decode reads the next YAML-encoded value from its input +// and stores it in the value pointed to by v. +// +// See the documentation for Unmarshal for details about the +// conversion of YAML into a Go value. +func (dec *Decoder) Decode(v interface{}) (err error) { + d := newDecoder(dec.strict) + defer handleErr(&err) + node := dec.parser.parse() + if node == nil { + return io.EOF + } + out := reflect.ValueOf(v) + if out.Kind() == reflect.Ptr && !out.IsNil() { + out = out.Elem() + } + d.unmarshal(node, out) + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +func unmarshal(in []byte, out interface{}, strict bool) (err error) { + defer handleErr(&err) + d := newDecoder(strict) + p := newParser(in) + defer p.destroy() + node := p.parse() + if node != nil { + v := reflect.ValueOf(out) + if v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + d.unmarshal(node, v) + } + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +// Marshal serializes the value provided into a YAML document. The structure +// of the generated document will reflect the structure of the value itself. +// Maps and pointers (to struct, string, int, etc) are accepted as the in value. +// +// Struct fields are only marshalled if they are exported (have an upper case +// first letter), and are marshalled using the field name lowercased as the +// default key. Custom keys may be defined via the "yaml" name in the field +// tag: the content preceding the first comma is used as the key, and the +// following comma-separated options are used to tweak the marshalling process. +// Conflicting names result in a runtime error. +// +// The field tag format accepted is: +// +// `(...) yaml:"[][,[,]]" (...)` +// +// The following flags are currently supported: +// +// omitempty Only include the field if it's not set to the zero +// value for the type or to empty slices or maps. +// Zero valued structs will be omitted if all their public +// fields are zero, unless they implement an IsZero +// method (see the IsZeroer interface type), in which +// case the field will be included if that method returns true. +// +// flow Marshal using a flow style (useful for structs, +// sequences and maps). +// +// inline Inline the field, which must be a struct or a map, +// causing all of its fields or keys to be processed as if +// they were part of the outer struct. For maps, keys must +// not conflict with the yaml keys of other struct fields. +// +// In addition, if the key is "-", the field is ignored. +// +// For example: +// +// type T struct { +// F int `yaml:"a,omitempty"` +// B int +// } +// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" +// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n" +// +func Marshal(in interface{}) (out []byte, err error) { + defer handleErr(&err) + e := newEncoder() + defer e.destroy() + e.marshalDoc("", reflect.ValueOf(in)) + e.finish() + out = e.out + return +} + +// An Encoder writes YAML values to an output stream. +type Encoder struct { + encoder *encoder +} + +// NewEncoder returns a new encoder that writes to w. +// The Encoder should be closed after use to flush all data +// to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + encoder: newEncoderWithWriter(w), + } +} + +// Encode writes the YAML encoding of v to the stream. +// If multiple items are encoded to the stream, the +// second and subsequent document will be preceded +// with a "---" document separator, but the first will not. +// +// See the documentation for Marshal for details about the conversion of Go +// values to YAML. +func (e *Encoder) Encode(v interface{}) (err error) { + defer handleErr(&err) + e.encoder.marshalDoc("", reflect.ValueOf(v)) + return nil +} + +// Close closes the encoder by writing any remaining data. +// It does not write a stream terminating string "...". +func (e *Encoder) Close() (err error) { + defer handleErr(&err) + e.encoder.finish() + return nil +} + +func handleErr(err *error) { + if v := recover(); v != nil { + if e, ok := v.(yamlError); ok { + *err = e.err + } else { + panic(v) + } + } +} + +type yamlError struct { + err error +} + +func fail(err error) { + panic(yamlError{err}) +} + +func failf(format string, args ...interface{}) { + panic(yamlError{fmt.Errorf("yaml: "+format, args...)}) +} + +// A TypeError is returned by Unmarshal when one or more fields in +// the YAML document cannot be properly decoded into the requested +// types. When this error is returned, the value is still +// unmarshaled partially. +type TypeError struct { + Errors []string +} + +func (e *TypeError) Error() string { + return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n ")) +} + +// -------------------------------------------------------------------------- +// Maintain a mapping of keys to structure field indexes + +// The code in this section was copied from mgo/bson. + +// structInfo holds details for the serialization of fields of +// a given struct. +type structInfo struct { + FieldsMap map[string]fieldInfo + FieldsList []fieldInfo + + // InlineMap is the number of the field in the struct that + // contains an ,inline map, or -1 if there's none. + InlineMap int +} + +type fieldInfo struct { + Key string + Num int + OmitEmpty bool + Flow bool + // Id holds the unique field identifier, so we can cheaply + // check for field duplicates without maintaining an extra map. + Id int + + // Inline holds the field index if the field is part of an inlined struct. + Inline []int +} + +var structMap = make(map[reflect.Type]*structInfo) +var fieldMapMutex sync.RWMutex + +func getStructInfo(st reflect.Type) (*structInfo, error) { + fieldMapMutex.RLock() + sinfo, found := structMap[st] + fieldMapMutex.RUnlock() + if found { + return sinfo, nil + } + + n := st.NumField() + fieldsMap := make(map[string]fieldInfo) + fieldsList := make([]fieldInfo, 0, n) + inlineMap := -1 + for i := 0; i != n; i++ { + field := st.Field(i) + if field.PkgPath != "" && !field.Anonymous { + continue // Private field + } + + info := fieldInfo{Num: i} + + tag := field.Tag.Get("yaml") + if tag == "" && strings.Index(string(field.Tag), ":") < 0 { + tag = string(field.Tag) + } + if tag == "-" { + continue + } + + inline := false + fields := strings.Split(tag, ",") + if len(fields) > 1 { + for _, flag := range fields[1:] { + switch flag { + case "omitempty": + info.OmitEmpty = true + case "flow": + info.Flow = true + case "inline": + inline = true + default: + return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)) + } + } + tag = fields[0] + } + + if inline { + switch field.Type.Kind() { + case reflect.Map: + if inlineMap >= 0 { + return nil, errors.New("Multiple ,inline maps in struct " + st.String()) + } + if field.Type.Key() != reflect.TypeOf("") { + return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String()) + } + inlineMap = info.Num + case reflect.Struct: + sinfo, err := getStructInfo(field.Type) + if err != nil { + return nil, err + } + for _, finfo := range sinfo.FieldsList { + if _, found := fieldsMap[finfo.Key]; found { + msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + if finfo.Inline == nil { + finfo.Inline = []int{i, finfo.Num} + } else { + finfo.Inline = append([]int{i}, finfo.Inline...) + } + finfo.Id = len(fieldsList) + fieldsMap[finfo.Key] = finfo + fieldsList = append(fieldsList, finfo) + } + default: + //return nil, errors.New("Option ,inline needs a struct value or map field") + return nil, errors.New("Option ,inline needs a struct value field") + } + continue + } + + if tag != "" { + info.Key = tag + } else { + info.Key = strings.ToLower(field.Name) + } + + if _, found = fieldsMap[info.Key]; found { + msg := "Duplicated key '" + info.Key + "' in struct " + st.String() + return nil, errors.New(msg) + } + + info.Id = len(fieldsList) + fieldsList = append(fieldsList, info) + fieldsMap[info.Key] = info + } + + sinfo = &structInfo{ + FieldsMap: fieldsMap, + FieldsList: fieldsList, + InlineMap: inlineMap, + } + + fieldMapMutex.Lock() + structMap[st] = sinfo + fieldMapMutex.Unlock() + return sinfo, nil +} + +// IsZeroer is used to check whether an object is zero to +// determine whether it should be omitted when marshaling +// with the omitempty flag. One notable implementation +// is time.Time. +type IsZeroer interface { + IsZero() bool +} + +func isZero(v reflect.Value) bool { + kind := v.Kind() + if z, ok := v.Interface().(IsZeroer); ok { + if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() { + return true + } + return z.IsZero() + } + switch kind { + case reflect.String: + return len(v.String()) == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Slice: + return v.Len() == 0 + case reflect.Map: + return v.Len() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Struct: + vt := v.Type() + for i := v.NumField() - 1; i >= 0; i-- { + if vt.Field(i).PkgPath != "" { + continue // Private field + } + if !isZero(v.Field(i)) { + return false + } + } + return true + } + return false +} diff --git a/vendor/gopkg.in/yaml.v2/yamlh.go b/vendor/gopkg.in/yaml.v2/yamlh.go new file mode 100644 index 00000000..e25cee56 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yamlh.go @@ -0,0 +1,738 @@ +package yaml + +import ( + "fmt" + "io" +) + +// The version directive data. +type yaml_version_directive_t struct { + major int8 // The major version number. + minor int8 // The minor version number. +} + +// The tag directive data. +type yaml_tag_directive_t struct { + handle []byte // The tag handle. + prefix []byte // The tag prefix. +} + +type yaml_encoding_t int + +// The stream encoding. +const ( + // Let the parser choose the encoding. + yaml_ANY_ENCODING yaml_encoding_t = iota + + yaml_UTF8_ENCODING // The default UTF-8 encoding. + yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM. + yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM. +) + +type yaml_break_t int + +// Line break types. +const ( + // Let the parser choose the break type. + yaml_ANY_BREAK yaml_break_t = iota + + yaml_CR_BREAK // Use CR for line breaks (Mac style). + yaml_LN_BREAK // Use LN for line breaks (Unix style). + yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style). +) + +type yaml_error_type_t int + +// Many bad things could happen with the parser and emitter. +const ( + // No error is produced. + yaml_NO_ERROR yaml_error_type_t = iota + + yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory. + yaml_READER_ERROR // Cannot read or decode the input stream. + yaml_SCANNER_ERROR // Cannot scan the input stream. + yaml_PARSER_ERROR // Cannot parse the input stream. + yaml_COMPOSER_ERROR // Cannot compose a YAML document. + yaml_WRITER_ERROR // Cannot write to the output stream. + yaml_EMITTER_ERROR // Cannot emit a YAML stream. +) + +// The pointer position. +type yaml_mark_t struct { + index int // The position index. + line int // The position line. + column int // The position column. +} + +// Node Styles + +type yaml_style_t int8 + +type yaml_scalar_style_t yaml_style_t + +// Scalar styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota + + yaml_PLAIN_SCALAR_STYLE // The plain scalar style. + yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style. + yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style. + yaml_LITERAL_SCALAR_STYLE // The literal scalar style. + yaml_FOLDED_SCALAR_STYLE // The folded scalar style. +) + +type yaml_sequence_style_t yaml_style_t + +// Sequence styles. +const ( + // Let the emitter choose the style. + yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota + + yaml_BLOCK_SEQUENCE_STYLE // The block sequence style. + yaml_FLOW_SEQUENCE_STYLE // The flow sequence style. +) + +type yaml_mapping_style_t yaml_style_t + +// Mapping styles. +const ( + // Let the emitter choose the style. + yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota + + yaml_BLOCK_MAPPING_STYLE // The block mapping style. + yaml_FLOW_MAPPING_STYLE // The flow mapping style. +) + +// Tokens + +type yaml_token_type_t int + +// Token types. +const ( + // An empty token. + yaml_NO_TOKEN yaml_token_type_t = iota + + yaml_STREAM_START_TOKEN // A STREAM-START token. + yaml_STREAM_END_TOKEN // A STREAM-END token. + + yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token. + yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token. + yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token. + yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token. + + yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token. + yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token. + yaml_BLOCK_END_TOKEN // A BLOCK-END token. + + yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token. + yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token. + yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token. + yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token. + + yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token. + yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token. + yaml_KEY_TOKEN // A KEY token. + yaml_VALUE_TOKEN // A VALUE token. + + yaml_ALIAS_TOKEN // An ALIAS token. + yaml_ANCHOR_TOKEN // An ANCHOR token. + yaml_TAG_TOKEN // A TAG token. + yaml_SCALAR_TOKEN // A SCALAR token. +) + +func (tt yaml_token_type_t) String() string { + switch tt { + case yaml_NO_TOKEN: + return "yaml_NO_TOKEN" + case yaml_STREAM_START_TOKEN: + return "yaml_STREAM_START_TOKEN" + case yaml_STREAM_END_TOKEN: + return "yaml_STREAM_END_TOKEN" + case yaml_VERSION_DIRECTIVE_TOKEN: + return "yaml_VERSION_DIRECTIVE_TOKEN" + case yaml_TAG_DIRECTIVE_TOKEN: + return "yaml_TAG_DIRECTIVE_TOKEN" + case yaml_DOCUMENT_START_TOKEN: + return "yaml_DOCUMENT_START_TOKEN" + case yaml_DOCUMENT_END_TOKEN: + return "yaml_DOCUMENT_END_TOKEN" + case yaml_BLOCK_SEQUENCE_START_TOKEN: + return "yaml_BLOCK_SEQUENCE_START_TOKEN" + case yaml_BLOCK_MAPPING_START_TOKEN: + return "yaml_BLOCK_MAPPING_START_TOKEN" + case yaml_BLOCK_END_TOKEN: + return "yaml_BLOCK_END_TOKEN" + case yaml_FLOW_SEQUENCE_START_TOKEN: + return "yaml_FLOW_SEQUENCE_START_TOKEN" + case yaml_FLOW_SEQUENCE_END_TOKEN: + return "yaml_FLOW_SEQUENCE_END_TOKEN" + case yaml_FLOW_MAPPING_START_TOKEN: + return "yaml_FLOW_MAPPING_START_TOKEN" + case yaml_FLOW_MAPPING_END_TOKEN: + return "yaml_FLOW_MAPPING_END_TOKEN" + case yaml_BLOCK_ENTRY_TOKEN: + return "yaml_BLOCK_ENTRY_TOKEN" + case yaml_FLOW_ENTRY_TOKEN: + return "yaml_FLOW_ENTRY_TOKEN" + case yaml_KEY_TOKEN: + return "yaml_KEY_TOKEN" + case yaml_VALUE_TOKEN: + return "yaml_VALUE_TOKEN" + case yaml_ALIAS_TOKEN: + return "yaml_ALIAS_TOKEN" + case yaml_ANCHOR_TOKEN: + return "yaml_ANCHOR_TOKEN" + case yaml_TAG_TOKEN: + return "yaml_TAG_TOKEN" + case yaml_SCALAR_TOKEN: + return "yaml_SCALAR_TOKEN" + } + return "" +} + +// The token structure. +type yaml_token_t struct { + // The token type. + typ yaml_token_type_t + + // The start/end of the token. + start_mark, end_mark yaml_mark_t + + // The stream encoding (for yaml_STREAM_START_TOKEN). + encoding yaml_encoding_t + + // The alias/anchor/scalar value or tag/tag directive handle + // (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN). + value []byte + + // The tag suffix (for yaml_TAG_TOKEN). + suffix []byte + + // The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN). + prefix []byte + + // The scalar style (for yaml_SCALAR_TOKEN). + style yaml_scalar_style_t + + // The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN). + major, minor int8 +} + +// Events + +type yaml_event_type_t int8 + +// Event types. +const ( + // An empty event. + yaml_NO_EVENT yaml_event_type_t = iota + + yaml_STREAM_START_EVENT // A STREAM-START event. + yaml_STREAM_END_EVENT // A STREAM-END event. + yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event. + yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event. + yaml_ALIAS_EVENT // An ALIAS event. + yaml_SCALAR_EVENT // A SCALAR event. + yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event. + yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event. + yaml_MAPPING_START_EVENT // A MAPPING-START event. + yaml_MAPPING_END_EVENT // A MAPPING-END event. +) + +var eventStrings = []string{ + yaml_NO_EVENT: "none", + yaml_STREAM_START_EVENT: "stream start", + yaml_STREAM_END_EVENT: "stream end", + yaml_DOCUMENT_START_EVENT: "document start", + yaml_DOCUMENT_END_EVENT: "document end", + yaml_ALIAS_EVENT: "alias", + yaml_SCALAR_EVENT: "scalar", + yaml_SEQUENCE_START_EVENT: "sequence start", + yaml_SEQUENCE_END_EVENT: "sequence end", + yaml_MAPPING_START_EVENT: "mapping start", + yaml_MAPPING_END_EVENT: "mapping end", +} + +func (e yaml_event_type_t) String() string { + if e < 0 || int(e) >= len(eventStrings) { + return fmt.Sprintf("unknown event %d", e) + } + return eventStrings[e] +} + +// The event structure. +type yaml_event_t struct { + + // The event type. + typ yaml_event_type_t + + // The start and end of the event. + start_mark, end_mark yaml_mark_t + + // The document encoding (for yaml_STREAM_START_EVENT). + encoding yaml_encoding_t + + // The version directive (for yaml_DOCUMENT_START_EVENT). + version_directive *yaml_version_directive_t + + // The list of tag directives (for yaml_DOCUMENT_START_EVENT). + tag_directives []yaml_tag_directive_t + + // The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT). + anchor []byte + + // The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + tag []byte + + // The scalar value (for yaml_SCALAR_EVENT). + value []byte + + // Is the document start/end indicator implicit, or the tag optional? + // (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT). + implicit bool + + // Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT). + quoted_implicit bool + + // The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT). + style yaml_style_t +} + +func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) } +func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) } +func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) } + +// Nodes + +const ( + yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null. + yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false. + yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values. + yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values. + yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values. + yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values. + + yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences. + yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping. + + // Not in original libyaml. + yaml_BINARY_TAG = "tag:yaml.org,2002:binary" + yaml_MERGE_TAG = "tag:yaml.org,2002:merge" + + yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str. + yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq. + yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map. +) + +type yaml_node_type_t int + +// Node types. +const ( + // An empty node. + yaml_NO_NODE yaml_node_type_t = iota + + yaml_SCALAR_NODE // A scalar node. + yaml_SEQUENCE_NODE // A sequence node. + yaml_MAPPING_NODE // A mapping node. +) + +// An element of a sequence node. +type yaml_node_item_t int + +// An element of a mapping node. +type yaml_node_pair_t struct { + key int // The key of the element. + value int // The value of the element. +} + +// The node structure. +type yaml_node_t struct { + typ yaml_node_type_t // The node type. + tag []byte // The node tag. + + // The node data. + + // The scalar parameters (for yaml_SCALAR_NODE). + scalar struct { + value []byte // The scalar value. + length int // The length of the scalar value. + style yaml_scalar_style_t // The scalar style. + } + + // The sequence parameters (for YAML_SEQUENCE_NODE). + sequence struct { + items_data []yaml_node_item_t // The stack of sequence items. + style yaml_sequence_style_t // The sequence style. + } + + // The mapping parameters (for yaml_MAPPING_NODE). + mapping struct { + pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value). + pairs_start *yaml_node_pair_t // The beginning of the stack. + pairs_end *yaml_node_pair_t // The end of the stack. + pairs_top *yaml_node_pair_t // The top of the stack. + style yaml_mapping_style_t // The mapping style. + } + + start_mark yaml_mark_t // The beginning of the node. + end_mark yaml_mark_t // The end of the node. + +} + +// The document structure. +type yaml_document_t struct { + + // The document nodes. + nodes []yaml_node_t + + // The version directive. + version_directive *yaml_version_directive_t + + // The list of tag directives. + tag_directives_data []yaml_tag_directive_t + tag_directives_start int // The beginning of the tag directives list. + tag_directives_end int // The end of the tag directives list. + + start_implicit int // Is the document start indicator implicit? + end_implicit int // Is the document end indicator implicit? + + // The start/end of the document. + start_mark, end_mark yaml_mark_t +} + +// The prototype of a read handler. +// +// The read handler is called when the parser needs to read more bytes from the +// source. The handler should write not more than size bytes to the buffer. +// The number of written bytes should be set to the size_read variable. +// +// [in,out] data A pointer to an application data specified by +// yaml_parser_set_input(). +// [out] buffer The buffer to write the data from the source. +// [in] size The size of the buffer. +// [out] size_read The actual number of bytes read from the source. +// +// On success, the handler should return 1. If the handler failed, +// the returned value should be 0. On EOF, the handler should set the +// size_read to 0 and return 1. +type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error) + +// This structure holds information about a potential simple key. +type yaml_simple_key_t struct { + possible bool // Is a simple key possible? + required bool // Is a simple key required? + token_number int // The number of the token. + mark yaml_mark_t // The position mark. +} + +// The states of the parser. +type yaml_parser_state_t int + +const ( + yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota + + yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document. + yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START. + yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_PARSE_BLOCK_NODE_STATE // Expect a block node. + yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence. + yaml_PARSE_FLOW_NODE_STATE // Expect a flow node. + yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence. + yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence. + yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence. + yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key. + yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value. + yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping. + yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry. + yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping. + yaml_PARSE_END_STATE // Expect nothing. +) + +func (ps yaml_parser_state_t) String() string { + switch ps { + case yaml_PARSE_STREAM_START_STATE: + return "yaml_PARSE_STREAM_START_STATE" + case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_START_STATE: + return "yaml_PARSE_DOCUMENT_START_STATE" + case yaml_PARSE_DOCUMENT_CONTENT_STATE: + return "yaml_PARSE_DOCUMENT_CONTENT_STATE" + case yaml_PARSE_DOCUMENT_END_STATE: + return "yaml_PARSE_DOCUMENT_END_STATE" + case yaml_PARSE_BLOCK_NODE_STATE: + return "yaml_PARSE_BLOCK_NODE_STATE" + case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE" + case yaml_PARSE_FLOW_NODE_STATE: + return "yaml_PARSE_FLOW_NODE_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_KEY_STATE: + return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE" + case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE: + return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE" + case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_KEY_STATE: + return "yaml_PARSE_FLOW_MAPPING_KEY_STATE" + case yaml_PARSE_FLOW_MAPPING_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE" + case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE" + case yaml_PARSE_END_STATE: + return "yaml_PARSE_END_STATE" + } + return "" +} + +// This structure holds aliases data. +type yaml_alias_data_t struct { + anchor []byte // The anchor. + index int // The node id. + mark yaml_mark_t // The anchor mark. +} + +// The parser structure. +// +// All members are internal. Manage the structure using the +// yaml_parser_ family of functions. +type yaml_parser_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + + problem string // Error description. + + // The byte about which the problem occurred. + problem_offset int + problem_value int + problem_mark yaml_mark_t + + // The error context. + context string + context_mark yaml_mark_t + + // Reader stuff + + read_handler yaml_read_handler_t // Read handler. + + input_reader io.Reader // File input data. + input []byte // String input data. + input_pos int + + eof bool // EOF flag + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + unread int // The number of unread characters in the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The input encoding. + + offset int // The offset of the current position (in bytes). + mark yaml_mark_t // The mark of the current position. + + // Scanner stuff + + stream_start_produced bool // Have we started to scan the input stream? + stream_end_produced bool // Have we reached the end of the input stream? + + flow_level int // The number of unclosed '[' and '{' indicators. + + tokens []yaml_token_t // The tokens queue. + tokens_head int // The head of the tokens queue. + tokens_parsed int // The number of tokens fetched from the queue. + token_available bool // Does the tokens queue contain a token ready for dequeueing. + + indent int // The current indentation level. + indents []int // The indentation levels stack. + + simple_key_allowed bool // May a simple key occur at the current position? + simple_keys []yaml_simple_key_t // The stack of simple keys. + + // Parser stuff + + state yaml_parser_state_t // The current parser state. + states []yaml_parser_state_t // The parser states stack. + marks []yaml_mark_t // The stack of marks. + tag_directives []yaml_tag_directive_t // The list of TAG directives. + + // Dumper stuff + + aliases []yaml_alias_data_t // The alias data. + + document *yaml_document_t // The currently parsed document. +} + +// Emitter Definitions + +// The prototype of a write handler. +// +// The write handler is called when the emitter needs to flush the accumulated +// characters to the output. The handler should write @a size bytes of the +// @a buffer to the output. +// +// @param[in,out] data A pointer to an application data specified by +// yaml_emitter_set_output(). +// @param[in] buffer The buffer with bytes to be written. +// @param[in] size The size of the buffer. +// +// @returns On success, the handler should return @c 1. If the handler failed, +// the returned value should be @c 0. +// +type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error + +type yaml_emitter_state_t int + +// The emitter states. +const ( + // Expect STREAM-START. + yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota + + yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END. + yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document. + yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END. + yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence. + yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence. + yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping. + yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping. + yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence. + yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence. + yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping. + yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping. + yaml_EMIT_END_STATE // Expect nothing. +) + +// The emitter structure. +// +// All members are internal. Manage the structure using the @c yaml_emitter_ +// family of functions. +type yaml_emitter_t struct { + + // Error handling + + error yaml_error_type_t // Error type. + problem string // Error description. + + // Writer stuff + + write_handler yaml_write_handler_t // Write handler. + + output_buffer *[]byte // String output data. + output_writer io.Writer // File output data. + + buffer []byte // The working buffer. + buffer_pos int // The current position of the buffer. + + raw_buffer []byte // The raw buffer. + raw_buffer_pos int // The current position of the buffer. + + encoding yaml_encoding_t // The stream encoding. + + // Emitter stuff + + canonical bool // If the output is in the canonical style? + best_indent int // The number of indentation spaces. + best_width int // The preferred width of the output lines. + unicode bool // Allow unescaped non-ASCII characters? + line_break yaml_break_t // The preferred line break. + + state yaml_emitter_state_t // The current emitter state. + states []yaml_emitter_state_t // The stack of states. + + events []yaml_event_t // The event queue. + events_head int // The head of the event queue. + + indents []int // The stack of indentation levels. + + tag_directives []yaml_tag_directive_t // The list of tag directives. + + indent int // The current indentation level. + + flow_level int // The current flow level. + + root_context bool // Is it the document root context? + sequence_context bool // Is it a sequence context? + mapping_context bool // Is it a mapping context? + simple_key_context bool // Is it a simple mapping key context? + + line int // The current line. + column int // The current column. + whitespace bool // If the last character was a whitespace? + indention bool // If the last character was an indentation character (' ', '-', '?', ':')? + open_ended bool // If an explicit document end is required? + + // Anchor analysis. + anchor_data struct { + anchor []byte // The anchor value. + alias bool // Is it an alias? + } + + // Tag analysis. + tag_data struct { + handle []byte // The tag handle. + suffix []byte // The tag suffix. + } + + // Scalar analysis. + scalar_data struct { + value []byte // The scalar value. + multiline bool // Does the scalar contain line breaks? + flow_plain_allowed bool // Can the scalar be expessed in the flow plain style? + block_plain_allowed bool // Can the scalar be expressed in the block plain style? + single_quoted_allowed bool // Can the scalar be expressed in the single quoted style? + block_allowed bool // Can the scalar be expressed in the literal or folded styles? + style yaml_scalar_style_t // The output style. + } + + // Dumper stuff + + opened bool // If the stream was already opened? + closed bool // If the stream was already closed? + + // The information associated with the document nodes. + anchors *struct { + references int // The number of references. + anchor int // The anchor id. + serialized bool // If the node has been emitted? + } + + last_anchor_id int // The last assigned anchor id. + + document *yaml_document_t // The currently emitted document. +} diff --git a/vendor/gopkg.in/yaml.v2/yamlprivateh.go b/vendor/gopkg.in/yaml.v2/yamlprivateh.go new file mode 100644 index 00000000..8110ce3c --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/yamlprivateh.go @@ -0,0 +1,173 @@ +package yaml + +const ( + // The size of the input raw buffer. + input_raw_buffer_size = 512 + + // The size of the input buffer. + // It should be possible to decode the whole raw buffer. + input_buffer_size = input_raw_buffer_size * 3 + + // The size of the output buffer. + output_buffer_size = 128 + + // The size of the output raw buffer. + // It should be possible to encode the whole output buffer. + output_raw_buffer_size = (output_buffer_size*2 + 2) + + // The size of other stacks and queues. + initial_stack_size = 16 + initial_queue_size = 16 + initial_string_size = 16 +) + +// Check if the character at the specified position is an alphabetical +// character, a digit, '_', or '-'. +func is_alpha(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-' +} + +// Check if the character at the specified position is a digit. +func is_digit(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' +} + +// Get the value of a digit. +func as_digit(b []byte, i int) int { + return int(b[i]) - '0' +} + +// Check if the character at the specified position is a hex-digit. +func is_hex(b []byte, i int) bool { + return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f' +} + +// Get the value of a hex-digit. +func as_hex(b []byte, i int) int { + bi := b[i] + if bi >= 'A' && bi <= 'F' { + return int(bi) - 'A' + 10 + } + if bi >= 'a' && bi <= 'f' { + return int(bi) - 'a' + 10 + } + return int(bi) - '0' +} + +// Check if the character is ASCII. +func is_ascii(b []byte, i int) bool { + return b[i] <= 0x7F +} + +// Check if the character at the start of the buffer can be printed unescaped. +func is_printable(b []byte, i int) bool { + return ((b[i] == 0x0A) || // . == #x0A + (b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E + (b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF + (b[i] > 0xC2 && b[i] < 0xED) || + (b[i] == 0xED && b[i+1] < 0xA0) || + (b[i] == 0xEE) || + (b[i] == 0xEF && // #xE000 <= . <= #xFFFD + !(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF + !(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF)))) +} + +// Check if the character at the specified position is NUL. +func is_z(b []byte, i int) bool { + return b[i] == 0x00 +} + +// Check if the beginning of the buffer is a BOM. +func is_bom(b []byte, i int) bool { + return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF +} + +// Check if the character at the specified position is space. +func is_space(b []byte, i int) bool { + return b[i] == ' ' +} + +// Check if the character at the specified position is tab. +func is_tab(b []byte, i int) bool { + return b[i] == '\t' +} + +// Check if the character at the specified position is blank (space or tab). +func is_blank(b []byte, i int) bool { + //return is_space(b, i) || is_tab(b, i) + return b[i] == ' ' || b[i] == '\t' +} + +// Check if the character at the specified position is a line break. +func is_break(b []byte, i int) bool { + return (b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029) +} + +func is_crlf(b []byte, i int) bool { + return b[i] == '\r' && b[i+1] == '\n' +} + +// Check if the character is a line break or NUL. +func is_breakz(b []byte, i int) bool { + //return is_break(b, i) || is_z(b, i) + return ( // is_break: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + // is_z: + b[i] == 0) +} + +// Check if the character is a line break, space, or NUL. +func is_spacez(b []byte, i int) bool { + //return is_space(b, i) || is_breakz(b, i) + return ( // is_space: + b[i] == ' ' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Check if the character is a line break, space, tab, or NUL. +func is_blankz(b []byte, i int) bool { + //return is_blank(b, i) || is_breakz(b, i) + return ( // is_blank: + b[i] == ' ' || b[i] == '\t' || + // is_breakz: + b[i] == '\r' || // CR (#xD) + b[i] == '\n' || // LF (#xA) + b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028) + b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029) + b[i] == 0) +} + +// Determine the width of the character. +func width(b byte) int { + // Don't replace these by a switch without first + // confirming that it is being inlined. + if b&0x80 == 0x00 { + return 1 + } + if b&0xE0 == 0xC0 { + return 2 + } + if b&0xF0 == 0xE0 { + return 3 + } + if b&0xF8 == 0xF0 { + return 4 + } + return 0 + +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 00000000..f456d4a4 --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,76 @@ +# github.com/albrow/jobs v0.4.2 +## explicit +github.com/albrow/jobs +# github.com/benmanns/goworker v0.1.3 +## explicit +github.com/benmanns/goworker +# github.com/bitly/go-simplejson v0.5.0 +## explicit +github.com/bitly/go-simplejson +# github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 +## explicit +# github.com/braintree/manners v0.0.0-20160418043613-82a8879fc5fd +## explicit +github.com/braintree/manners +# github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 +## explicit +github.com/cihub/seelog +github.com/cihub/seelog/archive +github.com/cihub/seelog/archive/gzip +github.com/cihub/seelog/archive/tar +github.com/cihub/seelog/archive/zip +# github.com/customerio/gospec v0.0.0-20130710230057-a5cc0e48aa39 +## explicit +# github.com/davecgh/go-spew v1.1.0 +github.com/davecgh/go-spew/spew +# github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 +## explicit +github.com/dchest/uniuri +# github.com/dustin/go-humanize v1.0.0 +## explicit +# github.com/garyburd/redigo v1.6.0 +## explicit +github.com/garyburd/redigo/internal +github.com/garyburd/redigo/redis +# github.com/gocraft/health v0.0.0-20170925182251-8675af27fef0 +## explicit +github.com/gocraft/health +github.com/gocraft/health/stack +# github.com/gocraft/web v0.0.0-20190207150652-9707327fb69b +## explicit +github.com/gocraft/web +# github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b +## explicit +github.com/golang/glog +# github.com/gomodule/redigo v2.0.0+incompatible +## explicit +github.com/gomodule/redigo/internal +github.com/gomodule/redigo/redis +# github.com/jrallison/go-workers v0.0.0-20180112190529-dbf81d0b75bb +## explicit +github.com/jrallison/go-workers +# github.com/jteeuwen/go-bindata v3.0.7+incompatible +## explicit +# github.com/kr/pretty v0.2.0 +## explicit +# github.com/orfjackal/nanospec.go v0.0.0-20120727230329-de4694c1d701 +## explicit +# github.com/pmezard/go-difflib v1.0.0 +github.com/pmezard/go-difflib/difflib +# github.com/robfig/cron/v3 v3.0.1 +## explicit +github.com/robfig/cron/v3 +# github.com/stretchr/testify v1.5.1 +## explicit +github.com/stretchr/testify/assert +# github.com/youtube/vitess v2.1.1+incompatible +## explicit +github.com/youtube/vitess/go/acl +github.com/youtube/vitess/go/cache +github.com/youtube/vitess/go/pools +github.com/youtube/vitess/go/sync2 +# golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 +## explicit +golang.org/x/net/context +# gopkg.in/yaml.v2 v2.2.2 +gopkg.in/yaml.v2