mirror of
https://github.com/Luzifer/vault-otp-ui.git
synced 2024-11-08 16:20:06 +00:00
Remove vendor folder
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
809ee3cc12
commit
14a3030720
765 changed files with 0 additions and 390962 deletions
8
vendor/github.com/Luzifer/rconfig/.travis.yml
generated
vendored
8
vendor/github.com/Luzifer/rconfig/.travis.yml
generated
vendored
|
@ -1,8 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.6
|
|
||||||
- 1.7
|
|
||||||
- tip
|
|
||||||
|
|
||||||
script: go test -v -race -cover ./...
|
|
9
vendor/github.com/Luzifer/rconfig/History.md
generated
vendored
9
vendor/github.com/Luzifer/rconfig/History.md
generated
vendored
|
@ -1,9 +0,0 @@
|
||||||
# 1.2.0 / 2017-06-19
|
|
||||||
|
|
||||||
* Add ParseAndValidate method
|
|
||||||
|
|
||||||
# 1.1.0 / 2016-06-28
|
|
||||||
|
|
||||||
* Support time.Duration config parameters
|
|
||||||
* Added goreportcard badge
|
|
||||||
* Added testcase for using bool with ENV and default
|
|
13
vendor/github.com/Luzifer/rconfig/LICENSE
generated
vendored
13
vendor/github.com/Luzifer/rconfig/LICENSE
generated
vendored
|
@ -1,13 +0,0 @@
|
||||||
Copyright 2015 Knut Ahlers <knut@ahlers.me>
|
|
||||||
|
|
||||||
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.
|
|
87
vendor/github.com/Luzifer/rconfig/README.md
generated
vendored
87
vendor/github.com/Luzifer/rconfig/README.md
generated
vendored
|
@ -1,87 +0,0 @@
|
||||||
[![Build Status](https://travis-ci.org/Luzifer/rconfig.svg?branch=master)](https://travis-ci.org/Luzifer/rconfig)
|
|
||||||
[![License: Apache v2.0](https://badge.luzifer.io/v1/badge?color=5d79b5&title=license&text=Apache+v2.0)](http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||||
[![Documentation](https://badge.luzifer.io/v1/badge?title=godoc&text=reference)](https://godoc.org/github.com/Luzifer/rconfig)
|
|
||||||
[![Go Report](http://goreportcard.com/badge/Luzifer/rconfig)](http://goreportcard.com/report/Luzifer/rconfig)
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
> Package rconfig implements a CLI configuration reader with struct-embedded defaults, environment variables and posix compatible flag parsing using the [pflag](https://github.com/spf13/pflag) library.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Install by running:
|
|
||||||
|
|
||||||
```
|
|
||||||
go get -u github.com/Luzifer/rconfig
|
|
||||||
```
|
|
||||||
|
|
||||||
OR fetch a specific version:
|
|
||||||
|
|
||||||
```
|
|
||||||
go get -u gopkg.in/luzifer/rconfig.v1
|
|
||||||
```
|
|
||||||
|
|
||||||
Run tests by running:
|
|
||||||
|
|
||||||
```
|
|
||||||
go test -v -race -cover github.com/Luzifer/rconfig
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
A very simple usecase is to just configure a struct inside the vars section of your `main.go` and to parse the commandline flags from the `main()` function:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Luzifer/rconfig"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
cfg = struct {
|
|
||||||
Username string `default:"unknown" flag:"user" description:"Your name"`
|
|
||||||
Details struct {
|
|
||||||
Age int `default:"25" flag:"age" env:"age" description:"Your age"`
|
|
||||||
}
|
|
||||||
}{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rconfig.Parse(&cfg)
|
|
||||||
|
|
||||||
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
|
|
||||||
cfg.Username,
|
|
||||||
cfg.Details.Age)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Provide variable defaults by using a file
|
|
||||||
|
|
||||||
Given you have a file `~/.myapp.yml` containing some secrets or usernames (for the example below username is assumed to be "luzifer") as a default configuration for your application you can use this source code to load the defaults from that file using the `vardefault` tag in your configuration struct.
|
|
||||||
|
|
||||||
The order of the directives (lower number = higher precedence):
|
|
||||||
|
|
||||||
1. Flags provided in command line
|
|
||||||
1. Environment variables
|
|
||||||
1. Variable defaults (`vardefault` tag in the struct)
|
|
||||||
1. `default` tag in the struct
|
|
||||||
|
|
||||||
```go
|
|
||||||
var cfg = struct {
|
|
||||||
Username string `vardefault:"username" flag:"username" description:"Your username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rconfig.SetVariableDefaults(rconfig.VarDefaultsFromYAMLFile("~/.myapp.yml"))
|
|
||||||
rconfig.Parse(&cfg)
|
|
||||||
|
|
||||||
fmt.Printf("Username = %s", cfg.Username)
|
|
||||||
// Output: Username = luzifer
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## More info
|
|
||||||
|
|
||||||
You can see the full reference documentation of the rconfig package [at godoc.org](https://godoc.org/github.com/Luzifer/rconfig), or through go's standard documentation system by running `godoc -http=:6060` and browsing to [http://localhost:6060/pkg/github.com/Luzifer/rconfig](http://localhost:6060/pkg/github.com/Luzifer/rconfig) after installation.
|
|
356
vendor/github.com/Luzifer/rconfig/config.go
generated
vendored
356
vendor/github.com/Luzifer/rconfig/config.go
generated
vendored
|
@ -1,356 +0,0 @@
|
||||||
// Package rconfig implements a CLI configuration reader with struct-embedded
|
|
||||||
// defaults, environment variables and posix compatible flag parsing using
|
|
||||||
// the pflag library.
|
|
||||||
package rconfig
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
validator "gopkg.in/validator.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
fs *pflag.FlagSet
|
|
||||||
variableDefaults map[string]string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
variableDefaults = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse takes the pointer to a struct filled with variables which should be read
|
|
||||||
// from ENV, default or flag. The precedence in this is flag > ENV > default. So
|
|
||||||
// if a flag is specified on the CLI it will overwrite the ENV and otherwise ENV
|
|
||||||
// overwrites the default specified.
|
|
||||||
//
|
|
||||||
// For your configuration struct you can use the following struct-tags to control
|
|
||||||
// the behavior of rconfig:
|
|
||||||
//
|
|
||||||
// default: Set a default value
|
|
||||||
// vardefault: Read the default value from the variable defaults
|
|
||||||
// env: Read the value from this environment variable
|
|
||||||
// flag: Flag to read in format "long,short" (for example "listen,l")
|
|
||||||
// description: A help text for Usage output to guide your users
|
|
||||||
//
|
|
||||||
// The format you need to specify those values you can see in the example to this
|
|
||||||
// function.
|
|
||||||
//
|
|
||||||
func Parse(config interface{}) error {
|
|
||||||
return parse(config, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAndValidate works exactly like Parse but implements an additional run of
|
|
||||||
// the go-validator package on the configuration struct. Therefore additonal struct
|
|
||||||
// tags are supported like described in the readme file of the go-validator package:
|
|
||||||
//
|
|
||||||
// https://github.com/go-validator/validator/tree/v2#usage
|
|
||||||
func ParseAndValidate(config interface{}) error {
|
|
||||||
return parseAndValidate(config, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Args returns the non-flag command-line arguments.
|
|
||||||
func Args() []string {
|
|
||||||
return fs.Args()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage prints a basic usage with the corresponding defaults for the flags to
|
|
||||||
// os.Stdout. The defaults are derived from the `default` struct-tag and the ENV.
|
|
||||||
func Usage() {
|
|
||||||
if fs != nil && fs.Parsed() {
|
|
||||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
|
||||||
fs.PrintDefaults()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetVariableDefaults presets the parser with a map of default values to be used
|
|
||||||
// when specifying the vardefault tag
|
|
||||||
func SetVariableDefaults(defaults map[string]string) {
|
|
||||||
variableDefaults = defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAndValidate(in interface{}, args []string) error {
|
|
||||||
if err := parse(in, args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return validator.Validate(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(in interface{}, args []string) error {
|
|
||||||
if args == nil {
|
|
||||||
args = os.Args
|
|
||||||
}
|
|
||||||
|
|
||||||
fs = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
|
|
||||||
if err := execTags(in, fs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.Parse(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
func execTags(in interface{}, fs *pflag.FlagSet) error {
|
|
||||||
if reflect.TypeOf(in).Kind() != reflect.Ptr {
|
|
||||||
return errors.New("Calling parser with non-pointer")
|
|
||||||
}
|
|
||||||
|
|
||||||
if reflect.ValueOf(in).Elem().Kind() != reflect.Struct {
|
|
||||||
return errors.New("Calling parser with pointer to non-struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
st := reflect.ValueOf(in).Elem()
|
|
||||||
for i := 0; i < st.NumField(); i++ {
|
|
||||||
valField := st.Field(i)
|
|
||||||
typeField := st.Type().Field(i)
|
|
||||||
|
|
||||||
if typeField.Tag.Get("default") == "" && typeField.Tag.Get("env") == "" && typeField.Tag.Get("flag") == "" && typeField.Type.Kind() != reflect.Struct {
|
|
||||||
// None of our supported tags is present and it's not a sub-struct
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
value := varDefault(typeField.Tag.Get("vardefault"), typeField.Tag.Get("default"))
|
|
||||||
value = envDefault(typeField.Tag.Get("env"), value)
|
|
||||||
parts := strings.Split(typeField.Tag.Get("flag"), ",")
|
|
||||||
|
|
||||||
switch typeField.Type {
|
|
||||||
case reflect.TypeOf(time.Duration(0)):
|
|
||||||
v, err := time.ParseDuration(value)
|
|
||||||
if err != nil {
|
|
||||||
if value == "" {
|
|
||||||
v = time.Duration(0)
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if typeField.Tag.Get("flag") != "" {
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.DurationVar(valField.Addr().Interface().(*time.Duration), parts[0], v, typeField.Tag.Get("description"))
|
|
||||||
} else {
|
|
||||||
fs.DurationVarP(valField.Addr().Interface().(*time.Duration), parts[0], parts[1], v, typeField.Tag.Get("description"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
valField.Set(reflect.ValueOf(v))
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch typeField.Type.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
if typeField.Tag.Get("flag") != "" {
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.StringVar(valField.Addr().Interface().(*string), parts[0], value, typeField.Tag.Get("description"))
|
|
||||||
} else {
|
|
||||||
fs.StringVarP(valField.Addr().Interface().(*string), parts[0], parts[1], value, typeField.Tag.Get("description"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
valField.SetString(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
v := value == "true"
|
|
||||||
if typeField.Tag.Get("flag") != "" {
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.BoolVar(valField.Addr().Interface().(*bool), parts[0], v, typeField.Tag.Get("description"))
|
|
||||||
} else {
|
|
||||||
fs.BoolVarP(valField.Addr().Interface().(*bool), parts[0], parts[1], v, typeField.Tag.Get("description"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
valField.SetBool(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
|
|
||||||
vt, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
if value == "" {
|
|
||||||
vt = 0
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typeField.Tag.Get("flag") != "" {
|
|
||||||
registerFlagInt(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
|
||||||
} else {
|
|
||||||
valField.SetInt(vt)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
vt, err := strconv.ParseUint(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
if value == "" {
|
|
||||||
vt = 0
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typeField.Tag.Get("flag") != "" {
|
|
||||||
registerFlagUint(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
|
||||||
} else {
|
|
||||||
valField.SetUint(vt)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
vt, err := strconv.ParseFloat(value, 64)
|
|
||||||
if err != nil {
|
|
||||||
if value == "" {
|
|
||||||
vt = 0.0
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typeField.Tag.Get("flag") != "" {
|
|
||||||
registerFlagFloat(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
|
||||||
} else {
|
|
||||||
valField.SetFloat(vt)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
if err := execTags(valField.Addr().Interface(), fs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
switch typeField.Type.Elem().Kind() {
|
|
||||||
case reflect.Int:
|
|
||||||
def := []int{}
|
|
||||||
for _, v := range strings.Split(value, ",") {
|
|
||||||
it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
def = append(def, int(it))
|
|
||||||
}
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.IntSliceVar(valField.Addr().Interface().(*[]int), parts[0], def, typeField.Tag.Get("description"))
|
|
||||||
} else {
|
|
||||||
fs.IntSliceVarP(valField.Addr().Interface().(*[]int), parts[0], parts[1], def, typeField.Tag.Get("description"))
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
del := typeField.Tag.Get("delimiter")
|
|
||||||
if len(del) == 0 {
|
|
||||||
del = ","
|
|
||||||
}
|
|
||||||
def := strings.Split(value, del)
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.StringSliceVar(valField.Addr().Interface().(*[]string), parts[0], def, typeField.Tag.Get("description"))
|
|
||||||
} else {
|
|
||||||
fs.StringSliceVarP(valField.Addr().Interface().(*[]string), parts[0], parts[1], def, typeField.Tag.Get("description"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt float64, desc string) {
|
|
||||||
switch t {
|
|
||||||
case reflect.Float32:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.Float32Var(field.(*float32), parts[0], float32(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.Float32VarP(field.(*float32), parts[0], parts[1], float32(vt), desc)
|
|
||||||
}
|
|
||||||
case reflect.Float64:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.Float64Var(field.(*float64), parts[0], float64(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.Float64VarP(field.(*float64), parts[0], parts[1], float64(vt), desc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerFlagInt(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt int64, desc string) {
|
|
||||||
switch t {
|
|
||||||
case reflect.Int:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.IntVar(field.(*int), parts[0], int(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.IntVarP(field.(*int), parts[0], parts[1], int(vt), desc)
|
|
||||||
}
|
|
||||||
case reflect.Int8:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.Int8Var(field.(*int8), parts[0], int8(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.Int8VarP(field.(*int8), parts[0], parts[1], int8(vt), desc)
|
|
||||||
}
|
|
||||||
case reflect.Int32:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.Int32Var(field.(*int32), parts[0], int32(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.Int32VarP(field.(*int32), parts[0], parts[1], int32(vt), desc)
|
|
||||||
}
|
|
||||||
case reflect.Int64:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.Int64Var(field.(*int64), parts[0], int64(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.Int64VarP(field.(*int64), parts[0], parts[1], int64(vt), desc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerFlagUint(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt uint64, desc string) {
|
|
||||||
switch t {
|
|
||||||
case reflect.Uint:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.UintVar(field.(*uint), parts[0], uint(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.UintVarP(field.(*uint), parts[0], parts[1], uint(vt), desc)
|
|
||||||
}
|
|
||||||
case reflect.Uint8:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.Uint8Var(field.(*uint8), parts[0], uint8(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.Uint8VarP(field.(*uint8), parts[0], parts[1], uint8(vt), desc)
|
|
||||||
}
|
|
||||||
case reflect.Uint16:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.Uint16Var(field.(*uint16), parts[0], uint16(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.Uint16VarP(field.(*uint16), parts[0], parts[1], uint16(vt), desc)
|
|
||||||
}
|
|
||||||
case reflect.Uint32:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.Uint32Var(field.(*uint32), parts[0], uint32(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.Uint32VarP(field.(*uint32), parts[0], parts[1], uint32(vt), desc)
|
|
||||||
}
|
|
||||||
case reflect.Uint64:
|
|
||||||
if len(parts) == 1 {
|
|
||||||
fs.Uint64Var(field.(*uint64), parts[0], uint64(vt), desc)
|
|
||||||
} else {
|
|
||||||
fs.Uint64VarP(field.(*uint64), parts[0], parts[1], uint64(vt), desc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func envDefault(env, def string) string {
|
|
||||||
value := def
|
|
||||||
|
|
||||||
if env != "" {
|
|
||||||
if e := os.Getenv(env); e != "" {
|
|
||||||
value = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func varDefault(name, def string) string {
|
|
||||||
value := def
|
|
||||||
|
|
||||||
if name != "" {
|
|
||||||
if v, ok := variableDefaults[name]; ok {
|
|
||||||
value = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
}
|
|
27
vendor/github.com/Luzifer/rconfig/vardefault_providers.go
generated
vendored
27
vendor/github.com/Luzifer/rconfig/vardefault_providers.go
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
package rconfig
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VarDefaultsFromYAMLFile reads contents of a file and calls VarDefaultsFromYAML
|
|
||||||
func VarDefaultsFromYAMLFile(filename string) map[string]string {
|
|
||||||
data, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return VarDefaultsFromYAML(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VarDefaultsFromYAML creates a vardefaults map from YAML raw data
|
|
||||||
func VarDefaultsFromYAML(in []byte) map[string]string {
|
|
||||||
out := make(map[string]string)
|
|
||||||
err := yaml.Unmarshal(in, &out)
|
|
||||||
if err != nil {
|
|
||||||
return make(map[string]string)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
1
vendor/github.com/Sirupsen/logrus/.gitignore
generated
vendored
1
vendor/github.com/Sirupsen/logrus/.gitignore
generated
vendored
|
@ -1 +0,0 @@
|
||||||
logrus
|
|
13
vendor/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
13
vendor/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
|
@ -1,13 +0,0 @@
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.9.x
|
|
||||||
- 1.10.x
|
|
||||||
env:
|
|
||||||
- GOMAXPROCS=4 GORACE=halt_on_error=1
|
|
||||||
install:
|
|
||||||
- go get github.com/stretchr/testify/assert
|
|
||||||
- go get gopkg.in/gemnasium/logrus-airbrake-hook.v2
|
|
||||||
- go get golang.org/x/sys/unix
|
|
||||||
- go get golang.org/x/sys/windows
|
|
||||||
script:
|
|
||||||
- go test -race -v ./...
|
|
123
vendor/github.com/Sirupsen/logrus/CHANGELOG.md
generated
vendored
123
vendor/github.com/Sirupsen/logrus/CHANGELOG.md
generated
vendored
|
@ -1,123 +0,0 @@
|
||||||
# 1.0.5
|
|
||||||
|
|
||||||
* Fix hooks race (#707)
|
|
||||||
* Fix panic deadlock (#695)
|
|
||||||
|
|
||||||
# 1.0.4
|
|
||||||
|
|
||||||
* Fix race when adding hooks (#612)
|
|
||||||
* Fix terminal check in AppEngine (#635)
|
|
||||||
|
|
||||||
# 1.0.3
|
|
||||||
|
|
||||||
* Replace example files with testable examples
|
|
||||||
|
|
||||||
# 1.0.2
|
|
||||||
|
|
||||||
* bug: quote non-string values in text formatter (#583)
|
|
||||||
* Make (*Logger) SetLevel a public method
|
|
||||||
|
|
||||||
# 1.0.1
|
|
||||||
|
|
||||||
* bug: fix escaping in text formatter (#575)
|
|
||||||
|
|
||||||
# 1.0.0
|
|
||||||
|
|
||||||
* Officially changed name to lower-case
|
|
||||||
* bug: colors on Windows 10 (#541)
|
|
||||||
* bug: fix race in accessing level (#512)
|
|
||||||
|
|
||||||
# 0.11.5
|
|
||||||
|
|
||||||
* feature: add writer and writerlevel to entry (#372)
|
|
||||||
|
|
||||||
# 0.11.4
|
|
||||||
|
|
||||||
* bug: fix undefined variable on solaris (#493)
|
|
||||||
|
|
||||||
# 0.11.3
|
|
||||||
|
|
||||||
* formatter: configure quoting of empty values (#484)
|
|
||||||
* formatter: configure quoting character (default is `"`) (#484)
|
|
||||||
* bug: fix not importing io correctly in non-linux environments (#481)
|
|
||||||
|
|
||||||
# 0.11.2
|
|
||||||
|
|
||||||
* bug: fix windows terminal detection (#476)
|
|
||||||
|
|
||||||
# 0.11.1
|
|
||||||
|
|
||||||
* bug: fix tty detection with custom out (#471)
|
|
||||||
|
|
||||||
# 0.11.0
|
|
||||||
|
|
||||||
* performance: Use bufferpool to allocate (#370)
|
|
||||||
* terminal: terminal detection for app-engine (#343)
|
|
||||||
* feature: exit handler (#375)
|
|
||||||
|
|
||||||
# 0.10.0
|
|
||||||
|
|
||||||
* feature: Add a test hook (#180)
|
|
||||||
* feature: `ParseLevel` is now case-insensitive (#326)
|
|
||||||
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
|
||||||
* performance: avoid re-allocations on `WithFields` (#335)
|
|
||||||
|
|
||||||
# 0.9.0
|
|
||||||
|
|
||||||
* logrus/text_formatter: don't emit empty msg
|
|
||||||
* logrus/hooks/airbrake: move out of main repository
|
|
||||||
* logrus/hooks/sentry: move out of main repository
|
|
||||||
* logrus/hooks/papertrail: move out of main repository
|
|
||||||
* logrus/hooks/bugsnag: move out of main repository
|
|
||||||
* logrus/core: run tests with `-race`
|
|
||||||
* logrus/core: detect TTY based on `stderr`
|
|
||||||
* logrus/core: support `WithError` on logger
|
|
||||||
* logrus/core: Solaris support
|
|
||||||
|
|
||||||
# 0.8.7
|
|
||||||
|
|
||||||
* logrus/core: fix possible race (#216)
|
|
||||||
* logrus/doc: small typo fixes and doc improvements
|
|
||||||
|
|
||||||
|
|
||||||
# 0.8.6
|
|
||||||
|
|
||||||
* hooks/raven: allow passing an initialized client
|
|
||||||
|
|
||||||
# 0.8.5
|
|
||||||
|
|
||||||
* logrus/core: revert #208
|
|
||||||
|
|
||||||
# 0.8.4
|
|
||||||
|
|
||||||
* formatter/text: fix data race (#218)
|
|
||||||
|
|
||||||
# 0.8.3
|
|
||||||
|
|
||||||
* logrus/core: fix entry log level (#208)
|
|
||||||
* logrus/core: improve performance of text formatter by 40%
|
|
||||||
* logrus/core: expose `LevelHooks` type
|
|
||||||
* logrus/core: add support for DragonflyBSD and NetBSD
|
|
||||||
* formatter/text: print structs more verbosely
|
|
||||||
|
|
||||||
# 0.8.2
|
|
||||||
|
|
||||||
* logrus: fix more Fatal family functions
|
|
||||||
|
|
||||||
# 0.8.1
|
|
||||||
|
|
||||||
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
|
||||||
|
|
||||||
# 0.8.0
|
|
||||||
|
|
||||||
* logrus: defaults to stderr instead of stdout
|
|
||||||
* hooks/sentry: add special field for `*http.Request`
|
|
||||||
* formatter/text: ignore Windows for colors
|
|
||||||
|
|
||||||
# 0.7.3
|
|
||||||
|
|
||||||
* formatter/\*: allow configuration of timestamp layout
|
|
||||||
|
|
||||||
# 0.7.2
|
|
||||||
|
|
||||||
* formatter/text: Add configuration option for time format (#158)
|
|
21
vendor/github.com/Sirupsen/logrus/LICENSE
generated
vendored
21
vendor/github.com/Sirupsen/logrus/LICENSE
generated
vendored
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Simon Eskildsen
|
|
||||||
|
|
||||||
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.
|
|
461
vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
461
vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
|
@ -1,461 +0,0 @@
|
||||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
|
|
||||||
|
|
||||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
|
||||||
the standard library logger.
|
|
||||||
|
|
||||||
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
|
||||||
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
|
||||||
this caused issues in the community and we needed a standard. Some environments
|
|
||||||
experienced problems with the upper-case variant, so the lower-case was decided.
|
|
||||||
Everything using `logrus` will need to use the lower-case:
|
|
||||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
|
||||||
|
|
||||||
To fix Glide, see [these
|
|
||||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
|
||||||
For an in-depth explanation of the casing issue, see [this
|
|
||||||
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
|
||||||
|
|
||||||
**Are you interested in assisting in maintaining Logrus?** Currently I have a
|
|
||||||
lot of obligations, and I am unable to provide Logrus with the maintainership it
|
|
||||||
needs. If you'd like to help, please reach out to me at `simon at author's
|
|
||||||
username dot com`.
|
|
||||||
|
|
||||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
|
||||||
plain text):
|
|
||||||
|
|
||||||
![Colored](http://i.imgur.com/PY7qMwd.png)
|
|
||||||
|
|
||||||
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
|
||||||
or Splunk:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
|
||||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
|
||||||
|
|
||||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
|
||||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
|
||||||
|
|
||||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
|
||||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
|
||||||
|
|
||||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
|
||||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
|
||||||
|
|
||||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
|
||||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
|
||||||
```
|
|
||||||
|
|
||||||
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
|
||||||
attached, the output is compatible with the
|
|
||||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
|
||||||
|
|
||||||
```text
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
|
||||||
exit status 1
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Case-sensitivity
|
|
||||||
|
|
||||||
The organization's name was changed to lower-case--and this will not be changed
|
|
||||||
back. If you are getting import conflicts due to case sensitivity, please use
|
|
||||||
the lower-case import: `github.com/sirupsen/logrus`.
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
The simplest way to use Logrus is simply the package-level exported logger:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
}).Info("A walrus appears")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
|
||||||
replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"`
|
|
||||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
|
||||||
want:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Log as JSON instead of the default ASCII formatter.
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
|
|
||||||
// Output to stdout instead of the default stderr
|
|
||||||
// Can be any io.Writer, see below for File example
|
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
|
|
||||||
// Only log the warning severity or above.
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 122,
|
|
||||||
}).Warn("The group's number increased tremendously!")
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 100,
|
|
||||||
}).Fatal("The ice breaks!")
|
|
||||||
|
|
||||||
// A common pattern is to re-use fields between logging statements by re-using
|
|
||||||
// the logrus.Entry returned from WithFields()
|
|
||||||
contextLogger := log.WithFields(log.Fields{
|
|
||||||
"common": "this is a common field",
|
|
||||||
"other": "I also should be logged always",
|
|
||||||
})
|
|
||||||
|
|
||||||
contextLogger.Info("I'll be logged with common and other field")
|
|
||||||
contextLogger.Info("Me too")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For more advanced usage such as logging to multiple locations from the same
|
|
||||||
application, you can also create an instance of the `logrus` Logger:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create a new instance of the logger. You can have any number of instances.
|
|
||||||
var log = logrus.New()
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// The API for setting attributes is a little different than the package level
|
|
||||||
// exported logger. See Godoc.
|
|
||||||
log.Out = os.Stdout
|
|
||||||
|
|
||||||
// You could set this to any `io.Writer` such as a file
|
|
||||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
|
||||||
// if err == nil {
|
|
||||||
// log.Out = file
|
|
||||||
// } else {
|
|
||||||
// log.Info("Failed to log to file, using default stderr")
|
|
||||||
// }
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fields
|
|
||||||
|
|
||||||
Logrus encourages careful, structured logging through logging fields instead of
|
|
||||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
|
||||||
to send event %s to topic %s with key %d")`, you should log the much more
|
|
||||||
discoverable:
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"event": event,
|
|
||||||
"topic": topic,
|
|
||||||
"key": key,
|
|
||||||
}).Fatal("Failed to send event")
|
|
||||||
```
|
|
||||||
|
|
||||||
We've found this API forces you to think about logging in a way that produces
|
|
||||||
much more useful logging messages. We've been in countless situations where just
|
|
||||||
a single added field to a log statement that was already there would've saved us
|
|
||||||
hours. The `WithFields` call is optional.
|
|
||||||
|
|
||||||
In general, with Logrus using any of the `printf`-family functions should be
|
|
||||||
seen as a hint you should add a field, however, you can still use the
|
|
||||||
`printf`-family functions with Logrus.
|
|
||||||
|
|
||||||
#### Default Fields
|
|
||||||
|
|
||||||
Often it's helpful to have fields _always_ attached to log statements in an
|
|
||||||
application or parts of one. For example, you may want to always log the
|
|
||||||
`request_id` and `user_ip` in the context of a request. Instead of writing
|
|
||||||
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
|
||||||
every line, you can create a `logrus.Entry` to pass around instead:
|
|
||||||
|
|
||||||
```go
|
|
||||||
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
|
||||||
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
|
||||||
requestLogger.Warn("something not great happened")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Hooks
|
|
||||||
|
|
||||||
You can add hooks for logging levels. For example to send errors to an exception
|
|
||||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
|
||||||
multiple places simultaneously, e.g. syslog.
|
|
||||||
|
|
||||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
|
||||||
`init`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
|
|
||||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
|
||||||
"log/syslog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
|
||||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
|
||||||
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
|
||||||
|
|
||||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to connect to local syslog daemon")
|
|
||||||
} else {
|
|
||||||
log.AddHook(hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
|
||||||
|
|
||||||
A list of currently known of service hook can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
|
||||||
|
|
||||||
|
|
||||||
#### Level logging
|
|
||||||
|
|
||||||
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.Debug("Useful debugging information.")
|
|
||||||
log.Info("Something noteworthy happened!")
|
|
||||||
log.Warn("You should probably take a look at this.")
|
|
||||||
log.Error("Something failed but I'm not quitting.")
|
|
||||||
// Calls os.Exit(1) after logging
|
|
||||||
log.Fatal("Bye.")
|
|
||||||
// Calls panic() after logging
|
|
||||||
log.Panic("I'm bailing.")
|
|
||||||
```
|
|
||||||
|
|
||||||
You can set the logging level on a `Logger`, then it will only log entries with
|
|
||||||
that severity or anything above it:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
```
|
|
||||||
|
|
||||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
|
||||||
environment if your application has that.
|
|
||||||
|
|
||||||
#### Entries
|
|
||||||
|
|
||||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
|
||||||
automatically added to all logging events:
|
|
||||||
|
|
||||||
1. `time`. The timestamp when the entry was created.
|
|
||||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
|
||||||
the `AddFields` call. E.g. `Failed to send event.`
|
|
||||||
3. `level`. The logging level. E.g. `info`.
|
|
||||||
|
|
||||||
#### Environments
|
|
||||||
|
|
||||||
Logrus has no notion of environment.
|
|
||||||
|
|
||||||
If you wish for hooks and formatters to only be used in specific environments,
|
|
||||||
you should handle that yourself. For example, if your application has a global
|
|
||||||
variable `Environment`, which is a string representation of the environment you
|
|
||||||
could do:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// do something here to set environment depending on an environment variable
|
|
||||||
// or command-line flag
|
|
||||||
if Environment == "production" {
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
} else {
|
|
||||||
// The TextFormatter is default, you don't actually have to do this.
|
|
||||||
log.SetFormatter(&log.TextFormatter{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This configuration is how `logrus` was intended to be used, but JSON in
|
|
||||||
production is mostly only useful if you do log aggregation with tools like
|
|
||||||
Splunk or Logstash.
|
|
||||||
|
|
||||||
#### Formatters
|
|
||||||
|
|
||||||
The built-in logging formatters are:
|
|
||||||
|
|
||||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
|
||||||
without colors.
|
|
||||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
|
||||||
field to `true`. To force no colored output even if there is a TTY set the
|
|
||||||
`DisableColors` field to `true`. For Windows, see
|
|
||||||
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
|
||||||
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
|
||||||
truncation set the `DisableLevelTruncation` field to `true`.
|
|
||||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
|
||||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
|
||||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
|
||||||
|
|
||||||
Third party logging formatters:
|
|
||||||
|
|
||||||
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
|
|
||||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
|
||||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
|
||||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
|
||||||
|
|
||||||
You can define your formatter by implementing the `Formatter` interface,
|
|
||||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
|
||||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
|
||||||
default ones (see Entries section above):
|
|
||||||
|
|
||||||
```go
|
|
||||||
type MyJSONFormatter struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetFormatter(new(MyJSONFormatter))
|
|
||||||
|
|
||||||
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
// Note this doesn't include Time, Level and Message which are available on
|
|
||||||
// the Entry. Consult `godoc` on information about those fields or read the
|
|
||||||
// source of the official loggers.
|
|
||||||
serialized, err := json.Marshal(entry.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
|
||||||
}
|
|
||||||
return append(serialized, '\n'), nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Logger as an `io.Writer`
|
|
||||||
|
|
||||||
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
|
||||||
|
|
||||||
```go
|
|
||||||
w := logger.Writer()
|
|
||||||
defer w.Close()
|
|
||||||
|
|
||||||
srv := http.Server{
|
|
||||||
// create a stdlib log.Logger that writes to
|
|
||||||
// logrus.Logger.
|
|
||||||
ErrorLog: log.New(w, "", 0),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Each line written to that writer will be printed the usual way, using formatters
|
|
||||||
and hooks. The level for those entries is `info`.
|
|
||||||
|
|
||||||
This means that we can override the standard library logger easily:
|
|
||||||
|
|
||||||
```go
|
|
||||||
logger := logrus.New()
|
|
||||||
logger.Formatter = &logrus.JSONFormatter{}
|
|
||||||
|
|
||||||
// Use logrus for standard log output
|
|
||||||
// Note that `log` here references stdlib's log
|
|
||||||
// Not logrus imported under the name `log`.
|
|
||||||
log.SetOutput(logger.Writer())
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Rotation
|
|
||||||
|
|
||||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
|
||||||
external program (like `logrotate(8)`) that can compress and delete old log
|
|
||||||
entries. It should not be a feature of the application-level logger.
|
|
||||||
|
|
||||||
#### Tools
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
| ---- | ----------- |
|
|
||||||
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
|
|
||||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
|
||||||
|
|
||||||
#### Testing
|
|
||||||
|
|
||||||
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
|
||||||
|
|
||||||
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
|
|
||||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
|
||||||
|
|
||||||
```go
|
|
||||||
import(
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSomething(t*testing.T){
|
|
||||||
logger, hook := test.NewNullLogger()
|
|
||||||
logger.Error("Helloerror")
|
|
||||||
|
|
||||||
assert.Equal(t, 1, len(hook.Entries))
|
|
||||||
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
|
||||||
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
|
||||||
|
|
||||||
hook.Reset()
|
|
||||||
assert.Nil(t, hook.LastEntry())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fatal handlers
|
|
||||||
|
|
||||||
Logrus can register one or more functions that will be called when any `fatal`
|
|
||||||
level message is logged. The registered handlers will be executed before
|
|
||||||
logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
|
|
||||||
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
|
||||||
|
|
||||||
```
|
|
||||||
...
|
|
||||||
handler := func() {
|
|
||||||
// gracefully shutdown something...
|
|
||||||
}
|
|
||||||
logrus.RegisterExitHandler(handler)
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Thread safety
|
|
||||||
|
|
||||||
By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs.
|
|
||||||
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
|
||||||
|
|
||||||
Situation when locking is not needed includes:
|
|
||||||
|
|
||||||
* You have no hooks registered, or hooks calling is already thread-safe.
|
|
||||||
|
|
||||||
* Writing to logger.Out is already thread-safe, for example:
|
|
||||||
|
|
||||||
1) logger.Out is protected by locks.
|
|
||||||
|
|
||||||
2) logger.Out is a os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allow multi-thread/multi-process writing)
|
|
||||||
|
|
||||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
|
64
vendor/github.com/Sirupsen/logrus/alt_exit.go
generated
vendored
64
vendor/github.com/Sirupsen/logrus/alt_exit.go
generated
vendored
|
@ -1,64 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
// The following code was sourced and modified from the
|
|
||||||
// https://github.com/tebeka/atexit package governed by the following license:
|
|
||||||
//
|
|
||||||
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var handlers = []func(){}
|
|
||||||
|
|
||||||
func runHandler(handler func()) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
handler()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHandlers() {
|
|
||||||
for _, handler := range handlers {
|
|
||||||
runHandler(handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
|
||||||
func Exit(code int) {
|
|
||||||
runHandlers()
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
|
|
||||||
// all handlers. The handlers will also be invoked when any Fatal log entry is
|
|
||||||
// made.
|
|
||||||
//
|
|
||||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
|
||||||
// message but also needs to gracefully shutdown. An example usecase could be
|
|
||||||
// closing database connections, or sending a alert that the application is
|
|
||||||
// closing.
|
|
||||||
func RegisterExitHandler(handler func()) {
|
|
||||||
handlers = append(handlers, handler)
|
|
||||||
}
|
|
14
vendor/github.com/Sirupsen/logrus/appveyor.yml
generated
vendored
14
vendor/github.com/Sirupsen/logrus/appveyor.yml
generated
vendored
|
@ -1,14 +0,0 @@
|
||||||
version: "{build}"
|
|
||||||
platform: x64
|
|
||||||
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
|
||||||
environment:
|
|
||||||
GOPATH: c:\gopath
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
install:
|
|
||||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
|
||||||
- go version
|
|
||||||
build_script:
|
|
||||||
- go get -t
|
|
||||||
- go test
|
|
26
vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
26
vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
|
||||||
|
|
||||||
|
|
||||||
The simplest way to use Logrus is simply the package-level exported logger:
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"number": 1,
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A walrus appears")
|
|
||||||
}
|
|
||||||
|
|
||||||
Output:
|
|
||||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
|
||||||
|
|
||||||
For a full guide visit https://github.com/sirupsen/logrus
|
|
||||||
*/
|
|
||||||
package logrus
|
|
300
vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
300
vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
|
@ -1,300 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var bufferPool *sync.Pool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
bufferPool = &sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return new(bytes.Buffer)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the key when adding errors using WithError.
|
|
||||||
var ErrorKey = "error"
|
|
||||||
|
|
||||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
|
||||||
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
|
||||||
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
|
||||||
// passed around as much as you wish to avoid field duplication.
|
|
||||||
type Entry struct {
|
|
||||||
Logger *Logger
|
|
||||||
|
|
||||||
// Contains all the fields set by the user.
|
|
||||||
Data Fields
|
|
||||||
|
|
||||||
// Time at which the log entry was created
|
|
||||||
Time time.Time
|
|
||||||
|
|
||||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
|
||||||
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
|
||||||
Level Level
|
|
||||||
|
|
||||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
|
||||||
Message string
|
|
||||||
|
|
||||||
// When formatter is called in entry.log(), an Buffer may be set to entry
|
|
||||||
Buffer *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEntry(logger *Logger) *Entry {
|
|
||||||
return &Entry{
|
|
||||||
Logger: logger,
|
|
||||||
// Default is five fields, give a little extra room
|
|
||||||
Data: make(Fields, 5),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the string representation from the reader and ultimately the
|
|
||||||
// formatter.
|
|
||||||
func (entry *Entry) String() (string, error) {
|
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
str := string(serialized)
|
|
||||||
return str, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
|
||||||
func (entry *Entry) WithError(err error) *Entry {
|
|
||||||
return entry.WithField(ErrorKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a single field to the Entry.
|
|
||||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
|
||||||
return entry.WithFields(Fields{key: value})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a map of fields to the Entry.
|
|
||||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
|
||||||
data := make(Fields, len(entry.Data)+len(fields))
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range fields {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overrides the time of the Entry.
|
|
||||||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
|
||||||
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function is not declared with a pointer value because otherwise
|
|
||||||
// race conditions will occur when using multiple goroutines
|
|
||||||
func (entry Entry) log(level Level, msg string) {
|
|
||||||
var buffer *bytes.Buffer
|
|
||||||
|
|
||||||
// Default to now, but allow users to override if they want.
|
|
||||||
//
|
|
||||||
// We don't have to worry about polluting future calls to Entry#log()
|
|
||||||
// with this assignment because this function is declared with a
|
|
||||||
// non-pointer receiver.
|
|
||||||
if entry.Time.IsZero() {
|
|
||||||
entry.Time = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.Level = level
|
|
||||||
entry.Message = msg
|
|
||||||
|
|
||||||
entry.fireHooks()
|
|
||||||
|
|
||||||
buffer = bufferPool.Get().(*bytes.Buffer)
|
|
||||||
buffer.Reset()
|
|
||||||
defer bufferPool.Put(buffer)
|
|
||||||
entry.Buffer = buffer
|
|
||||||
|
|
||||||
entry.write()
|
|
||||||
|
|
||||||
entry.Buffer = nil
|
|
||||||
|
|
||||||
// To avoid Entry#log() returning a value that only would make sense for
|
|
||||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
|
||||||
// directly here.
|
|
||||||
if level <= PanicLevel {
|
|
||||||
panic(&entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) fireHooks() {
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
defer entry.Logger.mu.Unlock()
|
|
||||||
err := entry.Logger.Hooks.Fire(entry.Level, entry)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) write() {
|
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
defer entry.Logger.mu.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
|
||||||
} else {
|
|
||||||
_, err = entry.Logger.Out.Write(serialized)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Debug(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= DebugLevel {
|
|
||||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Print(args ...interface{}) {
|
|
||||||
entry.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Info(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= InfoLevel {
|
|
||||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warn(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= WarnLevel {
|
|
||||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warning(args ...interface{}) {
|
|
||||||
entry.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Error(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= ErrorLevel {
|
|
||||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatal(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= FatalLevel {
|
|
||||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panic(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= PanicLevel {
|
|
||||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
panic(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry Printf family functions
|
|
||||||
|
|
||||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= DebugLevel {
|
|
||||||
entry.Debug(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= InfoLevel {
|
|
||||||
entry.Info(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
|
||||||
entry.Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= WarnLevel {
|
|
||||||
entry.Warn(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
|
||||||
entry.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= ErrorLevel {
|
|
||||||
entry.Error(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= FatalLevel {
|
|
||||||
entry.Fatal(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= PanicLevel {
|
|
||||||
entry.Panic(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry Println family functions
|
|
||||||
|
|
||||||
func (entry *Entry) Debugln(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= DebugLevel {
|
|
||||||
entry.Debug(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Infoln(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= InfoLevel {
|
|
||||||
entry.Info(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Println(args ...interface{}) {
|
|
||||||
entry.Infoln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warnln(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= WarnLevel {
|
|
||||||
entry.Warn(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warningln(args ...interface{}) {
|
|
||||||
entry.Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Errorln(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= ErrorLevel {
|
|
||||||
entry.Error(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= FatalLevel {
|
|
||||||
entry.Fatal(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panicln(args ...interface{}) {
|
|
||||||
if entry.Logger.level() >= PanicLevel {
|
|
||||||
entry.Panic(entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
|
||||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
|
||||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
|
||||||
// string allocation, we do the simplest thing.
|
|
||||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
|
||||||
msg := fmt.Sprintln(args...)
|
|
||||||
return msg[:len(msg)-1]
|
|
||||||
}
|
|
201
vendor/github.com/Sirupsen/logrus/exported.go
generated
vendored
201
vendor/github.com/Sirupsen/logrus/exported.go
generated
vendored
|
@ -1,201 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// std is the name of the standard logger in stdlib `log`
|
|
||||||
std = New()
|
|
||||||
)
|
|
||||||
|
|
||||||
func StandardLogger() *Logger {
|
|
||||||
return std
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOutput sets the standard logger output.
|
|
||||||
func SetOutput(out io.Writer) {
|
|
||||||
std.SetOutput(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFormatter sets the standard logger formatter.
|
|
||||||
func SetFormatter(formatter Formatter) {
|
|
||||||
std.mu.Lock()
|
|
||||||
defer std.mu.Unlock()
|
|
||||||
std.Formatter = formatter
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLevel sets the standard logger level.
|
|
||||||
func SetLevel(level Level) {
|
|
||||||
std.mu.Lock()
|
|
||||||
defer std.mu.Unlock()
|
|
||||||
std.SetLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLevel returns the standard logger level.
|
|
||||||
func GetLevel() Level {
|
|
||||||
std.mu.Lock()
|
|
||||||
defer std.mu.Unlock()
|
|
||||||
return std.level()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHook adds a hook to the standard logger hooks.
|
|
||||||
func AddHook(hook Hook) {
|
|
||||||
std.mu.Lock()
|
|
||||||
defer std.mu.Unlock()
|
|
||||||
std.Hooks.Add(hook)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
|
||||||
func WithError(err error) *Entry {
|
|
||||||
return std.WithField(ErrorKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithField creates an entry from the standard logger and adds a field to
|
|
||||||
// it. If you want multiple fields, use `WithFields`.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithField(key string, value interface{}) *Entry {
|
|
||||||
return std.WithField(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFields creates an entry from the standard logger and adds multiple
|
|
||||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
|
||||||
// once for each field.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithFields(fields Fields) *Entry {
|
|
||||||
return std.WithFields(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTime creats an entry from the standard logger and overrides the time of
|
|
||||||
// logs generated with it.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithTime(t time.Time) *Entry {
|
|
||||||
return std.WithTime(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug logs a message at level Debug on the standard logger.
|
|
||||||
func Debug(args ...interface{}) {
|
|
||||||
std.Debug(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print logs a message at level Info on the standard logger.
|
|
||||||
func Print(args ...interface{}) {
|
|
||||||
std.Print(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info logs a message at level Info on the standard logger.
|
|
||||||
func Info(args ...interface{}) {
|
|
||||||
std.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn logs a message at level Warn on the standard logger.
|
|
||||||
func Warn(args ...interface{}) {
|
|
||||||
std.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning logs a message at level Warn on the standard logger.
|
|
||||||
func Warning(args ...interface{}) {
|
|
||||||
std.Warning(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error logs a message at level Error on the standard logger.
|
|
||||||
func Error(args ...interface{}) {
|
|
||||||
std.Error(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panic logs a message at level Panic on the standard logger.
|
|
||||||
func Panic(args ...interface{}) {
|
|
||||||
std.Panic(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func Fatal(args ...interface{}) {
|
|
||||||
std.Fatal(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf logs a message at level Debug on the standard logger.
|
|
||||||
func Debugf(format string, args ...interface{}) {
|
|
||||||
std.Debugf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf logs a message at level Info on the standard logger.
|
|
||||||
func Printf(format string, args ...interface{}) {
|
|
||||||
std.Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infof logs a message at level Info on the standard logger.
|
|
||||||
func Infof(format string, args ...interface{}) {
|
|
||||||
std.Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warnf logs a message at level Warn on the standard logger.
|
|
||||||
func Warnf(format string, args ...interface{}) {
|
|
||||||
std.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningf logs a message at level Warn on the standard logger.
|
|
||||||
func Warningf(format string, args ...interface{}) {
|
|
||||||
std.Warningf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf logs a message at level Error on the standard logger.
|
|
||||||
func Errorf(format string, args ...interface{}) {
|
|
||||||
std.Errorf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panicf logs a message at level Panic on the standard logger.
|
|
||||||
func Panicf(format string, args ...interface{}) {
|
|
||||||
std.Panicf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func Fatalf(format string, args ...interface{}) {
|
|
||||||
std.Fatalf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugln logs a message at level Debug on the standard logger.
|
|
||||||
func Debugln(args ...interface{}) {
|
|
||||||
std.Debugln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println logs a message at level Info on the standard logger.
|
|
||||||
func Println(args ...interface{}) {
|
|
||||||
std.Println(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infoln logs a message at level Info on the standard logger.
|
|
||||||
func Infoln(args ...interface{}) {
|
|
||||||
std.Infoln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warnln logs a message at level Warn on the standard logger.
|
|
||||||
func Warnln(args ...interface{}) {
|
|
||||||
std.Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningln logs a message at level Warn on the standard logger.
|
|
||||||
func Warningln(args ...interface{}) {
|
|
||||||
std.Warningln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorln logs a message at level Error on the standard logger.
|
|
||||||
func Errorln(args ...interface{}) {
|
|
||||||
std.Errorln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panicln logs a message at level Panic on the standard logger.
|
|
||||||
func Panicln(args ...interface{}) {
|
|
||||||
std.Panicln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func Fatalln(args ...interface{}) {
|
|
||||||
std.Fatalln(args...)
|
|
||||||
}
|
|
51
vendor/github.com/Sirupsen/logrus/formatter.go
generated
vendored
51
vendor/github.com/Sirupsen/logrus/formatter.go
generated
vendored
|
@ -1,51 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
const defaultTimestampFormat = time.RFC3339
|
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
|
||||||
// `Entry`. It exposes all the fields, including the default ones:
|
|
||||||
//
|
|
||||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
|
||||||
// * `entry.Data["time"]`. The timestamp.
|
|
||||||
// * `entry.Data["level"]. The level the entry was logged at.
|
|
||||||
//
|
|
||||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
|
||||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
|
||||||
// logged to `logger.Out`.
|
|
||||||
type Formatter interface {
|
|
||||||
Format(*Entry) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
|
||||||
// dumping it. If this code wasn't there doing:
|
|
||||||
//
|
|
||||||
// logrus.WithField("level", 1).Info("hello")
|
|
||||||
//
|
|
||||||
// Would just silently drop the user provided level. Instead with this code
|
|
||||||
// it'll logged as:
|
|
||||||
//
|
|
||||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
|
||||||
//
|
|
||||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
|
||||||
// avoid code duplication between the two default formatters.
|
|
||||||
func prefixFieldClashes(data Fields, fieldMap FieldMap) {
|
|
||||||
timeKey := fieldMap.resolve(FieldKeyTime)
|
|
||||||
if t, ok := data[timeKey]; ok {
|
|
||||||
data["fields."+timeKey] = t
|
|
||||||
delete(data, timeKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgKey := fieldMap.resolve(FieldKeyMsg)
|
|
||||||
if m, ok := data[msgKey]; ok {
|
|
||||||
data["fields."+msgKey] = m
|
|
||||||
delete(data, msgKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
levelKey := fieldMap.resolve(FieldKeyLevel)
|
|
||||||
if l, ok := data[levelKey]; ok {
|
|
||||||
data["fields."+levelKey] = l
|
|
||||||
delete(data, levelKey)
|
|
||||||
}
|
|
||||||
}
|
|
34
vendor/github.com/Sirupsen/logrus/hooks.go
generated
vendored
34
vendor/github.com/Sirupsen/logrus/hooks.go
generated
vendored
|
@ -1,34 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
// A hook to be fired when logging on the logging levels returned from
|
|
||||||
// `Levels()` on your implementation of the interface. Note that this is not
|
|
||||||
// fired in a goroutine or a channel with workers, you should handle such
|
|
||||||
// functionality yourself if your call is non-blocking and you don't wish for
|
|
||||||
// the logging calls for levels returned from `Levels()` to block.
|
|
||||||
type Hook interface {
|
|
||||||
Levels() []Level
|
|
||||||
Fire(*Entry) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal type for storing the hooks on a logger instance.
|
|
||||||
type LevelHooks map[Level][]Hook
|
|
||||||
|
|
||||||
// Add a hook to an instance of logger. This is called with
|
|
||||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
|
||||||
func (hooks LevelHooks) Add(hook Hook) {
|
|
||||||
for _, level := range hook.Levels() {
|
|
||||||
hooks[level] = append(hooks[level], hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
|
||||||
// appropriate hooks for a log entry.
|
|
||||||
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
|
||||||
for _, hook := range hooks[level] {
|
|
||||||
if err := hook.Fire(entry); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
89
vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
89
vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
|
@ -1,89 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fieldKey string
|
|
||||||
|
|
||||||
// FieldMap allows customization of the key names for default fields.
|
|
||||||
type FieldMap map[fieldKey]string
|
|
||||||
|
|
||||||
// Default key names for the default fields
|
|
||||||
const (
|
|
||||||
FieldKeyMsg = "msg"
|
|
||||||
FieldKeyLevel = "level"
|
|
||||||
FieldKeyTime = "time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f FieldMap) resolve(key fieldKey) string {
|
|
||||||
if k, ok := f[key]; ok {
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONFormatter formats logs into parsable json
|
|
||||||
type JSONFormatter struct {
|
|
||||||
// TimestampFormat sets the format used for marshaling timestamps.
|
|
||||||
TimestampFormat string
|
|
||||||
|
|
||||||
// DisableTimestamp allows disabling automatic timestamps in output
|
|
||||||
DisableTimestamp bool
|
|
||||||
|
|
||||||
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
|
|
||||||
DataKey string
|
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for default fields.
|
|
||||||
// As an example:
|
|
||||||
// formatter := &JSONFormatter{
|
|
||||||
// FieldMap: FieldMap{
|
|
||||||
// FieldKeyTime: "@timestamp",
|
|
||||||
// FieldKeyLevel: "@level",
|
|
||||||
// FieldKeyMsg: "@message",
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
FieldMap FieldMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format renders a single log entry
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
data := make(Fields, len(entry.Data)+3)
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case error:
|
|
||||||
// Otherwise errors are ignored by `encoding/json`
|
|
||||||
// https://github.com/sirupsen/logrus/issues/137
|
|
||||||
data[k] = v.Error()
|
|
||||||
default:
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.DataKey != "" {
|
|
||||||
newData := make(Fields, 4)
|
|
||||||
newData[f.DataKey] = data
|
|
||||||
data = newData
|
|
||||||
}
|
|
||||||
|
|
||||||
prefixFieldClashes(data, f.FieldMap)
|
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
|
||||||
if timestampFormat == "" {
|
|
||||||
timestampFormat = defaultTimestampFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.DisableTimestamp {
|
|
||||||
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
|
||||||
}
|
|
||||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
|
||||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
|
||||||
|
|
||||||
serialized, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
|
||||||
}
|
|
||||||
return append(serialized, '\n'), nil
|
|
||||||
}
|
|
337
vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
337
vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
|
@ -1,337 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
|
||||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
|
||||||
// something more adventorous, such as logging to Kafka.
|
|
||||||
Out io.Writer
|
|
||||||
// Hooks for the logger instance. These allow firing events based on logging
|
|
||||||
// levels and log entries. For example, to send errors to an error tracking
|
|
||||||
// service, log to StatsD or dump the core on fatal errors.
|
|
||||||
Hooks LevelHooks
|
|
||||||
// All log entries pass through the formatter before logged to Out. The
|
|
||||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
|
||||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
|
||||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
|
||||||
// own that implements the `Formatter` interface, see the `README` or included
|
|
||||||
// formatters for examples.
|
|
||||||
Formatter Formatter
|
|
||||||
// The logging level the logger should log at. This is typically (and defaults
|
|
||||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
|
||||||
// logged.
|
|
||||||
Level Level
|
|
||||||
// Used to sync writing to the log. Locking is enabled by Default
|
|
||||||
mu MutexWrap
|
|
||||||
// Reusable empty entry
|
|
||||||
entryPool sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
type MutexWrap struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
disabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Lock() {
|
|
||||||
if !mw.disabled {
|
|
||||||
mw.lock.Lock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Unlock() {
|
|
||||||
if !mw.disabled {
|
|
||||||
mw.lock.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Disable() {
|
|
||||||
mw.disabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
|
||||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
|
||||||
// instantiate your own:
|
|
||||||
//
|
|
||||||
// var log = &Logger{
|
|
||||||
// Out: os.Stderr,
|
|
||||||
// Formatter: new(JSONFormatter),
|
|
||||||
// Hooks: make(LevelHooks),
|
|
||||||
// Level: logrus.DebugLevel,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// It's recommended to make this a global instance called `log`.
|
|
||||||
func New() *Logger {
|
|
||||||
return &Logger{
|
|
||||||
Out: os.Stderr,
|
|
||||||
Formatter: new(TextFormatter),
|
|
||||||
Hooks: make(LevelHooks),
|
|
||||||
Level: InfoLevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) newEntry() *Entry {
|
|
||||||
entry, ok := logger.entryPool.Get().(*Entry)
|
|
||||||
if ok {
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
return NewEntry(logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) releaseEntry(entry *Entry) {
|
|
||||||
logger.entryPool.Put(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a field to the log entry, note that it doesn't log until you call
|
|
||||||
// Debug, Print, Info, Warn, Error, Fatal or Panic. It only creates a log entry.
|
|
||||||
// If you want multiple fields, use `WithFields`.
|
|
||||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithField(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
|
||||||
// each `Field`.
|
|
||||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithFields(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an error as single field to the log entry. All it does is call
|
|
||||||
// `WithError` for the given `error`.
|
|
||||||
func (logger *Logger) WithError(err error) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overrides the time of the log entry.
|
|
||||||
func (logger *Logger) WithTime(t time.Time) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithTime(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
|
||||||
if logger.level() >= DebugLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Debugf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
|
||||||
if logger.level() >= InfoLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Infof(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Printf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
|
||||||
if logger.level() >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warnf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
|
||||||
if logger.level() >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warnf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
|
||||||
if logger.level() >= ErrorLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Errorf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
|
||||||
if logger.level() >= FatalLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Fatalf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
|
||||||
if logger.level() >= PanicLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Panicf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debug(args ...interface{}) {
|
|
||||||
if logger.level() >= DebugLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Debug(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Info(args ...interface{}) {
|
|
||||||
if logger.level() >= InfoLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Info(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Print(args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Info(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warn(args ...interface{}) {
|
|
||||||
if logger.level() >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warn(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warning(args ...interface{}) {
|
|
||||||
if logger.level() >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warn(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Error(args ...interface{}) {
|
|
||||||
if logger.level() >= ErrorLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Error(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatal(args ...interface{}) {
|
|
||||||
if logger.level() >= FatalLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Fatal(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panic(args ...interface{}) {
|
|
||||||
if logger.level() >= PanicLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Panic(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugln(args ...interface{}) {
|
|
||||||
if logger.level() >= DebugLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Debugln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infoln(args ...interface{}) {
|
|
||||||
if logger.level() >= InfoLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Infoln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Println(args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Println(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnln(args ...interface{}) {
|
|
||||||
if logger.level() >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warnln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningln(args ...interface{}) {
|
|
||||||
if logger.level() >= WarnLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Warnln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorln(args ...interface{}) {
|
|
||||||
if logger.level() >= ErrorLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Errorln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
|
||||||
if logger.level() >= FatalLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Fatalln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicln(args ...interface{}) {
|
|
||||||
if logger.level() >= PanicLevel {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Panicln(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//When file is opened with appending mode, it's safe to
|
|
||||||
//write concurrently to a file (within 4k message on Linux).
|
|
||||||
//In these cases user can choose to disable the lock.
|
|
||||||
func (logger *Logger) SetNoLock() {
|
|
||||||
logger.mu.Disable()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) level() Level {
|
|
||||||
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) SetLevel(level Level) {
|
|
||||||
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) SetOutput(out io.Writer) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.Out = out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) AddHook(hook Hook) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.Hooks.Add(hook)
|
|
||||||
}
|
|
143
vendor/github.com/Sirupsen/logrus/logrus.go
generated
vendored
143
vendor/github.com/Sirupsen/logrus/logrus.go
generated
vendored
|
@ -1,143 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fields type, used to pass to `WithFields`.
|
|
||||||
type Fields map[string]interface{}
|
|
||||||
|
|
||||||
// Level type
|
|
||||||
type Level uint32
|
|
||||||
|
|
||||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
|
||||||
func (level Level) String() string {
|
|
||||||
switch level {
|
|
||||||
case DebugLevel:
|
|
||||||
return "debug"
|
|
||||||
case InfoLevel:
|
|
||||||
return "info"
|
|
||||||
case WarnLevel:
|
|
||||||
return "warning"
|
|
||||||
case ErrorLevel:
|
|
||||||
return "error"
|
|
||||||
case FatalLevel:
|
|
||||||
return "fatal"
|
|
||||||
case PanicLevel:
|
|
||||||
return "panic"
|
|
||||||
}
|
|
||||||
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
|
||||||
func ParseLevel(lvl string) (Level, error) {
|
|
||||||
switch strings.ToLower(lvl) {
|
|
||||||
case "panic":
|
|
||||||
return PanicLevel, nil
|
|
||||||
case "fatal":
|
|
||||||
return FatalLevel, nil
|
|
||||||
case "error":
|
|
||||||
return ErrorLevel, nil
|
|
||||||
case "warn", "warning":
|
|
||||||
return WarnLevel, nil
|
|
||||||
case "info":
|
|
||||||
return InfoLevel, nil
|
|
||||||
case "debug":
|
|
||||||
return DebugLevel, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var l Level
|
|
||||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A constant exposing all logging levels
|
|
||||||
var AllLevels = []Level{
|
|
||||||
PanicLevel,
|
|
||||||
FatalLevel,
|
|
||||||
ErrorLevel,
|
|
||||||
WarnLevel,
|
|
||||||
InfoLevel,
|
|
||||||
DebugLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the different logging levels. You can set the logging level to log
|
|
||||||
// on your instance of logger, obtained with `logrus.New()`.
|
|
||||||
const (
|
|
||||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
|
||||||
// message passed to Debug, Info, ...
|
|
||||||
PanicLevel Level = iota
|
|
||||||
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
|
||||||
// logging level is set to Panic.
|
|
||||||
FatalLevel
|
|
||||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
|
||||||
// Commonly used for hooks to send errors to an error tracking service.
|
|
||||||
ErrorLevel
|
|
||||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
|
||||||
WarnLevel
|
|
||||||
// InfoLevel level. General operational entries about what's going on inside the
|
|
||||||
// application.
|
|
||||||
InfoLevel
|
|
||||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
|
||||||
DebugLevel
|
|
||||||
)
|
|
||||||
|
|
||||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
|
||||||
var (
|
|
||||||
_ StdLogger = &log.Logger{}
|
|
||||||
_ StdLogger = &Entry{}
|
|
||||||
_ StdLogger = &Logger{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// StdLogger is what your logrus-enabled library should take, that way
|
|
||||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
|
||||||
// interface, this is the closest we get, unfortunately.
|
|
||||||
type StdLogger interface {
|
|
||||||
Print(...interface{})
|
|
||||||
Printf(string, ...interface{})
|
|
||||||
Println(...interface{})
|
|
||||||
|
|
||||||
Fatal(...interface{})
|
|
||||||
Fatalf(string, ...interface{})
|
|
||||||
Fatalln(...interface{})
|
|
||||||
|
|
||||||
Panic(...interface{})
|
|
||||||
Panicf(string, ...interface{})
|
|
||||||
Panicln(...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// The FieldLogger interface generalizes the Entry and Logger types
|
|
||||||
type FieldLogger interface {
|
|
||||||
WithField(key string, value interface{}) *Entry
|
|
||||||
WithFields(fields Fields) *Entry
|
|
||||||
WithError(err error) *Entry
|
|
||||||
|
|
||||||
Debugf(format string, args ...interface{})
|
|
||||||
Infof(format string, args ...interface{})
|
|
||||||
Printf(format string, args ...interface{})
|
|
||||||
Warnf(format string, args ...interface{})
|
|
||||||
Warningf(format string, args ...interface{})
|
|
||||||
Errorf(format string, args ...interface{})
|
|
||||||
Fatalf(format string, args ...interface{})
|
|
||||||
Panicf(format string, args ...interface{})
|
|
||||||
|
|
||||||
Debug(args ...interface{})
|
|
||||||
Info(args ...interface{})
|
|
||||||
Print(args ...interface{})
|
|
||||||
Warn(args ...interface{})
|
|
||||||
Warning(args ...interface{})
|
|
||||||
Error(args ...interface{})
|
|
||||||
Fatal(args ...interface{})
|
|
||||||
Panic(args ...interface{})
|
|
||||||
|
|
||||||
Debugln(args ...interface{})
|
|
||||||
Infoln(args ...interface{})
|
|
||||||
Println(args ...interface{})
|
|
||||||
Warnln(args ...interface{})
|
|
||||||
Warningln(args ...interface{})
|
|
||||||
Errorln(args ...interface{})
|
|
||||||
Fatalln(args ...interface{})
|
|
||||||
Panicln(args ...interface{})
|
|
||||||
}
|
|
10
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
10
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
|
@ -1,10 +0,0 @@
|
||||||
// +build darwin freebsd openbsd netbsd dragonfly
|
|
||||||
// +build !appengine,!gopherjs
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const ioctlReadTermios = unix.TIOCGETA
|
|
||||||
|
|
||||||
type Termios unix.Termios
|
|
11
vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go
generated
vendored
11
vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go
generated
vendored
|
@ -1,11 +0,0 @@
|
||||||
// +build appengine gopherjs
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
19
vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go
generated
vendored
19
vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go
generated
vendored
|
@ -1,19 +0,0 @@
|
||||||
// +build !appengine,!gopherjs
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
switch v := w.(type) {
|
|
||||||
case *os.File:
|
|
||||||
return terminal.IsTerminal(int(v.Fd()))
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
14
vendor/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
14
vendor/github.com/Sirupsen/logrus/terminal_linux.go
generated
vendored
|
@ -1,14 +0,0 @@
|
||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2013 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 !appengine,!gopherjs
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const ioctlReadTermios = unix.TCGETS
|
|
||||||
|
|
||||||
type Termios unix.Termios
|
|
195
vendor/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
195
vendor/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
|
@ -1,195 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
nocolor = 0
|
|
||||||
red = 31
|
|
||||||
green = 32
|
|
||||||
yellow = 33
|
|
||||||
blue = 36
|
|
||||||
gray = 37
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
baseTimestamp time.Time
|
|
||||||
emptyFieldMap FieldMap
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
baseTimestamp = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextFormatter formats logs into text
|
|
||||||
type TextFormatter struct {
|
|
||||||
// Set to true to bypass checking for a TTY before outputting colors.
|
|
||||||
ForceColors bool
|
|
||||||
|
|
||||||
// Force disabling colors.
|
|
||||||
DisableColors bool
|
|
||||||
|
|
||||||
// Disable timestamp logging. useful when output is redirected to logging
|
|
||||||
// system that already adds timestamps.
|
|
||||||
DisableTimestamp bool
|
|
||||||
|
|
||||||
// Enable logging the full timestamp when a TTY is attached instead of just
|
|
||||||
// the time passed since beginning of execution.
|
|
||||||
FullTimestamp bool
|
|
||||||
|
|
||||||
// TimestampFormat to use for display when a full timestamp is printed
|
|
||||||
TimestampFormat string
|
|
||||||
|
|
||||||
// The fields are sorted by default for a consistent output. For applications
|
|
||||||
// that log extremely frequently and don't use the JSON formatter this may not
|
|
||||||
// be desired.
|
|
||||||
DisableSorting bool
|
|
||||||
|
|
||||||
// Disables the truncation of the level text to 4 characters.
|
|
||||||
DisableLevelTruncation bool
|
|
||||||
|
|
||||||
// QuoteEmptyFields will wrap empty fields in quotes if true
|
|
||||||
QuoteEmptyFields bool
|
|
||||||
|
|
||||||
// Whether the logger's out is to a terminal
|
|
||||||
isTerminal bool
|
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for default fields.
|
|
||||||
// As an example:
|
|
||||||
// formatter := &TextFormatter{
|
|
||||||
// FieldMap: FieldMap{
|
|
||||||
// FieldKeyTime: "@timestamp",
|
|
||||||
// FieldKeyLevel: "@level",
|
|
||||||
// FieldKeyMsg: "@message"}}
|
|
||||||
FieldMap FieldMap
|
|
||||||
|
|
||||||
sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) init(entry *Entry) {
|
|
||||||
if entry.Logger != nil {
|
|
||||||
f.isTerminal = checkIfTerminal(entry.Logger.Out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format renders a single log entry
|
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
prefixFieldClashes(entry.Data, f.FieldMap)
|
|
||||||
|
|
||||||
keys := make([]string, 0, len(entry.Data))
|
|
||||||
for k := range entry.Data {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.DisableSorting {
|
|
||||||
sort.Strings(keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
var b *bytes.Buffer
|
|
||||||
if entry.Buffer != nil {
|
|
||||||
b = entry.Buffer
|
|
||||||
} else {
|
|
||||||
b = &bytes.Buffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Do(func() { f.init(entry) })
|
|
||||||
|
|
||||||
isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
|
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
|
||||||
if timestampFormat == "" {
|
|
||||||
timestampFormat = defaultTimestampFormat
|
|
||||||
}
|
|
||||||
if isColored {
|
|
||||||
f.printColored(b, entry, keys, timestampFormat)
|
|
||||||
} else {
|
|
||||||
if !f.DisableTimestamp {
|
|
||||||
f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyTime), entry.Time.Format(timestampFormat))
|
|
||||||
}
|
|
||||||
f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyLevel), entry.Level.String())
|
|
||||||
if entry.Message != "" {
|
|
||||||
f.appendKeyValue(b, f.FieldMap.resolve(FieldKeyMsg), entry.Message)
|
|
||||||
}
|
|
||||||
for _, key := range keys {
|
|
||||||
f.appendKeyValue(b, key, entry.Data[key])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteByte('\n')
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
|
|
||||||
var levelColor int
|
|
||||||
switch entry.Level {
|
|
||||||
case DebugLevel:
|
|
||||||
levelColor = gray
|
|
||||||
case WarnLevel:
|
|
||||||
levelColor = yellow
|
|
||||||
case ErrorLevel, FatalLevel, PanicLevel:
|
|
||||||
levelColor = red
|
|
||||||
default:
|
|
||||||
levelColor = blue
|
|
||||||
}
|
|
||||||
|
|
||||||
levelText := strings.ToUpper(entry.Level.String())
|
|
||||||
if !f.DisableLevelTruncation {
|
|
||||||
levelText = levelText[0:4]
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.DisableTimestamp {
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
|
|
||||||
} else if !f.FullTimestamp {
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
|
|
||||||
}
|
|
||||||
for _, k := range keys {
|
|
||||||
v := entry.Data[k]
|
|
||||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
|
||||||
f.appendValue(b, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) needsQuoting(text string) bool {
|
|
||||||
if f.QuoteEmptyFields && len(text) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, ch := range text {
|
|
||||||
if !((ch >= 'a' && ch <= 'z') ||
|
|
||||||
(ch >= 'A' && ch <= 'Z') ||
|
|
||||||
(ch >= '0' && ch <= '9') ||
|
|
||||||
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
|
||||||
if b.Len() > 0 {
|
|
||||||
b.WriteByte(' ')
|
|
||||||
}
|
|
||||||
b.WriteString(key)
|
|
||||||
b.WriteByte('=')
|
|
||||||
f.appendValue(b, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
|
||||||
stringVal, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
stringVal = fmt.Sprint(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.needsQuoting(stringVal) {
|
|
||||||
b.WriteString(stringVal)
|
|
||||||
} else {
|
|
||||||
b.WriteString(fmt.Sprintf("%q", stringVal))
|
|
||||||
}
|
|
||||||
}
|
|
62
vendor/github.com/Sirupsen/logrus/writer.go
generated
vendored
62
vendor/github.com/Sirupsen/logrus/writer.go
generated
vendored
|
@ -1,62 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (logger *Logger) Writer() *io.PipeWriter {
|
|
||||||
return logger.WriterLevel(InfoLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
|
||||||
return NewEntry(logger).WriterLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Writer() *io.PipeWriter {
|
|
||||||
return entry.WriterLevel(InfoLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
|
||||||
reader, writer := io.Pipe()
|
|
||||||
|
|
||||||
var printFunc func(args ...interface{})
|
|
||||||
|
|
||||||
switch level {
|
|
||||||
case DebugLevel:
|
|
||||||
printFunc = entry.Debug
|
|
||||||
case InfoLevel:
|
|
||||||
printFunc = entry.Info
|
|
||||||
case WarnLevel:
|
|
||||||
printFunc = entry.Warn
|
|
||||||
case ErrorLevel:
|
|
||||||
printFunc = entry.Error
|
|
||||||
case FatalLevel:
|
|
||||||
printFunc = entry.Fatal
|
|
||||||
case PanicLevel:
|
|
||||||
printFunc = entry.Panic
|
|
||||||
default:
|
|
||||||
printFunc = entry.Print
|
|
||||||
}
|
|
||||||
|
|
||||||
go entry.writerScanner(reader, printFunc)
|
|
||||||
runtime.SetFinalizer(writer, writerFinalizer)
|
|
||||||
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
|
||||||
scanner := bufio.NewScanner(reader)
|
|
||||||
for scanner.Scan() {
|
|
||||||
printFunc(scanner.Text())
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
entry.Errorf("Error while reading from Writer: %s", err)
|
|
||||||
}
|
|
||||||
reader.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func writerFinalizer(writer *io.PipeWriter) {
|
|
||||||
writer.Close()
|
|
||||||
}
|
|
27
vendor/github.com/alecthomas/template/LICENSE
generated
vendored
27
vendor/github.com/alecthomas/template/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 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.
|
|
25
vendor/github.com/alecthomas/template/README.md
generated
vendored
25
vendor/github.com/alecthomas/template/README.md
generated
vendored
|
@ -1,25 +0,0 @@
|
||||||
# Go's `text/template` package with newline elision
|
|
||||||
|
|
||||||
This is a fork of Go 1.4's [text/template](http://golang.org/pkg/text/template/) package with one addition: a backslash immediately after a closing delimiter will delete all subsequent newlines until a non-newline.
|
|
||||||
|
|
||||||
eg.
|
|
||||||
|
|
||||||
```
|
|
||||||
{{if true}}\
|
|
||||||
hello
|
|
||||||
{{end}}\
|
|
||||||
```
|
|
||||||
|
|
||||||
Will result in:
|
|
||||||
|
|
||||||
```
|
|
||||||
hello\n
|
|
||||||
```
|
|
||||||
|
|
||||||
Rather than:
|
|
||||||
|
|
||||||
```
|
|
||||||
\n
|
|
||||||
hello\n
|
|
||||||
\n
|
|
||||||
```
|
|
406
vendor/github.com/alecthomas/template/doc.go
generated
vendored
406
vendor/github.com/alecthomas/template/doc.go
generated
vendored
|
@ -1,406 +0,0 @@
|
||||||
// Copyright 2011 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 template implements data-driven templates for generating textual output.
|
|
||||||
|
|
||||||
To generate HTML output, see package html/template, which has the same interface
|
|
||||||
as this package but automatically secures HTML output against certain attacks.
|
|
||||||
|
|
||||||
Templates are executed by applying them to a data structure. Annotations in the
|
|
||||||
template refer to elements of the data structure (typically a field of a struct
|
|
||||||
or a key in a map) to control execution and derive values to be displayed.
|
|
||||||
Execution of the template walks the structure and sets the cursor, represented
|
|
||||||
by a period '.' and called "dot", to the value at the current location in the
|
|
||||||
structure as execution proceeds.
|
|
||||||
|
|
||||||
The input text for a template is UTF-8-encoded text in any format.
|
|
||||||
"Actions"--data evaluations or control structures--are delimited by
|
|
||||||
"{{" and "}}"; all text outside actions is copied to the output unchanged.
|
|
||||||
Actions may not span newlines, although comments can.
|
|
||||||
|
|
||||||
Once parsed, a template may be executed safely in parallel.
|
|
||||||
|
|
||||||
Here is a trivial example that prints "17 items are made of wool".
|
|
||||||
|
|
||||||
type Inventory struct {
|
|
||||||
Material string
|
|
||||||
Count uint
|
|
||||||
}
|
|
||||||
sweaters := Inventory{"wool", 17}
|
|
||||||
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
|
|
||||||
if err != nil { panic(err) }
|
|
||||||
err = tmpl.Execute(os.Stdout, sweaters)
|
|
||||||
if err != nil { panic(err) }
|
|
||||||
|
|
||||||
More intricate examples appear below.
|
|
||||||
|
|
||||||
Actions
|
|
||||||
|
|
||||||
Here is the list of actions. "Arguments" and "pipelines" are evaluations of
|
|
||||||
data, defined in detail below.
|
|
||||||
|
|
||||||
*/
|
|
||||||
// {{/* a comment */}}
|
|
||||||
// A comment; discarded. May contain newlines.
|
|
||||||
// Comments do not nest and must start and end at the
|
|
||||||
// delimiters, as shown here.
|
|
||||||
/*
|
|
||||||
|
|
||||||
{{pipeline}}
|
|
||||||
The default textual representation of the value of the pipeline
|
|
||||||
is copied to the output.
|
|
||||||
|
|
||||||
{{if pipeline}} T1 {{end}}
|
|
||||||
If the value of the pipeline is empty, no output is generated;
|
|
||||||
otherwise, T1 is executed. The empty values are false, 0, any
|
|
||||||
nil pointer or interface value, and any array, slice, map, or
|
|
||||||
string of length zero.
|
|
||||||
Dot is unaffected.
|
|
||||||
|
|
||||||
{{if pipeline}} T1 {{else}} T0 {{end}}
|
|
||||||
If the value of the pipeline is empty, T0 is executed;
|
|
||||||
otherwise, T1 is executed. Dot is unaffected.
|
|
||||||
|
|
||||||
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
|
|
||||||
To simplify the appearance of if-else chains, the else action
|
|
||||||
of an if may include another if directly; the effect is exactly
|
|
||||||
the same as writing
|
|
||||||
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
|
|
||||||
|
|
||||||
{{range pipeline}} T1 {{end}}
|
|
||||||
The value of the pipeline must be an array, slice, map, or channel.
|
|
||||||
If the value of the pipeline has length zero, nothing is output;
|
|
||||||
otherwise, dot is set to the successive elements of the array,
|
|
||||||
slice, or map and T1 is executed. If the value is a map and the
|
|
||||||
keys are of basic type with a defined order ("comparable"), the
|
|
||||||
elements will be visited in sorted key order.
|
|
||||||
|
|
||||||
{{range pipeline}} T1 {{else}} T0 {{end}}
|
|
||||||
The value of the pipeline must be an array, slice, map, or channel.
|
|
||||||
If the value of the pipeline has length zero, dot is unaffected and
|
|
||||||
T0 is executed; otherwise, dot is set to the successive elements
|
|
||||||
of the array, slice, or map and T1 is executed.
|
|
||||||
|
|
||||||
{{template "name"}}
|
|
||||||
The template with the specified name is executed with nil data.
|
|
||||||
|
|
||||||
{{template "name" pipeline}}
|
|
||||||
The template with the specified name is executed with dot set
|
|
||||||
to the value of the pipeline.
|
|
||||||
|
|
||||||
{{with pipeline}} T1 {{end}}
|
|
||||||
If the value of the pipeline is empty, no output is generated;
|
|
||||||
otherwise, dot is set to the value of the pipeline and T1 is
|
|
||||||
executed.
|
|
||||||
|
|
||||||
{{with pipeline}} T1 {{else}} T0 {{end}}
|
|
||||||
If the value of the pipeline is empty, dot is unaffected and T0
|
|
||||||
is executed; otherwise, dot is set to the value of the pipeline
|
|
||||||
and T1 is executed.
|
|
||||||
|
|
||||||
Arguments
|
|
||||||
|
|
||||||
An argument is a simple value, denoted by one of the following.
|
|
||||||
|
|
||||||
- A boolean, string, character, integer, floating-point, imaginary
|
|
||||||
or complex constant in Go syntax. These behave like Go's untyped
|
|
||||||
constants, although raw strings may not span newlines.
|
|
||||||
- The keyword nil, representing an untyped Go nil.
|
|
||||||
- The character '.' (period):
|
|
||||||
.
|
|
||||||
The result is the value of dot.
|
|
||||||
- A variable name, which is a (possibly empty) alphanumeric string
|
|
||||||
preceded by a dollar sign, such as
|
|
||||||
$piOver2
|
|
||||||
or
|
|
||||||
$
|
|
||||||
The result is the value of the variable.
|
|
||||||
Variables are described below.
|
|
||||||
- The name of a field of the data, which must be a struct, preceded
|
|
||||||
by a period, such as
|
|
||||||
.Field
|
|
||||||
The result is the value of the field. Field invocations may be
|
|
||||||
chained:
|
|
||||||
.Field1.Field2
|
|
||||||
Fields can also be evaluated on variables, including chaining:
|
|
||||||
$x.Field1.Field2
|
|
||||||
- The name of a key of the data, which must be a map, preceded
|
|
||||||
by a period, such as
|
|
||||||
.Key
|
|
||||||
The result is the map element value indexed by the key.
|
|
||||||
Key invocations may be chained and combined with fields to any
|
|
||||||
depth:
|
|
||||||
.Field1.Key1.Field2.Key2
|
|
||||||
Although the key must be an alphanumeric identifier, unlike with
|
|
||||||
field names they do not need to start with an upper case letter.
|
|
||||||
Keys can also be evaluated on variables, including chaining:
|
|
||||||
$x.key1.key2
|
|
||||||
- The name of a niladic method of the data, preceded by a period,
|
|
||||||
such as
|
|
||||||
.Method
|
|
||||||
The result is the value of invoking the method with dot as the
|
|
||||||
receiver, dot.Method(). Such a method must have one return value (of
|
|
||||||
any type) or two return values, the second of which is an error.
|
|
||||||
If it has two and the returned error is non-nil, execution terminates
|
|
||||||
and an error is returned to the caller as the value of Execute.
|
|
||||||
Method invocations may be chained and combined with fields and keys
|
|
||||||
to any depth:
|
|
||||||
.Field1.Key1.Method1.Field2.Key2.Method2
|
|
||||||
Methods can also be evaluated on variables, including chaining:
|
|
||||||
$x.Method1.Field
|
|
||||||
- The name of a niladic function, such as
|
|
||||||
fun
|
|
||||||
The result is the value of invoking the function, fun(). The return
|
|
||||||
types and values behave as in methods. Functions and function
|
|
||||||
names are described below.
|
|
||||||
- A parenthesized instance of one the above, for grouping. The result
|
|
||||||
may be accessed by a field or map key invocation.
|
|
||||||
print (.F1 arg1) (.F2 arg2)
|
|
||||||
(.StructValuedMethod "arg").Field
|
|
||||||
|
|
||||||
Arguments may evaluate to any type; if they are pointers the implementation
|
|
||||||
automatically indirects to the base type when required.
|
|
||||||
If an evaluation yields a function value, such as a function-valued
|
|
||||||
field of a struct, the function is not invoked automatically, but it
|
|
||||||
can be used as a truth value for an if action and the like. To invoke
|
|
||||||
it, use the call function, defined below.
|
|
||||||
|
|
||||||
A pipeline is a possibly chained sequence of "commands". A command is a simple
|
|
||||||
value (argument) or a function or method call, possibly with multiple arguments:
|
|
||||||
|
|
||||||
Argument
|
|
||||||
The result is the value of evaluating the argument.
|
|
||||||
.Method [Argument...]
|
|
||||||
The method can be alone or the last element of a chain but,
|
|
||||||
unlike methods in the middle of a chain, it can take arguments.
|
|
||||||
The result is the value of calling the method with the
|
|
||||||
arguments:
|
|
||||||
dot.Method(Argument1, etc.)
|
|
||||||
functionName [Argument...]
|
|
||||||
The result is the value of calling the function associated
|
|
||||||
with the name:
|
|
||||||
function(Argument1, etc.)
|
|
||||||
Functions and function names are described below.
|
|
||||||
|
|
||||||
Pipelines
|
|
||||||
|
|
||||||
A pipeline may be "chained" by separating a sequence of commands with pipeline
|
|
||||||
characters '|'. In a chained pipeline, the result of the each command is
|
|
||||||
passed as the last argument of the following command. The output of the final
|
|
||||||
command in the pipeline is the value of the pipeline.
|
|
||||||
|
|
||||||
The output of a command will be either one value or two values, the second of
|
|
||||||
which has type error. If that second value is present and evaluates to
|
|
||||||
non-nil, execution terminates and the error is returned to the caller of
|
|
||||||
Execute.
|
|
||||||
|
|
||||||
Variables
|
|
||||||
|
|
||||||
A pipeline inside an action may initialize a variable to capture the result.
|
|
||||||
The initialization has syntax
|
|
||||||
|
|
||||||
$variable := pipeline
|
|
||||||
|
|
||||||
where $variable is the name of the variable. An action that declares a
|
|
||||||
variable produces no output.
|
|
||||||
|
|
||||||
If a "range" action initializes a variable, the variable is set to the
|
|
||||||
successive elements of the iteration. Also, a "range" may declare two
|
|
||||||
variables, separated by a comma:
|
|
||||||
|
|
||||||
range $index, $element := pipeline
|
|
||||||
|
|
||||||
in which case $index and $element are set to the successive values of the
|
|
||||||
array/slice index or map key and element, respectively. Note that if there is
|
|
||||||
only one variable, it is assigned the element; this is opposite to the
|
|
||||||
convention in Go range clauses.
|
|
||||||
|
|
||||||
A variable's scope extends to the "end" action of the control structure ("if",
|
|
||||||
"with", or "range") in which it is declared, or to the end of the template if
|
|
||||||
there is no such control structure. A template invocation does not inherit
|
|
||||||
variables from the point of its invocation.
|
|
||||||
|
|
||||||
When execution begins, $ is set to the data argument passed to Execute, that is,
|
|
||||||
to the starting value of dot.
|
|
||||||
|
|
||||||
Examples
|
|
||||||
|
|
||||||
Here are some example one-line templates demonstrating pipelines and variables.
|
|
||||||
All produce the quoted word "output":
|
|
||||||
|
|
||||||
{{"\"output\""}}
|
|
||||||
A string constant.
|
|
||||||
{{`"output"`}}
|
|
||||||
A raw string constant.
|
|
||||||
{{printf "%q" "output"}}
|
|
||||||
A function call.
|
|
||||||
{{"output" | printf "%q"}}
|
|
||||||
A function call whose final argument comes from the previous
|
|
||||||
command.
|
|
||||||
{{printf "%q" (print "out" "put")}}
|
|
||||||
A parenthesized argument.
|
|
||||||
{{"put" | printf "%s%s" "out" | printf "%q"}}
|
|
||||||
A more elaborate call.
|
|
||||||
{{"output" | printf "%s" | printf "%q"}}
|
|
||||||
A longer chain.
|
|
||||||
{{with "output"}}{{printf "%q" .}}{{end}}
|
|
||||||
A with action using dot.
|
|
||||||
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
|
||||||
A with action that creates and uses a variable.
|
|
||||||
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
|
||||||
A with action that uses the variable in another action.
|
|
||||||
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
|
||||||
The same, but pipelined.
|
|
||||||
|
|
||||||
Functions
|
|
||||||
|
|
||||||
During execution functions are found in two function maps: first in the
|
|
||||||
template, then in the global function map. By default, no functions are defined
|
|
||||||
in the template but the Funcs method can be used to add them.
|
|
||||||
|
|
||||||
Predefined global functions are named as follows.
|
|
||||||
|
|
||||||
and
|
|
||||||
Returns the boolean AND of its arguments by returning the
|
|
||||||
first empty argument or the last argument, that is,
|
|
||||||
"and x y" behaves as "if x then y else x". All the
|
|
||||||
arguments are evaluated.
|
|
||||||
call
|
|
||||||
Returns the result of calling the first argument, which
|
|
||||||
must be a function, with the remaining arguments as parameters.
|
|
||||||
Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
|
|
||||||
Y is a func-valued field, map entry, or the like.
|
|
||||||
The first argument must be the result of an evaluation
|
|
||||||
that yields a value of function type (as distinct from
|
|
||||||
a predefined function such as print). The function must
|
|
||||||
return either one or two result values, the second of which
|
|
||||||
is of type error. If the arguments don't match the function
|
|
||||||
or the returned error value is non-nil, execution stops.
|
|
||||||
html
|
|
||||||
Returns the escaped HTML equivalent of the textual
|
|
||||||
representation of its arguments.
|
|
||||||
index
|
|
||||||
Returns the result of indexing its first argument by the
|
|
||||||
following arguments. Thus "index x 1 2 3" is, in Go syntax,
|
|
||||||
x[1][2][3]. Each indexed item must be a map, slice, or array.
|
|
||||||
js
|
|
||||||
Returns the escaped JavaScript equivalent of the textual
|
|
||||||
representation of its arguments.
|
|
||||||
len
|
|
||||||
Returns the integer length of its argument.
|
|
||||||
not
|
|
||||||
Returns the boolean negation of its single argument.
|
|
||||||
or
|
|
||||||
Returns the boolean OR of its arguments by returning the
|
|
||||||
first non-empty argument or the last argument, that is,
|
|
||||||
"or x y" behaves as "if x then x else y". All the
|
|
||||||
arguments are evaluated.
|
|
||||||
print
|
|
||||||
An alias for fmt.Sprint
|
|
||||||
printf
|
|
||||||
An alias for fmt.Sprintf
|
|
||||||
println
|
|
||||||
An alias for fmt.Sprintln
|
|
||||||
urlquery
|
|
||||||
Returns the escaped value of the textual representation of
|
|
||||||
its arguments in a form suitable for embedding in a URL query.
|
|
||||||
|
|
||||||
The boolean functions take any zero value to be false and a non-zero
|
|
||||||
value to be true.
|
|
||||||
|
|
||||||
There is also a set of binary comparison operators defined as
|
|
||||||
functions:
|
|
||||||
|
|
||||||
eq
|
|
||||||
Returns the boolean truth of arg1 == arg2
|
|
||||||
ne
|
|
||||||
Returns the boolean truth of arg1 != arg2
|
|
||||||
lt
|
|
||||||
Returns the boolean truth of arg1 < arg2
|
|
||||||
le
|
|
||||||
Returns the boolean truth of arg1 <= arg2
|
|
||||||
gt
|
|
||||||
Returns the boolean truth of arg1 > arg2
|
|
||||||
ge
|
|
||||||
Returns the boolean truth of arg1 >= arg2
|
|
||||||
|
|
||||||
For simpler multi-way equality tests, eq (only) accepts two or more
|
|
||||||
arguments and compares the second and subsequent to the first,
|
|
||||||
returning in effect
|
|
||||||
|
|
||||||
arg1==arg2 || arg1==arg3 || arg1==arg4 ...
|
|
||||||
|
|
||||||
(Unlike with || in Go, however, eq is a function call and all the
|
|
||||||
arguments will be evaluated.)
|
|
||||||
|
|
||||||
The comparison functions work on basic types only (or named basic
|
|
||||||
types, such as "type Celsius float32"). They implement the Go rules
|
|
||||||
for comparison of values, except that size and exact type are
|
|
||||||
ignored, so any integer value, signed or unsigned, may be compared
|
|
||||||
with any other integer value. (The arithmetic value is compared,
|
|
||||||
not the bit pattern, so all negative integers are less than all
|
|
||||||
unsigned integers.) However, as usual, one may not compare an int
|
|
||||||
with a float32 and so on.
|
|
||||||
|
|
||||||
Associated templates
|
|
||||||
|
|
||||||
Each template is named by a string specified when it is created. Also, each
|
|
||||||
template is associated with zero or more other templates that it may invoke by
|
|
||||||
name; such associations are transitive and form a name space of templates.
|
|
||||||
|
|
||||||
A template may use a template invocation to instantiate another associated
|
|
||||||
template; see the explanation of the "template" action above. The name must be
|
|
||||||
that of a template associated with the template that contains the invocation.
|
|
||||||
|
|
||||||
Nested template definitions
|
|
||||||
|
|
||||||
When parsing a template, another template may be defined and associated with the
|
|
||||||
template being parsed. Template definitions must appear at the top level of the
|
|
||||||
template, much like global variables in a Go program.
|
|
||||||
|
|
||||||
The syntax of such definitions is to surround each template declaration with a
|
|
||||||
"define" and "end" action.
|
|
||||||
|
|
||||||
The define action names the template being created by providing a string
|
|
||||||
constant. Here is a simple example:
|
|
||||||
|
|
||||||
`{{define "T1"}}ONE{{end}}
|
|
||||||
{{define "T2"}}TWO{{end}}
|
|
||||||
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
|
|
||||||
{{template "T3"}}`
|
|
||||||
|
|
||||||
This defines two templates, T1 and T2, and a third T3 that invokes the other two
|
|
||||||
when it is executed. Finally it invokes T3. If executed this template will
|
|
||||||
produce the text
|
|
||||||
|
|
||||||
ONE TWO
|
|
||||||
|
|
||||||
By construction, a template may reside in only one association. If it's
|
|
||||||
necessary to have a template addressable from multiple associations, the
|
|
||||||
template definition must be parsed multiple times to create distinct *Template
|
|
||||||
values, or must be copied with the Clone or AddParseTree method.
|
|
||||||
|
|
||||||
Parse may be called multiple times to assemble the various associated templates;
|
|
||||||
see the ParseFiles and ParseGlob functions and methods for simple ways to parse
|
|
||||||
related templates stored in files.
|
|
||||||
|
|
||||||
A template may be executed directly or through ExecuteTemplate, which executes
|
|
||||||
an associated template identified by name. To invoke our example above, we
|
|
||||||
might write,
|
|
||||||
|
|
||||||
err := tmpl.Execute(os.Stdout, "no data needed")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("execution failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
or to invoke a particular template explicitly by name,
|
|
||||||
|
|
||||||
err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("execution failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
package template
|
|
845
vendor/github.com/alecthomas/template/exec.go
generated
vendored
845
vendor/github.com/alecthomas/template/exec.go
generated
vendored
|
@ -1,845 +0,0 @@
|
||||||
// Copyright 2011 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 template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/alecthomas/template/parse"
|
|
||||||
)
|
|
||||||
|
|
||||||
// state represents the state of an execution. It's not part of the
|
|
||||||
// template so that multiple executions of the same template
|
|
||||||
// can execute in parallel.
|
|
||||||
type state struct {
|
|
||||||
tmpl *Template
|
|
||||||
wr io.Writer
|
|
||||||
node parse.Node // current node, for errors
|
|
||||||
vars []variable // push-down stack of variable values.
|
|
||||||
}
|
|
||||||
|
|
||||||
// variable holds the dynamic value of a variable such as $, $x etc.
|
|
||||||
type variable struct {
|
|
||||||
name string
|
|
||||||
value reflect.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// push pushes a new variable on the stack.
|
|
||||||
func (s *state) push(name string, value reflect.Value) {
|
|
||||||
s.vars = append(s.vars, variable{name, value})
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark returns the length of the variable stack.
|
|
||||||
func (s *state) mark() int {
|
|
||||||
return len(s.vars)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pop pops the variable stack up to the mark.
|
|
||||||
func (s *state) pop(mark int) {
|
|
||||||
s.vars = s.vars[0:mark]
|
|
||||||
}
|
|
||||||
|
|
||||||
// setVar overwrites the top-nth variable on the stack. Used by range iterations.
|
|
||||||
func (s *state) setVar(n int, value reflect.Value) {
|
|
||||||
s.vars[len(s.vars)-n].value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// varValue returns the value of the named variable.
|
|
||||||
func (s *state) varValue(name string) reflect.Value {
|
|
||||||
for i := s.mark() - 1; i >= 0; i-- {
|
|
||||||
if s.vars[i].name == name {
|
|
||||||
return s.vars[i].value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.errorf("undefined variable: %s", name)
|
|
||||||
return zero
|
|
||||||
}
|
|
||||||
|
|
||||||
var zero reflect.Value
|
|
||||||
|
|
||||||
// at marks the state to be on node n, for error reporting.
|
|
||||||
func (s *state) at(node parse.Node) {
|
|
||||||
s.node = node
|
|
||||||
}
|
|
||||||
|
|
||||||
// doublePercent returns the string with %'s replaced by %%, if necessary,
|
|
||||||
// so it can be used safely inside a Printf format string.
|
|
||||||
func doublePercent(str string) string {
|
|
||||||
if strings.Contains(str, "%") {
|
|
||||||
str = strings.Replace(str, "%", "%%", -1)
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorf formats the error and terminates processing.
|
|
||||||
func (s *state) errorf(format string, args ...interface{}) {
|
|
||||||
name := doublePercent(s.tmpl.Name())
|
|
||||||
if s.node == nil {
|
|
||||||
format = fmt.Sprintf("template: %s: %s", name, format)
|
|
||||||
} else {
|
|
||||||
location, context := s.tmpl.ErrorContext(s.node)
|
|
||||||
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
|
|
||||||
}
|
|
||||||
panic(fmt.Errorf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// errRecover is the handler that turns panics into returns from the top
|
|
||||||
// level of Parse.
|
|
||||||
func errRecover(errp *error) {
|
|
||||||
e := recover()
|
|
||||||
if e != nil {
|
|
||||||
switch err := e.(type) {
|
|
||||||
case runtime.Error:
|
|
||||||
panic(e)
|
|
||||||
case error:
|
|
||||||
*errp = err
|
|
||||||
default:
|
|
||||||
panic(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecuteTemplate applies the template associated with t that has the given name
|
|
||||||
// to the specified data object and writes the output to wr.
|
|
||||||
// If an error occurs executing the template or writing its output,
|
|
||||||
// execution stops, but partial results may already have been written to
|
|
||||||
// the output writer.
|
|
||||||
// A template may be executed safely in parallel.
|
|
||||||
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
|
||||||
tmpl := t.tmpl[name]
|
|
||||||
if tmpl == nil {
|
|
||||||
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
|
|
||||||
}
|
|
||||||
return tmpl.Execute(wr, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute applies a parsed template to the specified data object,
|
|
||||||
// and writes the output to wr.
|
|
||||||
// If an error occurs executing the template or writing its output,
|
|
||||||
// execution stops, but partial results may already have been written to
|
|
||||||
// the output writer.
|
|
||||||
// A template may be executed safely in parallel.
|
|
||||||
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
|
||||||
defer errRecover(&err)
|
|
||||||
value := reflect.ValueOf(data)
|
|
||||||
state := &state{
|
|
||||||
tmpl: t,
|
|
||||||
wr: wr,
|
|
||||||
vars: []variable{{"$", value}},
|
|
||||||
}
|
|
||||||
t.init()
|
|
||||||
if t.Tree == nil || t.Root == nil {
|
|
||||||
var b bytes.Buffer
|
|
||||||
for name, tmpl := range t.tmpl {
|
|
||||||
if tmpl.Tree == nil || tmpl.Root == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if b.Len() > 0 {
|
|
||||||
b.WriteString(", ")
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&b, "%q", name)
|
|
||||||
}
|
|
||||||
var s string
|
|
||||||
if b.Len() > 0 {
|
|
||||||
s = "; defined templates are: " + b.String()
|
|
||||||
}
|
|
||||||
state.errorf("%q is an incomplete or empty template%s", t.Name(), s)
|
|
||||||
}
|
|
||||||
state.walk(value, t.Root)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk functions step through the major pieces of the template structure,
|
|
||||||
// generating output as they go.
|
|
||||||
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
|
||||||
s.at(node)
|
|
||||||
switch node := node.(type) {
|
|
||||||
case *parse.ActionNode:
|
|
||||||
// Do not pop variables so they persist until next end.
|
|
||||||
// Also, if the action declares variables, don't print the result.
|
|
||||||
val := s.evalPipeline(dot, node.Pipe)
|
|
||||||
if len(node.Pipe.Decl) == 0 {
|
|
||||||
s.printValue(node, val)
|
|
||||||
}
|
|
||||||
case *parse.IfNode:
|
|
||||||
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
|
||||||
case *parse.ListNode:
|
|
||||||
for _, node := range node.Nodes {
|
|
||||||
s.walk(dot, node)
|
|
||||||
}
|
|
||||||
case *parse.RangeNode:
|
|
||||||
s.walkRange(dot, node)
|
|
||||||
case *parse.TemplateNode:
|
|
||||||
s.walkTemplate(dot, node)
|
|
||||||
case *parse.TextNode:
|
|
||||||
if _, err := s.wr.Write(node.Text); err != nil {
|
|
||||||
s.errorf("%s", err)
|
|
||||||
}
|
|
||||||
case *parse.WithNode:
|
|
||||||
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
|
|
||||||
default:
|
|
||||||
s.errorf("unknown node: %s", node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
|
||||||
// are identical in behavior except that 'with' sets dot.
|
|
||||||
func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
|
|
||||||
defer s.pop(s.mark())
|
|
||||||
val := s.evalPipeline(dot, pipe)
|
|
||||||
truth, ok := isTrue(val)
|
|
||||||
if !ok {
|
|
||||||
s.errorf("if/with can't use %v", val)
|
|
||||||
}
|
|
||||||
if truth {
|
|
||||||
if typ == parse.NodeWith {
|
|
||||||
s.walk(val, list)
|
|
||||||
} else {
|
|
||||||
s.walk(dot, list)
|
|
||||||
}
|
|
||||||
} else if elseList != nil {
|
|
||||||
s.walk(dot, elseList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
|
||||||
// and whether the value has a meaningful truth value.
|
|
||||||
func isTrue(val reflect.Value) (truth, ok bool) {
|
|
||||||
if !val.IsValid() {
|
|
||||||
// Something like var x interface{}, never set. It's a form of nil.
|
|
||||||
return false, true
|
|
||||||
}
|
|
||||||
switch val.Kind() {
|
|
||||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
|
||||||
truth = val.Len() > 0
|
|
||||||
case reflect.Bool:
|
|
||||||
truth = val.Bool()
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
truth = val.Complex() != 0
|
|
||||||
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
|
|
||||||
truth = !val.IsNil()
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
truth = val.Int() != 0
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
truth = val.Float() != 0
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
truth = val.Uint() != 0
|
|
||||||
case reflect.Struct:
|
|
||||||
truth = true // Struct values are always true.
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return truth, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
|
||||||
s.at(r)
|
|
||||||
defer s.pop(s.mark())
|
|
||||||
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
|
||||||
// mark top of stack before any variables in the body are pushed.
|
|
||||||
mark := s.mark()
|
|
||||||
oneIteration := func(index, elem reflect.Value) {
|
|
||||||
// Set top var (lexically the second if there are two) to the element.
|
|
||||||
if len(r.Pipe.Decl) > 0 {
|
|
||||||
s.setVar(1, elem)
|
|
||||||
}
|
|
||||||
// Set next var (lexically the first if there are two) to the index.
|
|
||||||
if len(r.Pipe.Decl) > 1 {
|
|
||||||
s.setVar(2, index)
|
|
||||||
}
|
|
||||||
s.walk(elem, r.List)
|
|
||||||
s.pop(mark)
|
|
||||||
}
|
|
||||||
switch val.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
if val.Len() == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for i := 0; i < val.Len(); i++ {
|
|
||||||
oneIteration(reflect.ValueOf(i), val.Index(i))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case reflect.Map:
|
|
||||||
if val.Len() == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for _, key := range sortKeys(val.MapKeys()) {
|
|
||||||
oneIteration(key, val.MapIndex(key))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case reflect.Chan:
|
|
||||||
if val.IsNil() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
i := 0
|
|
||||||
for ; ; i++ {
|
|
||||||
elem, ok := val.Recv()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
oneIteration(reflect.ValueOf(i), elem)
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case reflect.Invalid:
|
|
||||||
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
|
||||||
default:
|
|
||||||
s.errorf("range can't iterate over %v", val)
|
|
||||||
}
|
|
||||||
if r.ElseList != nil {
|
|
||||||
s.walk(dot, r.ElseList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
|
|
||||||
s.at(t)
|
|
||||||
tmpl := s.tmpl.tmpl[t.Name]
|
|
||||||
if tmpl == nil {
|
|
||||||
s.errorf("template %q not defined", t.Name)
|
|
||||||
}
|
|
||||||
// Variables declared by the pipeline persist.
|
|
||||||
dot = s.evalPipeline(dot, t.Pipe)
|
|
||||||
newState := *s
|
|
||||||
newState.tmpl = tmpl
|
|
||||||
// No dynamic scoping: template invocations inherit no variables.
|
|
||||||
newState.vars = []variable{{"$", dot}}
|
|
||||||
newState.walk(dot, tmpl.Root)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Eval functions evaluate pipelines, commands, and their elements and extract
|
|
||||||
// values from the data structure by examining fields, calling methods, and so on.
|
|
||||||
// The printing of those values happens only through walk functions.
|
|
||||||
|
|
||||||
// evalPipeline returns the value acquired by evaluating a pipeline. If the
|
|
||||||
// pipeline has a variable declaration, the variable will be pushed on the
|
|
||||||
// stack. Callers should therefore pop the stack after they are finished
|
|
||||||
// executing commands depending on the pipeline value.
|
|
||||||
func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
|
|
||||||
if pipe == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.at(pipe)
|
|
||||||
for _, cmd := range pipe.Cmds {
|
|
||||||
value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
|
|
||||||
// If the object has type interface{}, dig down one level to the thing inside.
|
|
||||||
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
|
|
||||||
value = reflect.ValueOf(value.Interface()) // lovely!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, variable := range pipe.Decl {
|
|
||||||
s.push(variable.Ident[0], value)
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) notAFunction(args []parse.Node, final reflect.Value) {
|
|
||||||
if len(args) > 1 || final.IsValid() {
|
|
||||||
s.errorf("can't give argument to non-function %s", args[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
|
|
||||||
firstWord := cmd.Args[0]
|
|
||||||
switch n := firstWord.(type) {
|
|
||||||
case *parse.FieldNode:
|
|
||||||
return s.evalFieldNode(dot, n, cmd.Args, final)
|
|
||||||
case *parse.ChainNode:
|
|
||||||
return s.evalChainNode(dot, n, cmd.Args, final)
|
|
||||||
case *parse.IdentifierNode:
|
|
||||||
// Must be a function.
|
|
||||||
return s.evalFunction(dot, n, cmd, cmd.Args, final)
|
|
||||||
case *parse.PipeNode:
|
|
||||||
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
|
|
||||||
return s.evalPipeline(dot, n)
|
|
||||||
case *parse.VariableNode:
|
|
||||||
return s.evalVariableNode(dot, n, cmd.Args, final)
|
|
||||||
}
|
|
||||||
s.at(firstWord)
|
|
||||||
s.notAFunction(cmd.Args, final)
|
|
||||||
switch word := firstWord.(type) {
|
|
||||||
case *parse.BoolNode:
|
|
||||||
return reflect.ValueOf(word.True)
|
|
||||||
case *parse.DotNode:
|
|
||||||
return dot
|
|
||||||
case *parse.NilNode:
|
|
||||||
s.errorf("nil is not a command")
|
|
||||||
case *parse.NumberNode:
|
|
||||||
return s.idealConstant(word)
|
|
||||||
case *parse.StringNode:
|
|
||||||
return reflect.ValueOf(word.Text)
|
|
||||||
}
|
|
||||||
s.errorf("can't evaluate command %q", firstWord)
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
|
|
||||||
// idealConstant is called to return the value of a number in a context where
|
|
||||||
// we don't know the type. In that case, the syntax of the number tells us
|
|
||||||
// its type, and we use Go rules to resolve. Note there is no such thing as
|
|
||||||
// a uint ideal constant in this situation - the value must be of int type.
|
|
||||||
func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
|
|
||||||
// These are ideal constants but we don't know the type
|
|
||||||
// and we have no context. (If it was a method argument,
|
|
||||||
// we'd know what we need.) The syntax guides us to some extent.
|
|
||||||
s.at(constant)
|
|
||||||
switch {
|
|
||||||
case constant.IsComplex:
|
|
||||||
return reflect.ValueOf(constant.Complex128) // incontrovertible.
|
|
||||||
case constant.IsFloat && !isHexConstant(constant.Text) && strings.IndexAny(constant.Text, ".eE") >= 0:
|
|
||||||
return reflect.ValueOf(constant.Float64)
|
|
||||||
case constant.IsInt:
|
|
||||||
n := int(constant.Int64)
|
|
||||||
if int64(n) != constant.Int64 {
|
|
||||||
s.errorf("%s overflows int", constant.Text)
|
|
||||||
}
|
|
||||||
return reflect.ValueOf(n)
|
|
||||||
case constant.IsUint:
|
|
||||||
s.errorf("%s overflows int", constant.Text)
|
|
||||||
}
|
|
||||||
return zero
|
|
||||||
}
|
|
||||||
|
|
||||||
func isHexConstant(s string) bool {
|
|
||||||
return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
|
|
||||||
s.at(field)
|
|
||||||
return s.evalFieldChain(dot, dot, field, field.Ident, args, final)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value {
|
|
||||||
s.at(chain)
|
|
||||||
// (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields.
|
|
||||||
pipe := s.evalArg(dot, nil, chain.Node)
|
|
||||||
if len(chain.Field) == 0 {
|
|
||||||
s.errorf("internal error: no fields in evalChainNode")
|
|
||||||
}
|
|
||||||
return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
|
|
||||||
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
|
|
||||||
s.at(variable)
|
|
||||||
value := s.varValue(variable.Ident[0])
|
|
||||||
if len(variable.Ident) == 1 {
|
|
||||||
s.notAFunction(args, final)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final)
|
|
||||||
}
|
|
||||||
|
|
||||||
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
|
|
||||||
// dot is the environment in which to evaluate arguments, while
|
|
||||||
// receiver is the value being walked along the chain.
|
|
||||||
func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
|
|
||||||
n := len(ident)
|
|
||||||
for i := 0; i < n-1; i++ {
|
|
||||||
receiver = s.evalField(dot, ident[i], node, nil, zero, receiver)
|
|
||||||
}
|
|
||||||
// Now if it's a method, it gets the arguments.
|
|
||||||
return s.evalField(dot, ident[n-1], node, args, final, receiver)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
|
|
||||||
s.at(node)
|
|
||||||
name := node.Ident
|
|
||||||
function, ok := findFunction(name, s.tmpl)
|
|
||||||
if !ok {
|
|
||||||
s.errorf("%q is not a defined function", name)
|
|
||||||
}
|
|
||||||
return s.evalCall(dot, function, cmd, name, args, final)
|
|
||||||
}
|
|
||||||
|
|
||||||
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
|
||||||
// The 'final' argument represents the return value from the preceding
|
|
||||||
// value of the pipeline, if any.
|
|
||||||
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
|
|
||||||
if !receiver.IsValid() {
|
|
||||||
return zero
|
|
||||||
}
|
|
||||||
typ := receiver.Type()
|
|
||||||
receiver, _ = indirect(receiver)
|
|
||||||
// Unless it's an interface, need to get to a value of type *T to guarantee
|
|
||||||
// we see all methods of T and *T.
|
|
||||||
ptr := receiver
|
|
||||||
if ptr.Kind() != reflect.Interface && ptr.CanAddr() {
|
|
||||||
ptr = ptr.Addr()
|
|
||||||
}
|
|
||||||
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
|
||||||
return s.evalCall(dot, method, node, fieldName, args, final)
|
|
||||||
}
|
|
||||||
hasArgs := len(args) > 1 || final.IsValid()
|
|
||||||
// It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil.
|
|
||||||
receiver, isNil := indirect(receiver)
|
|
||||||
if isNil {
|
|
||||||
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
|
|
||||||
}
|
|
||||||
switch receiver.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
tField, ok := receiver.Type().FieldByName(fieldName)
|
|
||||||
if ok {
|
|
||||||
field := receiver.FieldByIndex(tField.Index)
|
|
||||||
if tField.PkgPath != "" { // field is unexported
|
|
||||||
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
|
|
||||||
}
|
|
||||||
// If it's a function, we must call it.
|
|
||||||
if hasArgs {
|
|
||||||
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
|
|
||||||
}
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
s.errorf("%s is not a field of struct type %s", fieldName, typ)
|
|
||||||
case reflect.Map:
|
|
||||||
// If it's a map, attempt to use the field name as a key.
|
|
||||||
nameVal := reflect.ValueOf(fieldName)
|
|
||||||
if nameVal.Type().AssignableTo(receiver.Type().Key()) {
|
|
||||||
if hasArgs {
|
|
||||||
s.errorf("%s is not a method but has arguments", fieldName)
|
|
||||||
}
|
|
||||||
return receiver.MapIndex(nameVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
|
||||||
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
|
||||||
)
|
|
||||||
|
|
||||||
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
|
||||||
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
|
||||||
// as the function itself.
|
|
||||||
func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
|
||||||
if args != nil {
|
|
||||||
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
|
||||||
}
|
|
||||||
typ := fun.Type()
|
|
||||||
numIn := len(args)
|
|
||||||
if final.IsValid() {
|
|
||||||
numIn++
|
|
||||||
}
|
|
||||||
numFixed := len(args)
|
|
||||||
if typ.IsVariadic() {
|
|
||||||
numFixed = typ.NumIn() - 1 // last arg is the variadic one.
|
|
||||||
if numIn < numFixed {
|
|
||||||
s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
|
|
||||||
}
|
|
||||||
} else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
|
|
||||||
s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args))
|
|
||||||
}
|
|
||||||
if !goodFunc(typ) {
|
|
||||||
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
|
||||||
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
|
||||||
}
|
|
||||||
// Build the arg list.
|
|
||||||
argv := make([]reflect.Value, numIn)
|
|
||||||
// Args must be evaluated. Fixed args first.
|
|
||||||
i := 0
|
|
||||||
for ; i < numFixed && i < len(args); i++ {
|
|
||||||
argv[i] = s.evalArg(dot, typ.In(i), args[i])
|
|
||||||
}
|
|
||||||
// Now the ... args.
|
|
||||||
if typ.IsVariadic() {
|
|
||||||
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
|
|
||||||
for ; i < len(args); i++ {
|
|
||||||
argv[i] = s.evalArg(dot, argType, args[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add final value if necessary.
|
|
||||||
if final.IsValid() {
|
|
||||||
t := typ.In(typ.NumIn() - 1)
|
|
||||||
if typ.IsVariadic() {
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
argv[i] = s.validateType(final, t)
|
|
||||||
}
|
|
||||||
result := fun.Call(argv)
|
|
||||||
// If we have an error that is not nil, stop execution and return that error to the caller.
|
|
||||||
if len(result) == 2 && !result[1].IsNil() {
|
|
||||||
s.at(node)
|
|
||||||
s.errorf("error calling %s: %s", name, result[1].Interface().(error))
|
|
||||||
}
|
|
||||||
return result[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
|
||||||
func canBeNil(typ reflect.Type) bool {
|
|
||||||
switch typ.Kind() {
|
|
||||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateType guarantees that the value is valid and assignable to the type.
|
|
||||||
func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
|
|
||||||
if !value.IsValid() {
|
|
||||||
if typ == nil || canBeNil(typ) {
|
|
||||||
// An untyped nil interface{}. Accept as a proper nil value.
|
|
||||||
return reflect.Zero(typ)
|
|
||||||
}
|
|
||||||
s.errorf("invalid value; expected %s", typ)
|
|
||||||
}
|
|
||||||
if typ != nil && !value.Type().AssignableTo(typ) {
|
|
||||||
if value.Kind() == reflect.Interface && !value.IsNil() {
|
|
||||||
value = value.Elem()
|
|
||||||
if value.Type().AssignableTo(typ) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
}
|
|
||||||
// Does one dereference or indirection work? We could do more, as we
|
|
||||||
// do with method receivers, but that gets messy and method receivers
|
|
||||||
// are much more constrained, so it makes more sense there than here.
|
|
||||||
// Besides, one is almost always all you need.
|
|
||||||
switch {
|
|
||||||
case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
|
|
||||||
value = value.Elem()
|
|
||||||
if !value.IsValid() {
|
|
||||||
s.errorf("dereference of nil pointer of type %s", typ)
|
|
||||||
}
|
|
||||||
case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
|
|
||||||
value = value.Addr()
|
|
||||||
default:
|
|
||||||
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
|
|
||||||
s.at(n)
|
|
||||||
switch arg := n.(type) {
|
|
||||||
case *parse.DotNode:
|
|
||||||
return s.validateType(dot, typ)
|
|
||||||
case *parse.NilNode:
|
|
||||||
if canBeNil(typ) {
|
|
||||||
return reflect.Zero(typ)
|
|
||||||
}
|
|
||||||
s.errorf("cannot assign nil to %s", typ)
|
|
||||||
case *parse.FieldNode:
|
|
||||||
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ)
|
|
||||||
case *parse.VariableNode:
|
|
||||||
return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
|
|
||||||
case *parse.PipeNode:
|
|
||||||
return s.validateType(s.evalPipeline(dot, arg), typ)
|
|
||||||
case *parse.IdentifierNode:
|
|
||||||
return s.evalFunction(dot, arg, arg, nil, zero)
|
|
||||||
case *parse.ChainNode:
|
|
||||||
return s.validateType(s.evalChainNode(dot, arg, nil, zero), typ)
|
|
||||||
}
|
|
||||||
switch typ.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return s.evalBool(typ, n)
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
return s.evalComplex(typ, n)
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return s.evalFloat(typ, n)
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return s.evalInteger(typ, n)
|
|
||||||
case reflect.Interface:
|
|
||||||
if typ.NumMethod() == 0 {
|
|
||||||
return s.evalEmptyInterface(dot, n)
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
return s.evalString(typ, n)
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
return s.evalUnsignedInteger(typ, n)
|
|
||||||
}
|
|
||||||
s.errorf("can't handle %s for arg of type %s", n, typ)
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
|
|
||||||
s.at(n)
|
|
||||||
if n, ok := n.(*parse.BoolNode); ok {
|
|
||||||
value := reflect.New(typ).Elem()
|
|
||||||
value.SetBool(n.True)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
s.errorf("expected bool; found %s", n)
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
|
|
||||||
s.at(n)
|
|
||||||
if n, ok := n.(*parse.StringNode); ok {
|
|
||||||
value := reflect.New(typ).Elem()
|
|
||||||
value.SetString(n.Text)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
s.errorf("expected string; found %s", n)
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
|
||||||
s.at(n)
|
|
||||||
if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
|
|
||||||
value := reflect.New(typ).Elem()
|
|
||||||
value.SetInt(n.Int64)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
s.errorf("expected integer; found %s", n)
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
|
||||||
s.at(n)
|
|
||||||
if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
|
|
||||||
value := reflect.New(typ).Elem()
|
|
||||||
value.SetUint(n.Uint64)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
s.errorf("expected unsigned integer; found %s", n)
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
|
|
||||||
s.at(n)
|
|
||||||
if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
|
|
||||||
value := reflect.New(typ).Elem()
|
|
||||||
value.SetFloat(n.Float64)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
s.errorf("expected float; found %s", n)
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
|
|
||||||
if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
|
|
||||||
value := reflect.New(typ).Elem()
|
|
||||||
value.SetComplex(n.Complex128)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
s.errorf("expected complex; found %s", n)
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
|
|
||||||
s.at(n)
|
|
||||||
switch n := n.(type) {
|
|
||||||
case *parse.BoolNode:
|
|
||||||
return reflect.ValueOf(n.True)
|
|
||||||
case *parse.DotNode:
|
|
||||||
return dot
|
|
||||||
case *parse.FieldNode:
|
|
||||||
return s.evalFieldNode(dot, n, nil, zero)
|
|
||||||
case *parse.IdentifierNode:
|
|
||||||
return s.evalFunction(dot, n, n, nil, zero)
|
|
||||||
case *parse.NilNode:
|
|
||||||
// NilNode is handled in evalArg, the only place that calls here.
|
|
||||||
s.errorf("evalEmptyInterface: nil (can't happen)")
|
|
||||||
case *parse.NumberNode:
|
|
||||||
return s.idealConstant(n)
|
|
||||||
case *parse.StringNode:
|
|
||||||
return reflect.ValueOf(n.Text)
|
|
||||||
case *parse.VariableNode:
|
|
||||||
return s.evalVariableNode(dot, n, nil, zero)
|
|
||||||
case *parse.PipeNode:
|
|
||||||
return s.evalPipeline(dot, n)
|
|
||||||
}
|
|
||||||
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
|
|
||||||
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
|
|
||||||
// We indirect through pointers and empty interfaces (only) because
|
|
||||||
// non-empty interfaces have methods we might need.
|
|
||||||
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
|
||||||
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
|
||||||
if v.IsNil() {
|
|
||||||
return v, true
|
|
||||||
}
|
|
||||||
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// printValue writes the textual representation of the value to the output of
|
|
||||||
// the template.
|
|
||||||
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
|
||||||
s.at(n)
|
|
||||||
iface, ok := printableValue(v)
|
|
||||||
if !ok {
|
|
||||||
s.errorf("can't print %s of type %s", n, v.Type())
|
|
||||||
}
|
|
||||||
fmt.Fprint(s.wr, iface)
|
|
||||||
}
|
|
||||||
|
|
||||||
// printableValue returns the, possibly indirected, interface value inside v that
|
|
||||||
// is best for a call to formatted printer.
|
|
||||||
func printableValue(v reflect.Value) (interface{}, bool) {
|
|
||||||
if v.Kind() == reflect.Ptr {
|
|
||||||
v, _ = indirect(v) // fmt.Fprint handles nil.
|
|
||||||
}
|
|
||||||
if !v.IsValid() {
|
|
||||||
return "<no value>", true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
|
||||||
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
|
|
||||||
v = v.Addr()
|
|
||||||
} else {
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Chan, reflect.Func:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v.Interface(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types to help sort the keys in a map for reproducible output.
|
|
||||||
|
|
||||||
type rvs []reflect.Value
|
|
||||||
|
|
||||||
func (x rvs) Len() int { return len(x) }
|
|
||||||
func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
||||||
|
|
||||||
type rvInts struct{ rvs }
|
|
||||||
|
|
||||||
func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() }
|
|
||||||
|
|
||||||
type rvUints struct{ rvs }
|
|
||||||
|
|
||||||
func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() }
|
|
||||||
|
|
||||||
type rvFloats struct{ rvs }
|
|
||||||
|
|
||||||
func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() }
|
|
||||||
|
|
||||||
type rvStrings struct{ rvs }
|
|
||||||
|
|
||||||
func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() }
|
|
||||||
|
|
||||||
// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys.
|
|
||||||
func sortKeys(v []reflect.Value) []reflect.Value {
|
|
||||||
if len(v) <= 1 {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
switch v[0].Kind() {
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
sort.Sort(rvFloats{v})
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
sort.Sort(rvInts{v})
|
|
||||||
case reflect.String:
|
|
||||||
sort.Sort(rvStrings{v})
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
sort.Sort(rvUints{v})
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
598
vendor/github.com/alecthomas/template/funcs.go
generated
vendored
598
vendor/github.com/alecthomas/template/funcs.go
generated
vendored
|
@ -1,598 +0,0 @@
|
||||||
// Copyright 2011 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 template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/url"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FuncMap is the type of the map defining the mapping from names to functions.
|
|
||||||
// Each function must have either a single return value, or two return values of
|
|
||||||
// which the second has type error. In that case, if the second (error)
|
|
||||||
// return value evaluates to non-nil during execution, execution terminates and
|
|
||||||
// Execute returns that error.
|
|
||||||
type FuncMap map[string]interface{}
|
|
||||||
|
|
||||||
var builtins = FuncMap{
|
|
||||||
"and": and,
|
|
||||||
"call": call,
|
|
||||||
"html": HTMLEscaper,
|
|
||||||
"index": index,
|
|
||||||
"js": JSEscaper,
|
|
||||||
"len": length,
|
|
||||||
"not": not,
|
|
||||||
"or": or,
|
|
||||||
"print": fmt.Sprint,
|
|
||||||
"printf": fmt.Sprintf,
|
|
||||||
"println": fmt.Sprintln,
|
|
||||||
"urlquery": URLQueryEscaper,
|
|
||||||
|
|
||||||
// Comparisons
|
|
||||||
"eq": eq, // ==
|
|
||||||
"ge": ge, // >=
|
|
||||||
"gt": gt, // >
|
|
||||||
"le": le, // <=
|
|
||||||
"lt": lt, // <
|
|
||||||
"ne": ne, // !=
|
|
||||||
}
|
|
||||||
|
|
||||||
var builtinFuncs = createValueFuncs(builtins)
|
|
||||||
|
|
||||||
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
|
|
||||||
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
|
|
||||||
m := make(map[string]reflect.Value)
|
|
||||||
addValueFuncs(m, funcMap)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
|
|
||||||
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
|
|
||||||
for name, fn := range in {
|
|
||||||
v := reflect.ValueOf(fn)
|
|
||||||
if v.Kind() != reflect.Func {
|
|
||||||
panic("value for " + name + " not a function")
|
|
||||||
}
|
|
||||||
if !goodFunc(v.Type()) {
|
|
||||||
panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
|
|
||||||
}
|
|
||||||
out[name] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// addFuncs adds to values the functions in funcs. It does no checking of the input -
|
|
||||||
// call addValueFuncs first.
|
|
||||||
func addFuncs(out, in FuncMap) {
|
|
||||||
for name, fn := range in {
|
|
||||||
out[name] = fn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// goodFunc checks that the function or method has the right result signature.
|
|
||||||
func goodFunc(typ reflect.Type) bool {
|
|
||||||
// We allow functions with 1 result or 2 results where the second is an error.
|
|
||||||
switch {
|
|
||||||
case typ.NumOut() == 1:
|
|
||||||
return true
|
|
||||||
case typ.NumOut() == 2 && typ.Out(1) == errorType:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// findFunction looks for a function in the template, and global map.
|
|
||||||
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
|
||||||
if tmpl != nil && tmpl.common != nil {
|
|
||||||
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
|
||||||
return fn, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if fn := builtinFuncs[name]; fn.IsValid() {
|
|
||||||
return fn, true
|
|
||||||
}
|
|
||||||
return reflect.Value{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indexing.
|
|
||||||
|
|
||||||
// index returns the result of indexing its first argument by the following
|
|
||||||
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
|
||||||
// indexed item must be a map, slice, or array.
|
|
||||||
func index(item interface{}, indices ...interface{}) (interface{}, error) {
|
|
||||||
v := reflect.ValueOf(item)
|
|
||||||
for _, i := range indices {
|
|
||||||
index := reflect.ValueOf(i)
|
|
||||||
var isNil bool
|
|
||||||
if v, isNil = indirect(v); isNil {
|
|
||||||
return nil, fmt.Errorf("index of nil pointer")
|
|
||||||
}
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice, reflect.String:
|
|
||||||
var x int64
|
|
||||||
switch index.Kind() {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
x = index.Int()
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
x = int64(index.Uint())
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
|
|
||||||
}
|
|
||||||
if x < 0 || x >= int64(v.Len()) {
|
|
||||||
return nil, fmt.Errorf("index out of range: %d", x)
|
|
||||||
}
|
|
||||||
v = v.Index(int(x))
|
|
||||||
case reflect.Map:
|
|
||||||
if !index.IsValid() {
|
|
||||||
index = reflect.Zero(v.Type().Key())
|
|
||||||
}
|
|
||||||
if !index.Type().AssignableTo(v.Type().Key()) {
|
|
||||||
return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
|
|
||||||
}
|
|
||||||
if x := v.MapIndex(index); x.IsValid() {
|
|
||||||
v = x
|
|
||||||
} else {
|
|
||||||
v = reflect.Zero(v.Type().Elem())
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("can't index item of type %s", v.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v.Interface(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Length
|
|
||||||
|
|
||||||
// length returns the length of the item, with an error if it has no defined length.
|
|
||||||
func length(item interface{}) (int, error) {
|
|
||||||
v, isNil := indirect(reflect.ValueOf(item))
|
|
||||||
if isNil {
|
|
||||||
return 0, fmt.Errorf("len of nil pointer")
|
|
||||||
}
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
|
|
||||||
return v.Len(), nil
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("len of type %s", v.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function invocation
|
|
||||||
|
|
||||||
// call returns the result of evaluating the first argument as a function.
|
|
||||||
// The function must return 1 result, or 2 results, the second of which is an error.
|
|
||||||
func call(fn interface{}, args ...interface{}) (interface{}, error) {
|
|
||||||
v := reflect.ValueOf(fn)
|
|
||||||
typ := v.Type()
|
|
||||||
if typ.Kind() != reflect.Func {
|
|
||||||
return nil, fmt.Errorf("non-function of type %s", typ)
|
|
||||||
}
|
|
||||||
if !goodFunc(typ) {
|
|
||||||
return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
|
|
||||||
}
|
|
||||||
numIn := typ.NumIn()
|
|
||||||
var dddType reflect.Type
|
|
||||||
if typ.IsVariadic() {
|
|
||||||
if len(args) < numIn-1 {
|
|
||||||
return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
|
|
||||||
}
|
|
||||||
dddType = typ.In(numIn - 1).Elem()
|
|
||||||
} else {
|
|
||||||
if len(args) != numIn {
|
|
||||||
return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
argv := make([]reflect.Value, len(args))
|
|
||||||
for i, arg := range args {
|
|
||||||
value := reflect.ValueOf(arg)
|
|
||||||
// Compute the expected type. Clumsy because of variadics.
|
|
||||||
var argType reflect.Type
|
|
||||||
if !typ.IsVariadic() || i < numIn-1 {
|
|
||||||
argType = typ.In(i)
|
|
||||||
} else {
|
|
||||||
argType = dddType
|
|
||||||
}
|
|
||||||
if !value.IsValid() && canBeNil(argType) {
|
|
||||||
value = reflect.Zero(argType)
|
|
||||||
}
|
|
||||||
if !value.Type().AssignableTo(argType) {
|
|
||||||
return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
|
|
||||||
}
|
|
||||||
argv[i] = value
|
|
||||||
}
|
|
||||||
result := v.Call(argv)
|
|
||||||
if len(result) == 2 && !result[1].IsNil() {
|
|
||||||
return result[0].Interface(), result[1].Interface().(error)
|
|
||||||
}
|
|
||||||
return result[0].Interface(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boolean logic.
|
|
||||||
|
|
||||||
func truth(a interface{}) bool {
|
|
||||||
t, _ := isTrue(reflect.ValueOf(a))
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// and computes the Boolean AND of its arguments, returning
|
|
||||||
// the first false argument it encounters, or the last argument.
|
|
||||||
func and(arg0 interface{}, args ...interface{}) interface{} {
|
|
||||||
if !truth(arg0) {
|
|
||||||
return arg0
|
|
||||||
}
|
|
||||||
for i := range args {
|
|
||||||
arg0 = args[i]
|
|
||||||
if !truth(arg0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arg0
|
|
||||||
}
|
|
||||||
|
|
||||||
// or computes the Boolean OR of its arguments, returning
|
|
||||||
// the first true argument it encounters, or the last argument.
|
|
||||||
func or(arg0 interface{}, args ...interface{}) interface{} {
|
|
||||||
if truth(arg0) {
|
|
||||||
return arg0
|
|
||||||
}
|
|
||||||
for i := range args {
|
|
||||||
arg0 = args[i]
|
|
||||||
if truth(arg0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arg0
|
|
||||||
}
|
|
||||||
|
|
||||||
// not returns the Boolean negation of its argument.
|
|
||||||
func not(arg interface{}) (truth bool) {
|
|
||||||
truth, _ = isTrue(reflect.ValueOf(arg))
|
|
||||||
return !truth
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comparison.
|
|
||||||
|
|
||||||
// TODO: Perhaps allow comparison between signed and unsigned integers.
|
|
||||||
|
|
||||||
var (
|
|
||||||
errBadComparisonType = errors.New("invalid type for comparison")
|
|
||||||
errBadComparison = errors.New("incompatible types for comparison")
|
|
||||||
errNoComparison = errors.New("missing argument for comparison")
|
|
||||||
)
|
|
||||||
|
|
||||||
type kind int
|
|
||||||
|
|
||||||
const (
|
|
||||||
invalidKind kind = iota
|
|
||||||
boolKind
|
|
||||||
complexKind
|
|
||||||
intKind
|
|
||||||
floatKind
|
|
||||||
integerKind
|
|
||||||
stringKind
|
|
||||||
uintKind
|
|
||||||
)
|
|
||||||
|
|
||||||
func basicKind(v reflect.Value) (kind, error) {
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return boolKind, nil
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return intKind, nil
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
return uintKind, nil
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return floatKind, nil
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
return complexKind, nil
|
|
||||||
case reflect.String:
|
|
||||||
return stringKind, nil
|
|
||||||
}
|
|
||||||
return invalidKind, errBadComparisonType
|
|
||||||
}
|
|
||||||
|
|
||||||
// eq evaluates the comparison a == b || a == c || ...
|
|
||||||
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
|
|
||||||
v1 := reflect.ValueOf(arg1)
|
|
||||||
k1, err := basicKind(v1)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if len(arg2) == 0 {
|
|
||||||
return false, errNoComparison
|
|
||||||
}
|
|
||||||
for _, arg := range arg2 {
|
|
||||||
v2 := reflect.ValueOf(arg)
|
|
||||||
k2, err := basicKind(v2)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
truth := false
|
|
||||||
if k1 != k2 {
|
|
||||||
// Special case: Can compare integer values regardless of type's sign.
|
|
||||||
switch {
|
|
||||||
case k1 == intKind && k2 == uintKind:
|
|
||||||
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
|
|
||||||
case k1 == uintKind && k2 == intKind:
|
|
||||||
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
|
|
||||||
default:
|
|
||||||
return false, errBadComparison
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch k1 {
|
|
||||||
case boolKind:
|
|
||||||
truth = v1.Bool() == v2.Bool()
|
|
||||||
case complexKind:
|
|
||||||
truth = v1.Complex() == v2.Complex()
|
|
||||||
case floatKind:
|
|
||||||
truth = v1.Float() == v2.Float()
|
|
||||||
case intKind:
|
|
||||||
truth = v1.Int() == v2.Int()
|
|
||||||
case stringKind:
|
|
||||||
truth = v1.String() == v2.String()
|
|
||||||
case uintKind:
|
|
||||||
truth = v1.Uint() == v2.Uint()
|
|
||||||
default:
|
|
||||||
panic("invalid kind")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if truth {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ne evaluates the comparison a != b.
|
|
||||||
func ne(arg1, arg2 interface{}) (bool, error) {
|
|
||||||
// != is the inverse of ==.
|
|
||||||
equal, err := eq(arg1, arg2)
|
|
||||||
return !equal, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// lt evaluates the comparison a < b.
|
|
||||||
func lt(arg1, arg2 interface{}) (bool, error) {
|
|
||||||
v1 := reflect.ValueOf(arg1)
|
|
||||||
k1, err := basicKind(v1)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
v2 := reflect.ValueOf(arg2)
|
|
||||||
k2, err := basicKind(v2)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
truth := false
|
|
||||||
if k1 != k2 {
|
|
||||||
// Special case: Can compare integer values regardless of type's sign.
|
|
||||||
switch {
|
|
||||||
case k1 == intKind && k2 == uintKind:
|
|
||||||
truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
|
|
||||||
case k1 == uintKind && k2 == intKind:
|
|
||||||
truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
|
|
||||||
default:
|
|
||||||
return false, errBadComparison
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch k1 {
|
|
||||||
case boolKind, complexKind:
|
|
||||||
return false, errBadComparisonType
|
|
||||||
case floatKind:
|
|
||||||
truth = v1.Float() < v2.Float()
|
|
||||||
case intKind:
|
|
||||||
truth = v1.Int() < v2.Int()
|
|
||||||
case stringKind:
|
|
||||||
truth = v1.String() < v2.String()
|
|
||||||
case uintKind:
|
|
||||||
truth = v1.Uint() < v2.Uint()
|
|
||||||
default:
|
|
||||||
panic("invalid kind")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return truth, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// le evaluates the comparison <= b.
|
|
||||||
func le(arg1, arg2 interface{}) (bool, error) {
|
|
||||||
// <= is < or ==.
|
|
||||||
lessThan, err := lt(arg1, arg2)
|
|
||||||
if lessThan || err != nil {
|
|
||||||
return lessThan, err
|
|
||||||
}
|
|
||||||
return eq(arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// gt evaluates the comparison a > b.
|
|
||||||
func gt(arg1, arg2 interface{}) (bool, error) {
|
|
||||||
// > is the inverse of <=.
|
|
||||||
lessOrEqual, err := le(arg1, arg2)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return !lessOrEqual, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ge evaluates the comparison a >= b.
|
|
||||||
func ge(arg1, arg2 interface{}) (bool, error) {
|
|
||||||
// >= is the inverse of <.
|
|
||||||
lessThan, err := lt(arg1, arg2)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return !lessThan, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTML escaping.
|
|
||||||
|
|
||||||
var (
|
|
||||||
htmlQuot = []byte(""") // shorter than """
|
|
||||||
htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
|
|
||||||
htmlAmp = []byte("&")
|
|
||||||
htmlLt = []byte("<")
|
|
||||||
htmlGt = []byte(">")
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
|
||||||
func HTMLEscape(w io.Writer, b []byte) {
|
|
||||||
last := 0
|
|
||||||
for i, c := range b {
|
|
||||||
var html []byte
|
|
||||||
switch c {
|
|
||||||
case '"':
|
|
||||||
html = htmlQuot
|
|
||||||
case '\'':
|
|
||||||
html = htmlApos
|
|
||||||
case '&':
|
|
||||||
html = htmlAmp
|
|
||||||
case '<':
|
|
||||||
html = htmlLt
|
|
||||||
case '>':
|
|
||||||
html = htmlGt
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
w.Write(b[last:i])
|
|
||||||
w.Write(html)
|
|
||||||
last = i + 1
|
|
||||||
}
|
|
||||||
w.Write(b[last:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
|
|
||||||
func HTMLEscapeString(s string) string {
|
|
||||||
// Avoid allocation if we can.
|
|
||||||
if strings.IndexAny(s, `'"&<>`) < 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
HTMLEscape(&b, []byte(s))
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
|
||||||
// representation of its arguments.
|
|
||||||
func HTMLEscaper(args ...interface{}) string {
|
|
||||||
return HTMLEscapeString(evalArgs(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
// JavaScript escaping.
|
|
||||||
|
|
||||||
var (
|
|
||||||
jsLowUni = []byte(`\u00`)
|
|
||||||
hex = []byte("0123456789ABCDEF")
|
|
||||||
|
|
||||||
jsBackslash = []byte(`\\`)
|
|
||||||
jsApos = []byte(`\'`)
|
|
||||||
jsQuot = []byte(`\"`)
|
|
||||||
jsLt = []byte(`\x3C`)
|
|
||||||
jsGt = []byte(`\x3E`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
|
||||||
func JSEscape(w io.Writer, b []byte) {
|
|
||||||
last := 0
|
|
||||||
for i := 0; i < len(b); i++ {
|
|
||||||
c := b[i]
|
|
||||||
|
|
||||||
if !jsIsSpecial(rune(c)) {
|
|
||||||
// fast path: nothing to do
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
w.Write(b[last:i])
|
|
||||||
|
|
||||||
if c < utf8.RuneSelf {
|
|
||||||
// Quotes, slashes and angle brackets get quoted.
|
|
||||||
// Control characters get written as \u00XX.
|
|
||||||
switch c {
|
|
||||||
case '\\':
|
|
||||||
w.Write(jsBackslash)
|
|
||||||
case '\'':
|
|
||||||
w.Write(jsApos)
|
|
||||||
case '"':
|
|
||||||
w.Write(jsQuot)
|
|
||||||
case '<':
|
|
||||||
w.Write(jsLt)
|
|
||||||
case '>':
|
|
||||||
w.Write(jsGt)
|
|
||||||
default:
|
|
||||||
w.Write(jsLowUni)
|
|
||||||
t, b := c>>4, c&0x0f
|
|
||||||
w.Write(hex[t : t+1])
|
|
||||||
w.Write(hex[b : b+1])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unicode rune.
|
|
||||||
r, size := utf8.DecodeRune(b[i:])
|
|
||||||
if unicode.IsPrint(r) {
|
|
||||||
w.Write(b[i : i+size])
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, "\\u%04X", r)
|
|
||||||
}
|
|
||||||
i += size - 1
|
|
||||||
}
|
|
||||||
last = i + 1
|
|
||||||
}
|
|
||||||
w.Write(b[last:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
|
|
||||||
func JSEscapeString(s string) string {
|
|
||||||
// Avoid allocation if we can.
|
|
||||||
if strings.IndexFunc(s, jsIsSpecial) < 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
JSEscape(&b, []byte(s))
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsIsSpecial(r rune) bool {
|
|
||||||
switch r {
|
|
||||||
case '\\', '\'', '"', '<', '>':
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return r < ' ' || utf8.RuneSelf <= r
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
|
||||||
// representation of its arguments.
|
|
||||||
func JSEscaper(args ...interface{}) string {
|
|
||||||
return JSEscapeString(evalArgs(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLQueryEscaper returns the escaped value of the textual representation of
|
|
||||||
// its arguments in a form suitable for embedding in a URL query.
|
|
||||||
func URLQueryEscaper(args ...interface{}) string {
|
|
||||||
return url.QueryEscape(evalArgs(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
|
|
||||||
// fmt.Sprint(args...)
|
|
||||||
// except that each argument is indirected (if a pointer), as required,
|
|
||||||
// using the same rules as the default string evaluation during template
|
|
||||||
// execution.
|
|
||||||
func evalArgs(args []interface{}) string {
|
|
||||||
ok := false
|
|
||||||
var s string
|
|
||||||
// Fast path for simple common case.
|
|
||||||
if len(args) == 1 {
|
|
||||||
s, ok = args[0].(string)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
for i, arg := range args {
|
|
||||||
a, ok := printableValue(reflect.ValueOf(arg))
|
|
||||||
if ok {
|
|
||||||
args[i] = a
|
|
||||||
} // else left fmt do its thing
|
|
||||||
}
|
|
||||||
s = fmt.Sprint(args...)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
108
vendor/github.com/alecthomas/template/helper.go
generated
vendored
108
vendor/github.com/alecthomas/template/helper.go
generated
vendored
|
@ -1,108 +0,0 @@
|
||||||
// Copyright 2011 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.
|
|
||||||
|
|
||||||
// Helper functions to make constructing templates easier.
|
|
||||||
|
|
||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Functions and methods to parse templates.
|
|
||||||
|
|
||||||
// Must is a helper that wraps a call to a function returning (*Template, error)
|
|
||||||
// and panics if the error is non-nil. It is intended for use in variable
|
|
||||||
// initializations such as
|
|
||||||
// var t = template.Must(template.New("name").Parse("text"))
|
|
||||||
func Must(t *Template, err error) *Template {
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFiles creates a new Template and parses the template definitions from
|
|
||||||
// the named files. The returned template's name will have the (base) name and
|
|
||||||
// (parsed) contents of the first file. There must be at least one file.
|
|
||||||
// If an error occurs, parsing stops and the returned *Template is nil.
|
|
||||||
func ParseFiles(filenames ...string) (*Template, error) {
|
|
||||||
return parseFiles(nil, filenames...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFiles parses the named files and associates the resulting templates with
|
|
||||||
// t. If an error occurs, parsing stops and the returned template is nil;
|
|
||||||
// otherwise it is t. There must be at least one file.
|
|
||||||
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
|
|
||||||
return parseFiles(t, filenames...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseFiles is the helper for the method and function. If the argument
|
|
||||||
// template is nil, it is created from the first file.
|
|
||||||
func parseFiles(t *Template, filenames ...string) (*Template, error) {
|
|
||||||
if len(filenames) == 0 {
|
|
||||||
// Not really a problem, but be consistent.
|
|
||||||
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
|
|
||||||
}
|
|
||||||
for _, filename := range filenames {
|
|
||||||
b, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s := string(b)
|
|
||||||
name := filepath.Base(filename)
|
|
||||||
// First template becomes return value if not already defined,
|
|
||||||
// and we use that one for subsequent New calls to associate
|
|
||||||
// all the templates together. Also, if this file has the same name
|
|
||||||
// as t, this file becomes the contents of t, so
|
|
||||||
// t, err := New(name).Funcs(xxx).ParseFiles(name)
|
|
||||||
// works. Otherwise we create a new template associated with t.
|
|
||||||
var tmpl *Template
|
|
||||||
if t == nil {
|
|
||||||
t = New(name)
|
|
||||||
}
|
|
||||||
if name == t.Name() {
|
|
||||||
tmpl = t
|
|
||||||
} else {
|
|
||||||
tmpl = t.New(name)
|
|
||||||
}
|
|
||||||
_, err = tmpl.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseGlob creates a new Template and parses the template definitions from the
|
|
||||||
// files identified by the pattern, which must match at least one file. The
|
|
||||||
// returned template will have the (base) name and (parsed) contents of the
|
|
||||||
// first file matched by the pattern. ParseGlob is equivalent to calling
|
|
||||||
// ParseFiles with the list of files matched by the pattern.
|
|
||||||
func ParseGlob(pattern string) (*Template, error) {
|
|
||||||
return parseGlob(nil, pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseGlob parses the template definitions in the files identified by the
|
|
||||||
// pattern and associates the resulting templates with t. The pattern is
|
|
||||||
// processed by filepath.Glob and must match at least one file. ParseGlob is
|
|
||||||
// equivalent to calling t.ParseFiles with the list of files matched by the
|
|
||||||
// pattern.
|
|
||||||
func (t *Template) ParseGlob(pattern string) (*Template, error) {
|
|
||||||
return parseGlob(t, pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseGlob is the implementation of the function and method ParseGlob.
|
|
||||||
func parseGlob(t *Template, pattern string) (*Template, error) {
|
|
||||||
filenames, err := filepath.Glob(pattern)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(filenames) == 0 {
|
|
||||||
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
|
|
||||||
}
|
|
||||||
return parseFiles(t, filenames...)
|
|
||||||
}
|
|
556
vendor/github.com/alecthomas/template/parse/lex.go
generated
vendored
556
vendor/github.com/alecthomas/template/parse/lex.go
generated
vendored
|
@ -1,556 +0,0 @@
|
||||||
// Copyright 2011 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 parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// item represents a token or text string returned from the scanner.
|
|
||||||
type item struct {
|
|
||||||
typ itemType // The type of this item.
|
|
||||||
pos Pos // The starting position, in bytes, of this item in the input string.
|
|
||||||
val string // The value of this item.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i item) String() string {
|
|
||||||
switch {
|
|
||||||
case i.typ == itemEOF:
|
|
||||||
return "EOF"
|
|
||||||
case i.typ == itemError:
|
|
||||||
return i.val
|
|
||||||
case i.typ > itemKeyword:
|
|
||||||
return fmt.Sprintf("<%s>", i.val)
|
|
||||||
case len(i.val) > 10:
|
|
||||||
return fmt.Sprintf("%.10q...", i.val)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%q", i.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// itemType identifies the type of lex items.
|
|
||||||
type itemType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
itemError itemType = iota // error occurred; value is text of error
|
|
||||||
itemBool // boolean constant
|
|
||||||
itemChar // printable ASCII character; grab bag for comma etc.
|
|
||||||
itemCharConstant // character constant
|
|
||||||
itemComplex // complex constant (1+2i); imaginary is just a number
|
|
||||||
itemColonEquals // colon-equals (':=') introducing a declaration
|
|
||||||
itemEOF
|
|
||||||
itemField // alphanumeric identifier starting with '.'
|
|
||||||
itemIdentifier // alphanumeric identifier not starting with '.'
|
|
||||||
itemLeftDelim // left action delimiter
|
|
||||||
itemLeftParen // '(' inside action
|
|
||||||
itemNumber // simple number, including imaginary
|
|
||||||
itemPipe // pipe symbol
|
|
||||||
itemRawString // raw quoted string (includes quotes)
|
|
||||||
itemRightDelim // right action delimiter
|
|
||||||
itemElideNewline // elide newline after right delim
|
|
||||||
itemRightParen // ')' inside action
|
|
||||||
itemSpace // run of spaces separating arguments
|
|
||||||
itemString // quoted string (includes quotes)
|
|
||||||
itemText // plain text
|
|
||||||
itemVariable // variable starting with '$', such as '$' or '$1' or '$hello'
|
|
||||||
// Keywords appear after all the rest.
|
|
||||||
itemKeyword // used only to delimit the keywords
|
|
||||||
itemDot // the cursor, spelled '.'
|
|
||||||
itemDefine // define keyword
|
|
||||||
itemElse // else keyword
|
|
||||||
itemEnd // end keyword
|
|
||||||
itemIf // if keyword
|
|
||||||
itemNil // the untyped nil constant, easiest to treat as a keyword
|
|
||||||
itemRange // range keyword
|
|
||||||
itemTemplate // template keyword
|
|
||||||
itemWith // with keyword
|
|
||||||
)
|
|
||||||
|
|
||||||
var key = map[string]itemType{
|
|
||||||
".": itemDot,
|
|
||||||
"define": itemDefine,
|
|
||||||
"else": itemElse,
|
|
||||||
"end": itemEnd,
|
|
||||||
"if": itemIf,
|
|
||||||
"range": itemRange,
|
|
||||||
"nil": itemNil,
|
|
||||||
"template": itemTemplate,
|
|
||||||
"with": itemWith,
|
|
||||||
}
|
|
||||||
|
|
||||||
const eof = -1
|
|
||||||
|
|
||||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
|
||||||
type stateFn func(*lexer) stateFn
|
|
||||||
|
|
||||||
// lexer holds the state of the scanner.
|
|
||||||
type lexer struct {
|
|
||||||
name string // the name of the input; used only for error reports
|
|
||||||
input string // the string being scanned
|
|
||||||
leftDelim string // start of action
|
|
||||||
rightDelim string // end of action
|
|
||||||
state stateFn // the next lexing function to enter
|
|
||||||
pos Pos // current position in the input
|
|
||||||
start Pos // start position of this item
|
|
||||||
width Pos // width of last rune read from input
|
|
||||||
lastPos Pos // position of most recent item returned by nextItem
|
|
||||||
items chan item // channel of scanned items
|
|
||||||
parenDepth int // nesting depth of ( ) exprs
|
|
||||||
}
|
|
||||||
|
|
||||||
// next returns the next rune in the input.
|
|
||||||
func (l *lexer) next() rune {
|
|
||||||
if int(l.pos) >= len(l.input) {
|
|
||||||
l.width = 0
|
|
||||||
return eof
|
|
||||||
}
|
|
||||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
|
||||||
l.width = Pos(w)
|
|
||||||
l.pos += l.width
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// peek returns but does not consume the next rune in the input.
|
|
||||||
func (l *lexer) peek() rune {
|
|
||||||
r := l.next()
|
|
||||||
l.backup()
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// backup steps back one rune. Can only be called once per call of next.
|
|
||||||
func (l *lexer) backup() {
|
|
||||||
l.pos -= l.width
|
|
||||||
}
|
|
||||||
|
|
||||||
// emit passes an item back to the client.
|
|
||||||
func (l *lexer) emit(t itemType) {
|
|
||||||
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
|
||||||
l.start = l.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore skips over the pending input before this point.
|
|
||||||
func (l *lexer) ignore() {
|
|
||||||
l.start = l.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// accept consumes the next rune if it's from the valid set.
|
|
||||||
func (l *lexer) accept(valid string) bool {
|
|
||||||
if strings.IndexRune(valid, l.next()) >= 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
l.backup()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// acceptRun consumes a run of runes from the valid set.
|
|
||||||
func (l *lexer) acceptRun(valid string) {
|
|
||||||
for strings.IndexRune(valid, l.next()) >= 0 {
|
|
||||||
}
|
|
||||||
l.backup()
|
|
||||||
}
|
|
||||||
|
|
||||||
// lineNumber reports which line we're on, based on the position of
|
|
||||||
// the previous item returned by nextItem. Doing it this way
|
|
||||||
// means we don't have to worry about peek double counting.
|
|
||||||
func (l *lexer) lineNumber() int {
|
|
||||||
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorf returns an error token and terminates the scan by passing
|
|
||||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
|
||||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
|
||||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextItem returns the next item from the input.
|
|
||||||
func (l *lexer) nextItem() item {
|
|
||||||
item := <-l.items
|
|
||||||
l.lastPos = item.pos
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
// lex creates a new scanner for the input string.
|
|
||||||
func lex(name, input, left, right string) *lexer {
|
|
||||||
if left == "" {
|
|
||||||
left = leftDelim
|
|
||||||
}
|
|
||||||
if right == "" {
|
|
||||||
right = rightDelim
|
|
||||||
}
|
|
||||||
l := &lexer{
|
|
||||||
name: name,
|
|
||||||
input: input,
|
|
||||||
leftDelim: left,
|
|
||||||
rightDelim: right,
|
|
||||||
items: make(chan item),
|
|
||||||
}
|
|
||||||
go l.run()
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// run runs the state machine for the lexer.
|
|
||||||
func (l *lexer) run() {
|
|
||||||
for l.state = lexText; l.state != nil; {
|
|
||||||
l.state = l.state(l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// state functions
|
|
||||||
|
|
||||||
const (
|
|
||||||
leftDelim = "{{"
|
|
||||||
rightDelim = "}}"
|
|
||||||
leftComment = "/*"
|
|
||||||
rightComment = "*/"
|
|
||||||
)
|
|
||||||
|
|
||||||
// lexText scans until an opening action delimiter, "{{".
|
|
||||||
func lexText(l *lexer) stateFn {
|
|
||||||
for {
|
|
||||||
if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
|
|
||||||
if l.pos > l.start {
|
|
||||||
l.emit(itemText)
|
|
||||||
}
|
|
||||||
return lexLeftDelim
|
|
||||||
}
|
|
||||||
if l.next() == eof {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Correctly reached EOF.
|
|
||||||
if l.pos > l.start {
|
|
||||||
l.emit(itemText)
|
|
||||||
}
|
|
||||||
l.emit(itemEOF)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexLeftDelim scans the left delimiter, which is known to be present.
|
|
||||||
func lexLeftDelim(l *lexer) stateFn {
|
|
||||||
l.pos += Pos(len(l.leftDelim))
|
|
||||||
if strings.HasPrefix(l.input[l.pos:], leftComment) {
|
|
||||||
return lexComment
|
|
||||||
}
|
|
||||||
l.emit(itemLeftDelim)
|
|
||||||
l.parenDepth = 0
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexComment scans a comment. The left comment marker is known to be present.
|
|
||||||
func lexComment(l *lexer) stateFn {
|
|
||||||
l.pos += Pos(len(leftComment))
|
|
||||||
i := strings.Index(l.input[l.pos:], rightComment)
|
|
||||||
if i < 0 {
|
|
||||||
return l.errorf("unclosed comment")
|
|
||||||
}
|
|
||||||
l.pos += Pos(i + len(rightComment))
|
|
||||||
if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
|
||||||
return l.errorf("comment ends before closing delimiter")
|
|
||||||
|
|
||||||
}
|
|
||||||
l.pos += Pos(len(l.rightDelim))
|
|
||||||
l.ignore()
|
|
||||||
return lexText
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexRightDelim scans the right delimiter, which is known to be present.
|
|
||||||
func lexRightDelim(l *lexer) stateFn {
|
|
||||||
l.pos += Pos(len(l.rightDelim))
|
|
||||||
l.emit(itemRightDelim)
|
|
||||||
if l.peek() == '\\' {
|
|
||||||
l.pos++
|
|
||||||
l.emit(itemElideNewline)
|
|
||||||
}
|
|
||||||
return lexText
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexInsideAction scans the elements inside action delimiters.
|
|
||||||
func lexInsideAction(l *lexer) stateFn {
|
|
||||||
// Either number, quoted string, or identifier.
|
|
||||||
// Spaces separate arguments; runs of spaces turn into itemSpace.
|
|
||||||
// Pipe symbols separate and are emitted.
|
|
||||||
if strings.HasPrefix(l.input[l.pos:], l.rightDelim+"\\") || strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
|
||||||
if l.parenDepth == 0 {
|
|
||||||
return lexRightDelim
|
|
||||||
}
|
|
||||||
return l.errorf("unclosed left paren")
|
|
||||||
}
|
|
||||||
switch r := l.next(); {
|
|
||||||
case r == eof || isEndOfLine(r):
|
|
||||||
return l.errorf("unclosed action")
|
|
||||||
case isSpace(r):
|
|
||||||
return lexSpace
|
|
||||||
case r == ':':
|
|
||||||
if l.next() != '=' {
|
|
||||||
return l.errorf("expected :=")
|
|
||||||
}
|
|
||||||
l.emit(itemColonEquals)
|
|
||||||
case r == '|':
|
|
||||||
l.emit(itemPipe)
|
|
||||||
case r == '"':
|
|
||||||
return lexQuote
|
|
||||||
case r == '`':
|
|
||||||
return lexRawQuote
|
|
||||||
case r == '$':
|
|
||||||
return lexVariable
|
|
||||||
case r == '\'':
|
|
||||||
return lexChar
|
|
||||||
case r == '.':
|
|
||||||
// special look-ahead for ".field" so we don't break l.backup().
|
|
||||||
if l.pos < Pos(len(l.input)) {
|
|
||||||
r := l.input[l.pos]
|
|
||||||
if r < '0' || '9' < r {
|
|
||||||
return lexField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fallthrough // '.' can start a number.
|
|
||||||
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
|
|
||||||
l.backup()
|
|
||||||
return lexNumber
|
|
||||||
case isAlphaNumeric(r):
|
|
||||||
l.backup()
|
|
||||||
return lexIdentifier
|
|
||||||
case r == '(':
|
|
||||||
l.emit(itemLeftParen)
|
|
||||||
l.parenDepth++
|
|
||||||
return lexInsideAction
|
|
||||||
case r == ')':
|
|
||||||
l.emit(itemRightParen)
|
|
||||||
l.parenDepth--
|
|
||||||
if l.parenDepth < 0 {
|
|
||||||
return l.errorf("unexpected right paren %#U", r)
|
|
||||||
}
|
|
||||||
return lexInsideAction
|
|
||||||
case r <= unicode.MaxASCII && unicode.IsPrint(r):
|
|
||||||
l.emit(itemChar)
|
|
||||||
return lexInsideAction
|
|
||||||
default:
|
|
||||||
return l.errorf("unrecognized character in action: %#U", r)
|
|
||||||
}
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexSpace scans a run of space characters.
|
|
||||||
// One space has already been seen.
|
|
||||||
func lexSpace(l *lexer) stateFn {
|
|
||||||
for isSpace(l.peek()) {
|
|
||||||
l.next()
|
|
||||||
}
|
|
||||||
l.emit(itemSpace)
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexIdentifier scans an alphanumeric.
|
|
||||||
func lexIdentifier(l *lexer) stateFn {
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
switch r := l.next(); {
|
|
||||||
case isAlphaNumeric(r):
|
|
||||||
// absorb.
|
|
||||||
default:
|
|
||||||
l.backup()
|
|
||||||
word := l.input[l.start:l.pos]
|
|
||||||
if !l.atTerminator() {
|
|
||||||
return l.errorf("bad character %#U", r)
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case key[word] > itemKeyword:
|
|
||||||
l.emit(key[word])
|
|
||||||
case word[0] == '.':
|
|
||||||
l.emit(itemField)
|
|
||||||
case word == "true", word == "false":
|
|
||||||
l.emit(itemBool)
|
|
||||||
default:
|
|
||||||
l.emit(itemIdentifier)
|
|
||||||
}
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexField scans a field: .Alphanumeric.
|
|
||||||
// The . has been scanned.
|
|
||||||
func lexField(l *lexer) stateFn {
|
|
||||||
return lexFieldOrVariable(l, itemField)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexVariable scans a Variable: $Alphanumeric.
|
|
||||||
// The $ has been scanned.
|
|
||||||
func lexVariable(l *lexer) stateFn {
|
|
||||||
if l.atTerminator() { // Nothing interesting follows -> "$".
|
|
||||||
l.emit(itemVariable)
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
return lexFieldOrVariable(l, itemVariable)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexVariable scans a field or variable: [.$]Alphanumeric.
|
|
||||||
// The . or $ has been scanned.
|
|
||||||
func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
|
|
||||||
if l.atTerminator() { // Nothing interesting follows -> "." or "$".
|
|
||||||
if typ == itemVariable {
|
|
||||||
l.emit(itemVariable)
|
|
||||||
} else {
|
|
||||||
l.emit(itemDot)
|
|
||||||
}
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
var r rune
|
|
||||||
for {
|
|
||||||
r = l.next()
|
|
||||||
if !isAlphaNumeric(r) {
|
|
||||||
l.backup()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !l.atTerminator() {
|
|
||||||
return l.errorf("bad character %#U", r)
|
|
||||||
}
|
|
||||||
l.emit(typ)
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
|
|
||||||
// atTerminator reports whether the input is at valid termination character to
|
|
||||||
// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases
|
|
||||||
// like "$x+2" not being acceptable without a space, in case we decide one
|
|
||||||
// day to implement arithmetic.
|
|
||||||
func (l *lexer) atTerminator() bool {
|
|
||||||
r := l.peek()
|
|
||||||
if isSpace(r) || isEndOfLine(r) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
switch r {
|
|
||||||
case eof, '.', ',', '|', ':', ')', '(':
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
|
|
||||||
// succeed but should fail) but only in extremely rare cases caused by willfully
|
|
||||||
// bad choice of delimiter.
|
|
||||||
if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexChar scans a character constant. The initial quote is already
|
|
||||||
// scanned. Syntax checking is done by the parser.
|
|
||||||
func lexChar(l *lexer) stateFn {
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
switch l.next() {
|
|
||||||
case '\\':
|
|
||||||
if r := l.next(); r != eof && r != '\n' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case eof, '\n':
|
|
||||||
return l.errorf("unterminated character constant")
|
|
||||||
case '\'':
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
l.emit(itemCharConstant)
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
|
||||||
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
|
||||||
// and "089" - but when it's wrong the input is invalid and the parser (via
|
|
||||||
// strconv) will notice.
|
|
||||||
func lexNumber(l *lexer) stateFn {
|
|
||||||
if !l.scanNumber() {
|
|
||||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
|
||||||
}
|
|
||||||
if sign := l.peek(); sign == '+' || sign == '-' {
|
|
||||||
// Complex: 1+2i. No spaces, must end in 'i'.
|
|
||||||
if !l.scanNumber() || l.input[l.pos-1] != 'i' {
|
|
||||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
|
||||||
}
|
|
||||||
l.emit(itemComplex)
|
|
||||||
} else {
|
|
||||||
l.emit(itemNumber)
|
|
||||||
}
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lexer) scanNumber() bool {
|
|
||||||
// Optional leading sign.
|
|
||||||
l.accept("+-")
|
|
||||||
// Is it hex?
|
|
||||||
digits := "0123456789"
|
|
||||||
if l.accept("0") && l.accept("xX") {
|
|
||||||
digits = "0123456789abcdefABCDEF"
|
|
||||||
}
|
|
||||||
l.acceptRun(digits)
|
|
||||||
if l.accept(".") {
|
|
||||||
l.acceptRun(digits)
|
|
||||||
}
|
|
||||||
if l.accept("eE") {
|
|
||||||
l.accept("+-")
|
|
||||||
l.acceptRun("0123456789")
|
|
||||||
}
|
|
||||||
// Is it imaginary?
|
|
||||||
l.accept("i")
|
|
||||||
// Next thing mustn't be alphanumeric.
|
|
||||||
if isAlphaNumeric(l.peek()) {
|
|
||||||
l.next()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexQuote scans a quoted string.
|
|
||||||
func lexQuote(l *lexer) stateFn {
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
switch l.next() {
|
|
||||||
case '\\':
|
|
||||||
if r := l.next(); r != eof && r != '\n' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case eof, '\n':
|
|
||||||
return l.errorf("unterminated quoted string")
|
|
||||||
case '"':
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
l.emit(itemString)
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexRawQuote scans a raw quoted string.
|
|
||||||
func lexRawQuote(l *lexer) stateFn {
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
switch l.next() {
|
|
||||||
case eof, '\n':
|
|
||||||
return l.errorf("unterminated raw quoted string")
|
|
||||||
case '`':
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
l.emit(itemRawString)
|
|
||||||
return lexInsideAction
|
|
||||||
}
|
|
||||||
|
|
||||||
// isSpace reports whether r is a space character.
|
|
||||||
func isSpace(r rune) bool {
|
|
||||||
return r == ' ' || r == '\t'
|
|
||||||
}
|
|
||||||
|
|
||||||
// isEndOfLine reports whether r is an end-of-line character.
|
|
||||||
func isEndOfLine(r rune) bool {
|
|
||||||
return r == '\r' || r == '\n'
|
|
||||||
}
|
|
||||||
|
|
||||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
|
||||||
func isAlphaNumeric(r rune) bool {
|
|
||||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
|
||||||
}
|
|
834
vendor/github.com/alecthomas/template/parse/node.go
generated
vendored
834
vendor/github.com/alecthomas/template/parse/node.go
generated
vendored
|
@ -1,834 +0,0 @@
|
||||||
// Copyright 2011 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.
|
|
||||||
|
|
||||||
// Parse nodes.
|
|
||||||
|
|
||||||
package parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var textFormat = "%s" // Changed to "%q" in tests for better error messages.
|
|
||||||
|
|
||||||
// A Node is an element in the parse tree. The interface is trivial.
|
|
||||||
// The interface contains an unexported method so that only
|
|
||||||
// types local to this package can satisfy it.
|
|
||||||
type Node interface {
|
|
||||||
Type() NodeType
|
|
||||||
String() string
|
|
||||||
// Copy does a deep copy of the Node and all its components.
|
|
||||||
// To avoid type assertions, some XxxNodes also have specialized
|
|
||||||
// CopyXxx methods that return *XxxNode.
|
|
||||||
Copy() Node
|
|
||||||
Position() Pos // byte position of start of node in full original input string
|
|
||||||
// tree returns the containing *Tree.
|
|
||||||
// It is unexported so all implementations of Node are in this package.
|
|
||||||
tree() *Tree
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeType identifies the type of a parse tree node.
|
|
||||||
type NodeType int
|
|
||||||
|
|
||||||
// Pos represents a byte position in the original input text from which
|
|
||||||
// this template was parsed.
|
|
||||||
type Pos int
|
|
||||||
|
|
||||||
func (p Pos) Position() Pos {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns itself and provides an easy default implementation
|
|
||||||
// for embedding in a Node. Embedded in all non-trivial Nodes.
|
|
||||||
func (t NodeType) Type() NodeType {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
NodeText NodeType = iota // Plain text.
|
|
||||||
NodeAction // A non-control action such as a field evaluation.
|
|
||||||
NodeBool // A boolean constant.
|
|
||||||
NodeChain // A sequence of field accesses.
|
|
||||||
NodeCommand // An element of a pipeline.
|
|
||||||
NodeDot // The cursor, dot.
|
|
||||||
nodeElse // An else action. Not added to tree.
|
|
||||||
nodeEnd // An end action. Not added to tree.
|
|
||||||
NodeField // A field or method name.
|
|
||||||
NodeIdentifier // An identifier; always a function name.
|
|
||||||
NodeIf // An if action.
|
|
||||||
NodeList // A list of Nodes.
|
|
||||||
NodeNil // An untyped nil constant.
|
|
||||||
NodeNumber // A numerical constant.
|
|
||||||
NodePipe // A pipeline of commands.
|
|
||||||
NodeRange // A range action.
|
|
||||||
NodeString // A string constant.
|
|
||||||
NodeTemplate // A template invocation action.
|
|
||||||
NodeVariable // A $ variable.
|
|
||||||
NodeWith // A with action.
|
|
||||||
)
|
|
||||||
|
|
||||||
// Nodes.
|
|
||||||
|
|
||||||
// ListNode holds a sequence of nodes.
|
|
||||||
type ListNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Nodes []Node // The element nodes in lexical order.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newList(pos Pos) *ListNode {
|
|
||||||
return &ListNode{tr: t, NodeType: NodeList, Pos: pos}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ListNode) append(n Node) {
|
|
||||||
l.Nodes = append(l.Nodes, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ListNode) tree() *Tree {
|
|
||||||
return l.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ListNode) String() string {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
for _, n := range l.Nodes {
|
|
||||||
fmt.Fprint(b, n)
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ListNode) CopyList() *ListNode {
|
|
||||||
if l == nil {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
n := l.tr.newList(l.Pos)
|
|
||||||
for _, elem := range l.Nodes {
|
|
||||||
n.append(elem.Copy())
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ListNode) Copy() Node {
|
|
||||||
return l.CopyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextNode holds plain text.
|
|
||||||
type TextNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Text []byte // The text; may span newlines.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newText(pos Pos, text string) *TextNode {
|
|
||||||
return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TextNode) String() string {
|
|
||||||
return fmt.Sprintf(textFormat, t.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TextNode) tree() *Tree {
|
|
||||||
return t.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TextNode) Copy() Node {
|
|
||||||
return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PipeNode holds a pipeline with optional declaration
|
|
||||||
type PipeNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
|
||||||
Decl []*VariableNode // Variable declarations in lexical order.
|
|
||||||
Cmds []*CommandNode // The commands in lexical order.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode {
|
|
||||||
return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PipeNode) append(command *CommandNode) {
|
|
||||||
p.Cmds = append(p.Cmds, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PipeNode) String() string {
|
|
||||||
s := ""
|
|
||||||
if len(p.Decl) > 0 {
|
|
||||||
for i, v := range p.Decl {
|
|
||||||
if i > 0 {
|
|
||||||
s += ", "
|
|
||||||
}
|
|
||||||
s += v.String()
|
|
||||||
}
|
|
||||||
s += " := "
|
|
||||||
}
|
|
||||||
for i, c := range p.Cmds {
|
|
||||||
if i > 0 {
|
|
||||||
s += " | "
|
|
||||||
}
|
|
||||||
s += c.String()
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PipeNode) tree() *Tree {
|
|
||||||
return p.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PipeNode) CopyPipe() *PipeNode {
|
|
||||||
if p == nil {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
var decl []*VariableNode
|
|
||||||
for _, d := range p.Decl {
|
|
||||||
decl = append(decl, d.Copy().(*VariableNode))
|
|
||||||
}
|
|
||||||
n := p.tr.newPipeline(p.Pos, p.Line, decl)
|
|
||||||
for _, c := range p.Cmds {
|
|
||||||
n.append(c.Copy().(*CommandNode))
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PipeNode) Copy() Node {
|
|
||||||
return p.CopyPipe()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActionNode holds an action (something bounded by delimiters).
|
|
||||||
// Control actions have their own nodes; ActionNode represents simple
|
|
||||||
// ones such as field evaluations and parenthesized pipelines.
|
|
||||||
type ActionNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
|
||||||
Pipe *PipeNode // The pipeline in the action.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
|
|
||||||
return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ActionNode) String() string {
|
|
||||||
return fmt.Sprintf("{{%s}}", a.Pipe)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ActionNode) tree() *Tree {
|
|
||||||
return a.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ActionNode) Copy() Node {
|
|
||||||
return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommandNode holds a command (a pipeline inside an evaluating action).
|
|
||||||
type CommandNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Args []Node // Arguments in lexical order: Identifier, field, or constant.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newCommand(pos Pos) *CommandNode {
|
|
||||||
return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandNode) append(arg Node) {
|
|
||||||
c.Args = append(c.Args, arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandNode) String() string {
|
|
||||||
s := ""
|
|
||||||
for i, arg := range c.Args {
|
|
||||||
if i > 0 {
|
|
||||||
s += " "
|
|
||||||
}
|
|
||||||
if arg, ok := arg.(*PipeNode); ok {
|
|
||||||
s += "(" + arg.String() + ")"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s += arg.String()
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandNode) tree() *Tree {
|
|
||||||
return c.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandNode) Copy() Node {
|
|
||||||
if c == nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
n := c.tr.newCommand(c.Pos)
|
|
||||||
for _, c := range c.Args {
|
|
||||||
n.append(c.Copy())
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// IdentifierNode holds an identifier.
|
|
||||||
type IdentifierNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Ident string // The identifier's name.
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIdentifier returns a new IdentifierNode with the given identifier name.
|
|
||||||
func NewIdentifier(ident string) *IdentifierNode {
|
|
||||||
return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature.
|
|
||||||
// Chained for convenience.
|
|
||||||
// TODO: fix one day?
|
|
||||||
func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
|
|
||||||
i.Pos = pos
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature.
|
|
||||||
// Chained for convenience.
|
|
||||||
// TODO: fix one day?
|
|
||||||
func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode {
|
|
||||||
i.tr = t
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IdentifierNode) String() string {
|
|
||||||
return i.Ident
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IdentifierNode) tree() *Tree {
|
|
||||||
return i.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IdentifierNode) Copy() Node {
|
|
||||||
return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VariableNode holds a list of variable names, possibly with chained field
|
|
||||||
// accesses. The dollar sign is part of the (first) name.
|
|
||||||
type VariableNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Ident []string // Variable name and fields in lexical order.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
|
|
||||||
return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VariableNode) String() string {
|
|
||||||
s := ""
|
|
||||||
for i, id := range v.Ident {
|
|
||||||
if i > 0 {
|
|
||||||
s += "."
|
|
||||||
}
|
|
||||||
s += id
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VariableNode) tree() *Tree {
|
|
||||||
return v.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VariableNode) Copy() Node {
|
|
||||||
return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DotNode holds the special identifier '.'.
|
|
||||||
type DotNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newDot(pos Pos) *DotNode {
|
|
||||||
return &DotNode{tr: t, NodeType: NodeDot, Pos: pos}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DotNode) Type() NodeType {
|
|
||||||
// Override method on embedded NodeType for API compatibility.
|
|
||||||
// TODO: Not really a problem; could change API without effect but
|
|
||||||
// api tool complains.
|
|
||||||
return NodeDot
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DotNode) String() string {
|
|
||||||
return "."
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DotNode) tree() *Tree {
|
|
||||||
return d.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DotNode) Copy() Node {
|
|
||||||
return d.tr.newDot(d.Pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NilNode holds the special identifier 'nil' representing an untyped nil constant.
|
|
||||||
type NilNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newNil(pos Pos) *NilNode {
|
|
||||||
return &NilNode{tr: t, NodeType: NodeNil, Pos: pos}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NilNode) Type() NodeType {
|
|
||||||
// Override method on embedded NodeType for API compatibility.
|
|
||||||
// TODO: Not really a problem; could change API without effect but
|
|
||||||
// api tool complains.
|
|
||||||
return NodeNil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NilNode) String() string {
|
|
||||||
return "nil"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NilNode) tree() *Tree {
|
|
||||||
return n.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NilNode) Copy() Node {
|
|
||||||
return n.tr.newNil(n.Pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FieldNode holds a field (identifier starting with '.').
|
|
||||||
// The names may be chained ('.x.y').
|
|
||||||
// The period is dropped from each ident.
|
|
||||||
type FieldNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Ident []string // The identifiers in lexical order.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newField(pos Pos, ident string) *FieldNode {
|
|
||||||
return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FieldNode) String() string {
|
|
||||||
s := ""
|
|
||||||
for _, id := range f.Ident {
|
|
||||||
s += "." + id
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FieldNode) tree() *Tree {
|
|
||||||
return f.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FieldNode) Copy() Node {
|
|
||||||
return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
|
|
||||||
// The names may be chained ('.x.y').
|
|
||||||
// The periods are dropped from each ident.
|
|
||||||
type ChainNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Node Node
|
|
||||||
Field []string // The identifiers in lexical order.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newChain(pos Pos, node Node) *ChainNode {
|
|
||||||
return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds the named field (which should start with a period) to the end of the chain.
|
|
||||||
func (c *ChainNode) Add(field string) {
|
|
||||||
if len(field) == 0 || field[0] != '.' {
|
|
||||||
panic("no dot in field")
|
|
||||||
}
|
|
||||||
field = field[1:] // Remove leading dot.
|
|
||||||
if field == "" {
|
|
||||||
panic("empty field")
|
|
||||||
}
|
|
||||||
c.Field = append(c.Field, field)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ChainNode) String() string {
|
|
||||||
s := c.Node.String()
|
|
||||||
if _, ok := c.Node.(*PipeNode); ok {
|
|
||||||
s = "(" + s + ")"
|
|
||||||
}
|
|
||||||
for _, field := range c.Field {
|
|
||||||
s += "." + field
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ChainNode) tree() *Tree {
|
|
||||||
return c.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ChainNode) Copy() Node {
|
|
||||||
return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolNode holds a boolean constant.
|
|
||||||
type BoolNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
True bool // The value of the boolean constant.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newBool(pos Pos, true bool) *BoolNode {
|
|
||||||
return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoolNode) String() string {
|
|
||||||
if b.True {
|
|
||||||
return "true"
|
|
||||||
}
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoolNode) tree() *Tree {
|
|
||||||
return b.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoolNode) Copy() Node {
|
|
||||||
return b.tr.newBool(b.Pos, b.True)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumberNode holds a number: signed or unsigned integer, float, or complex.
|
|
||||||
// The value is parsed and stored under all the types that can represent the value.
|
|
||||||
// This simulates in a small amount of code the behavior of Go's ideal constants.
|
|
||||||
type NumberNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
IsInt bool // Number has an integral value.
|
|
||||||
IsUint bool // Number has an unsigned integral value.
|
|
||||||
IsFloat bool // Number has a floating-point value.
|
|
||||||
IsComplex bool // Number is complex.
|
|
||||||
Int64 int64 // The signed integer value.
|
|
||||||
Uint64 uint64 // The unsigned integer value.
|
|
||||||
Float64 float64 // The floating-point value.
|
|
||||||
Complex128 complex128 // The complex value.
|
|
||||||
Text string // The original textual representation from the input.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
|
|
||||||
n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text}
|
|
||||||
switch typ {
|
|
||||||
case itemCharConstant:
|
|
||||||
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if tail != "'" {
|
|
||||||
return nil, fmt.Errorf("malformed character constant: %s", text)
|
|
||||||
}
|
|
||||||
n.Int64 = int64(rune)
|
|
||||||
n.IsInt = true
|
|
||||||
n.Uint64 = uint64(rune)
|
|
||||||
n.IsUint = true
|
|
||||||
n.Float64 = float64(rune) // odd but those are the rules.
|
|
||||||
n.IsFloat = true
|
|
||||||
return n, nil
|
|
||||||
case itemComplex:
|
|
||||||
// fmt.Sscan can parse the pair, so let it do the work.
|
|
||||||
if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
n.IsComplex = true
|
|
||||||
n.simplifyComplex()
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
// Imaginary constants can only be complex unless they are zero.
|
|
||||||
if len(text) > 0 && text[len(text)-1] == 'i' {
|
|
||||||
f, err := strconv.ParseFloat(text[:len(text)-1], 64)
|
|
||||||
if err == nil {
|
|
||||||
n.IsComplex = true
|
|
||||||
n.Complex128 = complex(0, f)
|
|
||||||
n.simplifyComplex()
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Do integer test first so we get 0x123 etc.
|
|
||||||
u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below.
|
|
||||||
if err == nil {
|
|
||||||
n.IsUint = true
|
|
||||||
n.Uint64 = u
|
|
||||||
}
|
|
||||||
i, err := strconv.ParseInt(text, 0, 64)
|
|
||||||
if err == nil {
|
|
||||||
n.IsInt = true
|
|
||||||
n.Int64 = i
|
|
||||||
if i == 0 {
|
|
||||||
n.IsUint = true // in case of -0.
|
|
||||||
n.Uint64 = u
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If an integer extraction succeeded, promote the float.
|
|
||||||
if n.IsInt {
|
|
||||||
n.IsFloat = true
|
|
||||||
n.Float64 = float64(n.Int64)
|
|
||||||
} else if n.IsUint {
|
|
||||||
n.IsFloat = true
|
|
||||||
n.Float64 = float64(n.Uint64)
|
|
||||||
} else {
|
|
||||||
f, err := strconv.ParseFloat(text, 64)
|
|
||||||
if err == nil {
|
|
||||||
n.IsFloat = true
|
|
||||||
n.Float64 = f
|
|
||||||
// If a floating-point extraction succeeded, extract the int if needed.
|
|
||||||
if !n.IsInt && float64(int64(f)) == f {
|
|
||||||
n.IsInt = true
|
|
||||||
n.Int64 = int64(f)
|
|
||||||
}
|
|
||||||
if !n.IsUint && float64(uint64(f)) == f {
|
|
||||||
n.IsUint = true
|
|
||||||
n.Uint64 = uint64(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !n.IsInt && !n.IsUint && !n.IsFloat {
|
|
||||||
return nil, fmt.Errorf("illegal number syntax: %q", text)
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// simplifyComplex pulls out any other types that are represented by the complex number.
|
|
||||||
// These all require that the imaginary part be zero.
|
|
||||||
func (n *NumberNode) simplifyComplex() {
|
|
||||||
n.IsFloat = imag(n.Complex128) == 0
|
|
||||||
if n.IsFloat {
|
|
||||||
n.Float64 = real(n.Complex128)
|
|
||||||
n.IsInt = float64(int64(n.Float64)) == n.Float64
|
|
||||||
if n.IsInt {
|
|
||||||
n.Int64 = int64(n.Float64)
|
|
||||||
}
|
|
||||||
n.IsUint = float64(uint64(n.Float64)) == n.Float64
|
|
||||||
if n.IsUint {
|
|
||||||
n.Uint64 = uint64(n.Float64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NumberNode) String() string {
|
|
||||||
return n.Text
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NumberNode) tree() *Tree {
|
|
||||||
return n.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NumberNode) Copy() Node {
|
|
||||||
nn := new(NumberNode)
|
|
||||||
*nn = *n // Easy, fast, correct.
|
|
||||||
return nn
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringNode holds a string constant. The value has been "unquoted".
|
|
||||||
type StringNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Quoted string // The original text of the string, with quotes.
|
|
||||||
Text string // The string, after quote processing.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newString(pos Pos, orig, text string) *StringNode {
|
|
||||||
return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StringNode) String() string {
|
|
||||||
return s.Quoted
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StringNode) tree() *Tree {
|
|
||||||
return s.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StringNode) Copy() Node {
|
|
||||||
return s.tr.newString(s.Pos, s.Quoted, s.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// endNode represents an {{end}} action.
|
|
||||||
// It does not appear in the final parse tree.
|
|
||||||
type endNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newEnd(pos Pos) *endNode {
|
|
||||||
return &endNode{tr: t, NodeType: nodeEnd, Pos: pos}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *endNode) String() string {
|
|
||||||
return "{{end}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *endNode) tree() *Tree {
|
|
||||||
return e.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *endNode) Copy() Node {
|
|
||||||
return e.tr.newEnd(e.Pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// elseNode represents an {{else}} action. Does not appear in the final tree.
|
|
||||||
type elseNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newElse(pos Pos, line int) *elseNode {
|
|
||||||
return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *elseNode) Type() NodeType {
|
|
||||||
return nodeElse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *elseNode) String() string {
|
|
||||||
return "{{else}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *elseNode) tree() *Tree {
|
|
||||||
return e.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *elseNode) Copy() Node {
|
|
||||||
return e.tr.newElse(e.Pos, e.Line)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BranchNode is the common representation of if, range, and with.
|
|
||||||
type BranchNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
|
||||||
Pipe *PipeNode // The pipeline to be evaluated.
|
|
||||||
List *ListNode // What to execute if the value is non-empty.
|
|
||||||
ElseList *ListNode // What to execute if the value is empty (nil if absent).
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BranchNode) String() string {
|
|
||||||
name := ""
|
|
||||||
switch b.NodeType {
|
|
||||||
case NodeIf:
|
|
||||||
name = "if"
|
|
||||||
case NodeRange:
|
|
||||||
name = "range"
|
|
||||||
case NodeWith:
|
|
||||||
name = "with"
|
|
||||||
default:
|
|
||||||
panic("unknown branch type")
|
|
||||||
}
|
|
||||||
if b.ElseList != nil {
|
|
||||||
return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BranchNode) tree() *Tree {
|
|
||||||
return b.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BranchNode) Copy() Node {
|
|
||||||
switch b.NodeType {
|
|
||||||
case NodeIf:
|
|
||||||
return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
|
||||||
case NodeRange:
|
|
||||||
return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
|
||||||
case NodeWith:
|
|
||||||
return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
|
||||||
default:
|
|
||||||
panic("unknown branch type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IfNode represents an {{if}} action and its commands.
|
|
||||||
type IfNode struct {
|
|
||||||
BranchNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
|
|
||||||
return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IfNode) Copy() Node {
|
|
||||||
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangeNode represents a {{range}} action and its commands.
|
|
||||||
type RangeNode struct {
|
|
||||||
BranchNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
|
|
||||||
return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RangeNode) Copy() Node {
|
|
||||||
return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNode represents a {{with}} action and its commands.
|
|
||||||
type WithNode struct {
|
|
||||||
BranchNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
|
|
||||||
return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WithNode) Copy() Node {
|
|
||||||
return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TemplateNode represents a {{template}} action.
|
|
||||||
type TemplateNode struct {
|
|
||||||
NodeType
|
|
||||||
Pos
|
|
||||||
tr *Tree
|
|
||||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
|
||||||
Name string // The name of the template (unquoted).
|
|
||||||
Pipe *PipeNode // The command to evaluate as dot for the template.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
|
|
||||||
return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TemplateNode) String() string {
|
|
||||||
if t.Pipe == nil {
|
|
||||||
return fmt.Sprintf("{{template %q}}", t.Name)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TemplateNode) tree() *Tree {
|
|
||||||
return t.tr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TemplateNode) Copy() Node {
|
|
||||||
return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
|
|
||||||
}
|
|
700
vendor/github.com/alecthomas/template/parse/parse.go
generated
vendored
700
vendor/github.com/alecthomas/template/parse/parse.go
generated
vendored
|
@ -1,700 +0,0 @@
|
||||||
// Copyright 2011 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 parse builds parse trees for templates as defined by text/template
|
|
||||||
// and html/template. Clients should use those packages to construct templates
|
|
||||||
// rather than this one, which provides shared internal data structures not
|
|
||||||
// intended for general use.
|
|
||||||
package parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tree is the representation of a single parsed template.
|
|
||||||
type Tree struct {
|
|
||||||
Name string // name of the template represented by the tree.
|
|
||||||
ParseName string // name of the top-level template during parsing, for error messages.
|
|
||||||
Root *ListNode // top-level root of the tree.
|
|
||||||
text string // text parsed to create the template (or its parent)
|
|
||||||
// Parsing only; cleared after parse.
|
|
||||||
funcs []map[string]interface{}
|
|
||||||
lex *lexer
|
|
||||||
token [3]item // three-token lookahead for parser.
|
|
||||||
peekCount int
|
|
||||||
vars []string // variables defined at the moment.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
|
||||||
func (t *Tree) Copy() *Tree {
|
|
||||||
if t == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Tree{
|
|
||||||
Name: t.Name,
|
|
||||||
ParseName: t.ParseName,
|
|
||||||
Root: t.Root.CopyList(),
|
|
||||||
text: t.text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse returns a map from template name to parse.Tree, created by parsing the
|
|
||||||
// templates described in the argument string. The top-level template will be
|
|
||||||
// given the specified name. If an error is encountered, parsing stops and an
|
|
||||||
// empty map is returned with the error.
|
|
||||||
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) {
|
|
||||||
treeSet = make(map[string]*Tree)
|
|
||||||
t := New(name)
|
|
||||||
t.text = text
|
|
||||||
_, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// next returns the next token.
|
|
||||||
func (t *Tree) next() item {
|
|
||||||
if t.peekCount > 0 {
|
|
||||||
t.peekCount--
|
|
||||||
} else {
|
|
||||||
t.token[0] = t.lex.nextItem()
|
|
||||||
}
|
|
||||||
return t.token[t.peekCount]
|
|
||||||
}
|
|
||||||
|
|
||||||
// backup backs the input stream up one token.
|
|
||||||
func (t *Tree) backup() {
|
|
||||||
t.peekCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
// backup2 backs the input stream up two tokens.
|
|
||||||
// The zeroth token is already there.
|
|
||||||
func (t *Tree) backup2(t1 item) {
|
|
||||||
t.token[1] = t1
|
|
||||||
t.peekCount = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// backup3 backs the input stream up three tokens
|
|
||||||
// The zeroth token is already there.
|
|
||||||
func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back.
|
|
||||||
t.token[1] = t1
|
|
||||||
t.token[2] = t2
|
|
||||||
t.peekCount = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// peek returns but does not consume the next token.
|
|
||||||
func (t *Tree) peek() item {
|
|
||||||
if t.peekCount > 0 {
|
|
||||||
return t.token[t.peekCount-1]
|
|
||||||
}
|
|
||||||
t.peekCount = 1
|
|
||||||
t.token[0] = t.lex.nextItem()
|
|
||||||
return t.token[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextNonSpace returns the next non-space token.
|
|
||||||
func (t *Tree) nextNonSpace() (token item) {
|
|
||||||
for {
|
|
||||||
token = t.next()
|
|
||||||
if token.typ != itemSpace {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
// peekNonSpace returns but does not consume the next non-space token.
|
|
||||||
func (t *Tree) peekNonSpace() (token item) {
|
|
||||||
for {
|
|
||||||
token = t.next()
|
|
||||||
if token.typ != itemSpace {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.backup()
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parsing.
|
|
||||||
|
|
||||||
// New allocates a new parse tree with the given name.
|
|
||||||
func New(name string, funcs ...map[string]interface{}) *Tree {
|
|
||||||
return &Tree{
|
|
||||||
Name: name,
|
|
||||||
funcs: funcs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorContext returns a textual representation of the location of the node in the input text.
|
|
||||||
// The receiver is only used when the node does not have a pointer to the tree inside,
|
|
||||||
// which can occur in old code.
|
|
||||||
func (t *Tree) ErrorContext(n Node) (location, context string) {
|
|
||||||
pos := int(n.Position())
|
|
||||||
tree := n.tree()
|
|
||||||
if tree == nil {
|
|
||||||
tree = t
|
|
||||||
}
|
|
||||||
text := tree.text[:pos]
|
|
||||||
byteNum := strings.LastIndex(text, "\n")
|
|
||||||
if byteNum == -1 {
|
|
||||||
byteNum = pos // On first line.
|
|
||||||
} else {
|
|
||||||
byteNum++ // After the newline.
|
|
||||||
byteNum = pos - byteNum
|
|
||||||
}
|
|
||||||
lineNum := 1 + strings.Count(text, "\n")
|
|
||||||
context = n.String()
|
|
||||||
if len(context) > 20 {
|
|
||||||
context = fmt.Sprintf("%.20s...", context)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorf formats the error and terminates processing.
|
|
||||||
func (t *Tree) errorf(format string, args ...interface{}) {
|
|
||||||
t.Root = nil
|
|
||||||
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format)
|
|
||||||
panic(fmt.Errorf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// error terminates processing.
|
|
||||||
func (t *Tree) error(err error) {
|
|
||||||
t.errorf("%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// expect consumes the next token and guarantees it has the required type.
|
|
||||||
func (t *Tree) expect(expected itemType, context string) item {
|
|
||||||
token := t.nextNonSpace()
|
|
||||||
if token.typ != expected {
|
|
||||||
t.unexpected(token, context)
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
// expectOneOf consumes the next token and guarantees it has one of the required types.
|
|
||||||
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
|
|
||||||
token := t.nextNonSpace()
|
|
||||||
if token.typ != expected1 && token.typ != expected2 {
|
|
||||||
t.unexpected(token, context)
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
// unexpected complains about the token and terminates processing.
|
|
||||||
func (t *Tree) unexpected(token item, context string) {
|
|
||||||
t.errorf("unexpected %s in %s", token, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// recover is the handler that turns panics into returns from the top level of Parse.
|
|
||||||
func (t *Tree) recover(errp *error) {
|
|
||||||
e := recover()
|
|
||||||
if e != nil {
|
|
||||||
if _, ok := e.(runtime.Error); ok {
|
|
||||||
panic(e)
|
|
||||||
}
|
|
||||||
if t != nil {
|
|
||||||
t.stopParse()
|
|
||||||
}
|
|
||||||
*errp = e.(error)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// startParse initializes the parser, using the lexer.
|
|
||||||
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) {
|
|
||||||
t.Root = nil
|
|
||||||
t.lex = lex
|
|
||||||
t.vars = []string{"$"}
|
|
||||||
t.funcs = funcs
|
|
||||||
}
|
|
||||||
|
|
||||||
// stopParse terminates parsing.
|
|
||||||
func (t *Tree) stopParse() {
|
|
||||||
t.lex = nil
|
|
||||||
t.vars = nil
|
|
||||||
t.funcs = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the template definition string to construct a representation of
|
|
||||||
// the template for execution. If either action delimiter string is empty, the
|
|
||||||
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
|
||||||
// the treeSet map.
|
|
||||||
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
|
|
||||||
defer t.recover(&err)
|
|
||||||
t.ParseName = t.Name
|
|
||||||
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim))
|
|
||||||
t.text = text
|
|
||||||
t.parse(treeSet)
|
|
||||||
t.add(treeSet)
|
|
||||||
t.stopParse()
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// add adds tree to the treeSet.
|
|
||||||
func (t *Tree) add(treeSet map[string]*Tree) {
|
|
||||||
tree := treeSet[t.Name]
|
|
||||||
if tree == nil || IsEmptyTree(tree.Root) {
|
|
||||||
treeSet[t.Name] = t
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !IsEmptyTree(t.Root) {
|
|
||||||
t.errorf("template: multiple definition of template %q", t.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmptyTree reports whether this tree (node) is empty of everything but space.
|
|
||||||
func IsEmptyTree(n Node) bool {
|
|
||||||
switch n := n.(type) {
|
|
||||||
case nil:
|
|
||||||
return true
|
|
||||||
case *ActionNode:
|
|
||||||
case *IfNode:
|
|
||||||
case *ListNode:
|
|
||||||
for _, node := range n.Nodes {
|
|
||||||
if !IsEmptyTree(node) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case *RangeNode:
|
|
||||||
case *TemplateNode:
|
|
||||||
case *TextNode:
|
|
||||||
return len(bytes.TrimSpace(n.Text)) == 0
|
|
||||||
case *WithNode:
|
|
||||||
default:
|
|
||||||
panic("unknown node: " + n.String())
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse is the top-level parser for a template, essentially the same
|
|
||||||
// as itemList except it also parses {{define}} actions.
|
|
||||||
// It runs to EOF.
|
|
||||||
func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
|
|
||||||
t.Root = t.newList(t.peek().pos)
|
|
||||||
for t.peek().typ != itemEOF {
|
|
||||||
if t.peek().typ == itemLeftDelim {
|
|
||||||
delim := t.next()
|
|
||||||
if t.nextNonSpace().typ == itemDefine {
|
|
||||||
newT := New("definition") // name will be updated once we know it.
|
|
||||||
newT.text = t.text
|
|
||||||
newT.ParseName = t.ParseName
|
|
||||||
newT.startParse(t.funcs, t.lex)
|
|
||||||
newT.parseDefinition(treeSet)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.backup2(delim)
|
|
||||||
}
|
|
||||||
n := t.textOrAction()
|
|
||||||
if n.Type() == nodeEnd {
|
|
||||||
t.errorf("unexpected %s", n)
|
|
||||||
}
|
|
||||||
t.Root.append(n)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDefinition parses a {{define}} ... {{end}} template definition and
|
|
||||||
// installs the definition in the treeSet map. The "define" keyword has already
|
|
||||||
// been scanned.
|
|
||||||
func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
|
|
||||||
const context = "define clause"
|
|
||||||
name := t.expectOneOf(itemString, itemRawString, context)
|
|
||||||
var err error
|
|
||||||
t.Name, err = strconv.Unquote(name.val)
|
|
||||||
if err != nil {
|
|
||||||
t.error(err)
|
|
||||||
}
|
|
||||||
t.expect(itemRightDelim, context)
|
|
||||||
var end Node
|
|
||||||
t.Root, end = t.itemList()
|
|
||||||
if end.Type() != nodeEnd {
|
|
||||||
t.errorf("unexpected %s in %s", end, context)
|
|
||||||
}
|
|
||||||
t.add(treeSet)
|
|
||||||
t.stopParse()
|
|
||||||
}
|
|
||||||
|
|
||||||
// itemList:
|
|
||||||
// textOrAction*
|
|
||||||
// Terminates at {{end}} or {{else}}, returned separately.
|
|
||||||
func (t *Tree) itemList() (list *ListNode, next Node) {
|
|
||||||
list = t.newList(t.peekNonSpace().pos)
|
|
||||||
for t.peekNonSpace().typ != itemEOF {
|
|
||||||
n := t.textOrAction()
|
|
||||||
switch n.Type() {
|
|
||||||
case nodeEnd, nodeElse:
|
|
||||||
return list, n
|
|
||||||
}
|
|
||||||
list.append(n)
|
|
||||||
}
|
|
||||||
t.errorf("unexpected EOF")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// textOrAction:
|
|
||||||
// text | action
|
|
||||||
func (t *Tree) textOrAction() Node {
|
|
||||||
switch token := t.nextNonSpace(); token.typ {
|
|
||||||
case itemElideNewline:
|
|
||||||
return t.elideNewline()
|
|
||||||
case itemText:
|
|
||||||
return t.newText(token.pos, token.val)
|
|
||||||
case itemLeftDelim:
|
|
||||||
return t.action()
|
|
||||||
default:
|
|
||||||
t.unexpected(token, "input")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// elideNewline:
|
|
||||||
// Remove newlines trailing rightDelim if \\ is present.
|
|
||||||
func (t *Tree) elideNewline() Node {
|
|
||||||
token := t.peek()
|
|
||||||
if token.typ != itemText {
|
|
||||||
t.unexpected(token, "input")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
t.next()
|
|
||||||
stripped := strings.TrimLeft(token.val, "\n\r")
|
|
||||||
diff := len(token.val) - len(stripped)
|
|
||||||
if diff > 0 {
|
|
||||||
// This is a bit nasty. We mutate the token in-place to remove
|
|
||||||
// preceding newlines.
|
|
||||||
token.pos += Pos(diff)
|
|
||||||
token.val = stripped
|
|
||||||
}
|
|
||||||
return t.newText(token.pos, token.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Action:
|
|
||||||
// control
|
|
||||||
// command ("|" command)*
|
|
||||||
// Left delim is past. Now get actions.
|
|
||||||
// First word could be a keyword such as range.
|
|
||||||
func (t *Tree) action() (n Node) {
|
|
||||||
switch token := t.nextNonSpace(); token.typ {
|
|
||||||
case itemElse:
|
|
||||||
return t.elseControl()
|
|
||||||
case itemEnd:
|
|
||||||
return t.endControl()
|
|
||||||
case itemIf:
|
|
||||||
return t.ifControl()
|
|
||||||
case itemRange:
|
|
||||||
return t.rangeControl()
|
|
||||||
case itemTemplate:
|
|
||||||
return t.templateControl()
|
|
||||||
case itemWith:
|
|
||||||
return t.withControl()
|
|
||||||
}
|
|
||||||
t.backup()
|
|
||||||
// Do not pop variables; they persist until "end".
|
|
||||||
return t.newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipeline:
|
|
||||||
// declarations? command ('|' command)*
|
|
||||||
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
|
|
||||||
var decl []*VariableNode
|
|
||||||
pos := t.peekNonSpace().pos
|
|
||||||
// Are there declarations?
|
|
||||||
for {
|
|
||||||
if v := t.peekNonSpace(); v.typ == itemVariable {
|
|
||||||
t.next()
|
|
||||||
// Since space is a token, we need 3-token look-ahead here in the worst case:
|
|
||||||
// in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an
|
|
||||||
// argument variable rather than a declaration. So remember the token
|
|
||||||
// adjacent to the variable so we can push it back if necessary.
|
|
||||||
tokenAfterVariable := t.peek()
|
|
||||||
if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
|
|
||||||
t.nextNonSpace()
|
|
||||||
variable := t.newVariable(v.pos, v.val)
|
|
||||||
decl = append(decl, variable)
|
|
||||||
t.vars = append(t.vars, v.val)
|
|
||||||
if next.typ == itemChar && next.val == "," {
|
|
||||||
if context == "range" && len(decl) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.errorf("too many declarations in %s", context)
|
|
||||||
}
|
|
||||||
} else if tokenAfterVariable.typ == itemSpace {
|
|
||||||
t.backup3(v, tokenAfterVariable)
|
|
||||||
} else {
|
|
||||||
t.backup2(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pipe = t.newPipeline(pos, t.lex.lineNumber(), decl)
|
|
||||||
for {
|
|
||||||
switch token := t.nextNonSpace(); token.typ {
|
|
||||||
case itemRightDelim, itemRightParen:
|
|
||||||
if len(pipe.Cmds) == 0 {
|
|
||||||
t.errorf("missing value for %s", context)
|
|
||||||
}
|
|
||||||
if token.typ == itemRightParen {
|
|
||||||
t.backup()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
|
|
||||||
itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen:
|
|
||||||
t.backup()
|
|
||||||
pipe.append(t.command())
|
|
||||||
default:
|
|
||||||
t.unexpected(token, context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
|
|
||||||
defer t.popVars(len(t.vars))
|
|
||||||
line = t.lex.lineNumber()
|
|
||||||
pipe = t.pipeline(context)
|
|
||||||
var next Node
|
|
||||||
list, next = t.itemList()
|
|
||||||
switch next.Type() {
|
|
||||||
case nodeEnd: //done
|
|
||||||
case nodeElse:
|
|
||||||
if allowElseIf {
|
|
||||||
// Special case for "else if". If the "else" is followed immediately by an "if",
|
|
||||||
// the elseControl will have left the "if" token pending. Treat
|
|
||||||
// {{if a}}_{{else if b}}_{{end}}
|
|
||||||
// as
|
|
||||||
// {{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
|
|
||||||
// To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
|
|
||||||
// is assumed. This technique works even for long if-else-if chains.
|
|
||||||
// TODO: Should we allow else-if in with and range?
|
|
||||||
if t.peek().typ == itemIf {
|
|
||||||
t.next() // Consume the "if" token.
|
|
||||||
elseList = t.newList(next.Position())
|
|
||||||
elseList.append(t.ifControl())
|
|
||||||
// Do not consume the next item - only one {{end}} required.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elseList, next = t.itemList()
|
|
||||||
if next.Type() != nodeEnd {
|
|
||||||
t.errorf("expected end; found %s", next)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pipe.Position(), line, pipe, list, elseList
|
|
||||||
}
|
|
||||||
|
|
||||||
// If:
|
|
||||||
// {{if pipeline}} itemList {{end}}
|
|
||||||
// {{if pipeline}} itemList {{else}} itemList {{end}}
|
|
||||||
// If keyword is past.
|
|
||||||
func (t *Tree) ifControl() Node {
|
|
||||||
return t.newIf(t.parseControl(true, "if"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range:
|
|
||||||
// {{range pipeline}} itemList {{end}}
|
|
||||||
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
|
||||||
// Range keyword is past.
|
|
||||||
func (t *Tree) rangeControl() Node {
|
|
||||||
return t.newRange(t.parseControl(false, "range"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// With:
|
|
||||||
// {{with pipeline}} itemList {{end}}
|
|
||||||
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
|
||||||
// If keyword is past.
|
|
||||||
func (t *Tree) withControl() Node {
|
|
||||||
return t.newWith(t.parseControl(false, "with"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// End:
|
|
||||||
// {{end}}
|
|
||||||
// End keyword is past.
|
|
||||||
func (t *Tree) endControl() Node {
|
|
||||||
return t.newEnd(t.expect(itemRightDelim, "end").pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Else:
|
|
||||||
// {{else}}
|
|
||||||
// Else keyword is past.
|
|
||||||
func (t *Tree) elseControl() Node {
|
|
||||||
// Special case for "else if".
|
|
||||||
peek := t.peekNonSpace()
|
|
||||||
if peek.typ == itemIf {
|
|
||||||
// We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
|
|
||||||
return t.newElse(peek.pos, t.lex.lineNumber())
|
|
||||||
}
|
|
||||||
return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Template:
|
|
||||||
// {{template stringValue pipeline}}
|
|
||||||
// Template keyword is past. The name must be something that can evaluate
|
|
||||||
// to a string.
|
|
||||||
func (t *Tree) templateControl() Node {
|
|
||||||
var name string
|
|
||||||
token := t.nextNonSpace()
|
|
||||||
switch token.typ {
|
|
||||||
case itemString, itemRawString:
|
|
||||||
s, err := strconv.Unquote(token.val)
|
|
||||||
if err != nil {
|
|
||||||
t.error(err)
|
|
||||||
}
|
|
||||||
name = s
|
|
||||||
default:
|
|
||||||
t.unexpected(token, "template invocation")
|
|
||||||
}
|
|
||||||
var pipe *PipeNode
|
|
||||||
if t.nextNonSpace().typ != itemRightDelim {
|
|
||||||
t.backup()
|
|
||||||
// Do not pop variables; they persist until "end".
|
|
||||||
pipe = t.pipeline("template")
|
|
||||||
}
|
|
||||||
return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
|
|
||||||
}
|
|
||||||
|
|
||||||
// command:
|
|
||||||
// operand (space operand)*
|
|
||||||
// space-separated arguments up to a pipeline character or right delimiter.
|
|
||||||
// we consume the pipe character but leave the right delim to terminate the action.
|
|
||||||
func (t *Tree) command() *CommandNode {
|
|
||||||
cmd := t.newCommand(t.peekNonSpace().pos)
|
|
||||||
for {
|
|
||||||
t.peekNonSpace() // skip leading spaces.
|
|
||||||
operand := t.operand()
|
|
||||||
if operand != nil {
|
|
||||||
cmd.append(operand)
|
|
||||||
}
|
|
||||||
switch token := t.next(); token.typ {
|
|
||||||
case itemSpace:
|
|
||||||
continue
|
|
||||||
case itemError:
|
|
||||||
t.errorf("%s", token.val)
|
|
||||||
case itemRightDelim, itemRightParen:
|
|
||||||
t.backup()
|
|
||||||
case itemPipe:
|
|
||||||
default:
|
|
||||||
t.errorf("unexpected %s in operand; missing space?", token)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if len(cmd.Args) == 0 {
|
|
||||||
t.errorf("empty command")
|
|
||||||
}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// operand:
|
|
||||||
// term .Field*
|
|
||||||
// An operand is a space-separated component of a command,
|
|
||||||
// a term possibly followed by field accesses.
|
|
||||||
// A nil return means the next item is not an operand.
|
|
||||||
func (t *Tree) operand() Node {
|
|
||||||
node := t.term()
|
|
||||||
if node == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if t.peek().typ == itemField {
|
|
||||||
chain := t.newChain(t.peek().pos, node)
|
|
||||||
for t.peek().typ == itemField {
|
|
||||||
chain.Add(t.next().val)
|
|
||||||
}
|
|
||||||
// Compatibility with original API: If the term is of type NodeField
|
|
||||||
// or NodeVariable, just put more fields on the original.
|
|
||||||
// Otherwise, keep the Chain node.
|
|
||||||
// TODO: Switch to Chains always when we can.
|
|
||||||
switch node.Type() {
|
|
||||||
case NodeField:
|
|
||||||
node = t.newField(chain.Position(), chain.String())
|
|
||||||
case NodeVariable:
|
|
||||||
node = t.newVariable(chain.Position(), chain.String())
|
|
||||||
default:
|
|
||||||
node = chain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
// term:
|
|
||||||
// literal (number, string, nil, boolean)
|
|
||||||
// function (identifier)
|
|
||||||
// .
|
|
||||||
// .Field
|
|
||||||
// $
|
|
||||||
// '(' pipeline ')'
|
|
||||||
// A term is a simple "expression".
|
|
||||||
// A nil return means the next item is not a term.
|
|
||||||
func (t *Tree) term() Node {
|
|
||||||
switch token := t.nextNonSpace(); token.typ {
|
|
||||||
case itemError:
|
|
||||||
t.errorf("%s", token.val)
|
|
||||||
case itemIdentifier:
|
|
||||||
if !t.hasFunction(token.val) {
|
|
||||||
t.errorf("function %q not defined", token.val)
|
|
||||||
}
|
|
||||||
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
|
||||||
case itemDot:
|
|
||||||
return t.newDot(token.pos)
|
|
||||||
case itemNil:
|
|
||||||
return t.newNil(token.pos)
|
|
||||||
case itemVariable:
|
|
||||||
return t.useVar(token.pos, token.val)
|
|
||||||
case itemField:
|
|
||||||
return t.newField(token.pos, token.val)
|
|
||||||
case itemBool:
|
|
||||||
return t.newBool(token.pos, token.val == "true")
|
|
||||||
case itemCharConstant, itemComplex, itemNumber:
|
|
||||||
number, err := t.newNumber(token.pos, token.val, token.typ)
|
|
||||||
if err != nil {
|
|
||||||
t.error(err)
|
|
||||||
}
|
|
||||||
return number
|
|
||||||
case itemLeftParen:
|
|
||||||
pipe := t.pipeline("parenthesized pipeline")
|
|
||||||
if token := t.next(); token.typ != itemRightParen {
|
|
||||||
t.errorf("unclosed right paren: unexpected %s", token)
|
|
||||||
}
|
|
||||||
return pipe
|
|
||||||
case itemString, itemRawString:
|
|
||||||
s, err := strconv.Unquote(token.val)
|
|
||||||
if err != nil {
|
|
||||||
t.error(err)
|
|
||||||
}
|
|
||||||
return t.newString(token.pos, token.val, s)
|
|
||||||
}
|
|
||||||
t.backup()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasFunction reports if a function name exists in the Tree's maps.
|
|
||||||
func (t *Tree) hasFunction(name string) bool {
|
|
||||||
for _, funcMap := range t.funcs {
|
|
||||||
if funcMap == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if funcMap[name] != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// popVars trims the variable list to the specified length
|
|
||||||
func (t *Tree) popVars(n int) {
|
|
||||||
t.vars = t.vars[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
// useVar returns a node for a variable reference. It errors if the
|
|
||||||
// variable is not defined.
|
|
||||||
func (t *Tree) useVar(pos Pos, name string) Node {
|
|
||||||
v := t.newVariable(pos, name)
|
|
||||||
for _, varName := range t.vars {
|
|
||||||
if varName == v.Ident[0] {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.errorf("undefined variable %q", v.Ident[0])
|
|
||||||
return nil
|
|
||||||
}
|
|
218
vendor/github.com/alecthomas/template/template.go
generated
vendored
218
vendor/github.com/alecthomas/template/template.go
generated
vendored
|
@ -1,218 +0,0 @@
|
||||||
// Copyright 2011 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 template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/alecthomas/template/parse"
|
|
||||||
)
|
|
||||||
|
|
||||||
// common holds the information shared by related templates.
|
|
||||||
type common struct {
|
|
||||||
tmpl map[string]*Template
|
|
||||||
// We use two maps, one for parsing and one for execution.
|
|
||||||
// This separation makes the API cleaner since it doesn't
|
|
||||||
// expose reflection to the client.
|
|
||||||
parseFuncs FuncMap
|
|
||||||
execFuncs map[string]reflect.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Template is the representation of a parsed template. The *parse.Tree
|
|
||||||
// field is exported only for use by html/template and should be treated
|
|
||||||
// as unexported by all other clients.
|
|
||||||
type Template struct {
|
|
||||||
name string
|
|
||||||
*parse.Tree
|
|
||||||
*common
|
|
||||||
leftDelim string
|
|
||||||
rightDelim string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New allocates a new template with the given name.
|
|
||||||
func New(name string) *Template {
|
|
||||||
return &Template{
|
|
||||||
name: name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the template.
|
|
||||||
func (t *Template) Name() string {
|
|
||||||
return t.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// New allocates a new template associated with the given one and with the same
|
|
||||||
// delimiters. The association, which is transitive, allows one template to
|
|
||||||
// invoke another with a {{template}} action.
|
|
||||||
func (t *Template) New(name string) *Template {
|
|
||||||
t.init()
|
|
||||||
return &Template{
|
|
||||||
name: name,
|
|
||||||
common: t.common,
|
|
||||||
leftDelim: t.leftDelim,
|
|
||||||
rightDelim: t.rightDelim,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Template) init() {
|
|
||||||
if t.common == nil {
|
|
||||||
t.common = new(common)
|
|
||||||
t.tmpl = make(map[string]*Template)
|
|
||||||
t.parseFuncs = make(FuncMap)
|
|
||||||
t.execFuncs = make(map[string]reflect.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone returns a duplicate of the template, including all associated
|
|
||||||
// templates. The actual representation is not copied, but the name space of
|
|
||||||
// associated templates is, so further calls to Parse in the copy will add
|
|
||||||
// templates to the copy but not to the original. Clone can be used to prepare
|
|
||||||
// common templates and use them with variant definitions for other templates
|
|
||||||
// by adding the variants after the clone is made.
|
|
||||||
func (t *Template) Clone() (*Template, error) {
|
|
||||||
nt := t.copy(nil)
|
|
||||||
nt.init()
|
|
||||||
nt.tmpl[t.name] = nt
|
|
||||||
for k, v := range t.tmpl {
|
|
||||||
if k == t.name { // Already installed.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// The associated templates share nt's common structure.
|
|
||||||
tmpl := v.copy(nt.common)
|
|
||||||
nt.tmpl[k] = tmpl
|
|
||||||
}
|
|
||||||
for k, v := range t.parseFuncs {
|
|
||||||
nt.parseFuncs[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range t.execFuncs {
|
|
||||||
nt.execFuncs[k] = v
|
|
||||||
}
|
|
||||||
return nt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy returns a shallow copy of t, with common set to the argument.
|
|
||||||
func (t *Template) copy(c *common) *Template {
|
|
||||||
nt := New(t.name)
|
|
||||||
nt.Tree = t.Tree
|
|
||||||
nt.common = c
|
|
||||||
nt.leftDelim = t.leftDelim
|
|
||||||
nt.rightDelim = t.rightDelim
|
|
||||||
return nt
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddParseTree creates a new template with the name and parse tree
|
|
||||||
// and associates it with t.
|
|
||||||
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
|
||||||
if t.common != nil && t.tmpl[name] != nil {
|
|
||||||
return nil, fmt.Errorf("template: redefinition of template %q", name)
|
|
||||||
}
|
|
||||||
nt := t.New(name)
|
|
||||||
nt.Tree = tree
|
|
||||||
t.tmpl[name] = nt
|
|
||||||
return nt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Templates returns a slice of the templates associated with t, including t
|
|
||||||
// itself.
|
|
||||||
func (t *Template) Templates() []*Template {
|
|
||||||
if t.common == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Return a slice so we don't expose the map.
|
|
||||||
m := make([]*Template, 0, len(t.tmpl))
|
|
||||||
for _, v := range t.tmpl {
|
|
||||||
m = append(m, v)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delims sets the action delimiters to the specified strings, to be used in
|
|
||||||
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
|
|
||||||
// definitions will inherit the settings. An empty delimiter stands for the
|
|
||||||
// corresponding default: {{ or }}.
|
|
||||||
// The return value is the template, so calls can be chained.
|
|
||||||
func (t *Template) Delims(left, right string) *Template {
|
|
||||||
t.leftDelim = left
|
|
||||||
t.rightDelim = right
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Funcs adds the elements of the argument map to the template's function map.
|
|
||||||
// It panics if a value in the map is not a function with appropriate return
|
|
||||||
// type. However, it is legal to overwrite elements of the map. The return
|
|
||||||
// value is the template, so calls can be chained.
|
|
||||||
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
|
||||||
t.init()
|
|
||||||
addValueFuncs(t.execFuncs, funcMap)
|
|
||||||
addFuncs(t.parseFuncs, funcMap)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup returns the template with the given name that is associated with t,
|
|
||||||
// or nil if there is no such template.
|
|
||||||
func (t *Template) Lookup(name string) *Template {
|
|
||||||
if t.common == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return t.tmpl[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses a string into a template. Nested template definitions will be
|
|
||||||
// associated with the top-level template t. Parse may be called multiple times
|
|
||||||
// to parse definitions of templates to associate with t. It is an error if a
|
|
||||||
// resulting template is non-empty (contains content other than template
|
|
||||||
// definitions) and would replace a non-empty template with the same name.
|
|
||||||
// (In multiple calls to Parse with the same receiver template, only one call
|
|
||||||
// can contain text other than space, comments, and template definitions.)
|
|
||||||
func (t *Template) Parse(text string) (*Template, error) {
|
|
||||||
t.init()
|
|
||||||
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Add the newly parsed trees, including the one for t, into our common structure.
|
|
||||||
for name, tree := range trees {
|
|
||||||
// If the name we parsed is the name of this template, overwrite this template.
|
|
||||||
// The associate method checks it's not a redefinition.
|
|
||||||
tmpl := t
|
|
||||||
if name != t.name {
|
|
||||||
tmpl = t.New(name)
|
|
||||||
}
|
|
||||||
// Even if t == tmpl, we need to install it in the common.tmpl map.
|
|
||||||
if replace, err := t.associate(tmpl, tree); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if replace {
|
|
||||||
tmpl.Tree = tree
|
|
||||||
}
|
|
||||||
tmpl.leftDelim = t.leftDelim
|
|
||||||
tmpl.rightDelim = t.rightDelim
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// associate installs the new template into the group of templates associated
|
|
||||||
// with t. It is an error to reuse a name except to overwrite an empty
|
|
||||||
// template. The two are already known to share the common structure.
|
|
||||||
// The boolean return value reports wither to store this tree as t.Tree.
|
|
||||||
func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) {
|
|
||||||
if new.common != t.common {
|
|
||||||
panic("internal error: associate not common")
|
|
||||||
}
|
|
||||||
name := new.name
|
|
||||||
if old := t.tmpl[name]; old != nil {
|
|
||||||
oldIsEmpty := parse.IsEmptyTree(old.Root)
|
|
||||||
newIsEmpty := parse.IsEmptyTree(tree.Root)
|
|
||||||
if newIsEmpty {
|
|
||||||
// Whether old is empty or not, new is empty; no reason to replace old.
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
if !oldIsEmpty {
|
|
||||||
return false, fmt.Errorf("template: redefinition of template %q", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.tmpl[name] = new
|
|
||||||
return true, nil
|
|
||||||
}
|
|
1
vendor/github.com/boombuler/barcode/.gitignore
generated
vendored
1
vendor/github.com/boombuler/barcode/.gitignore
generated
vendored
|
@ -1 +0,0 @@
|
||||||
.vscode/
|
|
21
vendor/github.com/boombuler/barcode/LICENSE
generated
vendored
21
vendor/github.com/boombuler/barcode/LICENSE
generated
vendored
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Florian Sundermann
|
|
||||||
|
|
||||||
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.
|
|
53
vendor/github.com/boombuler/barcode/README.md
generated
vendored
53
vendor/github.com/boombuler/barcode/README.md
generated
vendored
|
@ -1,53 +0,0 @@
|
||||||
[![Join the chat at https://gitter.im/golang-barcode/Lobby](https://badges.gitter.im/golang-barcode/Lobby.svg)](https://gitter.im/golang-barcode/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
||||||
|
|
||||||
## Introduction ##
|
|
||||||
|
|
||||||
This is a package for GO which can be used to create different types of barcodes.
|
|
||||||
|
|
||||||
## Supported Barcode Types ##
|
|
||||||
* 2 of 5
|
|
||||||
* Aztec Code
|
|
||||||
* Codabar
|
|
||||||
* Code 128
|
|
||||||
* Code 39
|
|
||||||
* Code 93
|
|
||||||
* Datamatrix
|
|
||||||
* EAN 13
|
|
||||||
* EAN 8
|
|
||||||
* PDF 417
|
|
||||||
* QR Code
|
|
||||||
|
|
||||||
## Example ##
|
|
||||||
|
|
||||||
This is a simple example on how to create a QR-Code and write it to a png-file
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/png"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/boombuler/barcode"
|
|
||||||
"github.com/boombuler/barcode/qr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create the barcode
|
|
||||||
qrCode, _ := qr.Encode("Hello World", qr.M, qr.Auto)
|
|
||||||
|
|
||||||
// Scale the barcode to 200x200 pixels
|
|
||||||
qrCode, _ = barcode.Scale(qrCode, 200, 200)
|
|
||||||
|
|
||||||
// create the output file
|
|
||||||
file, _ := os.Create("qrcode.png")
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// encode the barcode as png
|
|
||||||
png.Encode(file, qrCode)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation ##
|
|
||||||
See [GoDoc](https://godoc.org/github.com/boombuler/barcode)
|
|
||||||
|
|
||||||
To create a barcode use the Encode function from one of the subpackages.
|
|
42
vendor/github.com/boombuler/barcode/barcode.go
generated
vendored
42
vendor/github.com/boombuler/barcode/barcode.go
generated
vendored
|
@ -1,42 +0,0 @@
|
||||||
package barcode
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
|
|
||||||
const (
|
|
||||||
TypeAztec = "Aztec"
|
|
||||||
TypeCodabar = "Codabar"
|
|
||||||
TypeCode128 = "Code 128"
|
|
||||||
TypeCode39 = "Code 39"
|
|
||||||
TypeCode93 = "Code 93"
|
|
||||||
TypeDataMatrix = "DataMatrix"
|
|
||||||
TypeEAN8 = "EAN 8"
|
|
||||||
TypeEAN13 = "EAN 13"
|
|
||||||
TypePDF = "PDF417"
|
|
||||||
TypeQR = "QR Code"
|
|
||||||
Type2of5 = "2 of 5"
|
|
||||||
Type2of5Interleaved = "2 of 5 (interleaved)"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Contains some meta information about a barcode
|
|
||||||
type Metadata struct {
|
|
||||||
// the name of the barcode kind
|
|
||||||
CodeKind string
|
|
||||||
// contains 1 for 1D barcodes or 2 for 2D barcodes
|
|
||||||
Dimensions byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// a rendered and encoded barcode
|
|
||||||
type Barcode interface {
|
|
||||||
image.Image
|
|
||||||
// returns some meta information about the barcode
|
|
||||||
Metadata() Metadata
|
|
||||||
// the data that was encoded in this barcode
|
|
||||||
Content() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional interface that some barcodes might implement to provide
|
|
||||||
// the value of its checksum.
|
|
||||||
type BarcodeIntCS interface {
|
|
||||||
Barcode
|
|
||||||
CheckSum() int
|
|
||||||
}
|
|
66
vendor/github.com/boombuler/barcode/qr/alphanumeric.go
generated
vendored
66
vendor/github.com/boombuler/barcode/qr/alphanumeric.go
generated
vendored
|
@ -1,66 +0,0 @@
|
||||||
package qr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/boombuler/barcode/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
const charSet string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
|
|
||||||
|
|
||||||
func stringToAlphaIdx(content string) <-chan int {
|
|
||||||
result := make(chan int)
|
|
||||||
go func() {
|
|
||||||
for _, r := range content {
|
|
||||||
idx := strings.IndexRune(charSet, r)
|
|
||||||
result <- idx
|
|
||||||
if idx < 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(result)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeAlphaNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
|
||||||
|
|
||||||
contentLenIsOdd := len(content)%2 == 1
|
|
||||||
contentBitCount := (len(content) / 2) * 11
|
|
||||||
if contentLenIsOdd {
|
|
||||||
contentBitCount += 6
|
|
||||||
}
|
|
||||||
vi := findSmallestVersionInfo(ecl, alphaNumericMode, contentBitCount)
|
|
||||||
if vi == nil {
|
|
||||||
return nil, nil, errors.New("To much data to encode")
|
|
||||||
}
|
|
||||||
|
|
||||||
res := new(utils.BitList)
|
|
||||||
res.AddBits(int(alphaNumericMode), 4)
|
|
||||||
res.AddBits(len(content), vi.charCountBits(alphaNumericMode))
|
|
||||||
|
|
||||||
encoder := stringToAlphaIdx(content)
|
|
||||||
|
|
||||||
for idx := 0; idx < len(content)/2; idx++ {
|
|
||||||
c1 := <-encoder
|
|
||||||
c2 := <-encoder
|
|
||||||
if c1 < 0 || c2 < 0 {
|
|
||||||
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
|
|
||||||
}
|
|
||||||
res.AddBits(c1*45+c2, 11)
|
|
||||||
}
|
|
||||||
if contentLenIsOdd {
|
|
||||||
c := <-encoder
|
|
||||||
if c < 0 {
|
|
||||||
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
|
|
||||||
}
|
|
||||||
res.AddBits(c, 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
addPaddingAndTerminator(res, vi)
|
|
||||||
|
|
||||||
return res, vi, nil
|
|
||||||
}
|
|
23
vendor/github.com/boombuler/barcode/qr/automatic.go
generated
vendored
23
vendor/github.com/boombuler/barcode/qr/automatic.go
generated
vendored
|
@ -1,23 +0,0 @@
|
||||||
package qr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/boombuler/barcode/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func encodeAuto(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
|
||||||
bits, vi, _ := Numeric.getEncoder()(content, ecl)
|
|
||||||
if bits != nil && vi != nil {
|
|
||||||
return bits, vi, nil
|
|
||||||
}
|
|
||||||
bits, vi, _ = AlphaNumeric.getEncoder()(content, ecl)
|
|
||||||
if bits != nil && vi != nil {
|
|
||||||
return bits, vi, nil
|
|
||||||
}
|
|
||||||
bits, vi, _ = Unicode.getEncoder()(content, ecl)
|
|
||||||
if bits != nil && vi != nil {
|
|
||||||
return bits, vi, nil
|
|
||||||
}
|
|
||||||
return nil, nil, fmt.Errorf("No encoding found to encode \"%s\"", content)
|
|
||||||
}
|
|
59
vendor/github.com/boombuler/barcode/qr/blocks.go
generated
vendored
59
vendor/github.com/boombuler/barcode/qr/blocks.go
generated
vendored
|
@ -1,59 +0,0 @@
|
||||||
package qr
|
|
||||||
|
|
||||||
type block struct {
|
|
||||||
data []byte
|
|
||||||
ecc []byte
|
|
||||||
}
|
|
||||||
type blockList []*block
|
|
||||||
|
|
||||||
func splitToBlocks(data <-chan byte, vi *versionInfo) blockList {
|
|
||||||
result := make(blockList, vi.NumberOfBlocksInGroup1+vi.NumberOfBlocksInGroup2)
|
|
||||||
|
|
||||||
for b := 0; b < int(vi.NumberOfBlocksInGroup1); b++ {
|
|
||||||
blk := new(block)
|
|
||||||
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup1)
|
|
||||||
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup1); cw++ {
|
|
||||||
blk.data[cw] = <-data
|
|
||||||
}
|
|
||||||
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
|
|
||||||
result[b] = blk
|
|
||||||
}
|
|
||||||
|
|
||||||
for b := 0; b < int(vi.NumberOfBlocksInGroup2); b++ {
|
|
||||||
blk := new(block)
|
|
||||||
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup2)
|
|
||||||
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup2); cw++ {
|
|
||||||
blk.data[cw] = <-data
|
|
||||||
}
|
|
||||||
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
|
|
||||||
result[int(vi.NumberOfBlocksInGroup1)+b] = blk
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bl blockList) interleave(vi *versionInfo) []byte {
|
|
||||||
var maxCodewordCount int
|
|
||||||
if vi.DataCodeWordsPerBlockInGroup1 > vi.DataCodeWordsPerBlockInGroup2 {
|
|
||||||
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup1)
|
|
||||||
} else {
|
|
||||||
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup2)
|
|
||||||
}
|
|
||||||
resultLen := (vi.DataCodeWordsPerBlockInGroup1+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup1 +
|
|
||||||
(vi.DataCodeWordsPerBlockInGroup2+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup2
|
|
||||||
|
|
||||||
result := make([]byte, 0, resultLen)
|
|
||||||
for i := 0; i < maxCodewordCount; i++ {
|
|
||||||
for b := 0; b < len(bl); b++ {
|
|
||||||
if len(bl[b].data) > i {
|
|
||||||
result = append(result, bl[b].data[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i < int(vi.ErrorCorrectionCodewordsPerBlock); i++ {
|
|
||||||
for b := 0; b < len(bl); b++ {
|
|
||||||
result = append(result, bl[b].ecc[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
416
vendor/github.com/boombuler/barcode/qr/encoder.go
generated
vendored
416
vendor/github.com/boombuler/barcode/qr/encoder.go
generated
vendored
|
@ -1,416 +0,0 @@
|
||||||
// Package qr can be used to create QR barcodes.
|
|
||||||
package qr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
|
|
||||||
"github.com/boombuler/barcode"
|
|
||||||
"github.com/boombuler/barcode/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type encodeFn func(content string, eccLevel ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error)
|
|
||||||
|
|
||||||
// Encoding mode for QR Codes.
|
|
||||||
type Encoding byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Auto will choose ths best matching encoding
|
|
||||||
Auto Encoding = iota
|
|
||||||
// Numeric encoding only encodes numbers [0-9]
|
|
||||||
Numeric
|
|
||||||
// AlphaNumeric encoding only encodes uppercase letters, numbers and [Space], $, %, *, +, -, ., /, :
|
|
||||||
AlphaNumeric
|
|
||||||
// Unicode encoding encodes the string as utf-8
|
|
||||||
Unicode
|
|
||||||
// only for testing purpose
|
|
||||||
unknownEncoding
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e Encoding) getEncoder() encodeFn {
|
|
||||||
switch e {
|
|
||||||
case Auto:
|
|
||||||
return encodeAuto
|
|
||||||
case Numeric:
|
|
||||||
return encodeNumeric
|
|
||||||
case AlphaNumeric:
|
|
||||||
return encodeAlphaNumeric
|
|
||||||
case Unicode:
|
|
||||||
return encodeUnicode
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Encoding) String() string {
|
|
||||||
switch e {
|
|
||||||
case Auto:
|
|
||||||
return "Auto"
|
|
||||||
case Numeric:
|
|
||||||
return "Numeric"
|
|
||||||
case AlphaNumeric:
|
|
||||||
return "AlphaNumeric"
|
|
||||||
case Unicode:
|
|
||||||
return "Unicode"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode returns a QR barcode with the given content, error correction level and uses the given encoding
|
|
||||||
func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) {
|
|
||||||
bits, vi, err := mode.getEncoder()(content, level)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks := splitToBlocks(bits.IterateBytes(), vi)
|
|
||||||
data := blocks.interleave(vi)
|
|
||||||
result := render(data, vi)
|
|
||||||
result.content = content
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func render(data []byte, vi *versionInfo) *qrcode {
|
|
||||||
dim := vi.modulWidth()
|
|
||||||
results := make([]*qrcode, 8)
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
results[i] = newBarcode(dim)
|
|
||||||
}
|
|
||||||
|
|
||||||
occupied := newBarcode(dim)
|
|
||||||
|
|
||||||
setAll := func(x int, y int, val bool) {
|
|
||||||
occupied.Set(x, y, true)
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
results[i].Set(x, y, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drawFinderPatterns(vi, setAll)
|
|
||||||
drawAlignmentPatterns(occupied, vi, setAll)
|
|
||||||
|
|
||||||
//Timing Pattern:
|
|
||||||
var i int
|
|
||||||
for i = 0; i < dim; i++ {
|
|
||||||
if !occupied.Get(i, 6) {
|
|
||||||
setAll(i, 6, i%2 == 0)
|
|
||||||
}
|
|
||||||
if !occupied.Get(6, i) {
|
|
||||||
setAll(6, i, i%2 == 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Dark Module
|
|
||||||
setAll(8, dim-8, true)
|
|
||||||
|
|
||||||
drawVersionInfo(vi, setAll)
|
|
||||||
drawFormatInfo(vi, -1, occupied.Set)
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
drawFormatInfo(vi, i, results[i].Set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the data
|
|
||||||
var curBitNo int
|
|
||||||
|
|
||||||
for pos := range iterateModules(occupied) {
|
|
||||||
var curBit bool
|
|
||||||
if curBitNo < len(data)*8 {
|
|
||||||
curBit = ((data[curBitNo/8] >> uint(7-(curBitNo%8))) & 1) == 1
|
|
||||||
} else {
|
|
||||||
curBit = false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
setMasked(pos.X, pos.Y, curBit, i, results[i].Set)
|
|
||||||
}
|
|
||||||
curBitNo++
|
|
||||||
}
|
|
||||||
|
|
||||||
lowestPenalty := ^uint(0)
|
|
||||||
lowestPenaltyIdx := -1
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
p := results[i].calcPenalty()
|
|
||||||
if p < lowestPenalty {
|
|
||||||
lowestPenalty = p
|
|
||||||
lowestPenaltyIdx = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results[lowestPenaltyIdx]
|
|
||||||
}
|
|
||||||
|
|
||||||
func setMasked(x, y int, val bool, mask int, set func(int, int, bool)) {
|
|
||||||
switch mask {
|
|
||||||
case 0:
|
|
||||||
val = val != (((y + x) % 2) == 0)
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
val = val != ((y % 2) == 0)
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
val = val != ((x % 3) == 0)
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
val = val != (((y + x) % 3) == 0)
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
val = val != (((y/2 + x/3) % 2) == 0)
|
|
||||||
break
|
|
||||||
case 5:
|
|
||||||
val = val != (((y*x)%2)+((y*x)%3) == 0)
|
|
||||||
break
|
|
||||||
case 6:
|
|
||||||
val = val != ((((y*x)%2)+((y*x)%3))%2 == 0)
|
|
||||||
break
|
|
||||||
case 7:
|
|
||||||
val = val != ((((y+x)%2)+((y*x)%3))%2 == 0)
|
|
||||||
}
|
|
||||||
set(x, y, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func iterateModules(occupied *qrcode) <-chan image.Point {
|
|
||||||
result := make(chan image.Point)
|
|
||||||
allPoints := make(chan image.Point)
|
|
||||||
go func() {
|
|
||||||
curX := occupied.dimension - 1
|
|
||||||
curY := occupied.dimension - 1
|
|
||||||
isUpward := true
|
|
||||||
|
|
||||||
for true {
|
|
||||||
if isUpward {
|
|
||||||
allPoints <- image.Pt(curX, curY)
|
|
||||||
allPoints <- image.Pt(curX-1, curY)
|
|
||||||
curY--
|
|
||||||
if curY < 0 {
|
|
||||||
curY = 0
|
|
||||||
curX -= 2
|
|
||||||
if curX == 6 {
|
|
||||||
curX--
|
|
||||||
}
|
|
||||||
if curX < 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
isUpward = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
allPoints <- image.Pt(curX, curY)
|
|
||||||
allPoints <- image.Pt(curX-1, curY)
|
|
||||||
curY++
|
|
||||||
if curY >= occupied.dimension {
|
|
||||||
curY = occupied.dimension - 1
|
|
||||||
curX -= 2
|
|
||||||
if curX == 6 {
|
|
||||||
curX--
|
|
||||||
}
|
|
||||||
isUpward = true
|
|
||||||
if curX < 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(allPoints)
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
for pt := range allPoints {
|
|
||||||
if !occupied.Get(pt.X, pt.Y) {
|
|
||||||
result <- pt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(result)
|
|
||||||
}()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawFinderPatterns(vi *versionInfo, set func(int, int, bool)) {
|
|
||||||
dim := vi.modulWidth()
|
|
||||||
drawPattern := func(xoff int, yoff int) {
|
|
||||||
for x := -1; x < 8; x++ {
|
|
||||||
for y := -1; y < 8; y++ {
|
|
||||||
val := (x == 0 || x == 6 || y == 0 || y == 6 || (x > 1 && x < 5 && y > 1 && y < 5)) && (x <= 6 && y <= 6 && x >= 0 && y >= 0)
|
|
||||||
|
|
||||||
if x+xoff >= 0 && x+xoff < dim && y+yoff >= 0 && y+yoff < dim {
|
|
||||||
set(x+xoff, y+yoff, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
drawPattern(0, 0)
|
|
||||||
drawPattern(0, dim-7)
|
|
||||||
drawPattern(dim-7, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawAlignmentPatterns(occupied *qrcode, vi *versionInfo, set func(int, int, bool)) {
|
|
||||||
drawPattern := func(xoff int, yoff int) {
|
|
||||||
for x := -2; x <= 2; x++ {
|
|
||||||
for y := -2; y <= 2; y++ {
|
|
||||||
val := x == -2 || x == 2 || y == -2 || y == 2 || (x == 0 && y == 0)
|
|
||||||
set(x+xoff, y+yoff, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
positions := vi.alignmentPatternPlacements()
|
|
||||||
|
|
||||||
for _, x := range positions {
|
|
||||||
for _, y := range positions {
|
|
||||||
if occupied.Get(x, y) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
drawPattern(x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var formatInfos = map[ErrorCorrectionLevel]map[int][]bool{
|
|
||||||
L: {
|
|
||||||
0: []bool{true, true, true, false, true, true, true, true, true, false, false, false, true, false, false},
|
|
||||||
1: []bool{true, true, true, false, false, true, false, true, true, true, true, false, false, true, true},
|
|
||||||
2: []bool{true, true, true, true, true, false, true, true, false, true, false, true, false, true, false},
|
|
||||||
3: []bool{true, true, true, true, false, false, false, true, false, false, true, true, true, false, true},
|
|
||||||
4: []bool{true, true, false, false, true, true, false, false, false, true, false, true, true, true, true},
|
|
||||||
5: []bool{true, true, false, false, false, true, true, false, false, false, true, true, false, false, false},
|
|
||||||
6: []bool{true, true, false, true, true, false, false, false, true, false, false, false, false, false, true},
|
|
||||||
7: []bool{true, true, false, true, false, false, true, false, true, true, true, false, true, true, false},
|
|
||||||
},
|
|
||||||
M: {
|
|
||||||
0: []bool{true, false, true, false, true, false, false, false, false, false, true, false, false, true, false},
|
|
||||||
1: []bool{true, false, true, false, false, false, true, false, false, true, false, false, true, false, true},
|
|
||||||
2: []bool{true, false, true, true, true, true, false, false, true, true, true, true, true, false, false},
|
|
||||||
3: []bool{true, false, true, true, false, true, true, false, true, false, false, true, false, true, true},
|
|
||||||
4: []bool{true, false, false, false, true, false, true, true, true, true, true, true, false, false, true},
|
|
||||||
5: []bool{true, false, false, false, false, false, false, true, true, false, false, true, true, true, false},
|
|
||||||
6: []bool{true, false, false, true, true, true, true, true, false, false, true, false, true, true, true},
|
|
||||||
7: []bool{true, false, false, true, false, true, false, true, false, true, false, false, false, false, false},
|
|
||||||
},
|
|
||||||
Q: {
|
|
||||||
0: []bool{false, true, true, false, true, false, true, false, true, false, true, true, true, true, true},
|
|
||||||
1: []bool{false, true, true, false, false, false, false, false, true, true, false, true, false, false, false},
|
|
||||||
2: []bool{false, true, true, true, true, true, true, false, false, true, true, false, false, false, true},
|
|
||||||
3: []bool{false, true, true, true, false, true, false, false, false, false, false, false, true, true, false},
|
|
||||||
4: []bool{false, true, false, false, true, false, false, true, false, true, true, false, true, false, false},
|
|
||||||
5: []bool{false, true, false, false, false, false, true, true, false, false, false, false, false, true, true},
|
|
||||||
6: []bool{false, true, false, true, true, true, false, true, true, false, true, true, false, true, false},
|
|
||||||
7: []bool{false, true, false, true, false, true, true, true, true, true, false, true, true, false, true},
|
|
||||||
},
|
|
||||||
H: {
|
|
||||||
0: []bool{false, false, true, false, true, true, false, true, false, false, false, true, false, false, true},
|
|
||||||
1: []bool{false, false, true, false, false, true, true, true, false, true, true, true, true, true, false},
|
|
||||||
2: []bool{false, false, true, true, true, false, false, true, true, true, false, false, true, true, true},
|
|
||||||
3: []bool{false, false, true, true, false, false, true, true, true, false, true, false, false, false, false},
|
|
||||||
4: []bool{false, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
|
|
||||||
5: []bool{false, false, false, false, false, true, false, false, true, false, true, false, true, false, true},
|
|
||||||
6: []bool{false, false, false, true, true, false, true, false, false, false, false, true, true, false, false},
|
|
||||||
7: []bool{false, false, false, true, false, false, false, false, false, true, true, true, false, true, true},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawFormatInfo(vi *versionInfo, usedMask int, set func(int, int, bool)) {
|
|
||||||
var formatInfo []bool
|
|
||||||
|
|
||||||
if usedMask == -1 {
|
|
||||||
formatInfo = []bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true} // Set all to true cause -1 --> occupied mask.
|
|
||||||
} else {
|
|
||||||
formatInfo = formatInfos[vi.Level][usedMask]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(formatInfo) == 15 {
|
|
||||||
dim := vi.modulWidth()
|
|
||||||
set(0, 8, formatInfo[0])
|
|
||||||
set(1, 8, formatInfo[1])
|
|
||||||
set(2, 8, formatInfo[2])
|
|
||||||
set(3, 8, formatInfo[3])
|
|
||||||
set(4, 8, formatInfo[4])
|
|
||||||
set(5, 8, formatInfo[5])
|
|
||||||
set(7, 8, formatInfo[6])
|
|
||||||
set(8, 8, formatInfo[7])
|
|
||||||
set(8, 7, formatInfo[8])
|
|
||||||
set(8, 5, formatInfo[9])
|
|
||||||
set(8, 4, formatInfo[10])
|
|
||||||
set(8, 3, formatInfo[11])
|
|
||||||
set(8, 2, formatInfo[12])
|
|
||||||
set(8, 1, formatInfo[13])
|
|
||||||
set(8, 0, formatInfo[14])
|
|
||||||
|
|
||||||
set(8, dim-1, formatInfo[0])
|
|
||||||
set(8, dim-2, formatInfo[1])
|
|
||||||
set(8, dim-3, formatInfo[2])
|
|
||||||
set(8, dim-4, formatInfo[3])
|
|
||||||
set(8, dim-5, formatInfo[4])
|
|
||||||
set(8, dim-6, formatInfo[5])
|
|
||||||
set(8, dim-7, formatInfo[6])
|
|
||||||
set(dim-8, 8, formatInfo[7])
|
|
||||||
set(dim-7, 8, formatInfo[8])
|
|
||||||
set(dim-6, 8, formatInfo[9])
|
|
||||||
set(dim-5, 8, formatInfo[10])
|
|
||||||
set(dim-4, 8, formatInfo[11])
|
|
||||||
set(dim-3, 8, formatInfo[12])
|
|
||||||
set(dim-2, 8, formatInfo[13])
|
|
||||||
set(dim-1, 8, formatInfo[14])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var versionInfoBitsByVersion = map[byte][]bool{
|
|
||||||
7: []bool{false, false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false},
|
|
||||||
8: []bool{false, false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false},
|
|
||||||
9: []bool{false, false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true},
|
|
||||||
10: []bool{false, false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true},
|
|
||||||
11: []bool{false, false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false},
|
|
||||||
12: []bool{false, false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
|
|
||||||
13: []bool{false, false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true},
|
|
||||||
14: []bool{false, false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true},
|
|
||||||
15: []bool{false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false},
|
|
||||||
16: []bool{false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false},
|
|
||||||
17: []bool{false, true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true},
|
|
||||||
18: []bool{false, true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true},
|
|
||||||
19: []bool{false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false},
|
|
||||||
20: []bool{false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true, false},
|
|
||||||
21: []bool{false, true, false, true, false, true, false, true, true, false, true, false, false, false, false, false, true, true},
|
|
||||||
22: []bool{false, true, false, true, true, false, true, false, false, false, true, true, false, false, true, false, false, true},
|
|
||||||
23: []bool{false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false, false},
|
|
||||||
24: []bool{false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false, false},
|
|
||||||
25: []bool{false, true, true, false, false, true, false, false, false, true, true, true, true, false, false, false, false, true},
|
|
||||||
26: []bool{false, true, true, false, true, false, true, true, true, true, true, false, true, false, true, false, true, true},
|
|
||||||
27: []bool{false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true, false},
|
|
||||||
28: []bool{false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true, false},
|
|
||||||
29: []bool{false, true, true, true, false, true, false, false, true, true, false, false, true, true, true, true, true, true},
|
|
||||||
30: []bool{false, true, true, true, true, false, true, true, false, true, false, true, true, true, false, true, false, true},
|
|
||||||
31: []bool{false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false, false},
|
|
||||||
32: []bool{true, false, false, false, false, false, true, false, false, true, true, true, false, true, false, true, false, true},
|
|
||||||
33: []bool{true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false, false},
|
|
||||||
34: []bool{true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true, false},
|
|
||||||
35: []bool{true, false, false, false, true, true, false, true, true, true, true, false, false, true, true, true, true, true},
|
|
||||||
36: []bool{true, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, true, true},
|
|
||||||
37: []bool{true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true, false},
|
|
||||||
38: []bool{true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false, false},
|
|
||||||
39: []bool{true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, false, false, true},
|
|
||||||
40: []bool{true, false, true, false, false, false, true, true, false, false, false, true, true, false, true, false, false, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawVersionInfo(vi *versionInfo, set func(int, int, bool)) {
|
|
||||||
versionInfoBits, ok := versionInfoBitsByVersion[vi.Version]
|
|
||||||
|
|
||||||
if ok && len(versionInfoBits) > 0 {
|
|
||||||
for i := 0; i < len(versionInfoBits); i++ {
|
|
||||||
x := (vi.modulWidth() - 11) + i%3
|
|
||||||
y := i / 3
|
|
||||||
set(x, y, versionInfoBits[len(versionInfoBits)-i-1])
|
|
||||||
set(y, x, versionInfoBits[len(versionInfoBits)-i-1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func addPaddingAndTerminator(bl *utils.BitList, vi *versionInfo) {
|
|
||||||
for i := 0; i < 4 && bl.Len() < vi.totalDataBytes()*8; i++ {
|
|
||||||
bl.AddBit(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
for bl.Len()%8 != 0 {
|
|
||||||
bl.AddBit(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; bl.Len() < vi.totalDataBytes()*8; i++ {
|
|
||||||
if i%2 == 0 {
|
|
||||||
bl.AddByte(236)
|
|
||||||
} else {
|
|
||||||
bl.AddByte(17)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
29
vendor/github.com/boombuler/barcode/qr/errorcorrection.go
generated
vendored
29
vendor/github.com/boombuler/barcode/qr/errorcorrection.go
generated
vendored
|
@ -1,29 +0,0 @@
|
||||||
package qr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/boombuler/barcode/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type errorCorrection struct {
|
|
||||||
rs *utils.ReedSolomonEncoder
|
|
||||||
}
|
|
||||||
|
|
||||||
var ec = newErrorCorrection()
|
|
||||||
|
|
||||||
func newErrorCorrection() *errorCorrection {
|
|
||||||
fld := utils.NewGaloisField(285, 256, 0)
|
|
||||||
return &errorCorrection{utils.NewReedSolomonEncoder(fld)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec *errorCorrection) calcECC(data []byte, eccCount byte) []byte {
|
|
||||||
dataInts := make([]int, len(data))
|
|
||||||
for i := 0; i < len(data); i++ {
|
|
||||||
dataInts[i] = int(data[i])
|
|
||||||
}
|
|
||||||
res := ec.rs.Encode(dataInts, int(eccCount))
|
|
||||||
result := make([]byte, len(res))
|
|
||||||
for i := 0; i < len(res); i++ {
|
|
||||||
result[i] = byte(res[i])
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
56
vendor/github.com/boombuler/barcode/qr/numeric.go
generated
vendored
56
vendor/github.com/boombuler/barcode/qr/numeric.go
generated
vendored
|
@ -1,56 +0,0 @@
|
||||||
package qr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/boombuler/barcode/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func encodeNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
|
||||||
contentBitCount := (len(content) / 3) * 10
|
|
||||||
switch len(content) % 3 {
|
|
||||||
case 1:
|
|
||||||
contentBitCount += 4
|
|
||||||
case 2:
|
|
||||||
contentBitCount += 7
|
|
||||||
}
|
|
||||||
vi := findSmallestVersionInfo(ecl, numericMode, contentBitCount)
|
|
||||||
if vi == nil {
|
|
||||||
return nil, nil, errors.New("To much data to encode")
|
|
||||||
}
|
|
||||||
res := new(utils.BitList)
|
|
||||||
res.AddBits(int(numericMode), 4)
|
|
||||||
res.AddBits(len(content), vi.charCountBits(numericMode))
|
|
||||||
|
|
||||||
for pos := 0; pos < len(content); pos += 3 {
|
|
||||||
var curStr string
|
|
||||||
if pos+3 <= len(content) {
|
|
||||||
curStr = content[pos : pos+3]
|
|
||||||
} else {
|
|
||||||
curStr = content[pos:]
|
|
||||||
}
|
|
||||||
|
|
||||||
i, err := strconv.Atoi(curStr)
|
|
||||||
if err != nil || i < 0 {
|
|
||||||
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, Numeric)
|
|
||||||
}
|
|
||||||
var bitCnt byte
|
|
||||||
switch len(curStr) % 3 {
|
|
||||||
case 0:
|
|
||||||
bitCnt = 10
|
|
||||||
case 1:
|
|
||||||
bitCnt = 4
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
bitCnt = 7
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
res.AddBits(i, bitCnt)
|
|
||||||
}
|
|
||||||
|
|
||||||
addPaddingAndTerminator(res, vi)
|
|
||||||
return res, vi, nil
|
|
||||||
}
|
|
166
vendor/github.com/boombuler/barcode/qr/qrcode.go
generated
vendored
166
vendor/github.com/boombuler/barcode/qr/qrcode.go
generated
vendored
|
@ -1,166 +0,0 @@
|
||||||
package qr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/boombuler/barcode"
|
|
||||||
"github.com/boombuler/barcode/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type qrcode struct {
|
|
||||||
dimension int
|
|
||||||
data *utils.BitList
|
|
||||||
content string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) Content() string {
|
|
||||||
return qr.content
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) Metadata() barcode.Metadata {
|
|
||||||
return barcode.Metadata{barcode.TypeQR, 2}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) ColorModel() color.Model {
|
|
||||||
return color.Gray16Model
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) Bounds() image.Rectangle {
|
|
||||||
return image.Rect(0, 0, qr.dimension, qr.dimension)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) At(x, y int) color.Color {
|
|
||||||
if qr.Get(x, y) {
|
|
||||||
return color.Black
|
|
||||||
}
|
|
||||||
return color.White
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) Get(x, y int) bool {
|
|
||||||
return qr.data.GetBit(x*qr.dimension + y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) Set(x, y int, val bool) {
|
|
||||||
qr.data.SetBit(x*qr.dimension+y, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) calcPenalty() uint {
|
|
||||||
return qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) calcPenaltyRule1() uint {
|
|
||||||
var result uint
|
|
||||||
for x := 0; x < qr.dimension; x++ {
|
|
||||||
checkForX := false
|
|
||||||
var cntX uint
|
|
||||||
checkForY := false
|
|
||||||
var cntY uint
|
|
||||||
|
|
||||||
for y := 0; y < qr.dimension; y++ {
|
|
||||||
if qr.Get(x, y) == checkForX {
|
|
||||||
cntX++
|
|
||||||
} else {
|
|
||||||
checkForX = !checkForX
|
|
||||||
if cntX >= 5 {
|
|
||||||
result += cntX - 2
|
|
||||||
}
|
|
||||||
cntX = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if qr.Get(y, x) == checkForY {
|
|
||||||
cntY++
|
|
||||||
} else {
|
|
||||||
checkForY = !checkForY
|
|
||||||
if cntY >= 5 {
|
|
||||||
result += cntY - 2
|
|
||||||
}
|
|
||||||
cntY = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cntX >= 5 {
|
|
||||||
result += cntX - 2
|
|
||||||
}
|
|
||||||
if cntY >= 5 {
|
|
||||||
result += cntY - 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) calcPenaltyRule2() uint {
|
|
||||||
var result uint
|
|
||||||
for x := 0; x < qr.dimension-1; x++ {
|
|
||||||
for y := 0; y < qr.dimension-1; y++ {
|
|
||||||
check := qr.Get(x, y)
|
|
||||||
if qr.Get(x, y+1) == check && qr.Get(x+1, y) == check && qr.Get(x+1, y+1) == check {
|
|
||||||
result += 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) calcPenaltyRule3() uint {
|
|
||||||
pattern1 := []bool{true, false, true, true, true, false, true, false, false, false, false}
|
|
||||||
pattern2 := []bool{false, false, false, false, true, false, true, true, true, false, true}
|
|
||||||
|
|
||||||
var result uint
|
|
||||||
for x := 0; x <= qr.dimension-len(pattern1); x++ {
|
|
||||||
for y := 0; y < qr.dimension; y++ {
|
|
||||||
pattern1XFound := true
|
|
||||||
pattern2XFound := true
|
|
||||||
pattern1YFound := true
|
|
||||||
pattern2YFound := true
|
|
||||||
|
|
||||||
for i := 0; i < len(pattern1); i++ {
|
|
||||||
iv := qr.Get(x+i, y)
|
|
||||||
if iv != pattern1[i] {
|
|
||||||
pattern1XFound = false
|
|
||||||
}
|
|
||||||
if iv != pattern2[i] {
|
|
||||||
pattern2XFound = false
|
|
||||||
}
|
|
||||||
iv = qr.Get(y, x+i)
|
|
||||||
if iv != pattern1[i] {
|
|
||||||
pattern1YFound = false
|
|
||||||
}
|
|
||||||
if iv != pattern2[i] {
|
|
||||||
pattern2YFound = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pattern1XFound || pattern2XFound {
|
|
||||||
result += 40
|
|
||||||
}
|
|
||||||
if pattern1YFound || pattern2YFound {
|
|
||||||
result += 40
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qr *qrcode) calcPenaltyRule4() uint {
|
|
||||||
totalNum := qr.data.Len()
|
|
||||||
trueCnt := 0
|
|
||||||
for i := 0; i < totalNum; i++ {
|
|
||||||
if qr.data.GetBit(i) {
|
|
||||||
trueCnt++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
percDark := float64(trueCnt) * 100 / float64(totalNum)
|
|
||||||
floor := math.Abs(math.Floor(percDark/5) - 10)
|
|
||||||
ceil := math.Abs(math.Ceil(percDark/5) - 10)
|
|
||||||
return uint(math.Min(floor, ceil) * 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBarcode(dim int) *qrcode {
|
|
||||||
res := new(qrcode)
|
|
||||||
res.dimension = dim
|
|
||||||
res.data = utils.NewBitList(dim * dim)
|
|
||||||
return res
|
|
||||||
}
|
|
27
vendor/github.com/boombuler/barcode/qr/unicode.go
generated
vendored
27
vendor/github.com/boombuler/barcode/qr/unicode.go
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
package qr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/boombuler/barcode/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func encodeUnicode(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
|
|
||||||
data := []byte(content)
|
|
||||||
|
|
||||||
vi := findSmallestVersionInfo(ecl, byteMode, len(data)*8)
|
|
||||||
if vi == nil {
|
|
||||||
return nil, nil, errors.New("To much data to encode")
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's not correct to add the unicode bytes to the result directly but most readers can't handle the
|
|
||||||
// required ECI header...
|
|
||||||
res := new(utils.BitList)
|
|
||||||
res.AddBits(int(byteMode), 4)
|
|
||||||
res.AddBits(len(content), vi.charCountBits(byteMode))
|
|
||||||
for _, b := range data {
|
|
||||||
res.AddByte(b)
|
|
||||||
}
|
|
||||||
addPaddingAndTerminator(res, vi)
|
|
||||||
return res, vi, nil
|
|
||||||
}
|
|
310
vendor/github.com/boombuler/barcode/qr/versioninfo.go
generated
vendored
310
vendor/github.com/boombuler/barcode/qr/versioninfo.go
generated
vendored
|
@ -1,310 +0,0 @@
|
||||||
package qr
|
|
||||||
|
|
||||||
import "math"
|
|
||||||
|
|
||||||
// ErrorCorrectionLevel indicates the amount of "backup data" stored in the QR code
|
|
||||||
type ErrorCorrectionLevel byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
// L recovers 7% of data
|
|
||||||
L ErrorCorrectionLevel = iota
|
|
||||||
// M recovers 15% of data
|
|
||||||
M
|
|
||||||
// Q recovers 25% of data
|
|
||||||
Q
|
|
||||||
// H recovers 30% of data
|
|
||||||
H
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ecl ErrorCorrectionLevel) String() string {
|
|
||||||
switch ecl {
|
|
||||||
case L:
|
|
||||||
return "L"
|
|
||||||
case M:
|
|
||||||
return "M"
|
|
||||||
case Q:
|
|
||||||
return "Q"
|
|
||||||
case H:
|
|
||||||
return "H"
|
|
||||||
}
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
type encodingMode byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
numericMode encodingMode = 1
|
|
||||||
alphaNumericMode encodingMode = 2
|
|
||||||
byteMode encodingMode = 4
|
|
||||||
kanjiMode encodingMode = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
type versionInfo struct {
|
|
||||||
Version byte
|
|
||||||
Level ErrorCorrectionLevel
|
|
||||||
ErrorCorrectionCodewordsPerBlock byte
|
|
||||||
NumberOfBlocksInGroup1 byte
|
|
||||||
DataCodeWordsPerBlockInGroup1 byte
|
|
||||||
NumberOfBlocksInGroup2 byte
|
|
||||||
DataCodeWordsPerBlockInGroup2 byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var versionInfos = []*versionInfo{
|
|
||||||
&versionInfo{1, L, 7, 1, 19, 0, 0},
|
|
||||||
&versionInfo{1, M, 10, 1, 16, 0, 0},
|
|
||||||
&versionInfo{1, Q, 13, 1, 13, 0, 0},
|
|
||||||
&versionInfo{1, H, 17, 1, 9, 0, 0},
|
|
||||||
&versionInfo{2, L, 10, 1, 34, 0, 0},
|
|
||||||
&versionInfo{2, M, 16, 1, 28, 0, 0},
|
|
||||||
&versionInfo{2, Q, 22, 1, 22, 0, 0},
|
|
||||||
&versionInfo{2, H, 28, 1, 16, 0, 0},
|
|
||||||
&versionInfo{3, L, 15, 1, 55, 0, 0},
|
|
||||||
&versionInfo{3, M, 26, 1, 44, 0, 0},
|
|
||||||
&versionInfo{3, Q, 18, 2, 17, 0, 0},
|
|
||||||
&versionInfo{3, H, 22, 2, 13, 0, 0},
|
|
||||||
&versionInfo{4, L, 20, 1, 80, 0, 0},
|
|
||||||
&versionInfo{4, M, 18, 2, 32, 0, 0},
|
|
||||||
&versionInfo{4, Q, 26, 2, 24, 0, 0},
|
|
||||||
&versionInfo{4, H, 16, 4, 9, 0, 0},
|
|
||||||
&versionInfo{5, L, 26, 1, 108, 0, 0},
|
|
||||||
&versionInfo{5, M, 24, 2, 43, 0, 0},
|
|
||||||
&versionInfo{5, Q, 18, 2, 15, 2, 16},
|
|
||||||
&versionInfo{5, H, 22, 2, 11, 2, 12},
|
|
||||||
&versionInfo{6, L, 18, 2, 68, 0, 0},
|
|
||||||
&versionInfo{6, M, 16, 4, 27, 0, 0},
|
|
||||||
&versionInfo{6, Q, 24, 4, 19, 0, 0},
|
|
||||||
&versionInfo{6, H, 28, 4, 15, 0, 0},
|
|
||||||
&versionInfo{7, L, 20, 2, 78, 0, 0},
|
|
||||||
&versionInfo{7, M, 18, 4, 31, 0, 0},
|
|
||||||
&versionInfo{7, Q, 18, 2, 14, 4, 15},
|
|
||||||
&versionInfo{7, H, 26, 4, 13, 1, 14},
|
|
||||||
&versionInfo{8, L, 24, 2, 97, 0, 0},
|
|
||||||
&versionInfo{8, M, 22, 2, 38, 2, 39},
|
|
||||||
&versionInfo{8, Q, 22, 4, 18, 2, 19},
|
|
||||||
&versionInfo{8, H, 26, 4, 14, 2, 15},
|
|
||||||
&versionInfo{9, L, 30, 2, 116, 0, 0},
|
|
||||||
&versionInfo{9, M, 22, 3, 36, 2, 37},
|
|
||||||
&versionInfo{9, Q, 20, 4, 16, 4, 17},
|
|
||||||
&versionInfo{9, H, 24, 4, 12, 4, 13},
|
|
||||||
&versionInfo{10, L, 18, 2, 68, 2, 69},
|
|
||||||
&versionInfo{10, M, 26, 4, 43, 1, 44},
|
|
||||||
&versionInfo{10, Q, 24, 6, 19, 2, 20},
|
|
||||||
&versionInfo{10, H, 28, 6, 15, 2, 16},
|
|
||||||
&versionInfo{11, L, 20, 4, 81, 0, 0},
|
|
||||||
&versionInfo{11, M, 30, 1, 50, 4, 51},
|
|
||||||
&versionInfo{11, Q, 28, 4, 22, 4, 23},
|
|
||||||
&versionInfo{11, H, 24, 3, 12, 8, 13},
|
|
||||||
&versionInfo{12, L, 24, 2, 92, 2, 93},
|
|
||||||
&versionInfo{12, M, 22, 6, 36, 2, 37},
|
|
||||||
&versionInfo{12, Q, 26, 4, 20, 6, 21},
|
|
||||||
&versionInfo{12, H, 28, 7, 14, 4, 15},
|
|
||||||
&versionInfo{13, L, 26, 4, 107, 0, 0},
|
|
||||||
&versionInfo{13, M, 22, 8, 37, 1, 38},
|
|
||||||
&versionInfo{13, Q, 24, 8, 20, 4, 21},
|
|
||||||
&versionInfo{13, H, 22, 12, 11, 4, 12},
|
|
||||||
&versionInfo{14, L, 30, 3, 115, 1, 116},
|
|
||||||
&versionInfo{14, M, 24, 4, 40, 5, 41},
|
|
||||||
&versionInfo{14, Q, 20, 11, 16, 5, 17},
|
|
||||||
&versionInfo{14, H, 24, 11, 12, 5, 13},
|
|
||||||
&versionInfo{15, L, 22, 5, 87, 1, 88},
|
|
||||||
&versionInfo{15, M, 24, 5, 41, 5, 42},
|
|
||||||
&versionInfo{15, Q, 30, 5, 24, 7, 25},
|
|
||||||
&versionInfo{15, H, 24, 11, 12, 7, 13},
|
|
||||||
&versionInfo{16, L, 24, 5, 98, 1, 99},
|
|
||||||
&versionInfo{16, M, 28, 7, 45, 3, 46},
|
|
||||||
&versionInfo{16, Q, 24, 15, 19, 2, 20},
|
|
||||||
&versionInfo{16, H, 30, 3, 15, 13, 16},
|
|
||||||
&versionInfo{17, L, 28, 1, 107, 5, 108},
|
|
||||||
&versionInfo{17, M, 28, 10, 46, 1, 47},
|
|
||||||
&versionInfo{17, Q, 28, 1, 22, 15, 23},
|
|
||||||
&versionInfo{17, H, 28, 2, 14, 17, 15},
|
|
||||||
&versionInfo{18, L, 30, 5, 120, 1, 121},
|
|
||||||
&versionInfo{18, M, 26, 9, 43, 4, 44},
|
|
||||||
&versionInfo{18, Q, 28, 17, 22, 1, 23},
|
|
||||||
&versionInfo{18, H, 28, 2, 14, 19, 15},
|
|
||||||
&versionInfo{19, L, 28, 3, 113, 4, 114},
|
|
||||||
&versionInfo{19, M, 26, 3, 44, 11, 45},
|
|
||||||
&versionInfo{19, Q, 26, 17, 21, 4, 22},
|
|
||||||
&versionInfo{19, H, 26, 9, 13, 16, 14},
|
|
||||||
&versionInfo{20, L, 28, 3, 107, 5, 108},
|
|
||||||
&versionInfo{20, M, 26, 3, 41, 13, 42},
|
|
||||||
&versionInfo{20, Q, 30, 15, 24, 5, 25},
|
|
||||||
&versionInfo{20, H, 28, 15, 15, 10, 16},
|
|
||||||
&versionInfo{21, L, 28, 4, 116, 4, 117},
|
|
||||||
&versionInfo{21, M, 26, 17, 42, 0, 0},
|
|
||||||
&versionInfo{21, Q, 28, 17, 22, 6, 23},
|
|
||||||
&versionInfo{21, H, 30, 19, 16, 6, 17},
|
|
||||||
&versionInfo{22, L, 28, 2, 111, 7, 112},
|
|
||||||
&versionInfo{22, M, 28, 17, 46, 0, 0},
|
|
||||||
&versionInfo{22, Q, 30, 7, 24, 16, 25},
|
|
||||||
&versionInfo{22, H, 24, 34, 13, 0, 0},
|
|
||||||
&versionInfo{23, L, 30, 4, 121, 5, 122},
|
|
||||||
&versionInfo{23, M, 28, 4, 47, 14, 48},
|
|
||||||
&versionInfo{23, Q, 30, 11, 24, 14, 25},
|
|
||||||
&versionInfo{23, H, 30, 16, 15, 14, 16},
|
|
||||||
&versionInfo{24, L, 30, 6, 117, 4, 118},
|
|
||||||
&versionInfo{24, M, 28, 6, 45, 14, 46},
|
|
||||||
&versionInfo{24, Q, 30, 11, 24, 16, 25},
|
|
||||||
&versionInfo{24, H, 30, 30, 16, 2, 17},
|
|
||||||
&versionInfo{25, L, 26, 8, 106, 4, 107},
|
|
||||||
&versionInfo{25, M, 28, 8, 47, 13, 48},
|
|
||||||
&versionInfo{25, Q, 30, 7, 24, 22, 25},
|
|
||||||
&versionInfo{25, H, 30, 22, 15, 13, 16},
|
|
||||||
&versionInfo{26, L, 28, 10, 114, 2, 115},
|
|
||||||
&versionInfo{26, M, 28, 19, 46, 4, 47},
|
|
||||||
&versionInfo{26, Q, 28, 28, 22, 6, 23},
|
|
||||||
&versionInfo{26, H, 30, 33, 16, 4, 17},
|
|
||||||
&versionInfo{27, L, 30, 8, 122, 4, 123},
|
|
||||||
&versionInfo{27, M, 28, 22, 45, 3, 46},
|
|
||||||
&versionInfo{27, Q, 30, 8, 23, 26, 24},
|
|
||||||
&versionInfo{27, H, 30, 12, 15, 28, 16},
|
|
||||||
&versionInfo{28, L, 30, 3, 117, 10, 118},
|
|
||||||
&versionInfo{28, M, 28, 3, 45, 23, 46},
|
|
||||||
&versionInfo{28, Q, 30, 4, 24, 31, 25},
|
|
||||||
&versionInfo{28, H, 30, 11, 15, 31, 16},
|
|
||||||
&versionInfo{29, L, 30, 7, 116, 7, 117},
|
|
||||||
&versionInfo{29, M, 28, 21, 45, 7, 46},
|
|
||||||
&versionInfo{29, Q, 30, 1, 23, 37, 24},
|
|
||||||
&versionInfo{29, H, 30, 19, 15, 26, 16},
|
|
||||||
&versionInfo{30, L, 30, 5, 115, 10, 116},
|
|
||||||
&versionInfo{30, M, 28, 19, 47, 10, 48},
|
|
||||||
&versionInfo{30, Q, 30, 15, 24, 25, 25},
|
|
||||||
&versionInfo{30, H, 30, 23, 15, 25, 16},
|
|
||||||
&versionInfo{31, L, 30, 13, 115, 3, 116},
|
|
||||||
&versionInfo{31, M, 28, 2, 46, 29, 47},
|
|
||||||
&versionInfo{31, Q, 30, 42, 24, 1, 25},
|
|
||||||
&versionInfo{31, H, 30, 23, 15, 28, 16},
|
|
||||||
&versionInfo{32, L, 30, 17, 115, 0, 0},
|
|
||||||
&versionInfo{32, M, 28, 10, 46, 23, 47},
|
|
||||||
&versionInfo{32, Q, 30, 10, 24, 35, 25},
|
|
||||||
&versionInfo{32, H, 30, 19, 15, 35, 16},
|
|
||||||
&versionInfo{33, L, 30, 17, 115, 1, 116},
|
|
||||||
&versionInfo{33, M, 28, 14, 46, 21, 47},
|
|
||||||
&versionInfo{33, Q, 30, 29, 24, 19, 25},
|
|
||||||
&versionInfo{33, H, 30, 11, 15, 46, 16},
|
|
||||||
&versionInfo{34, L, 30, 13, 115, 6, 116},
|
|
||||||
&versionInfo{34, M, 28, 14, 46, 23, 47},
|
|
||||||
&versionInfo{34, Q, 30, 44, 24, 7, 25},
|
|
||||||
&versionInfo{34, H, 30, 59, 16, 1, 17},
|
|
||||||
&versionInfo{35, L, 30, 12, 121, 7, 122},
|
|
||||||
&versionInfo{35, M, 28, 12, 47, 26, 48},
|
|
||||||
&versionInfo{35, Q, 30, 39, 24, 14, 25},
|
|
||||||
&versionInfo{35, H, 30, 22, 15, 41, 16},
|
|
||||||
&versionInfo{36, L, 30, 6, 121, 14, 122},
|
|
||||||
&versionInfo{36, M, 28, 6, 47, 34, 48},
|
|
||||||
&versionInfo{36, Q, 30, 46, 24, 10, 25},
|
|
||||||
&versionInfo{36, H, 30, 2, 15, 64, 16},
|
|
||||||
&versionInfo{37, L, 30, 17, 122, 4, 123},
|
|
||||||
&versionInfo{37, M, 28, 29, 46, 14, 47},
|
|
||||||
&versionInfo{37, Q, 30, 49, 24, 10, 25},
|
|
||||||
&versionInfo{37, H, 30, 24, 15, 46, 16},
|
|
||||||
&versionInfo{38, L, 30, 4, 122, 18, 123},
|
|
||||||
&versionInfo{38, M, 28, 13, 46, 32, 47},
|
|
||||||
&versionInfo{38, Q, 30, 48, 24, 14, 25},
|
|
||||||
&versionInfo{38, H, 30, 42, 15, 32, 16},
|
|
||||||
&versionInfo{39, L, 30, 20, 117, 4, 118},
|
|
||||||
&versionInfo{39, M, 28, 40, 47, 7, 48},
|
|
||||||
&versionInfo{39, Q, 30, 43, 24, 22, 25},
|
|
||||||
&versionInfo{39, H, 30, 10, 15, 67, 16},
|
|
||||||
&versionInfo{40, L, 30, 19, 118, 6, 119},
|
|
||||||
&versionInfo{40, M, 28, 18, 47, 31, 48},
|
|
||||||
&versionInfo{40, Q, 30, 34, 24, 34, 25},
|
|
||||||
&versionInfo{40, H, 30, 20, 15, 61, 16},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vi *versionInfo) totalDataBytes() int {
|
|
||||||
g1Data := int(vi.NumberOfBlocksInGroup1) * int(vi.DataCodeWordsPerBlockInGroup1)
|
|
||||||
g2Data := int(vi.NumberOfBlocksInGroup2) * int(vi.DataCodeWordsPerBlockInGroup2)
|
|
||||||
return (g1Data + g2Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vi *versionInfo) charCountBits(m encodingMode) byte {
|
|
||||||
switch m {
|
|
||||||
case numericMode:
|
|
||||||
if vi.Version < 10 {
|
|
||||||
return 10
|
|
||||||
} else if vi.Version < 27 {
|
|
||||||
return 12
|
|
||||||
}
|
|
||||||
return 14
|
|
||||||
|
|
||||||
case alphaNumericMode:
|
|
||||||
if vi.Version < 10 {
|
|
||||||
return 9
|
|
||||||
} else if vi.Version < 27 {
|
|
||||||
return 11
|
|
||||||
}
|
|
||||||
return 13
|
|
||||||
|
|
||||||
case byteMode:
|
|
||||||
if vi.Version < 10 {
|
|
||||||
return 8
|
|
||||||
}
|
|
||||||
return 16
|
|
||||||
|
|
||||||
case kanjiMode:
|
|
||||||
if vi.Version < 10 {
|
|
||||||
return 8
|
|
||||||
} else if vi.Version < 27 {
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
return 12
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vi *versionInfo) modulWidth() int {
|
|
||||||
return ((int(vi.Version) - 1) * 4) + 21
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vi *versionInfo) alignmentPatternPlacements() []int {
|
|
||||||
if vi.Version == 1 {
|
|
||||||
return make([]int, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
first := 6
|
|
||||||
last := vi.modulWidth() - 7
|
|
||||||
space := float64(last - first)
|
|
||||||
count := int(math.Ceil(space/28)) + 1
|
|
||||||
|
|
||||||
result := make([]int, count)
|
|
||||||
result[0] = first
|
|
||||||
result[len(result)-1] = last
|
|
||||||
if count > 2 {
|
|
||||||
step := int(math.Ceil(float64(last-first) / float64(count-1)))
|
|
||||||
if step%2 == 1 {
|
|
||||||
frac := float64(last-first) / float64(count-1)
|
|
||||||
_, x := math.Modf(frac)
|
|
||||||
if x >= 0.5 {
|
|
||||||
frac = math.Ceil(frac)
|
|
||||||
} else {
|
|
||||||
frac = math.Floor(frac)
|
|
||||||
}
|
|
||||||
|
|
||||||
if int(frac)%2 == 0 {
|
|
||||||
step--
|
|
||||||
} else {
|
|
||||||
step++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i <= count-2; i++ {
|
|
||||||
result[i] = last - (step * (count - 1 - i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func findSmallestVersionInfo(ecl ErrorCorrectionLevel, mode encodingMode, dataBits int) *versionInfo {
|
|
||||||
dataBits = dataBits + 4 // mode indicator
|
|
||||||
for _, vi := range versionInfos {
|
|
||||||
if vi.Level == ecl {
|
|
||||||
if (vi.totalDataBytes() * 8) >= (dataBits + int(vi.charCountBits(mode))) {
|
|
||||||
return vi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
134
vendor/github.com/boombuler/barcode/scaledbarcode.go
generated
vendored
134
vendor/github.com/boombuler/barcode/scaledbarcode.go
generated
vendored
|
@ -1,134 +0,0 @@
|
||||||
package barcode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
type wrapFunc func(x, y int) color.Color
|
|
||||||
|
|
||||||
type scaledBarcode struct {
|
|
||||||
wrapped Barcode
|
|
||||||
wrapperFunc wrapFunc
|
|
||||||
rect image.Rectangle
|
|
||||||
}
|
|
||||||
|
|
||||||
type intCSscaledBC struct {
|
|
||||||
scaledBarcode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *scaledBarcode) Content() string {
|
|
||||||
return bc.wrapped.Content()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *scaledBarcode) Metadata() Metadata {
|
|
||||||
return bc.wrapped.Metadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *scaledBarcode) ColorModel() color.Model {
|
|
||||||
return bc.wrapped.ColorModel()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *scaledBarcode) Bounds() image.Rectangle {
|
|
||||||
return bc.rect
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *scaledBarcode) At(x, y int) color.Color {
|
|
||||||
return bc.wrapperFunc(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *intCSscaledBC) CheckSum() int {
|
|
||||||
if cs, ok := bc.wrapped.(BarcodeIntCS); ok {
|
|
||||||
return cs.CheckSum()
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scale returns a resized barcode with the given width and height.
|
|
||||||
func Scale(bc Barcode, width, height int) (Barcode, error) {
|
|
||||||
switch bc.Metadata().Dimensions {
|
|
||||||
case 1:
|
|
||||||
return scale1DCode(bc, width, height)
|
|
||||||
case 2:
|
|
||||||
return scale2DCode(bc, width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("unsupported barcode format")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newScaledBC(wrapped Barcode, wrapperFunc wrapFunc, rect image.Rectangle) Barcode {
|
|
||||||
result := &scaledBarcode{
|
|
||||||
wrapped: wrapped,
|
|
||||||
wrapperFunc: wrapperFunc,
|
|
||||||
rect: rect,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := wrapped.(BarcodeIntCS); ok {
|
|
||||||
return &intCSscaledBC{*result}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func scale2DCode(bc Barcode, width, height int) (Barcode, error) {
|
|
||||||
orgBounds := bc.Bounds()
|
|
||||||
orgWidth := orgBounds.Max.X - orgBounds.Min.X
|
|
||||||
orgHeight := orgBounds.Max.Y - orgBounds.Min.Y
|
|
||||||
|
|
||||||
factor := int(math.Min(float64(width)/float64(orgWidth), float64(height)/float64(orgHeight)))
|
|
||||||
if factor <= 0 {
|
|
||||||
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx%d", orgWidth, orgHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
offsetX := (width - (orgWidth * factor)) / 2
|
|
||||||
offsetY := (height - (orgHeight * factor)) / 2
|
|
||||||
|
|
||||||
wrap := func(x, y int) color.Color {
|
|
||||||
if x < offsetX || y < offsetY {
|
|
||||||
return color.White
|
|
||||||
}
|
|
||||||
x = (x - offsetX) / factor
|
|
||||||
y = (y - offsetY) / factor
|
|
||||||
if x >= orgWidth || y >= orgHeight {
|
|
||||||
return color.White
|
|
||||||
}
|
|
||||||
return bc.At(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
return newScaledBC(
|
|
||||||
bc,
|
|
||||||
wrap,
|
|
||||||
image.Rect(0, 0, width, height),
|
|
||||||
), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func scale1DCode(bc Barcode, width, height int) (Barcode, error) {
|
|
||||||
orgBounds := bc.Bounds()
|
|
||||||
orgWidth := orgBounds.Max.X - orgBounds.Min.X
|
|
||||||
factor := int(float64(width) / float64(orgWidth))
|
|
||||||
|
|
||||||
if factor <= 0 {
|
|
||||||
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx1", orgWidth)
|
|
||||||
}
|
|
||||||
offsetX := (width - (orgWidth * factor)) / 2
|
|
||||||
|
|
||||||
wrap := func(x, y int) color.Color {
|
|
||||||
if x < offsetX {
|
|
||||||
return color.White
|
|
||||||
}
|
|
||||||
x = (x - offsetX) / factor
|
|
||||||
|
|
||||||
if x >= orgWidth {
|
|
||||||
return color.White
|
|
||||||
}
|
|
||||||
return bc.At(x, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return newScaledBC(
|
|
||||||
bc,
|
|
||||||
wrap,
|
|
||||||
image.Rect(0, 0, width, height),
|
|
||||||
), nil
|
|
||||||
}
|
|
57
vendor/github.com/boombuler/barcode/utils/base1dcode.go
generated
vendored
57
vendor/github.com/boombuler/barcode/utils/base1dcode.go
generated
vendored
|
@ -1,57 +0,0 @@
|
||||||
// Package utils contain some utilities which are needed to create barcodes
|
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"github.com/boombuler/barcode"
|
|
||||||
)
|
|
||||||
|
|
||||||
type base1DCode struct {
|
|
||||||
*BitList
|
|
||||||
kind string
|
|
||||||
content string
|
|
||||||
}
|
|
||||||
|
|
||||||
type base1DCodeIntCS struct {
|
|
||||||
base1DCode
|
|
||||||
checksum int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *base1DCode) Content() string {
|
|
||||||
return c.content
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *base1DCode) Metadata() barcode.Metadata {
|
|
||||||
return barcode.Metadata{c.kind, 1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *base1DCode) ColorModel() color.Model {
|
|
||||||
return color.Gray16Model
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *base1DCode) Bounds() image.Rectangle {
|
|
||||||
return image.Rect(0, 0, c.Len(), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *base1DCode) At(x, y int) color.Color {
|
|
||||||
if c.GetBit(x) {
|
|
||||||
return color.Black
|
|
||||||
}
|
|
||||||
return color.White
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *base1DCodeIntCS) CheckSum() int {
|
|
||||||
return c.checksum
|
|
||||||
}
|
|
||||||
|
|
||||||
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
|
|
||||||
func New1DCodeIntCheckSum(codeKind, content string, bars *BitList, checksum int) barcode.BarcodeIntCS {
|
|
||||||
return &base1DCodeIntCS{base1DCode{bars, codeKind, content}, checksum}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
|
|
||||||
func New1DCode(codeKind, content string, bars *BitList) barcode.Barcode {
|
|
||||||
return &base1DCode{bars, codeKind, content}
|
|
||||||
}
|
|
119
vendor/github.com/boombuler/barcode/utils/bitlist.go
generated
vendored
119
vendor/github.com/boombuler/barcode/utils/bitlist.go
generated
vendored
|
@ -1,119 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
// BitList is a list that contains bits
|
|
||||||
type BitList struct {
|
|
||||||
count int
|
|
||||||
data []int32
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBitList returns a new BitList with the given length
|
|
||||||
// all bits are initialize with false
|
|
||||||
func NewBitList(capacity int) *BitList {
|
|
||||||
bl := new(BitList)
|
|
||||||
bl.count = capacity
|
|
||||||
x := 0
|
|
||||||
if capacity%32 != 0 {
|
|
||||||
x = 1
|
|
||||||
}
|
|
||||||
bl.data = make([]int32, capacity/32+x)
|
|
||||||
return bl
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of contained bits
|
|
||||||
func (bl *BitList) Len() int {
|
|
||||||
return bl.count
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bl *BitList) grow() {
|
|
||||||
growBy := len(bl.data)
|
|
||||||
if growBy < 128 {
|
|
||||||
growBy = 128
|
|
||||||
} else if growBy >= 1024 {
|
|
||||||
growBy = 1024
|
|
||||||
}
|
|
||||||
|
|
||||||
nd := make([]int32, len(bl.data)+growBy)
|
|
||||||
copy(nd, bl.data)
|
|
||||||
bl.data = nd
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddBit appends the given bits to the end of the list
|
|
||||||
func (bl *BitList) AddBit(bits ...bool) {
|
|
||||||
for _, bit := range bits {
|
|
||||||
itmIndex := bl.count / 32
|
|
||||||
for itmIndex >= len(bl.data) {
|
|
||||||
bl.grow()
|
|
||||||
}
|
|
||||||
bl.SetBit(bl.count, bit)
|
|
||||||
bl.count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBit sets the bit at the given index to the given value
|
|
||||||
func (bl *BitList) SetBit(index int, value bool) {
|
|
||||||
itmIndex := index / 32
|
|
||||||
itmBitShift := 31 - (index % 32)
|
|
||||||
if value {
|
|
||||||
bl.data[itmIndex] = bl.data[itmIndex] | 1<<uint(itmBitShift)
|
|
||||||
} else {
|
|
||||||
bl.data[itmIndex] = bl.data[itmIndex] & ^(1 << uint(itmBitShift))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBit returns the bit at the given index
|
|
||||||
func (bl *BitList) GetBit(index int) bool {
|
|
||||||
itmIndex := index / 32
|
|
||||||
itmBitShift := 31 - (index % 32)
|
|
||||||
return ((bl.data[itmIndex] >> uint(itmBitShift)) & 1) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddByte appends all 8 bits of the given byte to the end of the list
|
|
||||||
func (bl *BitList) AddByte(b byte) {
|
|
||||||
for i := 7; i >= 0; i-- {
|
|
||||||
bl.AddBit(((b >> uint(i)) & 1) == 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddBits appends the last (LSB) 'count' bits of 'b' the the end of the list
|
|
||||||
func (bl *BitList) AddBits(b int, count byte) {
|
|
||||||
for i := int(count) - 1; i >= 0; i-- {
|
|
||||||
bl.AddBit(((b >> uint(i)) & 1) == 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBytes returns all bits of the BitList as a []byte
|
|
||||||
func (bl *BitList) GetBytes() []byte {
|
|
||||||
len := bl.count >> 3
|
|
||||||
if (bl.count % 8) != 0 {
|
|
||||||
len++
|
|
||||||
}
|
|
||||||
result := make([]byte, len)
|
|
||||||
for i := 0; i < len; i++ {
|
|
||||||
shift := (3 - (i % 4)) * 8
|
|
||||||
result[i] = (byte)((bl.data[i/4] >> uint(shift)) & 0xFF)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// IterateBytes iterates through all bytes contained in the BitList
|
|
||||||
func (bl *BitList) IterateBytes() <-chan byte {
|
|
||||||
res := make(chan byte)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
c := bl.count
|
|
||||||
shift := 24
|
|
||||||
i := 0
|
|
||||||
for c > 0 {
|
|
||||||
res <- byte((bl.data[i] >> uint(shift)) & 0xFF)
|
|
||||||
shift -= 8
|
|
||||||
if shift < 0 {
|
|
||||||
shift = 24
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
c -= 8
|
|
||||||
}
|
|
||||||
close(res)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
65
vendor/github.com/boombuler/barcode/utils/galoisfield.go
generated
vendored
65
vendor/github.com/boombuler/barcode/utils/galoisfield.go
generated
vendored
|
@ -1,65 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
// GaloisField encapsulates galois field arithmetics
|
|
||||||
type GaloisField struct {
|
|
||||||
Size int
|
|
||||||
Base int
|
|
||||||
ALogTbl []int
|
|
||||||
LogTbl []int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGaloisField creates a new galois field
|
|
||||||
func NewGaloisField(pp, fieldSize, b int) *GaloisField {
|
|
||||||
result := new(GaloisField)
|
|
||||||
|
|
||||||
result.Size = fieldSize
|
|
||||||
result.Base = b
|
|
||||||
result.ALogTbl = make([]int, fieldSize)
|
|
||||||
result.LogTbl = make([]int, fieldSize)
|
|
||||||
|
|
||||||
x := 1
|
|
||||||
for i := 0; i < fieldSize; i++ {
|
|
||||||
result.ALogTbl[i] = x
|
|
||||||
x = x * 2
|
|
||||||
if x >= fieldSize {
|
|
||||||
x = (x ^ pp) & (fieldSize - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < fieldSize; i++ {
|
|
||||||
result.LogTbl[result.ALogTbl[i]] = int(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gf *GaloisField) Zero() *GFPoly {
|
|
||||||
return NewGFPoly(gf, []int{0})
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddOrSub add or substract two numbers
|
|
||||||
func (gf *GaloisField) AddOrSub(a, b int) int {
|
|
||||||
return a ^ b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiply multiplys two numbers
|
|
||||||
func (gf *GaloisField) Multiply(a, b int) int {
|
|
||||||
if a == 0 || b == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return gf.ALogTbl[(gf.LogTbl[a]+gf.LogTbl[b])%(gf.Size-1)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Divide divides two numbers
|
|
||||||
func (gf *GaloisField) Divide(a, b int) int {
|
|
||||||
if b == 0 {
|
|
||||||
panic("divide by zero")
|
|
||||||
} else if a == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return gf.ALogTbl[(gf.LogTbl[a]-gf.LogTbl[b])%(gf.Size-1)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gf *GaloisField) Invers(num int) int {
|
|
||||||
return gf.ALogTbl[(gf.Size-1)-gf.LogTbl[num]]
|
|
||||||
}
|
|
103
vendor/github.com/boombuler/barcode/utils/gfpoly.go
generated
vendored
103
vendor/github.com/boombuler/barcode/utils/gfpoly.go
generated
vendored
|
@ -1,103 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
type GFPoly struct {
|
|
||||||
gf *GaloisField
|
|
||||||
Coefficients []int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gp *GFPoly) Degree() int {
|
|
||||||
return len(gp.Coefficients) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gp *GFPoly) Zero() bool {
|
|
||||||
return gp.Coefficients[0] == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCoefficient returns the coefficient of x ^ degree
|
|
||||||
func (gp *GFPoly) GetCoefficient(degree int) int {
|
|
||||||
return gp.Coefficients[gp.Degree()-degree]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gp *GFPoly) AddOrSubstract(other *GFPoly) *GFPoly {
|
|
||||||
if gp.Zero() {
|
|
||||||
return other
|
|
||||||
} else if other.Zero() {
|
|
||||||
return gp
|
|
||||||
}
|
|
||||||
smallCoeff := gp.Coefficients
|
|
||||||
largeCoeff := other.Coefficients
|
|
||||||
if len(smallCoeff) > len(largeCoeff) {
|
|
||||||
largeCoeff, smallCoeff = smallCoeff, largeCoeff
|
|
||||||
}
|
|
||||||
sumDiff := make([]int, len(largeCoeff))
|
|
||||||
lenDiff := len(largeCoeff) - len(smallCoeff)
|
|
||||||
copy(sumDiff, largeCoeff[:lenDiff])
|
|
||||||
for i := lenDiff; i < len(largeCoeff); i++ {
|
|
||||||
sumDiff[i] = int(gp.gf.AddOrSub(int(smallCoeff[i-lenDiff]), int(largeCoeff[i])))
|
|
||||||
}
|
|
||||||
return NewGFPoly(gp.gf, sumDiff)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gp *GFPoly) MultByMonominal(degree int, coeff int) *GFPoly {
|
|
||||||
if coeff == 0 {
|
|
||||||
return gp.gf.Zero()
|
|
||||||
}
|
|
||||||
size := len(gp.Coefficients)
|
|
||||||
result := make([]int, size+degree)
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
result[i] = int(gp.gf.Multiply(int(gp.Coefficients[i]), int(coeff)))
|
|
||||||
}
|
|
||||||
return NewGFPoly(gp.gf, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gp *GFPoly) Multiply(other *GFPoly) *GFPoly {
|
|
||||||
if gp.Zero() || other.Zero() {
|
|
||||||
return gp.gf.Zero()
|
|
||||||
}
|
|
||||||
aCoeff := gp.Coefficients
|
|
||||||
aLen := len(aCoeff)
|
|
||||||
bCoeff := other.Coefficients
|
|
||||||
bLen := len(bCoeff)
|
|
||||||
product := make([]int, aLen+bLen-1)
|
|
||||||
for i := 0; i < aLen; i++ {
|
|
||||||
ac := int(aCoeff[i])
|
|
||||||
for j := 0; j < bLen; j++ {
|
|
||||||
bc := int(bCoeff[j])
|
|
||||||
product[i+j] = int(gp.gf.AddOrSub(int(product[i+j]), gp.gf.Multiply(ac, bc)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NewGFPoly(gp.gf, product)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gp *GFPoly) Divide(other *GFPoly) (quotient *GFPoly, remainder *GFPoly) {
|
|
||||||
quotient = gp.gf.Zero()
|
|
||||||
remainder = gp
|
|
||||||
fld := gp.gf
|
|
||||||
denomLeadTerm := other.GetCoefficient(other.Degree())
|
|
||||||
inversDenomLeadTerm := fld.Invers(int(denomLeadTerm))
|
|
||||||
for remainder.Degree() >= other.Degree() && !remainder.Zero() {
|
|
||||||
degreeDiff := remainder.Degree() - other.Degree()
|
|
||||||
scale := int(fld.Multiply(int(remainder.GetCoefficient(remainder.Degree())), inversDenomLeadTerm))
|
|
||||||
term := other.MultByMonominal(degreeDiff, scale)
|
|
||||||
itQuot := NewMonominalPoly(fld, degreeDiff, scale)
|
|
||||||
quotient = quotient.AddOrSubstract(itQuot)
|
|
||||||
remainder = remainder.AddOrSubstract(term)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMonominalPoly(field *GaloisField, degree int, coeff int) *GFPoly {
|
|
||||||
if coeff == 0 {
|
|
||||||
return field.Zero()
|
|
||||||
}
|
|
||||||
result := make([]int, degree+1)
|
|
||||||
result[0] = coeff
|
|
||||||
return NewGFPoly(field, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGFPoly(field *GaloisField, coefficients []int) *GFPoly {
|
|
||||||
for len(coefficients) > 1 && coefficients[0] == 0 {
|
|
||||||
coefficients = coefficients[1:]
|
|
||||||
}
|
|
||||||
return &GFPoly{field, coefficients}
|
|
||||||
}
|
|
44
vendor/github.com/boombuler/barcode/utils/reedsolomon.go
generated
vendored
44
vendor/github.com/boombuler/barcode/utils/reedsolomon.go
generated
vendored
|
@ -1,44 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ReedSolomonEncoder struct {
|
|
||||||
gf *GaloisField
|
|
||||||
polynomes []*GFPoly
|
|
||||||
m *sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewReedSolomonEncoder(gf *GaloisField) *ReedSolomonEncoder {
|
|
||||||
return &ReedSolomonEncoder{
|
|
||||||
gf, []*GFPoly{NewGFPoly(gf, []int{1})}, new(sync.Mutex),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *ReedSolomonEncoder) getPolynomial(degree int) *GFPoly {
|
|
||||||
rs.m.Lock()
|
|
||||||
defer rs.m.Unlock()
|
|
||||||
|
|
||||||
if degree >= len(rs.polynomes) {
|
|
||||||
last := rs.polynomes[len(rs.polynomes)-1]
|
|
||||||
for d := len(rs.polynomes); d <= degree; d++ {
|
|
||||||
next := last.Multiply(NewGFPoly(rs.gf, []int{1, rs.gf.ALogTbl[d-1+rs.gf.Base]}))
|
|
||||||
rs.polynomes = append(rs.polynomes, next)
|
|
||||||
last = next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rs.polynomes[degree]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *ReedSolomonEncoder) Encode(data []int, eccCount int) []int {
|
|
||||||
generator := rs.getPolynomial(eccCount)
|
|
||||||
info := NewGFPoly(rs.gf, data)
|
|
||||||
info = info.MultByMonominal(eccCount, 1)
|
|
||||||
_, remainder := info.Divide(generator)
|
|
||||||
|
|
||||||
result := make([]int, eccCount)
|
|
||||||
numZero := int(eccCount) - len(remainder.Coefficients)
|
|
||||||
copy(result[numZero:], remainder.Coefficients)
|
|
||||||
return result
|
|
||||||
}
|
|
19
vendor/github.com/boombuler/barcode/utils/runeint.go
generated
vendored
19
vendor/github.com/boombuler/barcode/utils/runeint.go
generated
vendored
|
@ -1,19 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
// RuneToInt converts a rune between '0' and '9' to an integer between 0 and 9
|
|
||||||
// If the rune is outside of this range -1 is returned.
|
|
||||||
func RuneToInt(r rune) int {
|
|
||||||
if r >= '0' && r <= '9' {
|
|
||||||
return int(r - '0')
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntToRune converts a digit 0 - 9 to the rune '0' - '9'. If the given int is outside
|
|
||||||
// of this range 'F' is returned!
|
|
||||||
func IntToRune(i int) rune {
|
|
||||||
if i >= 0 && i <= 9 {
|
|
||||||
return rune(i + '0')
|
|
||||||
}
|
|
||||||
return 'F'
|
|
||||||
}
|
|
16
vendor/github.com/golang/snappy/.gitignore
generated
vendored
16
vendor/github.com/golang/snappy/.gitignore
generated
vendored
|
@ -1,16 +0,0 @@
|
||||||
cmd/snappytool/snappytool
|
|
||||||
testdata/bench
|
|
||||||
|
|
||||||
# These explicitly listed benchmark data files are for an obsolete version of
|
|
||||||
# snappy_test.go.
|
|
||||||
testdata/alice29.txt
|
|
||||||
testdata/asyoulik.txt
|
|
||||||
testdata/fireworks.jpeg
|
|
||||||
testdata/geo.protodata
|
|
||||||
testdata/html
|
|
||||||
testdata/html_x_4
|
|
||||||
testdata/kppkn.gtb
|
|
||||||
testdata/lcet10.txt
|
|
||||||
testdata/paper-100k.pdf
|
|
||||||
testdata/plrabn12.txt
|
|
||||||
testdata/urls.10K
|
|
15
vendor/github.com/golang/snappy/AUTHORS
generated
vendored
15
vendor/github.com/golang/snappy/AUTHORS
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
# This is the official list of Snappy-Go authors for copyright purposes.
|
|
||||||
# This file is distinct from the CONTRIBUTORS files.
|
|
||||||
# See the latter for an explanation.
|
|
||||||
|
|
||||||
# Names should be added to this file as
|
|
||||||
# Name or Organization <email address>
|
|
||||||
# The email address is not required for organizations.
|
|
||||||
|
|
||||||
# Please keep the list sorted.
|
|
||||||
|
|
||||||
Damian Gryski <dgryski@gmail.com>
|
|
||||||
Google Inc.
|
|
||||||
Jan Mercl <0xjnml@gmail.com>
|
|
||||||
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
|
||||||
Sebastien Binet <seb.binet@gmail.com>
|
|
37
vendor/github.com/golang/snappy/CONTRIBUTORS
generated
vendored
37
vendor/github.com/golang/snappy/CONTRIBUTORS
generated
vendored
|
@ -1,37 +0,0 @@
|
||||||
# This is the official list of people who can contribute
|
|
||||||
# (and typically have contributed) code to the Snappy-Go repository.
|
|
||||||
# The AUTHORS file lists the copyright holders; this file
|
|
||||||
# lists people. For example, Google employees are listed here
|
|
||||||
# but not in AUTHORS, because Google holds the copyright.
|
|
||||||
#
|
|
||||||
# The submission process automatically checks to make sure
|
|
||||||
# that people submitting code are listed in this file (by email address).
|
|
||||||
#
|
|
||||||
# Names should be added to this file only after verifying that
|
|
||||||
# the individual or the individual's organization has agreed to
|
|
||||||
# the appropriate Contributor License Agreement, found here:
|
|
||||||
#
|
|
||||||
# http://code.google.com/legal/individual-cla-v1.0.html
|
|
||||||
# http://code.google.com/legal/corporate-cla-v1.0.html
|
|
||||||
#
|
|
||||||
# The agreement for individuals can be filled out on the web.
|
|
||||||
#
|
|
||||||
# When adding J Random Contributor's name to this file,
|
|
||||||
# either J's name or J's organization's name should be
|
|
||||||
# added to the AUTHORS file, depending on whether the
|
|
||||||
# individual or corporate CLA was used.
|
|
||||||
|
|
||||||
# Names should be added to this file like so:
|
|
||||||
# Name <email address>
|
|
||||||
|
|
||||||
# Please keep the list sorted.
|
|
||||||
|
|
||||||
Damian Gryski <dgryski@gmail.com>
|
|
||||||
Jan Mercl <0xjnml@gmail.com>
|
|
||||||
Kai Backman <kaib@golang.org>
|
|
||||||
Marc-Antoine Ruel <maruel@chromium.org>
|
|
||||||
Nigel Tao <nigeltao@golang.org>
|
|
||||||
Rob Pike <r@golang.org>
|
|
||||||
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
|
||||||
Russ Cox <rsc@golang.org>
|
|
||||||
Sebastien Binet <seb.binet@gmail.com>
|
|
27
vendor/github.com/golang/snappy/LICENSE
generated
vendored
27
vendor/github.com/golang/snappy/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2011 The Snappy-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.
|
|
107
vendor/github.com/golang/snappy/README
generated
vendored
107
vendor/github.com/golang/snappy/README
generated
vendored
|
@ -1,107 +0,0 @@
|
||||||
The Snappy compression format in the Go programming language.
|
|
||||||
|
|
||||||
To download and install from source:
|
|
||||||
$ go get github.com/golang/snappy
|
|
||||||
|
|
||||||
Unless otherwise noted, the Snappy-Go source files are distributed
|
|
||||||
under the BSD-style license found in the LICENSE file.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Benchmarks.
|
|
||||||
|
|
||||||
The golang/snappy benchmarks include compressing (Z) and decompressing (U) ten
|
|
||||||
or so files, the same set used by the C++ Snappy code (github.com/google/snappy
|
|
||||||
and note the "google", not "golang"). On an "Intel(R) Core(TM) i7-3770 CPU @
|
|
||||||
3.40GHz", Go's GOARCH=amd64 numbers as of 2016-05-29:
|
|
||||||
|
|
||||||
"go test -test.bench=."
|
|
||||||
|
|
||||||
_UFlat0-8 2.19GB/s ± 0% html
|
|
||||||
_UFlat1-8 1.41GB/s ± 0% urls
|
|
||||||
_UFlat2-8 23.5GB/s ± 2% jpg
|
|
||||||
_UFlat3-8 1.91GB/s ± 0% jpg_200
|
|
||||||
_UFlat4-8 14.0GB/s ± 1% pdf
|
|
||||||
_UFlat5-8 1.97GB/s ± 0% html4
|
|
||||||
_UFlat6-8 814MB/s ± 0% txt1
|
|
||||||
_UFlat7-8 785MB/s ± 0% txt2
|
|
||||||
_UFlat8-8 857MB/s ± 0% txt3
|
|
||||||
_UFlat9-8 719MB/s ± 1% txt4
|
|
||||||
_UFlat10-8 2.84GB/s ± 0% pb
|
|
||||||
_UFlat11-8 1.05GB/s ± 0% gaviota
|
|
||||||
|
|
||||||
_ZFlat0-8 1.04GB/s ± 0% html
|
|
||||||
_ZFlat1-8 534MB/s ± 0% urls
|
|
||||||
_ZFlat2-8 15.7GB/s ± 1% jpg
|
|
||||||
_ZFlat3-8 740MB/s ± 3% jpg_200
|
|
||||||
_ZFlat4-8 9.20GB/s ± 1% pdf
|
|
||||||
_ZFlat5-8 991MB/s ± 0% html4
|
|
||||||
_ZFlat6-8 379MB/s ± 0% txt1
|
|
||||||
_ZFlat7-8 352MB/s ± 0% txt2
|
|
||||||
_ZFlat8-8 396MB/s ± 1% txt3
|
|
||||||
_ZFlat9-8 327MB/s ± 1% txt4
|
|
||||||
_ZFlat10-8 1.33GB/s ± 1% pb
|
|
||||||
_ZFlat11-8 605MB/s ± 1% gaviota
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"go test -test.bench=. -tags=noasm"
|
|
||||||
|
|
||||||
_UFlat0-8 621MB/s ± 2% html
|
|
||||||
_UFlat1-8 494MB/s ± 1% urls
|
|
||||||
_UFlat2-8 23.2GB/s ± 1% jpg
|
|
||||||
_UFlat3-8 1.12GB/s ± 1% jpg_200
|
|
||||||
_UFlat4-8 4.35GB/s ± 1% pdf
|
|
||||||
_UFlat5-8 609MB/s ± 0% html4
|
|
||||||
_UFlat6-8 296MB/s ± 0% txt1
|
|
||||||
_UFlat7-8 288MB/s ± 0% txt2
|
|
||||||
_UFlat8-8 309MB/s ± 1% txt3
|
|
||||||
_UFlat9-8 280MB/s ± 1% txt4
|
|
||||||
_UFlat10-8 753MB/s ± 0% pb
|
|
||||||
_UFlat11-8 400MB/s ± 0% gaviota
|
|
||||||
|
|
||||||
_ZFlat0-8 409MB/s ± 1% html
|
|
||||||
_ZFlat1-8 250MB/s ± 1% urls
|
|
||||||
_ZFlat2-8 12.3GB/s ± 1% jpg
|
|
||||||
_ZFlat3-8 132MB/s ± 0% jpg_200
|
|
||||||
_ZFlat4-8 2.92GB/s ± 0% pdf
|
|
||||||
_ZFlat5-8 405MB/s ± 1% html4
|
|
||||||
_ZFlat6-8 179MB/s ± 1% txt1
|
|
||||||
_ZFlat7-8 170MB/s ± 1% txt2
|
|
||||||
_ZFlat8-8 189MB/s ± 1% txt3
|
|
||||||
_ZFlat9-8 164MB/s ± 1% txt4
|
|
||||||
_ZFlat10-8 479MB/s ± 1% pb
|
|
||||||
_ZFlat11-8 270MB/s ± 1% gaviota
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
For comparison (Go's encoded output is byte-for-byte identical to C++'s), here
|
|
||||||
are the numbers from C++ Snappy's
|
|
||||||
|
|
||||||
make CXXFLAGS="-O2 -DNDEBUG -g" clean snappy_unittest.log && cat snappy_unittest.log
|
|
||||||
|
|
||||||
BM_UFlat/0 2.4GB/s html
|
|
||||||
BM_UFlat/1 1.4GB/s urls
|
|
||||||
BM_UFlat/2 21.8GB/s jpg
|
|
||||||
BM_UFlat/3 1.5GB/s jpg_200
|
|
||||||
BM_UFlat/4 13.3GB/s pdf
|
|
||||||
BM_UFlat/5 2.1GB/s html4
|
|
||||||
BM_UFlat/6 1.0GB/s txt1
|
|
||||||
BM_UFlat/7 959.4MB/s txt2
|
|
||||||
BM_UFlat/8 1.0GB/s txt3
|
|
||||||
BM_UFlat/9 864.5MB/s txt4
|
|
||||||
BM_UFlat/10 2.9GB/s pb
|
|
||||||
BM_UFlat/11 1.2GB/s gaviota
|
|
||||||
|
|
||||||
BM_ZFlat/0 944.3MB/s html (22.31 %)
|
|
||||||
BM_ZFlat/1 501.6MB/s urls (47.78 %)
|
|
||||||
BM_ZFlat/2 14.3GB/s jpg (99.95 %)
|
|
||||||
BM_ZFlat/3 538.3MB/s jpg_200 (73.00 %)
|
|
||||||
BM_ZFlat/4 8.3GB/s pdf (83.30 %)
|
|
||||||
BM_ZFlat/5 903.5MB/s html4 (22.52 %)
|
|
||||||
BM_ZFlat/6 336.0MB/s txt1 (57.88 %)
|
|
||||||
BM_ZFlat/7 312.3MB/s txt2 (61.91 %)
|
|
||||||
BM_ZFlat/8 353.1MB/s txt3 (54.99 %)
|
|
||||||
BM_ZFlat/9 289.9MB/s txt4 (66.26 %)
|
|
||||||
BM_ZFlat/10 1.2GB/s pb (19.68 %)
|
|
||||||
BM_ZFlat/11 527.4MB/s gaviota (37.72 %)
|
|
237
vendor/github.com/golang/snappy/decode.go
generated
vendored
237
vendor/github.com/golang/snappy/decode.go
generated
vendored
|
@ -1,237 +0,0 @@
|
||||||
// Copyright 2011 The Snappy-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 snappy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrCorrupt reports that the input is invalid.
|
|
||||||
ErrCorrupt = errors.New("snappy: corrupt input")
|
|
||||||
// ErrTooLarge reports that the uncompressed length is too large.
|
|
||||||
ErrTooLarge = errors.New("snappy: decoded block is too large")
|
|
||||||
// ErrUnsupported reports that the input isn't supported.
|
|
||||||
ErrUnsupported = errors.New("snappy: unsupported input")
|
|
||||||
|
|
||||||
errUnsupportedLiteralLength = errors.New("snappy: unsupported literal length")
|
|
||||||
)
|
|
||||||
|
|
||||||
// DecodedLen returns the length of the decoded block.
|
|
||||||
func DecodedLen(src []byte) (int, error) {
|
|
||||||
v, _, err := decodedLen(src)
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodedLen returns the length of the decoded block and the number of bytes
|
|
||||||
// that the length header occupied.
|
|
||||||
func decodedLen(src []byte) (blockLen, headerLen int, err error) {
|
|
||||||
v, n := binary.Uvarint(src)
|
|
||||||
if n <= 0 || v > 0xffffffff {
|
|
||||||
return 0, 0, ErrCorrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
const wordSize = 32 << (^uint(0) >> 32 & 1)
|
|
||||||
if wordSize == 32 && v > 0x7fffffff {
|
|
||||||
return 0, 0, ErrTooLarge
|
|
||||||
}
|
|
||||||
return int(v), n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
decodeErrCodeCorrupt = 1
|
|
||||||
decodeErrCodeUnsupportedLiteralLength = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// Decode returns the decoded form of src. The returned slice may be a sub-
|
|
||||||
// slice of dst if dst was large enough to hold the entire decoded block.
|
|
||||||
// Otherwise, a newly allocated slice will be returned.
|
|
||||||
//
|
|
||||||
// The dst and src must not overlap. It is valid to pass a nil dst.
|
|
||||||
func Decode(dst, src []byte) ([]byte, error) {
|
|
||||||
dLen, s, err := decodedLen(src)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if dLen <= len(dst) {
|
|
||||||
dst = dst[:dLen]
|
|
||||||
} else {
|
|
||||||
dst = make([]byte, dLen)
|
|
||||||
}
|
|
||||||
switch decode(dst, src[s:]) {
|
|
||||||
case 0:
|
|
||||||
return dst, nil
|
|
||||||
case decodeErrCodeUnsupportedLiteralLength:
|
|
||||||
return nil, errUnsupportedLiteralLength
|
|
||||||
}
|
|
||||||
return nil, ErrCorrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReader returns a new Reader that decompresses from r, using the framing
|
|
||||||
// format described at
|
|
||||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
|
||||||
func NewReader(r io.Reader) *Reader {
|
|
||||||
return &Reader{
|
|
||||||
r: r,
|
|
||||||
decoded: make([]byte, maxBlockSize),
|
|
||||||
buf: make([]byte, maxEncodedLenOfMaxBlockSize+checksumSize),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reader is an io.Reader that can read Snappy-compressed bytes.
|
|
||||||
type Reader struct {
|
|
||||||
r io.Reader
|
|
||||||
err error
|
|
||||||
decoded []byte
|
|
||||||
buf []byte
|
|
||||||
// decoded[i:j] contains decoded bytes that have not yet been passed on.
|
|
||||||
i, j int
|
|
||||||
readHeader bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset discards any buffered data, resets all state, and switches the Snappy
|
|
||||||
// reader to read from r. This permits reusing a Reader rather than allocating
|
|
||||||
// a new one.
|
|
||||||
func (r *Reader) Reset(reader io.Reader) {
|
|
||||||
r.r = reader
|
|
||||||
r.err = nil
|
|
||||||
r.i = 0
|
|
||||||
r.j = 0
|
|
||||||
r.readHeader = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) readFull(p []byte, allowEOF bool) (ok bool) {
|
|
||||||
if _, r.err = io.ReadFull(r.r, p); r.err != nil {
|
|
||||||
if r.err == io.ErrUnexpectedEOF || (r.err == io.EOF && !allowEOF) {
|
|
||||||
r.err = ErrCorrupt
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read satisfies the io.Reader interface.
|
|
||||||
func (r *Reader) Read(p []byte) (int, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
if r.i < r.j {
|
|
||||||
n := copy(p, r.decoded[r.i:r.j])
|
|
||||||
r.i += n
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
if !r.readFull(r.buf[:4], true) {
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
chunkType := r.buf[0]
|
|
||||||
if !r.readHeader {
|
|
||||||
if chunkType != chunkTypeStreamIdentifier {
|
|
||||||
r.err = ErrCorrupt
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
r.readHeader = true
|
|
||||||
}
|
|
||||||
chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16
|
|
||||||
if chunkLen > len(r.buf) {
|
|
||||||
r.err = ErrUnsupported
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The chunk types are specified at
|
|
||||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
|
||||||
switch chunkType {
|
|
||||||
case chunkTypeCompressedData:
|
|
||||||
// Section 4.2. Compressed data (chunk type 0x00).
|
|
||||||
if chunkLen < checksumSize {
|
|
||||||
r.err = ErrCorrupt
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
buf := r.buf[:chunkLen]
|
|
||||||
if !r.readFull(buf, false) {
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
|
||||||
buf = buf[checksumSize:]
|
|
||||||
|
|
||||||
n, err := DecodedLen(buf)
|
|
||||||
if err != nil {
|
|
||||||
r.err = err
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
if n > len(r.decoded) {
|
|
||||||
r.err = ErrCorrupt
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
if _, err := Decode(r.decoded, buf); err != nil {
|
|
||||||
r.err = err
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
if crc(r.decoded[:n]) != checksum {
|
|
||||||
r.err = ErrCorrupt
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
r.i, r.j = 0, n
|
|
||||||
continue
|
|
||||||
|
|
||||||
case chunkTypeUncompressedData:
|
|
||||||
// Section 4.3. Uncompressed data (chunk type 0x01).
|
|
||||||
if chunkLen < checksumSize {
|
|
||||||
r.err = ErrCorrupt
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
buf := r.buf[:checksumSize]
|
|
||||||
if !r.readFull(buf, false) {
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
|
||||||
// Read directly into r.decoded instead of via r.buf.
|
|
||||||
n := chunkLen - checksumSize
|
|
||||||
if n > len(r.decoded) {
|
|
||||||
r.err = ErrCorrupt
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
if !r.readFull(r.decoded[:n], false) {
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
if crc(r.decoded[:n]) != checksum {
|
|
||||||
r.err = ErrCorrupt
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
r.i, r.j = 0, n
|
|
||||||
continue
|
|
||||||
|
|
||||||
case chunkTypeStreamIdentifier:
|
|
||||||
// Section 4.1. Stream identifier (chunk type 0xff).
|
|
||||||
if chunkLen != len(magicBody) {
|
|
||||||
r.err = ErrCorrupt
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
if !r.readFull(r.buf[:len(magicBody)], false) {
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
for i := 0; i < len(magicBody); i++ {
|
|
||||||
if r.buf[i] != magicBody[i] {
|
|
||||||
r.err = ErrCorrupt
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if chunkType <= 0x7f {
|
|
||||||
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
|
|
||||||
r.err = ErrUnsupported
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
// Section 4.4 Padding (chunk type 0xfe).
|
|
||||||
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
|
|
||||||
if !r.readFull(r.buf[:chunkLen], false) {
|
|
||||||
return 0, r.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
14
vendor/github.com/golang/snappy/decode_amd64.go
generated
vendored
14
vendor/github.com/golang/snappy/decode_amd64.go
generated
vendored
|
@ -1,14 +0,0 @@
|
||||||
// Copyright 2016 The Snappy-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 !appengine
|
|
||||||
// +build gc
|
|
||||||
// +build !noasm
|
|
||||||
|
|
||||||
package snappy
|
|
||||||
|
|
||||||
// decode has the same semantics as in decode_other.go.
|
|
||||||
//
|
|
||||||
//go:noescape
|
|
||||||
func decode(dst, src []byte) int
|
|
490
vendor/github.com/golang/snappy/decode_amd64.s
generated
vendored
490
vendor/github.com/golang/snappy/decode_amd64.s
generated
vendored
|
@ -1,490 +0,0 @@
|
||||||
// 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 !appengine
|
|
||||||
// +build gc
|
|
||||||
// +build !noasm
|
|
||||||
|
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
// The asm code generally follows the pure Go code in decode_other.go, except
|
|
||||||
// where marked with a "!!!".
|
|
||||||
|
|
||||||
// func decode(dst, src []byte) int
|
|
||||||
//
|
|
||||||
// All local variables fit into registers. The non-zero stack size is only to
|
|
||||||
// spill registers and push args when issuing a CALL. The register allocation:
|
|
||||||
// - AX scratch
|
|
||||||
// - BX scratch
|
|
||||||
// - CX length or x
|
|
||||||
// - DX offset
|
|
||||||
// - SI &src[s]
|
|
||||||
// - DI &dst[d]
|
|
||||||
// + R8 dst_base
|
|
||||||
// + R9 dst_len
|
|
||||||
// + R10 dst_base + dst_len
|
|
||||||
// + R11 src_base
|
|
||||||
// + R12 src_len
|
|
||||||
// + R13 src_base + src_len
|
|
||||||
// - R14 used by doCopy
|
|
||||||
// - R15 used by doCopy
|
|
||||||
//
|
|
||||||
// The registers R8-R13 (marked with a "+") are set at the start of the
|
|
||||||
// function, and after a CALL returns, and are not otherwise modified.
|
|
||||||
//
|
|
||||||
// The d variable is implicitly DI - R8, and len(dst)-d is R10 - DI.
|
|
||||||
// The s variable is implicitly SI - R11, and len(src)-s is R13 - SI.
|
|
||||||
TEXT ·decode(SB), NOSPLIT, $48-56
|
|
||||||
// Initialize SI, DI and R8-R13.
|
|
||||||
MOVQ dst_base+0(FP), R8
|
|
||||||
MOVQ dst_len+8(FP), R9
|
|
||||||
MOVQ R8, DI
|
|
||||||
MOVQ R8, R10
|
|
||||||
ADDQ R9, R10
|
|
||||||
MOVQ src_base+24(FP), R11
|
|
||||||
MOVQ src_len+32(FP), R12
|
|
||||||
MOVQ R11, SI
|
|
||||||
MOVQ R11, R13
|
|
||||||
ADDQ R12, R13
|
|
||||||
|
|
||||||
loop:
|
|
||||||
// for s < len(src)
|
|
||||||
CMPQ SI, R13
|
|
||||||
JEQ end
|
|
||||||
|
|
||||||
// CX = uint32(src[s])
|
|
||||||
//
|
|
||||||
// switch src[s] & 0x03
|
|
||||||
MOVBLZX (SI), CX
|
|
||||||
MOVL CX, BX
|
|
||||||
ANDL $3, BX
|
|
||||||
CMPL BX, $1
|
|
||||||
JAE tagCopy
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// The code below handles literal tags.
|
|
||||||
|
|
||||||
// case tagLiteral:
|
|
||||||
// x := uint32(src[s] >> 2)
|
|
||||||
// switch
|
|
||||||
SHRL $2, CX
|
|
||||||
CMPL CX, $60
|
|
||||||
JAE tagLit60Plus
|
|
||||||
|
|
||||||
// case x < 60:
|
|
||||||
// s++
|
|
||||||
INCQ SI
|
|
||||||
|
|
||||||
doLit:
|
|
||||||
// This is the end of the inner "switch", when we have a literal tag.
|
|
||||||
//
|
|
||||||
// We assume that CX == x and x fits in a uint32, where x is the variable
|
|
||||||
// used in the pure Go decode_other.go code.
|
|
||||||
|
|
||||||
// length = int(x) + 1
|
|
||||||
//
|
|
||||||
// Unlike the pure Go code, we don't need to check if length <= 0 because
|
|
||||||
// CX can hold 64 bits, so the increment cannot overflow.
|
|
||||||
INCQ CX
|
|
||||||
|
|
||||||
// Prepare to check if copying length bytes will run past the end of dst or
|
|
||||||
// src.
|
|
||||||
//
|
|
||||||
// AX = len(dst) - d
|
|
||||||
// BX = len(src) - s
|
|
||||||
MOVQ R10, AX
|
|
||||||
SUBQ DI, AX
|
|
||||||
MOVQ R13, BX
|
|
||||||
SUBQ SI, BX
|
|
||||||
|
|
||||||
// !!! Try a faster technique for short (16 or fewer bytes) copies.
|
|
||||||
//
|
|
||||||
// if length > 16 || len(dst)-d < 16 || len(src)-s < 16 {
|
|
||||||
// goto callMemmove // Fall back on calling runtime·memmove.
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The C++ snappy code calls this TryFastAppend. It also checks len(src)-s
|
|
||||||
// against 21 instead of 16, because it cannot assume that all of its input
|
|
||||||
// is contiguous in memory and so it needs to leave enough source bytes to
|
|
||||||
// read the next tag without refilling buffers, but Go's Decode assumes
|
|
||||||
// contiguousness (the src argument is a []byte).
|
|
||||||
CMPQ CX, $16
|
|
||||||
JGT callMemmove
|
|
||||||
CMPQ AX, $16
|
|
||||||
JLT callMemmove
|
|
||||||
CMPQ BX, $16
|
|
||||||
JLT callMemmove
|
|
||||||
|
|
||||||
// !!! Implement the copy from src to dst as a 16-byte load and store.
|
|
||||||
// (Decode's documentation says that dst and src must not overlap.)
|
|
||||||
//
|
|
||||||
// This always copies 16 bytes, instead of only length bytes, but that's
|
|
||||||
// OK. If the input is a valid Snappy encoding then subsequent iterations
|
|
||||||
// will fix up the overrun. Otherwise, Decode returns a nil []byte (and a
|
|
||||||
// non-nil error), so the overrun will be ignored.
|
|
||||||
//
|
|
||||||
// Note that on amd64, it is legal and cheap to issue unaligned 8-byte or
|
|
||||||
// 16-byte loads and stores. This technique probably wouldn't be as
|
|
||||||
// effective on architectures that are fussier about alignment.
|
|
||||||
MOVOU 0(SI), X0
|
|
||||||
MOVOU X0, 0(DI)
|
|
||||||
|
|
||||||
// d += length
|
|
||||||
// s += length
|
|
||||||
ADDQ CX, DI
|
|
||||||
ADDQ CX, SI
|
|
||||||
JMP loop
|
|
||||||
|
|
||||||
callMemmove:
|
|
||||||
// if length > len(dst)-d || length > len(src)-s { etc }
|
|
||||||
CMPQ CX, AX
|
|
||||||
JGT errCorrupt
|
|
||||||
CMPQ CX, BX
|
|
||||||
JGT errCorrupt
|
|
||||||
|
|
||||||
// copy(dst[d:], src[s:s+length])
|
|
||||||
//
|
|
||||||
// This means calling runtime·memmove(&dst[d], &src[s], length), so we push
|
|
||||||
// DI, SI and CX as arguments. Coincidentally, we also need to spill those
|
|
||||||
// three registers to the stack, to save local variables across the CALL.
|
|
||||||
MOVQ DI, 0(SP)
|
|
||||||
MOVQ SI, 8(SP)
|
|
||||||
MOVQ CX, 16(SP)
|
|
||||||
MOVQ DI, 24(SP)
|
|
||||||
MOVQ SI, 32(SP)
|
|
||||||
MOVQ CX, 40(SP)
|
|
||||||
CALL runtime·memmove(SB)
|
|
||||||
|
|
||||||
// Restore local variables: unspill registers from the stack and
|
|
||||||
// re-calculate R8-R13.
|
|
||||||
MOVQ 24(SP), DI
|
|
||||||
MOVQ 32(SP), SI
|
|
||||||
MOVQ 40(SP), CX
|
|
||||||
MOVQ dst_base+0(FP), R8
|
|
||||||
MOVQ dst_len+8(FP), R9
|
|
||||||
MOVQ R8, R10
|
|
||||||
ADDQ R9, R10
|
|
||||||
MOVQ src_base+24(FP), R11
|
|
||||||
MOVQ src_len+32(FP), R12
|
|
||||||
MOVQ R11, R13
|
|
||||||
ADDQ R12, R13
|
|
||||||
|
|
||||||
// d += length
|
|
||||||
// s += length
|
|
||||||
ADDQ CX, DI
|
|
||||||
ADDQ CX, SI
|
|
||||||
JMP loop
|
|
||||||
|
|
||||||
tagLit60Plus:
|
|
||||||
// !!! This fragment does the
|
|
||||||
//
|
|
||||||
// s += x - 58; if uint(s) > uint(len(src)) { etc }
|
|
||||||
//
|
|
||||||
// checks. In the asm version, we code it once instead of once per switch case.
|
|
||||||
ADDQ CX, SI
|
|
||||||
SUBQ $58, SI
|
|
||||||
MOVQ SI, BX
|
|
||||||
SUBQ R11, BX
|
|
||||||
CMPQ BX, R12
|
|
||||||
JA errCorrupt
|
|
||||||
|
|
||||||
// case x == 60:
|
|
||||||
CMPL CX, $61
|
|
||||||
JEQ tagLit61
|
|
||||||
JA tagLit62Plus
|
|
||||||
|
|
||||||
// x = uint32(src[s-1])
|
|
||||||
MOVBLZX -1(SI), CX
|
|
||||||
JMP doLit
|
|
||||||
|
|
||||||
tagLit61:
|
|
||||||
// case x == 61:
|
|
||||||
// x = uint32(src[s-2]) | uint32(src[s-1])<<8
|
|
||||||
MOVWLZX -2(SI), CX
|
|
||||||
JMP doLit
|
|
||||||
|
|
||||||
tagLit62Plus:
|
|
||||||
CMPL CX, $62
|
|
||||||
JA tagLit63
|
|
||||||
|
|
||||||
// case x == 62:
|
|
||||||
// x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
|
|
||||||
MOVWLZX -3(SI), CX
|
|
||||||
MOVBLZX -1(SI), BX
|
|
||||||
SHLL $16, BX
|
|
||||||
ORL BX, CX
|
|
||||||
JMP doLit
|
|
||||||
|
|
||||||
tagLit63:
|
|
||||||
// case x == 63:
|
|
||||||
// x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
|
||||||
MOVL -4(SI), CX
|
|
||||||
JMP doLit
|
|
||||||
|
|
||||||
// The code above handles literal tags.
|
|
||||||
// ----------------------------------------
|
|
||||||
// The code below handles copy tags.
|
|
||||||
|
|
||||||
tagCopy4:
|
|
||||||
// case tagCopy4:
|
|
||||||
// s += 5
|
|
||||||
ADDQ $5, SI
|
|
||||||
|
|
||||||
// if uint(s) > uint(len(src)) { etc }
|
|
||||||
MOVQ SI, BX
|
|
||||||
SUBQ R11, BX
|
|
||||||
CMPQ BX, R12
|
|
||||||
JA errCorrupt
|
|
||||||
|
|
||||||
// length = 1 + int(src[s-5])>>2
|
|
||||||
SHRQ $2, CX
|
|
||||||
INCQ CX
|
|
||||||
|
|
||||||
// offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
|
|
||||||
MOVLQZX -4(SI), DX
|
|
||||||
JMP doCopy
|
|
||||||
|
|
||||||
tagCopy2:
|
|
||||||
// case tagCopy2:
|
|
||||||
// s += 3
|
|
||||||
ADDQ $3, SI
|
|
||||||
|
|
||||||
// if uint(s) > uint(len(src)) { etc }
|
|
||||||
MOVQ SI, BX
|
|
||||||
SUBQ R11, BX
|
|
||||||
CMPQ BX, R12
|
|
||||||
JA errCorrupt
|
|
||||||
|
|
||||||
// length = 1 + int(src[s-3])>>2
|
|
||||||
SHRQ $2, CX
|
|
||||||
INCQ CX
|
|
||||||
|
|
||||||
// offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
|
|
||||||
MOVWQZX -2(SI), DX
|
|
||||||
JMP doCopy
|
|
||||||
|
|
||||||
tagCopy:
|
|
||||||
// We have a copy tag. We assume that:
|
|
||||||
// - BX == src[s] & 0x03
|
|
||||||
// - CX == src[s]
|
|
||||||
CMPQ BX, $2
|
|
||||||
JEQ tagCopy2
|
|
||||||
JA tagCopy4
|
|
||||||
|
|
||||||
// case tagCopy1:
|
|
||||||
// s += 2
|
|
||||||
ADDQ $2, SI
|
|
||||||
|
|
||||||
// if uint(s) > uint(len(src)) { etc }
|
|
||||||
MOVQ SI, BX
|
|
||||||
SUBQ R11, BX
|
|
||||||
CMPQ BX, R12
|
|
||||||
JA errCorrupt
|
|
||||||
|
|
||||||
// offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
|
|
||||||
MOVQ CX, DX
|
|
||||||
ANDQ $0xe0, DX
|
|
||||||
SHLQ $3, DX
|
|
||||||
MOVBQZX -1(SI), BX
|
|
||||||
ORQ BX, DX
|
|
||||||
|
|
||||||
// length = 4 + int(src[s-2])>>2&0x7
|
|
||||||
SHRQ $2, CX
|
|
||||||
ANDQ $7, CX
|
|
||||||
ADDQ $4, CX
|
|
||||||
|
|
||||||
doCopy:
|
|
||||||
// This is the end of the outer "switch", when we have a copy tag.
|
|
||||||
//
|
|
||||||
// We assume that:
|
|
||||||
// - CX == length && CX > 0
|
|
||||||
// - DX == offset
|
|
||||||
|
|
||||||
// if offset <= 0 { etc }
|
|
||||||
CMPQ DX, $0
|
|
||||||
JLE errCorrupt
|
|
||||||
|
|
||||||
// if d < offset { etc }
|
|
||||||
MOVQ DI, BX
|
|
||||||
SUBQ R8, BX
|
|
||||||
CMPQ BX, DX
|
|
||||||
JLT errCorrupt
|
|
||||||
|
|
||||||
// if length > len(dst)-d { etc }
|
|
||||||
MOVQ R10, BX
|
|
||||||
SUBQ DI, BX
|
|
||||||
CMPQ CX, BX
|
|
||||||
JGT errCorrupt
|
|
||||||
|
|
||||||
// forwardCopy(dst[d:d+length], dst[d-offset:]); d += length
|
|
||||||
//
|
|
||||||
// Set:
|
|
||||||
// - R14 = len(dst)-d
|
|
||||||
// - R15 = &dst[d-offset]
|
|
||||||
MOVQ R10, R14
|
|
||||||
SUBQ DI, R14
|
|
||||||
MOVQ DI, R15
|
|
||||||
SUBQ DX, R15
|
|
||||||
|
|
||||||
// !!! Try a faster technique for short (16 or fewer bytes) forward copies.
|
|
||||||
//
|
|
||||||
// First, try using two 8-byte load/stores, similar to the doLit technique
|
|
||||||
// above. Even if dst[d:d+length] and dst[d-offset:] can overlap, this is
|
|
||||||
// still OK if offset >= 8. Note that this has to be two 8-byte load/stores
|
|
||||||
// and not one 16-byte load/store, and the first store has to be before the
|
|
||||||
// second load, due to the overlap if offset is in the range [8, 16).
|
|
||||||
//
|
|
||||||
// if length > 16 || offset < 8 || len(dst)-d < 16 {
|
|
||||||
// goto slowForwardCopy
|
|
||||||
// }
|
|
||||||
// copy 16 bytes
|
|
||||||
// d += length
|
|
||||||
CMPQ CX, $16
|
|
||||||
JGT slowForwardCopy
|
|
||||||
CMPQ DX, $8
|
|
||||||
JLT slowForwardCopy
|
|
||||||
CMPQ R14, $16
|
|
||||||
JLT slowForwardCopy
|
|
||||||
MOVQ 0(R15), AX
|
|
||||||
MOVQ AX, 0(DI)
|
|
||||||
MOVQ 8(R15), BX
|
|
||||||
MOVQ BX, 8(DI)
|
|
||||||
ADDQ CX, DI
|
|
||||||
JMP loop
|
|
||||||
|
|
||||||
slowForwardCopy:
|
|
||||||
// !!! If the forward copy is longer than 16 bytes, or if offset < 8, we
|
|
||||||
// can still try 8-byte load stores, provided we can overrun up to 10 extra
|
|
||||||
// bytes. As above, the overrun will be fixed up by subsequent iterations
|
|
||||||
// of the outermost loop.
|
|
||||||
//
|
|
||||||
// The C++ snappy code calls this technique IncrementalCopyFastPath. Its
|
|
||||||
// commentary says:
|
|
||||||
//
|
|
||||||
// ----
|
|
||||||
//
|
|
||||||
// The main part of this loop is a simple copy of eight bytes at a time
|
|
||||||
// until we've copied (at least) the requested amount of bytes. However,
|
|
||||||
// if d and d-offset are less than eight bytes apart (indicating a
|
|
||||||
// repeating pattern of length < 8), we first need to expand the pattern in
|
|
||||||
// order to get the correct results. For instance, if the buffer looks like
|
|
||||||
// this, with the eight-byte <d-offset> and <d> patterns marked as
|
|
||||||
// intervals:
|
|
||||||
//
|
|
||||||
// abxxxxxxxxxxxx
|
|
||||||
// [------] d-offset
|
|
||||||
// [------] d
|
|
||||||
//
|
|
||||||
// a single eight-byte copy from <d-offset> to <d> will repeat the pattern
|
|
||||||
// once, after which we can move <d> two bytes without moving <d-offset>:
|
|
||||||
//
|
|
||||||
// ababxxxxxxxxxx
|
|
||||||
// [------] d-offset
|
|
||||||
// [------] d
|
|
||||||
//
|
|
||||||
// and repeat the exercise until the two no longer overlap.
|
|
||||||
//
|
|
||||||
// This allows us to do very well in the special case of one single byte
|
|
||||||
// repeated many times, without taking a big hit for more general cases.
|
|
||||||
//
|
|
||||||
// The worst case of extra writing past the end of the match occurs when
|
|
||||||
// offset == 1 and length == 1; the last copy will read from byte positions
|
|
||||||
// [0..7] and write to [4..11], whereas it was only supposed to write to
|
|
||||||
// position 1. Thus, ten excess bytes.
|
|
||||||
//
|
|
||||||
// ----
|
|
||||||
//
|
|
||||||
// That "10 byte overrun" worst case is confirmed by Go's
|
|
||||||
// TestSlowForwardCopyOverrun, which also tests the fixUpSlowForwardCopy
|
|
||||||
// and finishSlowForwardCopy algorithm.
|
|
||||||
//
|
|
||||||
// if length > len(dst)-d-10 {
|
|
||||||
// goto verySlowForwardCopy
|
|
||||||
// }
|
|
||||||
SUBQ $10, R14
|
|
||||||
CMPQ CX, R14
|
|
||||||
JGT verySlowForwardCopy
|
|
||||||
|
|
||||||
makeOffsetAtLeast8:
|
|
||||||
// !!! As above, expand the pattern so that offset >= 8 and we can use
|
|
||||||
// 8-byte load/stores.
|
|
||||||
//
|
|
||||||
// for offset < 8 {
|
|
||||||
// copy 8 bytes from dst[d-offset:] to dst[d:]
|
|
||||||
// length -= offset
|
|
||||||
// d += offset
|
|
||||||
// offset += offset
|
|
||||||
// // The two previous lines together means that d-offset, and therefore
|
|
||||||
// // R15, is unchanged.
|
|
||||||
// }
|
|
||||||
CMPQ DX, $8
|
|
||||||
JGE fixUpSlowForwardCopy
|
|
||||||
MOVQ (R15), BX
|
|
||||||
MOVQ BX, (DI)
|
|
||||||
SUBQ DX, CX
|
|
||||||
ADDQ DX, DI
|
|
||||||
ADDQ DX, DX
|
|
||||||
JMP makeOffsetAtLeast8
|
|
||||||
|
|
||||||
fixUpSlowForwardCopy:
|
|
||||||
// !!! Add length (which might be negative now) to d (implied by DI being
|
|
||||||
// &dst[d]) so that d ends up at the right place when we jump back to the
|
|
||||||
// top of the loop. Before we do that, though, we save DI to AX so that, if
|
|
||||||
// length is positive, copying the remaining length bytes will write to the
|
|
||||||
// right place.
|
|
||||||
MOVQ DI, AX
|
|
||||||
ADDQ CX, DI
|
|
||||||
|
|
||||||
finishSlowForwardCopy:
|
|
||||||
// !!! Repeat 8-byte load/stores until length <= 0. Ending with a negative
|
|
||||||
// length means that we overrun, but as above, that will be fixed up by
|
|
||||||
// subsequent iterations of the outermost loop.
|
|
||||||
CMPQ CX, $0
|
|
||||||
JLE loop
|
|
||||||
MOVQ (R15), BX
|
|
||||||
MOVQ BX, (AX)
|
|
||||||
ADDQ $8, R15
|
|
||||||
ADDQ $8, AX
|
|
||||||
SUBQ $8, CX
|
|
||||||
JMP finishSlowForwardCopy
|
|
||||||
|
|
||||||
verySlowForwardCopy:
|
|
||||||
// verySlowForwardCopy is a simple implementation of forward copy. In C
|
|
||||||
// parlance, this is a do/while loop instead of a while loop, since we know
|
|
||||||
// that length > 0. In Go syntax:
|
|
||||||
//
|
|
||||||
// for {
|
|
||||||
// dst[d] = dst[d - offset]
|
|
||||||
// d++
|
|
||||||
// length--
|
|
||||||
// if length == 0 {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
MOVB (R15), BX
|
|
||||||
MOVB BX, (DI)
|
|
||||||
INCQ R15
|
|
||||||
INCQ DI
|
|
||||||
DECQ CX
|
|
||||||
JNZ verySlowForwardCopy
|
|
||||||
JMP loop
|
|
||||||
|
|
||||||
// The code above handles copy tags.
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
end:
|
|
||||||
// This is the end of the "for s < len(src)".
|
|
||||||
//
|
|
||||||
// if d != len(dst) { etc }
|
|
||||||
CMPQ DI, R10
|
|
||||||
JNE errCorrupt
|
|
||||||
|
|
||||||
// return 0
|
|
||||||
MOVQ $0, ret+48(FP)
|
|
||||||
RET
|
|
||||||
|
|
||||||
errCorrupt:
|
|
||||||
// return decodeErrCodeCorrupt
|
|
||||||
MOVQ $1, ret+48(FP)
|
|
||||||
RET
|
|
101
vendor/github.com/golang/snappy/decode_other.go
generated
vendored
101
vendor/github.com/golang/snappy/decode_other.go
generated
vendored
|
@ -1,101 +0,0 @@
|
||||||
// Copyright 2016 The Snappy-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 !amd64 appengine !gc noasm
|
|
||||||
|
|
||||||
package snappy
|
|
||||||
|
|
||||||
// decode writes the decoding of src to dst. It assumes that the varint-encoded
|
|
||||||
// length of the decompressed bytes has already been read, and that len(dst)
|
|
||||||
// equals that length.
|
|
||||||
//
|
|
||||||
// It returns 0 on success or a decodeErrCodeXxx error code on failure.
|
|
||||||
func decode(dst, src []byte) int {
|
|
||||||
var d, s, offset, length int
|
|
||||||
for s < len(src) {
|
|
||||||
switch src[s] & 0x03 {
|
|
||||||
case tagLiteral:
|
|
||||||
x := uint32(src[s] >> 2)
|
|
||||||
switch {
|
|
||||||
case x < 60:
|
|
||||||
s++
|
|
||||||
case x == 60:
|
|
||||||
s += 2
|
|
||||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
|
||||||
return decodeErrCodeCorrupt
|
|
||||||
}
|
|
||||||
x = uint32(src[s-1])
|
|
||||||
case x == 61:
|
|
||||||
s += 3
|
|
||||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
|
||||||
return decodeErrCodeCorrupt
|
|
||||||
}
|
|
||||||
x = uint32(src[s-2]) | uint32(src[s-1])<<8
|
|
||||||
case x == 62:
|
|
||||||
s += 4
|
|
||||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
|
||||||
return decodeErrCodeCorrupt
|
|
||||||
}
|
|
||||||
x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
|
|
||||||
case x == 63:
|
|
||||||
s += 5
|
|
||||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
|
||||||
return decodeErrCodeCorrupt
|
|
||||||
}
|
|
||||||
x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
|
||||||
}
|
|
||||||
length = int(x) + 1
|
|
||||||
if length <= 0 {
|
|
||||||
return decodeErrCodeUnsupportedLiteralLength
|
|
||||||
}
|
|
||||||
if length > len(dst)-d || length > len(src)-s {
|
|
||||||
return decodeErrCodeCorrupt
|
|
||||||
}
|
|
||||||
copy(dst[d:], src[s:s+length])
|
|
||||||
d += length
|
|
||||||
s += length
|
|
||||||
continue
|
|
||||||
|
|
||||||
case tagCopy1:
|
|
||||||
s += 2
|
|
||||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
|
||||||
return decodeErrCodeCorrupt
|
|
||||||
}
|
|
||||||
length = 4 + int(src[s-2])>>2&0x7
|
|
||||||
offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
|
|
||||||
|
|
||||||
case tagCopy2:
|
|
||||||
s += 3
|
|
||||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
|
||||||
return decodeErrCodeCorrupt
|
|
||||||
}
|
|
||||||
length = 1 + int(src[s-3])>>2
|
|
||||||
offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
|
|
||||||
|
|
||||||
case tagCopy4:
|
|
||||||
s += 5
|
|
||||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
|
||||||
return decodeErrCodeCorrupt
|
|
||||||
}
|
|
||||||
length = 1 + int(src[s-5])>>2
|
|
||||||
offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset <= 0 || d < offset || length > len(dst)-d {
|
|
||||||
return decodeErrCodeCorrupt
|
|
||||||
}
|
|
||||||
// Copy from an earlier sub-slice of dst to a later sub-slice. Unlike
|
|
||||||
// the built-in copy function, this byte-by-byte copy always runs
|
|
||||||
// forwards, even if the slices overlap. Conceptually, this is:
|
|
||||||
//
|
|
||||||
// d += forwardCopy(dst[d:d+length], dst[d-offset:])
|
|
||||||
for end := d + length; d != end; d++ {
|
|
||||||
dst[d] = dst[d-offset]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if d != len(dst) {
|
|
||||||
return decodeErrCodeCorrupt
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
285
vendor/github.com/golang/snappy/encode.go
generated
vendored
285
vendor/github.com/golang/snappy/encode.go
generated
vendored
|
@ -1,285 +0,0 @@
|
||||||
// Copyright 2011 The Snappy-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 snappy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Encode returns the encoded form of src. The returned slice may be a sub-
|
|
||||||
// slice of dst if dst was large enough to hold the entire encoded block.
|
|
||||||
// Otherwise, a newly allocated slice will be returned.
|
|
||||||
//
|
|
||||||
// The dst and src must not overlap. It is valid to pass a nil dst.
|
|
||||||
func Encode(dst, src []byte) []byte {
|
|
||||||
if n := MaxEncodedLen(len(src)); n < 0 {
|
|
||||||
panic(ErrTooLarge)
|
|
||||||
} else if len(dst) < n {
|
|
||||||
dst = make([]byte, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The block starts with the varint-encoded length of the decompressed bytes.
|
|
||||||
d := binary.PutUvarint(dst, uint64(len(src)))
|
|
||||||
|
|
||||||
for len(src) > 0 {
|
|
||||||
p := src
|
|
||||||
src = nil
|
|
||||||
if len(p) > maxBlockSize {
|
|
||||||
p, src = p[:maxBlockSize], p[maxBlockSize:]
|
|
||||||
}
|
|
||||||
if len(p) < minNonLiteralBlockSize {
|
|
||||||
d += emitLiteral(dst[d:], p)
|
|
||||||
} else {
|
|
||||||
d += encodeBlock(dst[d:], p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst[:d]
|
|
||||||
}
|
|
||||||
|
|
||||||
// inputMargin is the minimum number of extra input bytes to keep, inside
|
|
||||||
// encodeBlock's inner loop. On some architectures, this margin lets us
|
|
||||||
// implement a fast path for emitLiteral, where the copy of short (<= 16 byte)
|
|
||||||
// literals can be implemented as a single load to and store from a 16-byte
|
|
||||||
// register. That literal's actual length can be as short as 1 byte, so this
|
|
||||||
// can copy up to 15 bytes too much, but that's OK as subsequent iterations of
|
|
||||||
// the encoding loop will fix up the copy overrun, and this inputMargin ensures
|
|
||||||
// that we don't overrun the dst and src buffers.
|
|
||||||
const inputMargin = 16 - 1
|
|
||||||
|
|
||||||
// minNonLiteralBlockSize is the minimum size of the input to encodeBlock that
|
|
||||||
// could be encoded with a copy tag. This is the minimum with respect to the
|
|
||||||
// algorithm used by encodeBlock, not a minimum enforced by the file format.
|
|
||||||
//
|
|
||||||
// The encoded output must start with at least a 1 byte literal, as there are
|
|
||||||
// no previous bytes to copy. A minimal (1 byte) copy after that, generated
|
|
||||||
// from an emitCopy call in encodeBlock's main loop, would require at least
|
|
||||||
// another inputMargin bytes, for the reason above: we want any emitLiteral
|
|
||||||
// calls inside encodeBlock's main loop to use the fast path if possible, which
|
|
||||||
// requires being able to overrun by inputMargin bytes. Thus,
|
|
||||||
// minNonLiteralBlockSize equals 1 + 1 + inputMargin.
|
|
||||||
//
|
|
||||||
// The C++ code doesn't use this exact threshold, but it could, as discussed at
|
|
||||||
// https://groups.google.com/d/topic/snappy-compression/oGbhsdIJSJ8/discussion
|
|
||||||
// The difference between Go (2+inputMargin) and C++ (inputMargin) is purely an
|
|
||||||
// optimization. It should not affect the encoded form. This is tested by
|
|
||||||
// TestSameEncodingAsCppShortCopies.
|
|
||||||
const minNonLiteralBlockSize = 1 + 1 + inputMargin
|
|
||||||
|
|
||||||
// MaxEncodedLen returns the maximum length of a snappy block, given its
|
|
||||||
// uncompressed length.
|
|
||||||
//
|
|
||||||
// It will return a negative value if srcLen is too large to encode.
|
|
||||||
func MaxEncodedLen(srcLen int) int {
|
|
||||||
n := uint64(srcLen)
|
|
||||||
if n > 0xffffffff {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
// Compressed data can be defined as:
|
|
||||||
// compressed := item* literal*
|
|
||||||
// item := literal* copy
|
|
||||||
//
|
|
||||||
// The trailing literal sequence has a space blowup of at most 62/60
|
|
||||||
// since a literal of length 60 needs one tag byte + one extra byte
|
|
||||||
// for length information.
|
|
||||||
//
|
|
||||||
// Item blowup is trickier to measure. Suppose the "copy" op copies
|
|
||||||
// 4 bytes of data. Because of a special check in the encoding code,
|
|
||||||
// we produce a 4-byte copy only if the offset is < 65536. Therefore
|
|
||||||
// the copy op takes 3 bytes to encode, and this type of item leads
|
|
||||||
// to at most the 62/60 blowup for representing literals.
|
|
||||||
//
|
|
||||||
// Suppose the "copy" op copies 5 bytes of data. If the offset is big
|
|
||||||
// enough, it will take 5 bytes to encode the copy op. Therefore the
|
|
||||||
// worst case here is a one-byte literal followed by a five-byte copy.
|
|
||||||
// That is, 6 bytes of input turn into 7 bytes of "compressed" data.
|
|
||||||
//
|
|
||||||
// This last factor dominates the blowup, so the final estimate is:
|
|
||||||
n = 32 + n + n/6
|
|
||||||
if n > 0xffffffff {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return int(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errClosed = errors.New("snappy: Writer is closed")
|
|
||||||
|
|
||||||
// NewWriter returns a new Writer that compresses to w.
|
|
||||||
//
|
|
||||||
// The Writer returned does not buffer writes. There is no need to Flush or
|
|
||||||
// Close such a Writer.
|
|
||||||
//
|
|
||||||
// Deprecated: the Writer returned is not suitable for many small writes, only
|
|
||||||
// for few large writes. Use NewBufferedWriter instead, which is efficient
|
|
||||||
// regardless of the frequency and shape of the writes, and remember to Close
|
|
||||||
// that Writer when done.
|
|
||||||
func NewWriter(w io.Writer) *Writer {
|
|
||||||
return &Writer{
|
|
||||||
w: w,
|
|
||||||
obuf: make([]byte, obufLen),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBufferedWriter returns a new Writer that compresses to w, using the
|
|
||||||
// framing format described at
|
|
||||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
|
||||||
//
|
|
||||||
// The Writer returned buffers writes. Users must call Close to guarantee all
|
|
||||||
// data has been forwarded to the underlying io.Writer. They may also call
|
|
||||||
// Flush zero or more times before calling Close.
|
|
||||||
func NewBufferedWriter(w io.Writer) *Writer {
|
|
||||||
return &Writer{
|
|
||||||
w: w,
|
|
||||||
ibuf: make([]byte, 0, maxBlockSize),
|
|
||||||
obuf: make([]byte, obufLen),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writer is an io.Writer that can write Snappy-compressed bytes.
|
|
||||||
type Writer struct {
|
|
||||||
w io.Writer
|
|
||||||
err error
|
|
||||||
|
|
||||||
// ibuf is a buffer for the incoming (uncompressed) bytes.
|
|
||||||
//
|
|
||||||
// Its use is optional. For backwards compatibility, Writers created by the
|
|
||||||
// NewWriter function have ibuf == nil, do not buffer incoming bytes, and
|
|
||||||
// therefore do not need to be Flush'ed or Close'd.
|
|
||||||
ibuf []byte
|
|
||||||
|
|
||||||
// obuf is a buffer for the outgoing (compressed) bytes.
|
|
||||||
obuf []byte
|
|
||||||
|
|
||||||
// wroteStreamHeader is whether we have written the stream header.
|
|
||||||
wroteStreamHeader bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset discards the writer's state and switches the Snappy writer to write to
|
|
||||||
// w. This permits reusing a Writer rather than allocating a new one.
|
|
||||||
func (w *Writer) Reset(writer io.Writer) {
|
|
||||||
w.w = writer
|
|
||||||
w.err = nil
|
|
||||||
if w.ibuf != nil {
|
|
||||||
w.ibuf = w.ibuf[:0]
|
|
||||||
}
|
|
||||||
w.wroteStreamHeader = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write satisfies the io.Writer interface.
|
|
||||||
func (w *Writer) Write(p []byte) (nRet int, errRet error) {
|
|
||||||
if w.ibuf == nil {
|
|
||||||
// Do not buffer incoming bytes. This does not perform or compress well
|
|
||||||
// if the caller of Writer.Write writes many small slices. This
|
|
||||||
// behavior is therefore deprecated, but still supported for backwards
|
|
||||||
// compatibility with code that doesn't explicitly Flush or Close.
|
|
||||||
return w.write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The remainder of this method is based on bufio.Writer.Write from the
|
|
||||||
// standard library.
|
|
||||||
|
|
||||||
for len(p) > (cap(w.ibuf)-len(w.ibuf)) && w.err == nil {
|
|
||||||
var n int
|
|
||||||
if len(w.ibuf) == 0 {
|
|
||||||
// Large write, empty buffer.
|
|
||||||
// Write directly from p to avoid copy.
|
|
||||||
n, _ = w.write(p)
|
|
||||||
} else {
|
|
||||||
n = copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
|
|
||||||
w.ibuf = w.ibuf[:len(w.ibuf)+n]
|
|
||||||
w.Flush()
|
|
||||||
}
|
|
||||||
nRet += n
|
|
||||||
p = p[n:]
|
|
||||||
}
|
|
||||||
if w.err != nil {
|
|
||||||
return nRet, w.err
|
|
||||||
}
|
|
||||||
n := copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
|
|
||||||
w.ibuf = w.ibuf[:len(w.ibuf)+n]
|
|
||||||
nRet += n
|
|
||||||
return nRet, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) write(p []byte) (nRet int, errRet error) {
|
|
||||||
if w.err != nil {
|
|
||||||
return 0, w.err
|
|
||||||
}
|
|
||||||
for len(p) > 0 {
|
|
||||||
obufStart := len(magicChunk)
|
|
||||||
if !w.wroteStreamHeader {
|
|
||||||
w.wroteStreamHeader = true
|
|
||||||
copy(w.obuf, magicChunk)
|
|
||||||
obufStart = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var uncompressed []byte
|
|
||||||
if len(p) > maxBlockSize {
|
|
||||||
uncompressed, p = p[:maxBlockSize], p[maxBlockSize:]
|
|
||||||
} else {
|
|
||||||
uncompressed, p = p, nil
|
|
||||||
}
|
|
||||||
checksum := crc(uncompressed)
|
|
||||||
|
|
||||||
// Compress the buffer, discarding the result if the improvement
|
|
||||||
// isn't at least 12.5%.
|
|
||||||
compressed := Encode(w.obuf[obufHeaderLen:], uncompressed)
|
|
||||||
chunkType := uint8(chunkTypeCompressedData)
|
|
||||||
chunkLen := 4 + len(compressed)
|
|
||||||
obufEnd := obufHeaderLen + len(compressed)
|
|
||||||
if len(compressed) >= len(uncompressed)-len(uncompressed)/8 {
|
|
||||||
chunkType = chunkTypeUncompressedData
|
|
||||||
chunkLen = 4 + len(uncompressed)
|
|
||||||
obufEnd = obufHeaderLen
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill in the per-chunk header that comes before the body.
|
|
||||||
w.obuf[len(magicChunk)+0] = chunkType
|
|
||||||
w.obuf[len(magicChunk)+1] = uint8(chunkLen >> 0)
|
|
||||||
w.obuf[len(magicChunk)+2] = uint8(chunkLen >> 8)
|
|
||||||
w.obuf[len(magicChunk)+3] = uint8(chunkLen >> 16)
|
|
||||||
w.obuf[len(magicChunk)+4] = uint8(checksum >> 0)
|
|
||||||
w.obuf[len(magicChunk)+5] = uint8(checksum >> 8)
|
|
||||||
w.obuf[len(magicChunk)+6] = uint8(checksum >> 16)
|
|
||||||
w.obuf[len(magicChunk)+7] = uint8(checksum >> 24)
|
|
||||||
|
|
||||||
if _, err := w.w.Write(w.obuf[obufStart:obufEnd]); err != nil {
|
|
||||||
w.err = err
|
|
||||||
return nRet, err
|
|
||||||
}
|
|
||||||
if chunkType == chunkTypeUncompressedData {
|
|
||||||
if _, err := w.w.Write(uncompressed); err != nil {
|
|
||||||
w.err = err
|
|
||||||
return nRet, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nRet += len(uncompressed)
|
|
||||||
}
|
|
||||||
return nRet, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush flushes the Writer to its underlying io.Writer.
|
|
||||||
func (w *Writer) Flush() error {
|
|
||||||
if w.err != nil {
|
|
||||||
return w.err
|
|
||||||
}
|
|
||||||
if len(w.ibuf) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
w.write(w.ibuf)
|
|
||||||
w.ibuf = w.ibuf[:0]
|
|
||||||
return w.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close calls Flush and then closes the Writer.
|
|
||||||
func (w *Writer) Close() error {
|
|
||||||
w.Flush()
|
|
||||||
ret := w.err
|
|
||||||
if w.err == nil {
|
|
||||||
w.err = errClosed
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
29
vendor/github.com/golang/snappy/encode_amd64.go
generated
vendored
29
vendor/github.com/golang/snappy/encode_amd64.go
generated
vendored
|
@ -1,29 +0,0 @@
|
||||||
// Copyright 2016 The Snappy-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 !appengine
|
|
||||||
// +build gc
|
|
||||||
// +build !noasm
|
|
||||||
|
|
||||||
package snappy
|
|
||||||
|
|
||||||
// emitLiteral has the same semantics as in encode_other.go.
|
|
||||||
//
|
|
||||||
//go:noescape
|
|
||||||
func emitLiteral(dst, lit []byte) int
|
|
||||||
|
|
||||||
// emitCopy has the same semantics as in encode_other.go.
|
|
||||||
//
|
|
||||||
//go:noescape
|
|
||||||
func emitCopy(dst []byte, offset, length int) int
|
|
||||||
|
|
||||||
// extendMatch has the same semantics as in encode_other.go.
|
|
||||||
//
|
|
||||||
//go:noescape
|
|
||||||
func extendMatch(src []byte, i, j int) int
|
|
||||||
|
|
||||||
// encodeBlock has the same semantics as in encode_other.go.
|
|
||||||
//
|
|
||||||
//go:noescape
|
|
||||||
func encodeBlock(dst, src []byte) (d int)
|
|
730
vendor/github.com/golang/snappy/encode_amd64.s
generated
vendored
730
vendor/github.com/golang/snappy/encode_amd64.s
generated
vendored
|
@ -1,730 +0,0 @@
|
||||||
// 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 !appengine
|
|
||||||
// +build gc
|
|
||||||
// +build !noasm
|
|
||||||
|
|
||||||
#include "textflag.h"
|
|
||||||
|
|
||||||
// The XXX lines assemble on Go 1.4, 1.5 and 1.7, but not 1.6, due to a
|
|
||||||
// Go toolchain regression. See https://github.com/golang/go/issues/15426 and
|
|
||||||
// https://github.com/golang/snappy/issues/29
|
|
||||||
//
|
|
||||||
// As a workaround, the package was built with a known good assembler, and
|
|
||||||
// those instructions were disassembled by "objdump -d" to yield the
|
|
||||||
// 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
|
||||||
// style comments, in AT&T asm syntax. Note that rsp here is a physical
|
|
||||||
// register, not Go/asm's SP pseudo-register (see https://golang.org/doc/asm).
|
|
||||||
// The instructions were then encoded as "BYTE $0x.." sequences, which assemble
|
|
||||||
// fine on Go 1.6.
|
|
||||||
|
|
||||||
// The asm code generally follows the pure Go code in encode_other.go, except
|
|
||||||
// where marked with a "!!!".
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// func emitLiteral(dst, lit []byte) int
|
|
||||||
//
|
|
||||||
// All local variables fit into registers. The register allocation:
|
|
||||||
// - AX len(lit)
|
|
||||||
// - BX n
|
|
||||||
// - DX return value
|
|
||||||
// - DI &dst[i]
|
|
||||||
// - R10 &lit[0]
|
|
||||||
//
|
|
||||||
// The 24 bytes of stack space is to call runtime·memmove.
|
|
||||||
//
|
|
||||||
// The unusual register allocation of local variables, such as R10 for the
|
|
||||||
// source pointer, matches the allocation used at the call site in encodeBlock,
|
|
||||||
// which makes it easier to manually inline this function.
|
|
||||||
TEXT ·emitLiteral(SB), NOSPLIT, $24-56
|
|
||||||
MOVQ dst_base+0(FP), DI
|
|
||||||
MOVQ lit_base+24(FP), R10
|
|
||||||
MOVQ lit_len+32(FP), AX
|
|
||||||
MOVQ AX, DX
|
|
||||||
MOVL AX, BX
|
|
||||||
SUBL $1, BX
|
|
||||||
|
|
||||||
CMPL BX, $60
|
|
||||||
JLT oneByte
|
|
||||||
CMPL BX, $256
|
|
||||||
JLT twoBytes
|
|
||||||
|
|
||||||
threeBytes:
|
|
||||||
MOVB $0xf4, 0(DI)
|
|
||||||
MOVW BX, 1(DI)
|
|
||||||
ADDQ $3, DI
|
|
||||||
ADDQ $3, DX
|
|
||||||
JMP memmove
|
|
||||||
|
|
||||||
twoBytes:
|
|
||||||
MOVB $0xf0, 0(DI)
|
|
||||||
MOVB BX, 1(DI)
|
|
||||||
ADDQ $2, DI
|
|
||||||
ADDQ $2, DX
|
|
||||||
JMP memmove
|
|
||||||
|
|
||||||
oneByte:
|
|
||||||
SHLB $2, BX
|
|
||||||
MOVB BX, 0(DI)
|
|
||||||
ADDQ $1, DI
|
|
||||||
ADDQ $1, DX
|
|
||||||
|
|
||||||
memmove:
|
|
||||||
MOVQ DX, ret+48(FP)
|
|
||||||
|
|
||||||
// copy(dst[i:], lit)
|
|
||||||
//
|
|
||||||
// This means calling runtime·memmove(&dst[i], &lit[0], len(lit)), so we push
|
|
||||||
// DI, R10 and AX as arguments.
|
|
||||||
MOVQ DI, 0(SP)
|
|
||||||
MOVQ R10, 8(SP)
|
|
||||||
MOVQ AX, 16(SP)
|
|
||||||
CALL runtime·memmove(SB)
|
|
||||||
RET
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// func emitCopy(dst []byte, offset, length int) int
|
|
||||||
//
|
|
||||||
// All local variables fit into registers. The register allocation:
|
|
||||||
// - AX length
|
|
||||||
// - SI &dst[0]
|
|
||||||
// - DI &dst[i]
|
|
||||||
// - R11 offset
|
|
||||||
//
|
|
||||||
// The unusual register allocation of local variables, such as R11 for the
|
|
||||||
// offset, matches the allocation used at the call site in encodeBlock, which
|
|
||||||
// makes it easier to manually inline this function.
|
|
||||||
TEXT ·emitCopy(SB), NOSPLIT, $0-48
|
|
||||||
MOVQ dst_base+0(FP), DI
|
|
||||||
MOVQ DI, SI
|
|
||||||
MOVQ offset+24(FP), R11
|
|
||||||
MOVQ length+32(FP), AX
|
|
||||||
|
|
||||||
loop0:
|
|
||||||
// for length >= 68 { etc }
|
|
||||||
CMPL AX, $68
|
|
||||||
JLT step1
|
|
||||||
|
|
||||||
// Emit a length 64 copy, encoded as 3 bytes.
|
|
||||||
MOVB $0xfe, 0(DI)
|
|
||||||
MOVW R11, 1(DI)
|
|
||||||
ADDQ $3, DI
|
|
||||||
SUBL $64, AX
|
|
||||||
JMP loop0
|
|
||||||
|
|
||||||
step1:
|
|
||||||
// if length > 64 { etc }
|
|
||||||
CMPL AX, $64
|
|
||||||
JLE step2
|
|
||||||
|
|
||||||
// Emit a length 60 copy, encoded as 3 bytes.
|
|
||||||
MOVB $0xee, 0(DI)
|
|
||||||
MOVW R11, 1(DI)
|
|
||||||
ADDQ $3, DI
|
|
||||||
SUBL $60, AX
|
|
||||||
|
|
||||||
step2:
|
|
||||||
// if length >= 12 || offset >= 2048 { goto step3 }
|
|
||||||
CMPL AX, $12
|
|
||||||
JGE step3
|
|
||||||
CMPL R11, $2048
|
|
||||||
JGE step3
|
|
||||||
|
|
||||||
// Emit the remaining copy, encoded as 2 bytes.
|
|
||||||
MOVB R11, 1(DI)
|
|
||||||
SHRL $8, R11
|
|
||||||
SHLB $5, R11
|
|
||||||
SUBB $4, AX
|
|
||||||
SHLB $2, AX
|
|
||||||
ORB AX, R11
|
|
||||||
ORB $1, R11
|
|
||||||
MOVB R11, 0(DI)
|
|
||||||
ADDQ $2, DI
|
|
||||||
|
|
||||||
// Return the number of bytes written.
|
|
||||||
SUBQ SI, DI
|
|
||||||
MOVQ DI, ret+40(FP)
|
|
||||||
RET
|
|
||||||
|
|
||||||
step3:
|
|
||||||
// Emit the remaining copy, encoded as 3 bytes.
|
|
||||||
SUBL $1, AX
|
|
||||||
SHLB $2, AX
|
|
||||||
ORB $2, AX
|
|
||||||
MOVB AX, 0(DI)
|
|
||||||
MOVW R11, 1(DI)
|
|
||||||
ADDQ $3, DI
|
|
||||||
|
|
||||||
// Return the number of bytes written.
|
|
||||||
SUBQ SI, DI
|
|
||||||
MOVQ DI, ret+40(FP)
|
|
||||||
RET
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// func extendMatch(src []byte, i, j int) int
|
|
||||||
//
|
|
||||||
// All local variables fit into registers. The register allocation:
|
|
||||||
// - DX &src[0]
|
|
||||||
// - SI &src[j]
|
|
||||||
// - R13 &src[len(src) - 8]
|
|
||||||
// - R14 &src[len(src)]
|
|
||||||
// - R15 &src[i]
|
|
||||||
//
|
|
||||||
// The unusual register allocation of local variables, such as R15 for a source
|
|
||||||
// pointer, matches the allocation used at the call site in encodeBlock, which
|
|
||||||
// makes it easier to manually inline this function.
|
|
||||||
TEXT ·extendMatch(SB), NOSPLIT, $0-48
|
|
||||||
MOVQ src_base+0(FP), DX
|
|
||||||
MOVQ src_len+8(FP), R14
|
|
||||||
MOVQ i+24(FP), R15
|
|
||||||
MOVQ j+32(FP), SI
|
|
||||||
ADDQ DX, R14
|
|
||||||
ADDQ DX, R15
|
|
||||||
ADDQ DX, SI
|
|
||||||
MOVQ R14, R13
|
|
||||||
SUBQ $8, R13
|
|
||||||
|
|
||||||
cmp8:
|
|
||||||
// As long as we are 8 or more bytes before the end of src, we can load and
|
|
||||||
// compare 8 bytes at a time. If those 8 bytes are equal, repeat.
|
|
||||||
CMPQ SI, R13
|
|
||||||
JA cmp1
|
|
||||||
MOVQ (R15), AX
|
|
||||||
MOVQ (SI), BX
|
|
||||||
CMPQ AX, BX
|
|
||||||
JNE bsf
|
|
||||||
ADDQ $8, R15
|
|
||||||
ADDQ $8, SI
|
|
||||||
JMP cmp8
|
|
||||||
|
|
||||||
bsf:
|
|
||||||
// If those 8 bytes were not equal, XOR the two 8 byte values, and return
|
|
||||||
// the index of the first byte that differs. The BSF instruction finds the
|
|
||||||
// least significant 1 bit, the amd64 architecture is little-endian, and
|
|
||||||
// the shift by 3 converts a bit index to a byte index.
|
|
||||||
XORQ AX, BX
|
|
||||||
BSFQ BX, BX
|
|
||||||
SHRQ $3, BX
|
|
||||||
ADDQ BX, SI
|
|
||||||
|
|
||||||
// Convert from &src[ret] to ret.
|
|
||||||
SUBQ DX, SI
|
|
||||||
MOVQ SI, ret+40(FP)
|
|
||||||
RET
|
|
||||||
|
|
||||||
cmp1:
|
|
||||||
// In src's tail, compare 1 byte at a time.
|
|
||||||
CMPQ SI, R14
|
|
||||||
JAE extendMatchEnd
|
|
||||||
MOVB (R15), AX
|
|
||||||
MOVB (SI), BX
|
|
||||||
CMPB AX, BX
|
|
||||||
JNE extendMatchEnd
|
|
||||||
ADDQ $1, R15
|
|
||||||
ADDQ $1, SI
|
|
||||||
JMP cmp1
|
|
||||||
|
|
||||||
extendMatchEnd:
|
|
||||||
// Convert from &src[ret] to ret.
|
|
||||||
SUBQ DX, SI
|
|
||||||
MOVQ SI, ret+40(FP)
|
|
||||||
RET
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// func encodeBlock(dst, src []byte) (d int)
|
|
||||||
//
|
|
||||||
// All local variables fit into registers, other than "var table". The register
|
|
||||||
// allocation:
|
|
||||||
// - AX . .
|
|
||||||
// - BX . .
|
|
||||||
// - CX 56 shift (note that amd64 shifts by non-immediates must use CX).
|
|
||||||
// - DX 64 &src[0], tableSize
|
|
||||||
// - SI 72 &src[s]
|
|
||||||
// - DI 80 &dst[d]
|
|
||||||
// - R9 88 sLimit
|
|
||||||
// - R10 . &src[nextEmit]
|
|
||||||
// - R11 96 prevHash, currHash, nextHash, offset
|
|
||||||
// - R12 104 &src[base], skip
|
|
||||||
// - R13 . &src[nextS], &src[len(src) - 8]
|
|
||||||
// - R14 . len(src), bytesBetweenHashLookups, &src[len(src)], x
|
|
||||||
// - R15 112 candidate
|
|
||||||
//
|
|
||||||
// The second column (56, 64, etc) is the stack offset to spill the registers
|
|
||||||
// when calling other functions. We could pack this slightly tighter, but it's
|
|
||||||
// simpler to have a dedicated spill map independent of the function called.
|
|
||||||
//
|
|
||||||
// "var table [maxTableSize]uint16" takes up 32768 bytes of stack space. An
|
|
||||||
// extra 56 bytes, to call other functions, and an extra 64 bytes, to spill
|
|
||||||
// local variables (registers) during calls gives 32768 + 56 + 64 = 32888.
|
|
||||||
TEXT ·encodeBlock(SB), 0, $32888-56
|
|
||||||
MOVQ dst_base+0(FP), DI
|
|
||||||
MOVQ src_base+24(FP), SI
|
|
||||||
MOVQ src_len+32(FP), R14
|
|
||||||
|
|
||||||
// shift, tableSize := uint32(32-8), 1<<8
|
|
||||||
MOVQ $24, CX
|
|
||||||
MOVQ $256, DX
|
|
||||||
|
|
||||||
calcShift:
|
|
||||||
// for ; tableSize < maxTableSize && tableSize < len(src); tableSize *= 2 {
|
|
||||||
// shift--
|
|
||||||
// }
|
|
||||||
CMPQ DX, $16384
|
|
||||||
JGE varTable
|
|
||||||
CMPQ DX, R14
|
|
||||||
JGE varTable
|
|
||||||
SUBQ $1, CX
|
|
||||||
SHLQ $1, DX
|
|
||||||
JMP calcShift
|
|
||||||
|
|
||||||
varTable:
|
|
||||||
// var table [maxTableSize]uint16
|
|
||||||
//
|
|
||||||
// In the asm code, unlike the Go code, we can zero-initialize only the
|
|
||||||
// first tableSize elements. Each uint16 element is 2 bytes and each MOVOU
|
|
||||||
// writes 16 bytes, so we can do only tableSize/8 writes instead of the
|
|
||||||
// 2048 writes that would zero-initialize all of table's 32768 bytes.
|
|
||||||
SHRQ $3, DX
|
|
||||||
LEAQ table-32768(SP), BX
|
|
||||||
PXOR X0, X0
|
|
||||||
|
|
||||||
memclr:
|
|
||||||
MOVOU X0, 0(BX)
|
|
||||||
ADDQ $16, BX
|
|
||||||
SUBQ $1, DX
|
|
||||||
JNZ memclr
|
|
||||||
|
|
||||||
// !!! DX = &src[0]
|
|
||||||
MOVQ SI, DX
|
|
||||||
|
|
||||||
// sLimit := len(src) - inputMargin
|
|
||||||
MOVQ R14, R9
|
|
||||||
SUBQ $15, R9
|
|
||||||
|
|
||||||
// !!! Pre-emptively spill CX, DX and R9 to the stack. Their values don't
|
|
||||||
// change for the rest of the function.
|
|
||||||
MOVQ CX, 56(SP)
|
|
||||||
MOVQ DX, 64(SP)
|
|
||||||
MOVQ R9, 88(SP)
|
|
||||||
|
|
||||||
// nextEmit := 0
|
|
||||||
MOVQ DX, R10
|
|
||||||
|
|
||||||
// s := 1
|
|
||||||
ADDQ $1, SI
|
|
||||||
|
|
||||||
// nextHash := hash(load32(src, s), shift)
|
|
||||||
MOVL 0(SI), R11
|
|
||||||
IMULL $0x1e35a7bd, R11
|
|
||||||
SHRL CX, R11
|
|
||||||
|
|
||||||
outer:
|
|
||||||
// for { etc }
|
|
||||||
|
|
||||||
// skip := 32
|
|
||||||
MOVQ $32, R12
|
|
||||||
|
|
||||||
// nextS := s
|
|
||||||
MOVQ SI, R13
|
|
||||||
|
|
||||||
// candidate := 0
|
|
||||||
MOVQ $0, R15
|
|
||||||
|
|
||||||
inner0:
|
|
||||||
// for { etc }
|
|
||||||
|
|
||||||
// s := nextS
|
|
||||||
MOVQ R13, SI
|
|
||||||
|
|
||||||
// bytesBetweenHashLookups := skip >> 5
|
|
||||||
MOVQ R12, R14
|
|
||||||
SHRQ $5, R14
|
|
||||||
|
|
||||||
// nextS = s + bytesBetweenHashLookups
|
|
||||||
ADDQ R14, R13
|
|
||||||
|
|
||||||
// skip += bytesBetweenHashLookups
|
|
||||||
ADDQ R14, R12
|
|
||||||
|
|
||||||
// if nextS > sLimit { goto emitRemainder }
|
|
||||||
MOVQ R13, AX
|
|
||||||
SUBQ DX, AX
|
|
||||||
CMPQ AX, R9
|
|
||||||
JA emitRemainder
|
|
||||||
|
|
||||||
// candidate = int(table[nextHash])
|
|
||||||
// XXX: MOVWQZX table-32768(SP)(R11*2), R15
|
|
||||||
// XXX: 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
|
||||||
BYTE $0x4e
|
|
||||||
BYTE $0x0f
|
|
||||||
BYTE $0xb7
|
|
||||||
BYTE $0x7c
|
|
||||||
BYTE $0x5c
|
|
||||||
BYTE $0x78
|
|
||||||
|
|
||||||
// table[nextHash] = uint16(s)
|
|
||||||
MOVQ SI, AX
|
|
||||||
SUBQ DX, AX
|
|
||||||
|
|
||||||
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
|
||||||
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
|
||||||
BYTE $0x66
|
|
||||||
BYTE $0x42
|
|
||||||
BYTE $0x89
|
|
||||||
BYTE $0x44
|
|
||||||
BYTE $0x5c
|
|
||||||
BYTE $0x78
|
|
||||||
|
|
||||||
// nextHash = hash(load32(src, nextS), shift)
|
|
||||||
MOVL 0(R13), R11
|
|
||||||
IMULL $0x1e35a7bd, R11
|
|
||||||
SHRL CX, R11
|
|
||||||
|
|
||||||
// if load32(src, s) != load32(src, candidate) { continue } break
|
|
||||||
MOVL 0(SI), AX
|
|
||||||
MOVL (DX)(R15*1), BX
|
|
||||||
CMPL AX, BX
|
|
||||||
JNE inner0
|
|
||||||
|
|
||||||
fourByteMatch:
|
|
||||||
// As per the encode_other.go code:
|
|
||||||
//
|
|
||||||
// A 4-byte match has been found. We'll later see etc.
|
|
||||||
|
|
||||||
// !!! Jump to a fast path for short (<= 16 byte) literals. See the comment
|
|
||||||
// on inputMargin in encode.go.
|
|
||||||
MOVQ SI, AX
|
|
||||||
SUBQ R10, AX
|
|
||||||
CMPQ AX, $16
|
|
||||||
JLE emitLiteralFastPath
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Begin inline of the emitLiteral call.
|
|
||||||
//
|
|
||||||
// d += emitLiteral(dst[d:], src[nextEmit:s])
|
|
||||||
|
|
||||||
MOVL AX, BX
|
|
||||||
SUBL $1, BX
|
|
||||||
|
|
||||||
CMPL BX, $60
|
|
||||||
JLT inlineEmitLiteralOneByte
|
|
||||||
CMPL BX, $256
|
|
||||||
JLT inlineEmitLiteralTwoBytes
|
|
||||||
|
|
||||||
inlineEmitLiteralThreeBytes:
|
|
||||||
MOVB $0xf4, 0(DI)
|
|
||||||
MOVW BX, 1(DI)
|
|
||||||
ADDQ $3, DI
|
|
||||||
JMP inlineEmitLiteralMemmove
|
|
||||||
|
|
||||||
inlineEmitLiteralTwoBytes:
|
|
||||||
MOVB $0xf0, 0(DI)
|
|
||||||
MOVB BX, 1(DI)
|
|
||||||
ADDQ $2, DI
|
|
||||||
JMP inlineEmitLiteralMemmove
|
|
||||||
|
|
||||||
inlineEmitLiteralOneByte:
|
|
||||||
SHLB $2, BX
|
|
||||||
MOVB BX, 0(DI)
|
|
||||||
ADDQ $1, DI
|
|
||||||
|
|
||||||
inlineEmitLiteralMemmove:
|
|
||||||
// Spill local variables (registers) onto the stack; call; unspill.
|
|
||||||
//
|
|
||||||
// copy(dst[i:], lit)
|
|
||||||
//
|
|
||||||
// This means calling runtime·memmove(&dst[i], &lit[0], len(lit)), so we push
|
|
||||||
// DI, R10 and AX as arguments.
|
|
||||||
MOVQ DI, 0(SP)
|
|
||||||
MOVQ R10, 8(SP)
|
|
||||||
MOVQ AX, 16(SP)
|
|
||||||
ADDQ AX, DI // Finish the "d +=" part of "d += emitLiteral(etc)".
|
|
||||||
MOVQ SI, 72(SP)
|
|
||||||
MOVQ DI, 80(SP)
|
|
||||||
MOVQ R15, 112(SP)
|
|
||||||
CALL runtime·memmove(SB)
|
|
||||||
MOVQ 56(SP), CX
|
|
||||||
MOVQ 64(SP), DX
|
|
||||||
MOVQ 72(SP), SI
|
|
||||||
MOVQ 80(SP), DI
|
|
||||||
MOVQ 88(SP), R9
|
|
||||||
MOVQ 112(SP), R15
|
|
||||||
JMP inner1
|
|
||||||
|
|
||||||
inlineEmitLiteralEnd:
|
|
||||||
// End inline of the emitLiteral call.
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
emitLiteralFastPath:
|
|
||||||
// !!! Emit the 1-byte encoding "uint8(len(lit)-1)<<2".
|
|
||||||
MOVB AX, BX
|
|
||||||
SUBB $1, BX
|
|
||||||
SHLB $2, BX
|
|
||||||
MOVB BX, (DI)
|
|
||||||
ADDQ $1, DI
|
|
||||||
|
|
||||||
// !!! Implement the copy from lit to dst as a 16-byte load and store.
|
|
||||||
// (Encode's documentation says that dst and src must not overlap.)
|
|
||||||
//
|
|
||||||
// This always copies 16 bytes, instead of only len(lit) bytes, but that's
|
|
||||||
// OK. Subsequent iterations will fix up the overrun.
|
|
||||||
//
|
|
||||||
// Note that on amd64, it is legal and cheap to issue unaligned 8-byte or
|
|
||||||
// 16-byte loads and stores. This technique probably wouldn't be as
|
|
||||||
// effective on architectures that are fussier about alignment.
|
|
||||||
MOVOU 0(R10), X0
|
|
||||||
MOVOU X0, 0(DI)
|
|
||||||
ADDQ AX, DI
|
|
||||||
|
|
||||||
inner1:
|
|
||||||
// for { etc }
|
|
||||||
|
|
||||||
// base := s
|
|
||||||
MOVQ SI, R12
|
|
||||||
|
|
||||||
// !!! offset := base - candidate
|
|
||||||
MOVQ R12, R11
|
|
||||||
SUBQ R15, R11
|
|
||||||
SUBQ DX, R11
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Begin inline of the extendMatch call.
|
|
||||||
//
|
|
||||||
// s = extendMatch(src, candidate+4, s+4)
|
|
||||||
|
|
||||||
// !!! R14 = &src[len(src)]
|
|
||||||
MOVQ src_len+32(FP), R14
|
|
||||||
ADDQ DX, R14
|
|
||||||
|
|
||||||
// !!! R13 = &src[len(src) - 8]
|
|
||||||
MOVQ R14, R13
|
|
||||||
SUBQ $8, R13
|
|
||||||
|
|
||||||
// !!! R15 = &src[candidate + 4]
|
|
||||||
ADDQ $4, R15
|
|
||||||
ADDQ DX, R15
|
|
||||||
|
|
||||||
// !!! s += 4
|
|
||||||
ADDQ $4, SI
|
|
||||||
|
|
||||||
inlineExtendMatchCmp8:
|
|
||||||
// As long as we are 8 or more bytes before the end of src, we can load and
|
|
||||||
// compare 8 bytes at a time. If those 8 bytes are equal, repeat.
|
|
||||||
CMPQ SI, R13
|
|
||||||
JA inlineExtendMatchCmp1
|
|
||||||
MOVQ (R15), AX
|
|
||||||
MOVQ (SI), BX
|
|
||||||
CMPQ AX, BX
|
|
||||||
JNE inlineExtendMatchBSF
|
|
||||||
ADDQ $8, R15
|
|
||||||
ADDQ $8, SI
|
|
||||||
JMP inlineExtendMatchCmp8
|
|
||||||
|
|
||||||
inlineExtendMatchBSF:
|
|
||||||
// If those 8 bytes were not equal, XOR the two 8 byte values, and return
|
|
||||||
// the index of the first byte that differs. The BSF instruction finds the
|
|
||||||
// least significant 1 bit, the amd64 architecture is little-endian, and
|
|
||||||
// the shift by 3 converts a bit index to a byte index.
|
|
||||||
XORQ AX, BX
|
|
||||||
BSFQ BX, BX
|
|
||||||
SHRQ $3, BX
|
|
||||||
ADDQ BX, SI
|
|
||||||
JMP inlineExtendMatchEnd
|
|
||||||
|
|
||||||
inlineExtendMatchCmp1:
|
|
||||||
// In src's tail, compare 1 byte at a time.
|
|
||||||
CMPQ SI, R14
|
|
||||||
JAE inlineExtendMatchEnd
|
|
||||||
MOVB (R15), AX
|
|
||||||
MOVB (SI), BX
|
|
||||||
CMPB AX, BX
|
|
||||||
JNE inlineExtendMatchEnd
|
|
||||||
ADDQ $1, R15
|
|
||||||
ADDQ $1, SI
|
|
||||||
JMP inlineExtendMatchCmp1
|
|
||||||
|
|
||||||
inlineExtendMatchEnd:
|
|
||||||
// End inline of the extendMatch call.
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
// ----------------------------------------
|
|
||||||
// Begin inline of the emitCopy call.
|
|
||||||
//
|
|
||||||
// d += emitCopy(dst[d:], base-candidate, s-base)
|
|
||||||
|
|
||||||
// !!! length := s - base
|
|
||||||
MOVQ SI, AX
|
|
||||||
SUBQ R12, AX
|
|
||||||
|
|
||||||
inlineEmitCopyLoop0:
|
|
||||||
// for length >= 68 { etc }
|
|
||||||
CMPL AX, $68
|
|
||||||
JLT inlineEmitCopyStep1
|
|
||||||
|
|
||||||
// Emit a length 64 copy, encoded as 3 bytes.
|
|
||||||
MOVB $0xfe, 0(DI)
|
|
||||||
MOVW R11, 1(DI)
|
|
||||||
ADDQ $3, DI
|
|
||||||
SUBL $64, AX
|
|
||||||
JMP inlineEmitCopyLoop0
|
|
||||||
|
|
||||||
inlineEmitCopyStep1:
|
|
||||||
// if length > 64 { etc }
|
|
||||||
CMPL AX, $64
|
|
||||||
JLE inlineEmitCopyStep2
|
|
||||||
|
|
||||||
// Emit a length 60 copy, encoded as 3 bytes.
|
|
||||||
MOVB $0xee, 0(DI)
|
|
||||||
MOVW R11, 1(DI)
|
|
||||||
ADDQ $3, DI
|
|
||||||
SUBL $60, AX
|
|
||||||
|
|
||||||
inlineEmitCopyStep2:
|
|
||||||
// if length >= 12 || offset >= 2048 { goto inlineEmitCopyStep3 }
|
|
||||||
CMPL AX, $12
|
|
||||||
JGE inlineEmitCopyStep3
|
|
||||||
CMPL R11, $2048
|
|
||||||
JGE inlineEmitCopyStep3
|
|
||||||
|
|
||||||
// Emit the remaining copy, encoded as 2 bytes.
|
|
||||||
MOVB R11, 1(DI)
|
|
||||||
SHRL $8, R11
|
|
||||||
SHLB $5, R11
|
|
||||||
SUBB $4, AX
|
|
||||||
SHLB $2, AX
|
|
||||||
ORB AX, R11
|
|
||||||
ORB $1, R11
|
|
||||||
MOVB R11, 0(DI)
|
|
||||||
ADDQ $2, DI
|
|
||||||
JMP inlineEmitCopyEnd
|
|
||||||
|
|
||||||
inlineEmitCopyStep3:
|
|
||||||
// Emit the remaining copy, encoded as 3 bytes.
|
|
||||||
SUBL $1, AX
|
|
||||||
SHLB $2, AX
|
|
||||||
ORB $2, AX
|
|
||||||
MOVB AX, 0(DI)
|
|
||||||
MOVW R11, 1(DI)
|
|
||||||
ADDQ $3, DI
|
|
||||||
|
|
||||||
inlineEmitCopyEnd:
|
|
||||||
// End inline of the emitCopy call.
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
// nextEmit = s
|
|
||||||
MOVQ SI, R10
|
|
||||||
|
|
||||||
// if s >= sLimit { goto emitRemainder }
|
|
||||||
MOVQ SI, AX
|
|
||||||
SUBQ DX, AX
|
|
||||||
CMPQ AX, R9
|
|
||||||
JAE emitRemainder
|
|
||||||
|
|
||||||
// As per the encode_other.go code:
|
|
||||||
//
|
|
||||||
// We could immediately etc.
|
|
||||||
|
|
||||||
// x := load64(src, s-1)
|
|
||||||
MOVQ -1(SI), R14
|
|
||||||
|
|
||||||
// prevHash := hash(uint32(x>>0), shift)
|
|
||||||
MOVL R14, R11
|
|
||||||
IMULL $0x1e35a7bd, R11
|
|
||||||
SHRL CX, R11
|
|
||||||
|
|
||||||
// table[prevHash] = uint16(s-1)
|
|
||||||
MOVQ SI, AX
|
|
||||||
SUBQ DX, AX
|
|
||||||
SUBQ $1, AX
|
|
||||||
|
|
||||||
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
|
||||||
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
|
||||||
BYTE $0x66
|
|
||||||
BYTE $0x42
|
|
||||||
BYTE $0x89
|
|
||||||
BYTE $0x44
|
|
||||||
BYTE $0x5c
|
|
||||||
BYTE $0x78
|
|
||||||
|
|
||||||
// currHash := hash(uint32(x>>8), shift)
|
|
||||||
SHRQ $8, R14
|
|
||||||
MOVL R14, R11
|
|
||||||
IMULL $0x1e35a7bd, R11
|
|
||||||
SHRL CX, R11
|
|
||||||
|
|
||||||
// candidate = int(table[currHash])
|
|
||||||
// XXX: MOVWQZX table-32768(SP)(R11*2), R15
|
|
||||||
// XXX: 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
|
||||||
BYTE $0x4e
|
|
||||||
BYTE $0x0f
|
|
||||||
BYTE $0xb7
|
|
||||||
BYTE $0x7c
|
|
||||||
BYTE $0x5c
|
|
||||||
BYTE $0x78
|
|
||||||
|
|
||||||
// table[currHash] = uint16(s)
|
|
||||||
ADDQ $1, AX
|
|
||||||
|
|
||||||
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
|
||||||
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
|
||||||
BYTE $0x66
|
|
||||||
BYTE $0x42
|
|
||||||
BYTE $0x89
|
|
||||||
BYTE $0x44
|
|
||||||
BYTE $0x5c
|
|
||||||
BYTE $0x78
|
|
||||||
|
|
||||||
// if uint32(x>>8) == load32(src, candidate) { continue }
|
|
||||||
MOVL (DX)(R15*1), BX
|
|
||||||
CMPL R14, BX
|
|
||||||
JEQ inner1
|
|
||||||
|
|
||||||
// nextHash = hash(uint32(x>>16), shift)
|
|
||||||
SHRQ $8, R14
|
|
||||||
MOVL R14, R11
|
|
||||||
IMULL $0x1e35a7bd, R11
|
|
||||||
SHRL CX, R11
|
|
||||||
|
|
||||||
// s++
|
|
||||||
ADDQ $1, SI
|
|
||||||
|
|
||||||
// break out of the inner1 for loop, i.e. continue the outer loop.
|
|
||||||
JMP outer
|
|
||||||
|
|
||||||
emitRemainder:
|
|
||||||
// if nextEmit < len(src) { etc }
|
|
||||||
MOVQ src_len+32(FP), AX
|
|
||||||
ADDQ DX, AX
|
|
||||||
CMPQ R10, AX
|
|
||||||
JEQ encodeBlockEnd
|
|
||||||
|
|
||||||
// d += emitLiteral(dst[d:], src[nextEmit:])
|
|
||||||
//
|
|
||||||
// Push args.
|
|
||||||
MOVQ DI, 0(SP)
|
|
||||||
MOVQ $0, 8(SP) // Unnecessary, as the callee ignores it, but conservative.
|
|
||||||
MOVQ $0, 16(SP) // Unnecessary, as the callee ignores it, but conservative.
|
|
||||||
MOVQ R10, 24(SP)
|
|
||||||
SUBQ R10, AX
|
|
||||||
MOVQ AX, 32(SP)
|
|
||||||
MOVQ AX, 40(SP) // Unnecessary, as the callee ignores it, but conservative.
|
|
||||||
|
|
||||||
// Spill local variables (registers) onto the stack; call; unspill.
|
|
||||||
MOVQ DI, 80(SP)
|
|
||||||
CALL ·emitLiteral(SB)
|
|
||||||
MOVQ 80(SP), DI
|
|
||||||
|
|
||||||
// Finish the "d +=" part of "d += emitLiteral(etc)".
|
|
||||||
ADDQ 48(SP), DI
|
|
||||||
|
|
||||||
encodeBlockEnd:
|
|
||||||
MOVQ dst_base+0(FP), AX
|
|
||||||
SUBQ AX, DI
|
|
||||||
MOVQ DI, d+48(FP)
|
|
||||||
RET
|
|
238
vendor/github.com/golang/snappy/encode_other.go
generated
vendored
238
vendor/github.com/golang/snappy/encode_other.go
generated
vendored
|
@ -1,238 +0,0 @@
|
||||||
// Copyright 2016 The Snappy-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 !amd64 appengine !gc noasm
|
|
||||||
|
|
||||||
package snappy
|
|
||||||
|
|
||||||
func load32(b []byte, i int) uint32 {
|
|
||||||
b = b[i : i+4 : len(b)] // Help the compiler eliminate bounds checks on the next line.
|
|
||||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
|
||||||
}
|
|
||||||
|
|
||||||
func load64(b []byte, i int) uint64 {
|
|
||||||
b = b[i : i+8 : len(b)] // Help the compiler eliminate bounds checks on the next line.
|
|
||||||
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
|
|
||||||
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
|
|
||||||
}
|
|
||||||
|
|
||||||
// emitLiteral writes a literal chunk and returns the number of bytes written.
|
|
||||||
//
|
|
||||||
// It assumes that:
|
|
||||||
// dst is long enough to hold the encoded bytes
|
|
||||||
// 1 <= len(lit) && len(lit) <= 65536
|
|
||||||
func emitLiteral(dst, lit []byte) int {
|
|
||||||
i, n := 0, uint(len(lit)-1)
|
|
||||||
switch {
|
|
||||||
case n < 60:
|
|
||||||
dst[0] = uint8(n)<<2 | tagLiteral
|
|
||||||
i = 1
|
|
||||||
case n < 1<<8:
|
|
||||||
dst[0] = 60<<2 | tagLiteral
|
|
||||||
dst[1] = uint8(n)
|
|
||||||
i = 2
|
|
||||||
default:
|
|
||||||
dst[0] = 61<<2 | tagLiteral
|
|
||||||
dst[1] = uint8(n)
|
|
||||||
dst[2] = uint8(n >> 8)
|
|
||||||
i = 3
|
|
||||||
}
|
|
||||||
return i + copy(dst[i:], lit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// emitCopy writes a copy chunk and returns the number of bytes written.
|
|
||||||
//
|
|
||||||
// It assumes that:
|
|
||||||
// dst is long enough to hold the encoded bytes
|
|
||||||
// 1 <= offset && offset <= 65535
|
|
||||||
// 4 <= length && length <= 65535
|
|
||||||
func emitCopy(dst []byte, offset, length int) int {
|
|
||||||
i := 0
|
|
||||||
// The maximum length for a single tagCopy1 or tagCopy2 op is 64 bytes. The
|
|
||||||
// threshold for this loop is a little higher (at 68 = 64 + 4), and the
|
|
||||||
// length emitted down below is is a little lower (at 60 = 64 - 4), because
|
|
||||||
// it's shorter to encode a length 67 copy as a length 60 tagCopy2 followed
|
|
||||||
// by a length 7 tagCopy1 (which encodes as 3+2 bytes) than to encode it as
|
|
||||||
// a length 64 tagCopy2 followed by a length 3 tagCopy2 (which encodes as
|
|
||||||
// 3+3 bytes). The magic 4 in the 64±4 is because the minimum length for a
|
|
||||||
// tagCopy1 op is 4 bytes, which is why a length 3 copy has to be an
|
|
||||||
// encodes-as-3-bytes tagCopy2 instead of an encodes-as-2-bytes tagCopy1.
|
|
||||||
for length >= 68 {
|
|
||||||
// Emit a length 64 copy, encoded as 3 bytes.
|
|
||||||
dst[i+0] = 63<<2 | tagCopy2
|
|
||||||
dst[i+1] = uint8(offset)
|
|
||||||
dst[i+2] = uint8(offset >> 8)
|
|
||||||
i += 3
|
|
||||||
length -= 64
|
|
||||||
}
|
|
||||||
if length > 64 {
|
|
||||||
// Emit a length 60 copy, encoded as 3 bytes.
|
|
||||||
dst[i+0] = 59<<2 | tagCopy2
|
|
||||||
dst[i+1] = uint8(offset)
|
|
||||||
dst[i+2] = uint8(offset >> 8)
|
|
||||||
i += 3
|
|
||||||
length -= 60
|
|
||||||
}
|
|
||||||
if length >= 12 || offset >= 2048 {
|
|
||||||
// Emit the remaining copy, encoded as 3 bytes.
|
|
||||||
dst[i+0] = uint8(length-1)<<2 | tagCopy2
|
|
||||||
dst[i+1] = uint8(offset)
|
|
||||||
dst[i+2] = uint8(offset >> 8)
|
|
||||||
return i + 3
|
|
||||||
}
|
|
||||||
// Emit the remaining copy, encoded as 2 bytes.
|
|
||||||
dst[i+0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1
|
|
||||||
dst[i+1] = uint8(offset)
|
|
||||||
return i + 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// extendMatch returns the largest k such that k <= len(src) and that
|
|
||||||
// src[i:i+k-j] and src[j:k] have the same contents.
|
|
||||||
//
|
|
||||||
// It assumes that:
|
|
||||||
// 0 <= i && i < j && j <= len(src)
|
|
||||||
func extendMatch(src []byte, i, j int) int {
|
|
||||||
for ; j < len(src) && src[i] == src[j]; i, j = i+1, j+1 {
|
|
||||||
}
|
|
||||||
return j
|
|
||||||
}
|
|
||||||
|
|
||||||
func hash(u, shift uint32) uint32 {
|
|
||||||
return (u * 0x1e35a7bd) >> shift
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It
|
|
||||||
// assumes that the varint-encoded length of the decompressed bytes has already
|
|
||||||
// been written.
|
|
||||||
//
|
|
||||||
// It also assumes that:
|
|
||||||
// len(dst) >= MaxEncodedLen(len(src)) &&
|
|
||||||
// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
|
|
||||||
func encodeBlock(dst, src []byte) (d int) {
|
|
||||||
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
|
|
||||||
// The table element type is uint16, as s < sLimit and sLimit < len(src)
|
|
||||||
// and len(src) <= maxBlockSize and maxBlockSize == 65536.
|
|
||||||
const (
|
|
||||||
maxTableSize = 1 << 14
|
|
||||||
// tableMask is redundant, but helps the compiler eliminate bounds
|
|
||||||
// checks.
|
|
||||||
tableMask = maxTableSize - 1
|
|
||||||
)
|
|
||||||
shift := uint32(32 - 8)
|
|
||||||
for tableSize := 1 << 8; tableSize < maxTableSize && tableSize < len(src); tableSize *= 2 {
|
|
||||||
shift--
|
|
||||||
}
|
|
||||||
// In Go, all array elements are zero-initialized, so there is no advantage
|
|
||||||
// to a smaller tableSize per se. However, it matches the C++ algorithm,
|
|
||||||
// and in the asm versions of this code, we can get away with zeroing only
|
|
||||||
// the first tableSize elements.
|
|
||||||
var table [maxTableSize]uint16
|
|
||||||
|
|
||||||
// sLimit is when to stop looking for offset/length copies. The inputMargin
|
|
||||||
// lets us use a fast path for emitLiteral in the main loop, while we are
|
|
||||||
// looking for copies.
|
|
||||||
sLimit := len(src) - inputMargin
|
|
||||||
|
|
||||||
// nextEmit is where in src the next emitLiteral should start from.
|
|
||||||
nextEmit := 0
|
|
||||||
|
|
||||||
// The encoded form must start with a literal, as there are no previous
|
|
||||||
// bytes to copy, so we start looking for hash matches at s == 1.
|
|
||||||
s := 1
|
|
||||||
nextHash := hash(load32(src, s), shift)
|
|
||||||
|
|
||||||
for {
|
|
||||||
// Copied from the C++ snappy implementation:
|
|
||||||
//
|
|
||||||
// Heuristic match skipping: If 32 bytes are scanned with no matches
|
|
||||||
// found, start looking only at every other byte. If 32 more bytes are
|
|
||||||
// scanned (or skipped), look at every third byte, etc.. When a match
|
|
||||||
// is found, immediately go back to looking at every byte. This is a
|
|
||||||
// small loss (~5% performance, ~0.1% density) for compressible data
|
|
||||||
// due to more bookkeeping, but for non-compressible data (such as
|
|
||||||
// JPEG) it's a huge win since the compressor quickly "realizes" the
|
|
||||||
// data is incompressible and doesn't bother looking for matches
|
|
||||||
// everywhere.
|
|
||||||
//
|
|
||||||
// The "skip" variable keeps track of how many bytes there are since
|
|
||||||
// the last match; dividing it by 32 (ie. right-shifting by five) gives
|
|
||||||
// the number of bytes to move ahead for each iteration.
|
|
||||||
skip := 32
|
|
||||||
|
|
||||||
nextS := s
|
|
||||||
candidate := 0
|
|
||||||
for {
|
|
||||||
s = nextS
|
|
||||||
bytesBetweenHashLookups := skip >> 5
|
|
||||||
nextS = s + bytesBetweenHashLookups
|
|
||||||
skip += bytesBetweenHashLookups
|
|
||||||
if nextS > sLimit {
|
|
||||||
goto emitRemainder
|
|
||||||
}
|
|
||||||
candidate = int(table[nextHash&tableMask])
|
|
||||||
table[nextHash&tableMask] = uint16(s)
|
|
||||||
nextHash = hash(load32(src, nextS), shift)
|
|
||||||
if load32(src, s) == load32(src, candidate) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A 4-byte match has been found. We'll later see if more than 4 bytes
|
|
||||||
// match. But, prior to the match, src[nextEmit:s] are unmatched. Emit
|
|
||||||
// them as literal bytes.
|
|
||||||
d += emitLiteral(dst[d:], src[nextEmit:s])
|
|
||||||
|
|
||||||
// Call emitCopy, and then see if another emitCopy could be our next
|
|
||||||
// move. Repeat until we find no match for the input immediately after
|
|
||||||
// what was consumed by the last emitCopy call.
|
|
||||||
//
|
|
||||||
// If we exit this loop normally then we need to call emitLiteral next,
|
|
||||||
// though we don't yet know how big the literal will be. We handle that
|
|
||||||
// by proceeding to the next iteration of the main loop. We also can
|
|
||||||
// exit this loop via goto if we get close to exhausting the input.
|
|
||||||
for {
|
|
||||||
// Invariant: we have a 4-byte match at s, and no need to emit any
|
|
||||||
// literal bytes prior to s.
|
|
||||||
base := s
|
|
||||||
|
|
||||||
// Extend the 4-byte match as long as possible.
|
|
||||||
//
|
|
||||||
// This is an inlined version of:
|
|
||||||
// s = extendMatch(src, candidate+4, s+4)
|
|
||||||
s += 4
|
|
||||||
for i := candidate + 4; s < len(src) && src[i] == src[s]; i, s = i+1, s+1 {
|
|
||||||
}
|
|
||||||
|
|
||||||
d += emitCopy(dst[d:], base-candidate, s-base)
|
|
||||||
nextEmit = s
|
|
||||||
if s >= sLimit {
|
|
||||||
goto emitRemainder
|
|
||||||
}
|
|
||||||
|
|
||||||
// We could immediately start working at s now, but to improve
|
|
||||||
// compression we first update the hash table at s-1 and at s. If
|
|
||||||
// another emitCopy is not our next move, also calculate nextHash
|
|
||||||
// at s+1. At least on GOARCH=amd64, these three hash calculations
|
|
||||||
// are faster as one load64 call (with some shifts) instead of
|
|
||||||
// three load32 calls.
|
|
||||||
x := load64(src, s-1)
|
|
||||||
prevHash := hash(uint32(x>>0), shift)
|
|
||||||
table[prevHash&tableMask] = uint16(s - 1)
|
|
||||||
currHash := hash(uint32(x>>8), shift)
|
|
||||||
candidate = int(table[currHash&tableMask])
|
|
||||||
table[currHash&tableMask] = uint16(s)
|
|
||||||
if uint32(x>>8) != load32(src, candidate) {
|
|
||||||
nextHash = hash(uint32(x>>16), shift)
|
|
||||||
s++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emitRemainder:
|
|
||||||
if nextEmit < len(src) {
|
|
||||||
d += emitLiteral(dst[d:], src[nextEmit:])
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
98
vendor/github.com/golang/snappy/snappy.go
generated
vendored
98
vendor/github.com/golang/snappy/snappy.go
generated
vendored
|
@ -1,98 +0,0 @@
|
||||||
// Copyright 2011 The Snappy-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 snappy implements the Snappy compression format. It aims for very
|
|
||||||
// high speeds and reasonable compression.
|
|
||||||
//
|
|
||||||
// There are actually two Snappy formats: block and stream. They are related,
|
|
||||||
// but different: trying to decompress block-compressed data as a Snappy stream
|
|
||||||
// will fail, and vice versa. The block format is the Decode and Encode
|
|
||||||
// functions and the stream format is the Reader and Writer types.
|
|
||||||
//
|
|
||||||
// The block format, the more common case, is used when the complete size (the
|
|
||||||
// number of bytes) of the original data is known upfront, at the time
|
|
||||||
// compression starts. The stream format, also known as the framing format, is
|
|
||||||
// for when that isn't always true.
|
|
||||||
//
|
|
||||||
// The canonical, C++ implementation is at https://github.com/google/snappy and
|
|
||||||
// it only implements the block format.
|
|
||||||
package snappy // import "github.com/golang/snappy"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash/crc32"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Each encoded block begins with the varint-encoded length of the decoded data,
|
|
||||||
followed by a sequence of chunks. Chunks begin and end on byte boundaries. The
|
|
||||||
first byte of each chunk is broken into its 2 least and 6 most significant bits
|
|
||||||
called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag.
|
|
||||||
Zero means a literal tag. All other values mean a copy tag.
|
|
||||||
|
|
||||||
For literal tags:
|
|
||||||
- If m < 60, the next 1 + m bytes are literal bytes.
|
|
||||||
- Otherwise, let n be the little-endian unsigned integer denoted by the next
|
|
||||||
m - 59 bytes. The next 1 + n bytes after that are literal bytes.
|
|
||||||
|
|
||||||
For copy tags, length bytes are copied from offset bytes ago, in the style of
|
|
||||||
Lempel-Ziv compression algorithms. In particular:
|
|
||||||
- For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12).
|
|
||||||
The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10
|
|
||||||
of the offset. The next byte is bits 0-7 of the offset.
|
|
||||||
- For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65).
|
|
||||||
The length is 1 + m. The offset is the little-endian unsigned integer
|
|
||||||
denoted by the next 2 bytes.
|
|
||||||
- For l == 3, this tag is a legacy format that is no longer issued by most
|
|
||||||
encoders. Nonetheless, the offset ranges in [0, 1<<32) and the length in
|
|
||||||
[1, 65). The length is 1 + m. The offset is the little-endian unsigned
|
|
||||||
integer denoted by the next 4 bytes.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
tagLiteral = 0x00
|
|
||||||
tagCopy1 = 0x01
|
|
||||||
tagCopy2 = 0x02
|
|
||||||
tagCopy4 = 0x03
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
checksumSize = 4
|
|
||||||
chunkHeaderSize = 4
|
|
||||||
magicChunk = "\xff\x06\x00\x00" + magicBody
|
|
||||||
magicBody = "sNaPpY"
|
|
||||||
|
|
||||||
// maxBlockSize is the maximum size of the input to encodeBlock. It is not
|
|
||||||
// part of the wire format per se, but some parts of the encoder assume
|
|
||||||
// that an offset fits into a uint16.
|
|
||||||
//
|
|
||||||
// Also, for the framing format (Writer type instead of Encode function),
|
|
||||||
// https://github.com/google/snappy/blob/master/framing_format.txt says
|
|
||||||
// that "the uncompressed data in a chunk must be no longer than 65536
|
|
||||||
// bytes".
|
|
||||||
maxBlockSize = 65536
|
|
||||||
|
|
||||||
// maxEncodedLenOfMaxBlockSize equals MaxEncodedLen(maxBlockSize), but is
|
|
||||||
// hard coded to be a const instead of a variable, so that obufLen can also
|
|
||||||
// be a const. Their equivalence is confirmed by
|
|
||||||
// TestMaxEncodedLenOfMaxBlockSize.
|
|
||||||
maxEncodedLenOfMaxBlockSize = 76490
|
|
||||||
|
|
||||||
obufHeaderLen = len(magicChunk) + checksumSize + chunkHeaderSize
|
|
||||||
obufLen = obufHeaderLen + maxEncodedLenOfMaxBlockSize
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
chunkTypeCompressedData = 0x00
|
|
||||||
chunkTypeUncompressedData = 0x01
|
|
||||||
chunkTypePadding = 0xfe
|
|
||||||
chunkTypeStreamIdentifier = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
var crcTable = crc32.MakeTable(crc32.Castagnoli)
|
|
||||||
|
|
||||||
// crc implements the checksum specified in section 3 of
|
|
||||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
|
||||||
func crc(b []byte) uint32 {
|
|
||||||
c := crc32.Update(0, crcTable, b)
|
|
||||||
return uint32(c>>15|c<<17) + 0xa282ead8
|
|
||||||
}
|
|
19
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
19
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
|
@ -1,19 +0,0 @@
|
||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.3
|
|
||||||
- go: 1.4
|
|
||||||
- go: 1.5
|
|
||||||
- go: 1.6
|
|
||||||
- go: 1.7
|
|
||||||
- go: tip
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go vet $(go list ./... | grep -v /vendor/)
|
|
||||||
- go test -v -race ./...
|
|
27
vendor/github.com/gorilla/context/LICENSE
generated
vendored
27
vendor/github.com/gorilla/context/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 Rodrigo Moraes. 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.
|
|
10
vendor/github.com/gorilla/context/README.md
generated
vendored
10
vendor/github.com/gorilla/context/README.md
generated
vendored
|
@ -1,10 +0,0 @@
|
||||||
context
|
|
||||||
=======
|
|
||||||
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
|
|
||||||
|
|
||||||
gorilla/context is a general purpose registry for global request variables.
|
|
||||||
|
|
||||||
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
|
|
||||||
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
|
|
||||||
|
|
||||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
|
143
vendor/github.com/gorilla/context/context.go
generated
vendored
143
vendor/github.com/gorilla/context/context.go
generated
vendored
|
@ -1,143 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla 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
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
mutex sync.RWMutex
|
|
||||||
data = make(map[*http.Request]map[interface{}]interface{})
|
|
||||||
datat = make(map[*http.Request]int64)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set stores a value for a given key in a given request.
|
|
||||||
func Set(r *http.Request, key, val interface{}) {
|
|
||||||
mutex.Lock()
|
|
||||||
if data[r] == nil {
|
|
||||||
data[r] = make(map[interface{}]interface{})
|
|
||||||
datat[r] = time.Now().Unix()
|
|
||||||
}
|
|
||||||
data[r][key] = val
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a value stored for a given key in a given request.
|
|
||||||
func Get(r *http.Request, key interface{}) interface{} {
|
|
||||||
mutex.RLock()
|
|
||||||
if ctx := data[r]; ctx != nil {
|
|
||||||
value := ctx[key]
|
|
||||||
mutex.RUnlock()
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOk returns stored value and presence state like multi-value return of map access.
|
|
||||||
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
|
||||||
mutex.RLock()
|
|
||||||
if _, ok := data[r]; ok {
|
|
||||||
value, ok := data[r][key]
|
|
||||||
mutex.RUnlock()
|
|
||||||
return value, ok
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
|
||||||
func GetAll(r *http.Request) map[interface{}]interface{} {
|
|
||||||
mutex.RLock()
|
|
||||||
if context, ok := data[r]; ok {
|
|
||||||
result := make(map[interface{}]interface{}, len(context))
|
|
||||||
for k, v := range context {
|
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
|
||||||
// the request was registered.
|
|
||||||
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
|
||||||
mutex.RLock()
|
|
||||||
context, ok := data[r]
|
|
||||||
result := make(map[interface{}]interface{}, len(context))
|
|
||||||
for k, v := range context {
|
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return result, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes a value stored for a given key in a given request.
|
|
||||||
func Delete(r *http.Request, key interface{}) {
|
|
||||||
mutex.Lock()
|
|
||||||
if data[r] != nil {
|
|
||||||
delete(data[r], key)
|
|
||||||
}
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear removes all values stored for a given request.
|
|
||||||
//
|
|
||||||
// This is usually called by a handler wrapper to clean up request
|
|
||||||
// variables at the end of a request lifetime. See ClearHandler().
|
|
||||||
func Clear(r *http.Request) {
|
|
||||||
mutex.Lock()
|
|
||||||
clear(r)
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear is Clear without the lock.
|
|
||||||
func clear(r *http.Request) {
|
|
||||||
delete(data, r)
|
|
||||||
delete(datat, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge removes request data stored for longer than maxAge, in seconds.
|
|
||||||
// It returns the amount of requests removed.
|
|
||||||
//
|
|
||||||
// If maxAge <= 0, all request data is removed.
|
|
||||||
//
|
|
||||||
// This is only used for sanity check: in case context cleaning was not
|
|
||||||
// properly set some request data can be kept forever, consuming an increasing
|
|
||||||
// amount of memory. In case this is detected, Purge() must be called
|
|
||||||
// periodically until the problem is fixed.
|
|
||||||
func Purge(maxAge int) int {
|
|
||||||
mutex.Lock()
|
|
||||||
count := 0
|
|
||||||
if maxAge <= 0 {
|
|
||||||
count = len(data)
|
|
||||||
data = make(map[*http.Request]map[interface{}]interface{})
|
|
||||||
datat = make(map[*http.Request]int64)
|
|
||||||
} else {
|
|
||||||
min := time.Now().Unix() - int64(maxAge)
|
|
||||||
for r := range data {
|
|
||||||
if datat[r] < min {
|
|
||||||
clear(r)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mutex.Unlock()
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearHandler wraps an http.Handler and clears request values at the end
|
|
||||||
// of a request lifetime.
|
|
||||||
func ClearHandler(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer Clear(r)
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
88
vendor/github.com/gorilla/context/doc.go
generated
vendored
88
vendor/github.com/gorilla/context/doc.go
generated
vendored
|
@ -1,88 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla 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 stores values shared during a request lifetime.
|
|
||||||
|
|
||||||
Note: gorilla/context, having been born well before `context.Context` existed,
|
|
||||||
does not play well > with the shallow copying of the request that
|
|
||||||
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
|
|
||||||
(added to net/http Go 1.7 onwards) performs. You should either use *just*
|
|
||||||
gorilla/context, or moving forward, the new `http.Request.Context()`.
|
|
||||||
|
|
||||||
For example, a router can set variables extracted from the URL and later
|
|
||||||
application handlers can access those values, or it can be used to store
|
|
||||||
sessions values to be saved at the end of a request. There are several
|
|
||||||
others common uses.
|
|
||||||
|
|
||||||
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
|
|
||||||
|
|
||||||
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
|
||||||
|
|
||||||
Here's the basic usage: first define the keys that you will need. The key
|
|
||||||
type is interface{} so a key can be of any type that supports equality.
|
|
||||||
Here we define a key using a custom int type to avoid name collisions:
|
|
||||||
|
|
||||||
package foo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type key int
|
|
||||||
|
|
||||||
const MyKey key = 0
|
|
||||||
|
|
||||||
Then set a variable. Variables are bound to an http.Request object, so you
|
|
||||||
need a request instance to set a value:
|
|
||||||
|
|
||||||
context.Set(r, MyKey, "bar")
|
|
||||||
|
|
||||||
The application can later access the variable using the same key you provided:
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// val is "bar".
|
|
||||||
val := context.Get(r, foo.MyKey)
|
|
||||||
|
|
||||||
// returns ("bar", true)
|
|
||||||
val, ok := context.GetOk(r, foo.MyKey)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
And that's all about the basic usage. We discuss some other ideas below.
|
|
||||||
|
|
||||||
Any type can be stored in the context. To enforce a given type, make the key
|
|
||||||
private and wrap Get() and Set() to accept and return values of a specific
|
|
||||||
type:
|
|
||||||
|
|
||||||
type key int
|
|
||||||
|
|
||||||
const mykey key = 0
|
|
||||||
|
|
||||||
// GetMyKey returns a value for this package from the request values.
|
|
||||||
func GetMyKey(r *http.Request) SomeType {
|
|
||||||
if rv := context.Get(r, mykey); rv != nil {
|
|
||||||
return rv.(SomeType)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMyKey sets a value for this package in the request values.
|
|
||||||
func SetMyKey(r *http.Request, val SomeType) {
|
|
||||||
context.Set(r, mykey, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
Variables must be cleared at the end of a request, to remove all values
|
|
||||||
that were stored. This can be done in an http.Handler, after a request was
|
|
||||||
served. Just call Clear() passing the request:
|
|
||||||
|
|
||||||
context.Clear(r)
|
|
||||||
|
|
||||||
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
|
|
||||||
variables at the end of a request lifetime.
|
|
||||||
|
|
||||||
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
|
|
||||||
so if you are using either of them you don't need to clear the context manually.
|
|
||||||
*/
|
|
||||||
package context
|
|
23
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
23
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
|
@ -1,23 +0,0 @@
|
||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.5.x
|
|
||||||
- go: 1.6.x
|
|
||||||
- go: 1.7.x
|
|
||||||
- go: 1.8.x
|
|
||||||
- go: 1.9.x
|
|
||||||
- go: 1.10.x
|
|
||||||
- go: tip
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- # Skip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go tool vet .
|
|
||||||
- go test -v -race ./...
|
|
11
vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md
generated
vendored
11
vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md
generated
vendored
|
@ -1,11 +0,0 @@
|
||||||
**What version of Go are you running?** (Paste the output of `go version`)
|
|
||||||
|
|
||||||
|
|
||||||
**What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`)
|
|
||||||
|
|
||||||
|
|
||||||
**Describe your problem** (and what you have tried so far)
|
|
||||||
|
|
||||||
|
|
||||||
**Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it)
|
|
||||||
|
|
27
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
27
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 Rodrigo Moraes. 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.
|
|
649
vendor/github.com/gorilla/mux/README.md
generated
vendored
649
vendor/github.com/gorilla/mux/README.md
generated
vendored
|
@ -1,649 +0,0 @@
|
||||||
# gorilla/mux
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
|
|
||||||
[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
|
|
||||||
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
|
|
||||||
|
|
||||||
![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
|
|
||||||
|
|
||||||
http://www.gorillatoolkit.org/pkg/mux
|
|
||||||
|
|
||||||
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
|
|
||||||
their respective handler.
|
|
||||||
|
|
||||||
The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
|
|
||||||
|
|
||||||
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
|
|
||||||
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
|
|
||||||
* URL hosts, paths and query values can have variables with an optional regular expression.
|
|
||||||
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
|
|
||||||
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
* [Install](#install)
|
|
||||||
* [Examples](#examples)
|
|
||||||
* [Matching Routes](#matching-routes)
|
|
||||||
* [Static Files](#static-files)
|
|
||||||
* [Registered URLs](#registered-urls)
|
|
||||||
* [Walking Routes](#walking-routes)
|
|
||||||
* [Graceful Shutdown](#graceful-shutdown)
|
|
||||||
* [Middleware](#middleware)
|
|
||||||
* [Testing Handlers](#testing-handlers)
|
|
||||||
* [Full Example](#full-example)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go get -u github.com/gorilla/mux
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Let's start registering a couple of URL paths and handlers:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", HomeHandler)
|
|
||||||
r.HandleFunc("/products", ProductsHandler)
|
|
||||||
r.HandleFunc("/articles", ArticlesHandler)
|
|
||||||
http.Handle("/", r)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.
|
|
||||||
|
|
||||||
Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/products/{key}", ProductHandler)
|
|
||||||
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
|
||||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
|
||||||
```
|
|
||||||
|
|
||||||
The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprintf(w, "Category: %v\n", vars["category"])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And this is all you need to know about the basic usage. More advanced options are explained below.
|
|
||||||
|
|
||||||
### Matching Routes
|
|
||||||
|
|
||||||
Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
// Only matches if domain is "www.example.com".
|
|
||||||
r.Host("www.example.com")
|
|
||||||
// Matches a dynamic subdomain.
|
|
||||||
r.Host("{subdomain:[a-z]+}.domain.com")
|
|
||||||
```
|
|
||||||
|
|
||||||
There are several other matchers that can be added. To match path prefixes:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.PathPrefix("/products/")
|
|
||||||
```
|
|
||||||
|
|
||||||
...or HTTP methods:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.Methods("GET", "POST")
|
|
||||||
```
|
|
||||||
|
|
||||||
...or URL schemes:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.Schemes("https")
|
|
||||||
```
|
|
||||||
|
|
||||||
...or header values:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.Headers("X-Requested-With", "XMLHttpRequest")
|
|
||||||
```
|
|
||||||
|
|
||||||
...or query values:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.Queries("key", "value")
|
|
||||||
```
|
|
||||||
|
|
||||||
...or to use a custom matcher function:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
|
||||||
return r.ProtoMajor == 0
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
...and finally, it is possible to combine several matchers in a single route:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.HandleFunc("/products", ProductsHandler).
|
|
||||||
Host("www.example.com").
|
|
||||||
Methods("GET").
|
|
||||||
Schemes("http")
|
|
||||||
```
|
|
||||||
|
|
||||||
Routes are tested in the order they were added to the router. If two routes match, the first one wins:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/specific", specificHandler)
|
|
||||||
r.PathPrefix("/").Handler(catchAllHandler)
|
|
||||||
```
|
|
||||||
|
|
||||||
Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
|
|
||||||
|
|
||||||
For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.Host("www.example.com").Subrouter()
|
|
||||||
```
|
|
||||||
|
|
||||||
Then register routes in the subrouter:
|
|
||||||
|
|
||||||
```go
|
|
||||||
s.HandleFunc("/products/", ProductsHandler)
|
|
||||||
s.HandleFunc("/products/{key}", ProductHandler)
|
|
||||||
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
|
||||||
```
|
|
||||||
|
|
||||||
The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
|
|
||||||
|
|
||||||
Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.
|
|
||||||
|
|
||||||
There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.PathPrefix("/products").Subrouter()
|
|
||||||
// "/products/"
|
|
||||||
s.HandleFunc("/", ProductsHandler)
|
|
||||||
// "/products/{key}/"
|
|
||||||
s.HandleFunc("/{key}/", ProductHandler)
|
|
||||||
// "/products/{key}/details"
|
|
||||||
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Static Files
|
|
||||||
|
|
||||||
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
|
|
||||||
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
|
|
||||||
request that matches "/static/\*". This makes it easy to serve static files with mux:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
var dir string
|
|
||||||
|
|
||||||
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
|
||||||
flag.Parse()
|
|
||||||
r := mux.NewRouter()
|
|
||||||
|
|
||||||
// This will serve files under http://localhost:8000/static/<filename>
|
|
||||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
|
||||||
|
|
||||||
srv := &http.Server{
|
|
||||||
Handler: r,
|
|
||||||
Addr: "127.0.0.1:8000",
|
|
||||||
// Good practice: enforce timeouts for servers you create!
|
|
||||||
WriteTimeout: 15 * time.Second,
|
|
||||||
ReadTimeout: 15 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Fatal(srv.ListenAndServe())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Registered URLs
|
|
||||||
|
|
||||||
Now let's see how to build registered URLs.
|
|
||||||
|
|
||||||
Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
```
|
|
||||||
|
|
||||||
To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:
|
|
||||||
|
|
||||||
```go
|
|
||||||
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
|
||||||
```
|
|
||||||
|
|
||||||
...and the result will be a `url.URL` with the following path:
|
|
||||||
|
|
||||||
```
|
|
||||||
"/articles/technology/42"
|
|
||||||
```
|
|
||||||
|
|
||||||
This also works for host and query value variables:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.Host("{subdomain}.domain.com").
|
|
||||||
Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
Queries("filter", "{filter}").
|
|
||||||
HandlerFunc(ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
|
|
||||||
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
|
||||||
"category", "technology",
|
|
||||||
"id", "42",
|
|
||||||
"filter", "gorilla")
|
|
||||||
```
|
|
||||||
|
|
||||||
All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
|
|
||||||
|
|
||||||
Regex support also exists for matching Headers within a route. For example, we could do:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.HeadersRegexp("Content-Type", "application/(text|json)")
|
|
||||||
```
|
|
||||||
|
|
||||||
...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
|
|
||||||
|
|
||||||
There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// "http://news.domain.com/"
|
|
||||||
host, err := r.Get("article").URLHost("subdomain", "news")
|
|
||||||
|
|
||||||
// "/articles/technology/42"
|
|
||||||
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
|
||||||
```
|
|
||||||
|
|
||||||
And if you use subrouters, host and path defined separately can be built as well:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.Host("{subdomain}.domain.com").Subrouter()
|
|
||||||
s.Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
HandlerFunc(ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
|
|
||||||
// "http://news.domain.com/articles/technology/42"
|
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
|
||||||
"category", "technology",
|
|
||||||
"id", "42")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Walking Routes
|
|
||||||
|
|
||||||
The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
|
|
||||||
the following prints all of the registered routes:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", handler)
|
|
||||||
r.HandleFunc("/products", handler).Methods("POST")
|
|
||||||
r.HandleFunc("/articles", handler).Methods("GET")
|
|
||||||
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
|
|
||||||
r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
|
|
||||||
err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
|
||||||
pathTemplate, err := route.GetPathTemplate()
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("ROUTE:", pathTemplate)
|
|
||||||
}
|
|
||||||
pathRegexp, err := route.GetPathRegexp()
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("Path regexp:", pathRegexp)
|
|
||||||
}
|
|
||||||
queriesTemplates, err := route.GetQueriesTemplates()
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
|
|
||||||
}
|
|
||||||
queriesRegexps, err := route.GetQueriesRegexp()
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
|
|
||||||
}
|
|
||||||
methods, err := route.GetMethods()
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("Methods:", strings.Join(methods, ","))
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Handle("/", r)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Graceful Shutdown
|
|
||||||
|
|
||||||
Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var wait time.Duration
|
|
||||||
flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
// Add your routes as needed
|
|
||||||
|
|
||||||
srv := &http.Server{
|
|
||||||
Addr: "0.0.0.0:8080",
|
|
||||||
// Good practice to set timeouts to avoid Slowloris attacks.
|
|
||||||
WriteTimeout: time.Second * 15,
|
|
||||||
ReadTimeout: time.Second * 15,
|
|
||||||
IdleTimeout: time.Second * 60,
|
|
||||||
Handler: r, // Pass our instance of gorilla/mux in.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run our server in a goroutine so that it doesn't block.
|
|
||||||
go func() {
|
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
|
|
||||||
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
|
|
||||||
signal.Notify(c, os.Interrupt)
|
|
||||||
|
|
||||||
// Block until we receive our signal.
|
|
||||||
<-c
|
|
||||||
|
|
||||||
// Create a deadline to wait for.
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), wait)
|
|
||||||
defer cancel()
|
|
||||||
// Doesn't block if no connections, but will otherwise wait
|
|
||||||
// until the timeout deadline.
|
|
||||||
srv.Shutdown(ctx)
|
|
||||||
// Optionally, you could run srv.Shutdown in a goroutine and block on
|
|
||||||
// <-ctx.Done() if your application should wait for other services
|
|
||||||
// to finalize based on context cancellation.
|
|
||||||
log.Println("shutting down")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Middleware
|
|
||||||
|
|
||||||
Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters.
|
|
||||||
Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking.
|
|
||||||
|
|
||||||
Mux middlewares are defined using the de facto standard type:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type MiddlewareFunc func(http.Handler) http.Handler
|
|
||||||
```
|
|
||||||
|
|
||||||
Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers.
|
|
||||||
|
|
||||||
A very basic middleware which logs the URI of the request being handled could be written as:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func loggingMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Do stuff here
|
|
||||||
log.Println(r.RequestURI)
|
|
||||||
// Call the next handler, which can be another middleware in the chain, or the final handler.
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Middlewares can be added to a router using `Router.Use()`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", handler)
|
|
||||||
r.Use(loggingMiddleware)
|
|
||||||
```
|
|
||||||
|
|
||||||
A more complex authentication middleware, which maps session token to users, could be written as:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Define our struct
|
|
||||||
type authenticationMiddleware struct {
|
|
||||||
tokenUsers map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize it somewhere
|
|
||||||
func (amw *authenticationMiddleware) Populate() {
|
|
||||||
amw.tokenUsers["00000000"] = "user0"
|
|
||||||
amw.tokenUsers["aaaaaaaa"] = "userA"
|
|
||||||
amw.tokenUsers["05f717e5"] = "randomUser"
|
|
||||||
amw.tokenUsers["deadbeef"] = "user0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware function, which will be called for each request
|
|
||||||
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
token := r.Header.Get("X-Session-Token")
|
|
||||||
|
|
||||||
if user, found := amw.tokenUsers[token]; found {
|
|
||||||
// We found the token in our map
|
|
||||||
log.Printf("Authenticated user %s\n", user)
|
|
||||||
// Pass down the request to the next middleware (or final handler)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
// Write an error and stop the handler chain
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", handler)
|
|
||||||
|
|
||||||
amw := authenticationMiddleware{}
|
|
||||||
amw.Populate()
|
|
||||||
|
|
||||||
r.Use(amw.Middleware)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
|
|
||||||
|
|
||||||
### Testing Handlers
|
|
||||||
|
|
||||||
Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.
|
|
||||||
|
|
||||||
First, our simple HTTP handler:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// endpoints.go
|
|
||||||
package main
|
|
||||||
|
|
||||||
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// A very simple health check.
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// In the future we could report back on the status of our DB, or our cache
|
|
||||||
// (e.g. Redis) by performing a simple PING, and include them in the response.
|
|
||||||
io.WriteString(w, `{"alive": true}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/health", HealthCheckHandler)
|
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe("localhost:8080", r))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Our test code:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// endpoints_test.go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHealthCheckHandler(t *testing.T) {
|
|
||||||
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
|
|
||||||
// pass 'nil' as the third parameter.
|
|
||||||
req, err := http.NewRequest("GET", "/health", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
handler := http.HandlerFunc(HealthCheckHandler)
|
|
||||||
|
|
||||||
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
|
|
||||||
// directly and pass in our Request and ResponseRecorder.
|
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
// Check the status code is what we expect.
|
|
||||||
if status := rr.Code; status != http.StatusOK {
|
|
||||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
|
||||||
status, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the response body is what we expect.
|
|
||||||
expected := `{"alive": true}`
|
|
||||||
if rr.Body.String() != expected {
|
|
||||||
t.Errorf("handler returned unexpected body: got %v want %v",
|
|
||||||
rr.Body.String(), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In the case that our routes have [variables](#examples), we can pass those in the request. We could write
|
|
||||||
[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple
|
|
||||||
possible route variables as needed.
|
|
||||||
|
|
||||||
```go
|
|
||||||
// endpoints.go
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
// A route with a route variable:
|
|
||||||
r.HandleFunc("/metrics/{type}", MetricsHandler)
|
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe("localhost:8080", r))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Our test file, with a table-driven test of `routeVariables`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// endpoints_test.go
|
|
||||||
func TestMetricsHandler(t *testing.T) {
|
|
||||||
tt := []struct{
|
|
||||||
routeVariable string
|
|
||||||
shouldPass bool
|
|
||||||
}{
|
|
||||||
{"goroutines", true},
|
|
||||||
{"heap", true},
|
|
||||||
{"counters", true},
|
|
||||||
{"queries", true},
|
|
||||||
{"adhadaeqm3k", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tt {
|
|
||||||
path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
|
|
||||||
req, err := http.NewRequest("GET", path, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Need to create a router that we can pass the request through so that the vars will be added to the context
|
|
||||||
router := mux.NewRouter()
|
|
||||||
router.HandleFunc("/metrics/{type}", MetricsHandler)
|
|
||||||
router.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
// In this case, our MetricsHandler returns a non-200 response
|
|
||||||
// for a route variable it doesn't know about.
|
|
||||||
if rr.Code == http.StatusOK && !tc.shouldPass {
|
|
||||||
t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
|
|
||||||
tc.routeVariable, rr.Code, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Full Example
|
|
||||||
|
|
||||||
Here's a complete, runnable example of a small `mux` based server:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"log"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func YourHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("Gorilla!\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
// Routes consist of a path and a handler function.
|
|
||||||
r.HandleFunc("/", YourHandler)
|
|
||||||
|
|
||||||
// Bind to a port and pass our router in
|
|
||||||
log.Fatal(http.ListenAndServe(":8000", r))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
BSD licensed. See the LICENSE file for details.
|
|
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
vendored
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
vendored
|
@ -1,26 +0,0 @@
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func contextGet(r *http.Request, key interface{}) interface{} {
|
|
||||||
return context.Get(r, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
|
||||||
if val == nil {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Set(r, key, val)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextClear(r *http.Request) {
|
|
||||||
context.Clear(r)
|
|
||||||
}
|
|
24
vendor/github.com/gorilla/mux/context_native.go
generated
vendored
24
vendor/github.com/gorilla/mux/context_native.go
generated
vendored
|
@ -1,24 +0,0 @@
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func contextGet(r *http.Request, key interface{}) interface{} {
|
|
||||||
return r.Context().Value(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
|
||||||
if val == nil {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.WithContext(context.WithValue(r.Context(), key, val))
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextClear(r *http.Request) {
|
|
||||||
return
|
|
||||||
}
|
|
306
vendor/github.com/gorilla/mux/doc.go
generated
vendored
306
vendor/github.com/gorilla/mux/doc.go
generated
vendored
|
@ -1,306 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla 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 mux implements a request router and dispatcher.
|
|
||||||
|
|
||||||
The name mux stands for "HTTP request multiplexer". Like the standard
|
|
||||||
http.ServeMux, mux.Router matches incoming requests against a list of
|
|
||||||
registered routes and calls a handler for the route that matches the URL
|
|
||||||
or other conditions. The main features are:
|
|
||||||
|
|
||||||
* Requests can be matched based on URL host, path, path prefix, schemes,
|
|
||||||
header and query values, HTTP methods or using custom matchers.
|
|
||||||
* URL hosts, paths and query values can have variables with an optional
|
|
||||||
regular expression.
|
|
||||||
* Registered URLs can be built, or "reversed", which helps maintaining
|
|
||||||
references to resources.
|
|
||||||
* Routes can be used as subrouters: nested routes are only tested if the
|
|
||||||
parent route matches. This is useful to define groups of routes that
|
|
||||||
share common conditions like a host, a path prefix or other repeated
|
|
||||||
attributes. As a bonus, this optimizes request matching.
|
|
||||||
* It implements the http.Handler interface so it is compatible with the
|
|
||||||
standard http.ServeMux.
|
|
||||||
|
|
||||||
Let's start registering a couple of URL paths and handlers:
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", HomeHandler)
|
|
||||||
r.HandleFunc("/products", ProductsHandler)
|
|
||||||
r.HandleFunc("/articles", ArticlesHandler)
|
|
||||||
http.Handle("/", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
Here we register three routes mapping URL paths to handlers. This is
|
|
||||||
equivalent to how http.HandleFunc() works: if an incoming request URL matches
|
|
||||||
one of the paths, the corresponding handler is called passing
|
|
||||||
(http.ResponseWriter, *http.Request) as parameters.
|
|
||||||
|
|
||||||
Paths can have variables. They are defined using the format {name} or
|
|
||||||
{name:pattern}. If a regular expression pattern is not defined, the matched
|
|
||||||
variable will be anything until the next slash. For example:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/products/{key}", ProductHandler)
|
|
||||||
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
|
||||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
|
||||||
|
|
||||||
Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:
|
|
||||||
|
|
||||||
r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)
|
|
||||||
|
|
||||||
The names are used to create a map of route variables which can be retrieved
|
|
||||||
calling mux.Vars():
|
|
||||||
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
category := vars["category"]
|
|
||||||
|
|
||||||
Note that if any capturing groups are present, mux will panic() during parsing. To prevent
|
|
||||||
this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
|
|
||||||
"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
|
|
||||||
when capturing groups were present.
|
|
||||||
|
|
||||||
And this is all you need to know about the basic usage. More advanced options
|
|
||||||
are explained below.
|
|
||||||
|
|
||||||
Routes can also be restricted to a domain or subdomain. Just define a host
|
|
||||||
pattern to be matched. They can also have variables:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
// Only matches if domain is "www.example.com".
|
|
||||||
r.Host("www.example.com")
|
|
||||||
// Matches a dynamic subdomain.
|
|
||||||
r.Host("{subdomain:[a-z]+}.domain.com")
|
|
||||||
|
|
||||||
There are several other matchers that can be added. To match path prefixes:
|
|
||||||
|
|
||||||
r.PathPrefix("/products/")
|
|
||||||
|
|
||||||
...or HTTP methods:
|
|
||||||
|
|
||||||
r.Methods("GET", "POST")
|
|
||||||
|
|
||||||
...or URL schemes:
|
|
||||||
|
|
||||||
r.Schemes("https")
|
|
||||||
|
|
||||||
...or header values:
|
|
||||||
|
|
||||||
r.Headers("X-Requested-With", "XMLHttpRequest")
|
|
||||||
|
|
||||||
...or query values:
|
|
||||||
|
|
||||||
r.Queries("key", "value")
|
|
||||||
|
|
||||||
...or to use a custom matcher function:
|
|
||||||
|
|
||||||
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
|
||||||
return r.ProtoMajor == 0
|
|
||||||
})
|
|
||||||
|
|
||||||
...and finally, it is possible to combine several matchers in a single route:
|
|
||||||
|
|
||||||
r.HandleFunc("/products", ProductsHandler).
|
|
||||||
Host("www.example.com").
|
|
||||||
Methods("GET").
|
|
||||||
Schemes("http")
|
|
||||||
|
|
||||||
Setting the same matching conditions again and again can be boring, so we have
|
|
||||||
a way to group several routes that share the same requirements.
|
|
||||||
We call it "subrouting".
|
|
||||||
|
|
||||||
For example, let's say we have several URLs that should only match when the
|
|
||||||
host is "www.example.com". Create a route for that host and get a "subrouter"
|
|
||||||
from it:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.Host("www.example.com").Subrouter()
|
|
||||||
|
|
||||||
Then register routes in the subrouter:
|
|
||||||
|
|
||||||
s.HandleFunc("/products/", ProductsHandler)
|
|
||||||
s.HandleFunc("/products/{key}", ProductHandler)
|
|
||||||
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
|
||||||
|
|
||||||
The three URL paths we registered above will only be tested if the domain is
|
|
||||||
"www.example.com", because the subrouter is tested first. This is not
|
|
||||||
only convenient, but also optimizes request matching. You can create
|
|
||||||
subrouters combining any attribute matchers accepted by a route.
|
|
||||||
|
|
||||||
Subrouters can be used to create domain or path "namespaces": you define
|
|
||||||
subrouters in a central place and then parts of the app can register its
|
|
||||||
paths relatively to a given subrouter.
|
|
||||||
|
|
||||||
There's one more thing about subroutes. When a subrouter has a path prefix,
|
|
||||||
the inner routes use it as base for their paths:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.PathPrefix("/products").Subrouter()
|
|
||||||
// "/products/"
|
|
||||||
s.HandleFunc("/", ProductsHandler)
|
|
||||||
// "/products/{key}/"
|
|
||||||
s.HandleFunc("/{key}/", ProductHandler)
|
|
||||||
// "/products/{key}/details"
|
|
||||||
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
|
||||||
|
|
||||||
Note that the path provided to PathPrefix() represents a "wildcard": calling
|
|
||||||
PathPrefix("/static/").Handler(...) means that the handler will be passed any
|
|
||||||
request that matches "/static/*". This makes it easy to serve static files with mux:
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var dir string
|
|
||||||
|
|
||||||
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
|
||||||
flag.Parse()
|
|
||||||
r := mux.NewRouter()
|
|
||||||
|
|
||||||
// This will serve files under http://localhost:8000/static/<filename>
|
|
||||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
|
||||||
|
|
||||||
srv := &http.Server{
|
|
||||||
Handler: r,
|
|
||||||
Addr: "127.0.0.1:8000",
|
|
||||||
// Good practice: enforce timeouts for servers you create!
|
|
||||||
WriteTimeout: 15 * time.Second,
|
|
||||||
ReadTimeout: 15 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Fatal(srv.ListenAndServe())
|
|
||||||
}
|
|
||||||
|
|
||||||
Now let's see how to build registered URLs.
|
|
||||||
|
|
||||||
Routes can be named. All routes that define a name can have their URLs built,
|
|
||||||
or "reversed". We define a name calling Name() on a route. For example:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
|
|
||||||
To build a URL, get the route and call the URL() method, passing a sequence of
|
|
||||||
key/value pairs for the route variables. For the previous route, we would do:
|
|
||||||
|
|
||||||
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
|
||||||
|
|
||||||
...and the result will be a url.URL with the following path:
|
|
||||||
|
|
||||||
"/articles/technology/42"
|
|
||||||
|
|
||||||
This also works for host and query value variables:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.Host("{subdomain}.domain.com").
|
|
||||||
Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
Queries("filter", "{filter}").
|
|
||||||
HandlerFunc(ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
|
|
||||||
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
|
||||||
"category", "technology",
|
|
||||||
"id", "42",
|
|
||||||
"filter", "gorilla")
|
|
||||||
|
|
||||||
All variables defined in the route are required, and their values must
|
|
||||||
conform to the corresponding patterns. These requirements guarantee that a
|
|
||||||
generated URL will always match a registered route -- the only exception is
|
|
||||||
for explicitly defined "build-only" routes which never match.
|
|
||||||
|
|
||||||
Regex support also exists for matching Headers within a route. For example, we could do:
|
|
||||||
|
|
||||||
r.HeadersRegexp("Content-Type", "application/(text|json)")
|
|
||||||
|
|
||||||
...and the route will match both requests with a Content-Type of `application/json` as well as
|
|
||||||
`application/text`
|
|
||||||
|
|
||||||
There's also a way to build only the URL host or path for a route:
|
|
||||||
use the methods URLHost() or URLPath() instead. For the previous route,
|
|
||||||
we would do:
|
|
||||||
|
|
||||||
// "http://news.domain.com/"
|
|
||||||
host, err := r.Get("article").URLHost("subdomain", "news")
|
|
||||||
|
|
||||||
// "/articles/technology/42"
|
|
||||||
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
|
||||||
|
|
||||||
And if you use subrouters, host and path defined separately can be built
|
|
||||||
as well:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.Host("{subdomain}.domain.com").Subrouter()
|
|
||||||
s.Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
HandlerFunc(ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
|
|
||||||
// "http://news.domain.com/articles/technology/42"
|
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
|
||||||
"category", "technology",
|
|
||||||
"id", "42")
|
|
||||||
|
|
||||||
Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking.
|
|
||||||
|
|
||||||
type MiddlewareFunc func(http.Handler) http.Handler
|
|
||||||
|
|
||||||
Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created).
|
|
||||||
|
|
||||||
A very basic middleware which logs the URI of the request being handled could be written as:
|
|
||||||
|
|
||||||
func simpleMw(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Do stuff here
|
|
||||||
log.Println(r.RequestURI)
|
|
||||||
// Call the next handler, which can be another middleware in the chain, or the final handler.
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Middlewares can be added to a router using `Router.Use()`:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", handler)
|
|
||||||
r.Use(simpleMw)
|
|
||||||
|
|
||||||
A more complex authentication middleware, which maps session token to users, could be written as:
|
|
||||||
|
|
||||||
// Define our struct
|
|
||||||
type authenticationMiddleware struct {
|
|
||||||
tokenUsers map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize it somewhere
|
|
||||||
func (amw *authenticationMiddleware) Populate() {
|
|
||||||
amw.tokenUsers["00000000"] = "user0"
|
|
||||||
amw.tokenUsers["aaaaaaaa"] = "userA"
|
|
||||||
amw.tokenUsers["05f717e5"] = "randomUser"
|
|
||||||
amw.tokenUsers["deadbeef"] = "user0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware function, which will be called for each request
|
|
||||||
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
token := r.Header.Get("X-Session-Token")
|
|
||||||
|
|
||||||
if user, found := amw.tokenUsers[token]; found {
|
|
||||||
// We found the token in our map
|
|
||||||
log.Printf("Authenticated user %s\n", user)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", handler)
|
|
||||||
|
|
||||||
amw := authenticationMiddleware{}
|
|
||||||
amw.Populate()
|
|
||||||
|
|
||||||
r.Use(amw.Middleware)
|
|
||||||
|
|
||||||
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package mux
|
|
72
vendor/github.com/gorilla/mux/middleware.go
generated
vendored
72
vendor/github.com/gorilla/mux/middleware.go
generated
vendored
|
@ -1,72 +0,0 @@
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.
|
|
||||||
// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed
|
|
||||||
// to it, and then calls the handler passed as parameter to the MiddlewareFunc.
|
|
||||||
type MiddlewareFunc func(http.Handler) http.Handler
|
|
||||||
|
|
||||||
// middleware interface is anything which implements a MiddlewareFunc named Middleware.
|
|
||||||
type middleware interface {
|
|
||||||
Middleware(handler http.Handler) http.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware allows MiddlewareFunc to implement the middleware interface.
|
|
||||||
func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler {
|
|
||||||
return mw(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
|
|
||||||
func (r *Router) Use(mwf ...MiddlewareFunc) {
|
|
||||||
for _, fn := range mwf {
|
|
||||||
r.middlewares = append(r.middlewares, fn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
|
|
||||||
func (r *Router) useInterface(mw middleware) {
|
|
||||||
r.middlewares = append(r.middlewares, mw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CORSMethodMiddleware sets the Access-Control-Allow-Methods response header
|
|
||||||
// on a request, by matching routes based only on paths. It also handles
|
|
||||||
// OPTIONS requests, by settings Access-Control-Allow-Methods, and then
|
|
||||||
// returning without calling the next http handler.
|
|
||||||
func CORSMethodMiddleware(r *Router) MiddlewareFunc {
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
var allMethods []string
|
|
||||||
|
|
||||||
err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
|
|
||||||
for _, m := range route.matchers {
|
|
||||||
if _, ok := m.(*routeRegexp); ok {
|
|
||||||
if m.Match(req, &RouteMatch{}) {
|
|
||||||
methods, err := route.GetMethods()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
allMethods = append(allMethods, methods...)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ","))
|
|
||||||
|
|
||||||
if req.Method == "OPTIONS" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next.ServeHTTP(w, req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
588
vendor/github.com/gorilla/mux/mux.go
generated
vendored
588
vendor/github.com/gorilla/mux/mux.go
generated
vendored
|
@ -1,588 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla 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 mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrMethodMismatch is returned when the method in the request does not match
|
|
||||||
// the method defined against the route.
|
|
||||||
ErrMethodMismatch = errors.New("method is not allowed")
|
|
||||||
// ErrNotFound is returned when no route match is found.
|
|
||||||
ErrNotFound = errors.New("no matching route was found")
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewRouter returns a new router instance.
|
|
||||||
func NewRouter() *Router {
|
|
||||||
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Router registers routes to be matched and dispatches a handler.
|
|
||||||
//
|
|
||||||
// It implements the http.Handler interface, so it can be registered to serve
|
|
||||||
// requests:
|
|
||||||
//
|
|
||||||
// var router = mux.NewRouter()
|
|
||||||
//
|
|
||||||
// func main() {
|
|
||||||
// http.Handle("/", router)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Or, for Google App Engine, register it in a init() function:
|
|
||||||
//
|
|
||||||
// func init() {
|
|
||||||
// http.Handle("/", router)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// This will send all incoming requests to the router.
|
|
||||||
type Router struct {
|
|
||||||
// Configurable Handler to be used when no route matches.
|
|
||||||
NotFoundHandler http.Handler
|
|
||||||
|
|
||||||
// Configurable Handler to be used when the request method does not match the route.
|
|
||||||
MethodNotAllowedHandler http.Handler
|
|
||||||
|
|
||||||
// Parent route, if this is a subrouter.
|
|
||||||
parent parentRoute
|
|
||||||
// Routes to be matched, in order.
|
|
||||||
routes []*Route
|
|
||||||
// Routes by name for URL building.
|
|
||||||
namedRoutes map[string]*Route
|
|
||||||
// See Router.StrictSlash(). This defines the flag for new routes.
|
|
||||||
strictSlash bool
|
|
||||||
// See Router.SkipClean(). This defines the flag for new routes.
|
|
||||||
skipClean bool
|
|
||||||
// If true, do not clear the request context after handling the request.
|
|
||||||
// This has no effect when go1.7+ is used, since the context is stored
|
|
||||||
// on the request itself.
|
|
||||||
KeepContext bool
|
|
||||||
// see Router.UseEncodedPath(). This defines a flag for all routes.
|
|
||||||
useEncodedPath bool
|
|
||||||
// Slice of middlewares to be called after a match is found
|
|
||||||
middlewares []middleware
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match attempts to match the given request against the router's registered routes.
|
|
||||||
//
|
|
||||||
// If the request matches a route of this router or one of its subrouters the Route,
|
|
||||||
// Handler, and Vars fields of the the match argument are filled and this function
|
|
||||||
// returns true.
|
|
||||||
//
|
|
||||||
// If the request does not match any of this router's or its subrouters' routes
|
|
||||||
// then this function returns false. If available, a reason for the match failure
|
|
||||||
// will be filled in the match argument's MatchErr field. If the match failure type
|
|
||||||
// (eg: not found) has a registered handler, the handler is assigned to the Handler
|
|
||||||
// field of the match argument.
|
|
||||||
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
|
||||||
for _, route := range r.routes {
|
|
||||||
if route.Match(req, match) {
|
|
||||||
// Build middleware chain if no error was found
|
|
||||||
if match.MatchErr == nil {
|
|
||||||
for i := len(r.middlewares) - 1; i >= 0; i-- {
|
|
||||||
match.Handler = r.middlewares[i].Middleware(match.Handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if match.MatchErr == ErrMethodMismatch {
|
|
||||||
if r.MethodNotAllowedHandler != nil {
|
|
||||||
match.Handler = r.MethodNotAllowedHandler
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closest match for a router (includes sub-routers)
|
|
||||||
if r.NotFoundHandler != nil {
|
|
||||||
match.Handler = r.NotFoundHandler
|
|
||||||
match.MatchErr = ErrNotFound
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
match.MatchErr = ErrNotFound
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP dispatches the handler registered in the matched route.
|
|
||||||
//
|
|
||||||
// When there is a match, the route variables can be retrieved calling
|
|
||||||
// mux.Vars(request).
|
|
||||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
if !r.skipClean {
|
|
||||||
path := req.URL.Path
|
|
||||||
if r.useEncodedPath {
|
|
||||||
path = req.URL.EscapedPath()
|
|
||||||
}
|
|
||||||
// Clean path to canonical form and redirect.
|
|
||||||
if p := cleanPath(path); p != path {
|
|
||||||
|
|
||||||
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
|
|
||||||
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
|
|
||||||
// http://code.google.com/p/go/issues/detail?id=5252
|
|
||||||
url := *req.URL
|
|
||||||
url.Path = p
|
|
||||||
p = url.String()
|
|
||||||
|
|
||||||
w.Header().Set("Location", p)
|
|
||||||
w.WriteHeader(http.StatusMovedPermanently)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var match RouteMatch
|
|
||||||
var handler http.Handler
|
|
||||||
if r.Match(req, &match) {
|
|
||||||
handler = match.Handler
|
|
||||||
req = setVars(req, match.Vars)
|
|
||||||
req = setCurrentRoute(req, match.Route)
|
|
||||||
}
|
|
||||||
|
|
||||||
if handler == nil && match.MatchErr == ErrMethodMismatch {
|
|
||||||
handler = methodNotAllowedHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
if handler == nil {
|
|
||||||
handler = http.NotFoundHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !r.KeepContext {
|
|
||||||
defer contextClear(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.ServeHTTP(w, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a route registered with the given name.
|
|
||||||
func (r *Router) Get(name string) *Route {
|
|
||||||
return r.getNamedRoutes()[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRoute returns a route registered with the given name. This method
|
|
||||||
// was renamed to Get() and remains here for backwards compatibility.
|
|
||||||
func (r *Router) GetRoute(name string) *Route {
|
|
||||||
return r.getNamedRoutes()[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictSlash defines the trailing slash behavior for new routes. The initial
|
|
||||||
// value is false.
|
|
||||||
//
|
|
||||||
// When true, if the route path is "/path/", accessing "/path" will perform a redirect
|
|
||||||
// to the former and vice versa. In other words, your application will always
|
|
||||||
// see the path as specified in the route.
|
|
||||||
//
|
|
||||||
// When false, if the route path is "/path", accessing "/path/" will not match
|
|
||||||
// this route and vice versa.
|
|
||||||
//
|
|
||||||
// The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for
|
|
||||||
// routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed
|
|
||||||
// request will be made as a GET by most clients. Use middleware or client settings
|
|
||||||
// to modify this behaviour as needed.
|
|
||||||
//
|
|
||||||
// Special case: when a route sets a path prefix using the PathPrefix() method,
|
|
||||||
// strict slash is ignored for that route because the redirect behavior can't
|
|
||||||
// be determined from a prefix alone. However, any subrouters created from that
|
|
||||||
// route inherit the original StrictSlash setting.
|
|
||||||
func (r *Router) StrictSlash(value bool) *Router {
|
|
||||||
r.strictSlash = value
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// SkipClean defines the path cleaning behaviour for new routes. The initial
|
|
||||||
// value is false. Users should be careful about which routes are not cleaned
|
|
||||||
//
|
|
||||||
// When true, if the route path is "/path//to", it will remain with the double
|
|
||||||
// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
|
|
||||||
//
|
|
||||||
// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
|
|
||||||
// become /fetch/http/xkcd.com/534
|
|
||||||
func (r *Router) SkipClean(value bool) *Router {
|
|
||||||
r.skipClean = value
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// UseEncodedPath tells the router to match the encoded original path
|
|
||||||
// to the routes.
|
|
||||||
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
|
|
||||||
//
|
|
||||||
// If not called, the router will match the unencoded path to the routes.
|
|
||||||
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
|
|
||||||
func (r *Router) UseEncodedPath() *Router {
|
|
||||||
r.useEncodedPath = true
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// parentRoute
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
func (r *Router) getBuildScheme() string {
|
|
||||||
if r.parent != nil {
|
|
||||||
return r.parent.getBuildScheme()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNamedRoutes returns the map where named routes are registered.
|
|
||||||
func (r *Router) getNamedRoutes() map[string]*Route {
|
|
||||||
if r.namedRoutes == nil {
|
|
||||||
if r.parent != nil {
|
|
||||||
r.namedRoutes = r.parent.getNamedRoutes()
|
|
||||||
} else {
|
|
||||||
r.namedRoutes = make(map[string]*Route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.namedRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRegexpGroup returns regexp definitions from the parent route, if any.
|
|
||||||
func (r *Router) getRegexpGroup() *routeRegexpGroup {
|
|
||||||
if r.parent != nil {
|
|
||||||
return r.parent.getRegexpGroup()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) buildVars(m map[string]string) map[string]string {
|
|
||||||
if r.parent != nil {
|
|
||||||
m = r.parent.buildVars(m)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Route factories
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// NewRoute registers an empty route.
|
|
||||||
func (r *Router) NewRoute() *Route {
|
|
||||||
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
|
|
||||||
r.routes = append(r.routes, route)
|
|
||||||
return route
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle registers a new route with a matcher for the URL path.
|
|
||||||
// See Route.Path() and Route.Handler().
|
|
||||||
func (r *Router) Handle(path string, handler http.Handler) *Route {
|
|
||||||
return r.NewRoute().Path(path).Handler(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFunc registers a new route with a matcher for the URL path.
|
|
||||||
// See Route.Path() and Route.HandlerFunc().
|
|
||||||
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
|
|
||||||
*http.Request)) *Route {
|
|
||||||
return r.NewRoute().Path(path).HandlerFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headers registers a new route with a matcher for request header values.
|
|
||||||
// See Route.Headers().
|
|
||||||
func (r *Router) Headers(pairs ...string) *Route {
|
|
||||||
return r.NewRoute().Headers(pairs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host registers a new route with a matcher for the URL host.
|
|
||||||
// See Route.Host().
|
|
||||||
func (r *Router) Host(tpl string) *Route {
|
|
||||||
return r.NewRoute().Host(tpl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatcherFunc registers a new route with a custom matcher function.
|
|
||||||
// See Route.MatcherFunc().
|
|
||||||
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
|
|
||||||
return r.NewRoute().MatcherFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods registers a new route with a matcher for HTTP methods.
|
|
||||||
// See Route.Methods().
|
|
||||||
func (r *Router) Methods(methods ...string) *Route {
|
|
||||||
return r.NewRoute().Methods(methods...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path registers a new route with a matcher for the URL path.
|
|
||||||
// See Route.Path().
|
|
||||||
func (r *Router) Path(tpl string) *Route {
|
|
||||||
return r.NewRoute().Path(tpl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathPrefix registers a new route with a matcher for the URL path prefix.
|
|
||||||
// See Route.PathPrefix().
|
|
||||||
func (r *Router) PathPrefix(tpl string) *Route {
|
|
||||||
return r.NewRoute().PathPrefix(tpl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queries registers a new route with a matcher for URL query values.
|
|
||||||
// See Route.Queries().
|
|
||||||
func (r *Router) Queries(pairs ...string) *Route {
|
|
||||||
return r.NewRoute().Queries(pairs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schemes registers a new route with a matcher for URL schemes.
|
|
||||||
// See Route.Schemes().
|
|
||||||
func (r *Router) Schemes(schemes ...string) *Route {
|
|
||||||
return r.NewRoute().Schemes(schemes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildVarsFunc registers a new route with a custom function for modifying
|
|
||||||
// route variables before building a URL.
|
|
||||||
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
|
|
||||||
return r.NewRoute().BuildVarsFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk walks the router and all its sub-routers, calling walkFn for each route
|
|
||||||
// in the tree. The routes are walked in the order they were added. Sub-routers
|
|
||||||
// are explored depth-first.
|
|
||||||
func (r *Router) Walk(walkFn WalkFunc) error {
|
|
||||||
return r.walk(walkFn, []*Route{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// SkipRouter is used as a return value from WalkFuncs to indicate that the
|
|
||||||
// router that walk is about to descend down to should be skipped.
|
|
||||||
var SkipRouter = errors.New("skip this router")
|
|
||||||
|
|
||||||
// WalkFunc is the type of the function called for each route visited by Walk.
|
|
||||||
// At every invocation, it is given the current route, and the current router,
|
|
||||||
// and a list of ancestor routes that lead to the current route.
|
|
||||||
type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
|
|
||||||
|
|
||||||
func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
|
|
||||||
for _, t := range r.routes {
|
|
||||||
err := walkFn(t, r, ancestors)
|
|
||||||
if err == SkipRouter {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, sr := range t.matchers {
|
|
||||||
if h, ok := sr.(*Router); ok {
|
|
||||||
ancestors = append(ancestors, t)
|
|
||||||
err := h.walk(walkFn, ancestors)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ancestors = ancestors[:len(ancestors)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if h, ok := t.handler.(*Router); ok {
|
|
||||||
ancestors = append(ancestors, t)
|
|
||||||
err := h.walk(walkFn, ancestors)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ancestors = ancestors[:len(ancestors)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Context
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// RouteMatch stores information about a matched route.
|
|
||||||
type RouteMatch struct {
|
|
||||||
Route *Route
|
|
||||||
Handler http.Handler
|
|
||||||
Vars map[string]string
|
|
||||||
|
|
||||||
// MatchErr is set to appropriate matching error
|
|
||||||
// It is set to ErrMethodMismatch if there is a mismatch in
|
|
||||||
// the request method and route method
|
|
||||||
MatchErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
type contextKey int
|
|
||||||
|
|
||||||
const (
|
|
||||||
varsKey contextKey = iota
|
|
||||||
routeKey
|
|
||||||
)
|
|
||||||
|
|
||||||
// Vars returns the route variables for the current request, if any.
|
|
||||||
func Vars(r *http.Request) map[string]string {
|
|
||||||
if rv := contextGet(r, varsKey); rv != nil {
|
|
||||||
return rv.(map[string]string)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentRoute returns the matched route for the current request, if any.
|
|
||||||
// This only works when called inside the handler of the matched route
|
|
||||||
// because the matched route is stored in the request context which is cleared
|
|
||||||
// after the handler returns, unless the KeepContext option is set on the
|
|
||||||
// Router.
|
|
||||||
func CurrentRoute(r *http.Request) *Route {
|
|
||||||
if rv := contextGet(r, routeKey); rv != nil {
|
|
||||||
return rv.(*Route)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setVars(r *http.Request, val interface{}) *http.Request {
|
|
||||||
return contextSet(r, varsKey, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
|
|
||||||
return contextSet(r, routeKey, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Helpers
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
|
||||||
// Borrowed from the net/http package.
|
|
||||||
func cleanPath(p string) string {
|
|
||||||
if p == "" {
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
if p[0] != '/' {
|
|
||||||
p = "/" + p
|
|
||||||
}
|
|
||||||
np := path.Clean(p)
|
|
||||||
// path.Clean removes trailing slash except for root;
|
|
||||||
// put the trailing slash back if necessary.
|
|
||||||
if p[len(p)-1] == '/' && np != "/" {
|
|
||||||
np += "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
return np
|
|
||||||
}
|
|
||||||
|
|
||||||
// uniqueVars returns an error if two slices contain duplicated strings.
|
|
||||||
func uniqueVars(s1, s2 []string) error {
|
|
||||||
for _, v1 := range s1 {
|
|
||||||
for _, v2 := range s2 {
|
|
||||||
if v1 == v2 {
|
|
||||||
return fmt.Errorf("mux: duplicated route variable %q", v2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkPairs returns the count of strings passed in, and an error if
|
|
||||||
// the count is not an even number.
|
|
||||||
func checkPairs(pairs ...string) (int, error) {
|
|
||||||
length := len(pairs)
|
|
||||||
if length%2 != 0 {
|
|
||||||
return length, fmt.Errorf(
|
|
||||||
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
|
||||||
}
|
|
||||||
return length, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapFromPairsToString converts variadic string parameters to a
|
|
||||||
// string to string map.
|
|
||||||
func mapFromPairsToString(pairs ...string) (map[string]string, error) {
|
|
||||||
length, err := checkPairs(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m := make(map[string]string, length/2)
|
|
||||||
for i := 0; i < length; i += 2 {
|
|
||||||
m[pairs[i]] = pairs[i+1]
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapFromPairsToRegex converts variadic string parameters to a
|
|
||||||
// string to regex map.
|
|
||||||
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
|
|
||||||
length, err := checkPairs(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m := make(map[string]*regexp.Regexp, length/2)
|
|
||||||
for i := 0; i < length; i += 2 {
|
|
||||||
regex, err := regexp.Compile(pairs[i+1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m[pairs[i]] = regex
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchInArray returns true if the given string value is in the array.
|
|
||||||
func matchInArray(arr []string, value string) bool {
|
|
||||||
for _, v := range arr {
|
|
||||||
if v == value {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchMapWithString returns true if the given key/value pairs exist in a given map.
|
|
||||||
func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
|
|
||||||
for k, v := range toCheck {
|
|
||||||
// Check if key exists.
|
|
||||||
if canonicalKey {
|
|
||||||
k = http.CanonicalHeaderKey(k)
|
|
||||||
}
|
|
||||||
if values := toMatch[k]; values == nil {
|
|
||||||
return false
|
|
||||||
} else if v != "" {
|
|
||||||
// If value was defined as an empty string we only check that the
|
|
||||||
// key exists. Otherwise we also check for equality.
|
|
||||||
valueExists := false
|
|
||||||
for _, value := range values {
|
|
||||||
if v == value {
|
|
||||||
valueExists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !valueExists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
|
|
||||||
// the given regex
|
|
||||||
func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
|
|
||||||
for k, v := range toCheck {
|
|
||||||
// Check if key exists.
|
|
||||||
if canonicalKey {
|
|
||||||
k = http.CanonicalHeaderKey(k)
|
|
||||||
}
|
|
||||||
if values := toMatch[k]; values == nil {
|
|
||||||
return false
|
|
||||||
} else if v != nil {
|
|
||||||
// If value was defined as an empty string we only check that the
|
|
||||||
// key exists. Otherwise we also check for equality.
|
|
||||||
valueExists := false
|
|
||||||
for _, value := range values {
|
|
||||||
if v.MatchString(value) {
|
|
||||||
valueExists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !valueExists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// methodNotAllowed replies to the request with an HTTP status code 405.
|
|
||||||
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// methodNotAllowedHandler returns a simple request handler
|
|
||||||
// that replies to each request with a status code 405.
|
|
||||||
func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }
|
|
332
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
332
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
|
@ -1,332 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla 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 mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type routeRegexpOptions struct {
|
|
||||||
strictSlash bool
|
|
||||||
useEncodedPath bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type regexpType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
regexpTypePath regexpType = 0
|
|
||||||
regexpTypeHost regexpType = 1
|
|
||||||
regexpTypePrefix regexpType = 2
|
|
||||||
regexpTypeQuery regexpType = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
// newRouteRegexp parses a route template and returns a routeRegexp,
|
|
||||||
// used to match a host, a path or a query string.
|
|
||||||
//
|
|
||||||
// It will extract named variables, assemble a regexp to be matched, create
|
|
||||||
// a "reverse" template to build URLs and compile regexps to validate variable
|
|
||||||
// values used in URL building.
|
|
||||||
//
|
|
||||||
// Previously we accepted only Python-like identifiers for variable
|
|
||||||
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
|
|
||||||
// name and pattern can't be empty, and names can't contain a colon.
|
|
||||||
func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) {
|
|
||||||
// Check if it is well-formed.
|
|
||||||
idxs, errBraces := braceIndices(tpl)
|
|
||||||
if errBraces != nil {
|
|
||||||
return nil, errBraces
|
|
||||||
}
|
|
||||||
// Backup the original.
|
|
||||||
template := tpl
|
|
||||||
// Now let's parse it.
|
|
||||||
defaultPattern := "[^/]+"
|
|
||||||
if typ == regexpTypeQuery {
|
|
||||||
defaultPattern = ".*"
|
|
||||||
} else if typ == regexpTypeHost {
|
|
||||||
defaultPattern = "[^.]+"
|
|
||||||
}
|
|
||||||
// Only match strict slash if not matching
|
|
||||||
if typ != regexpTypePath {
|
|
||||||
options.strictSlash = false
|
|
||||||
}
|
|
||||||
// Set a flag for strictSlash.
|
|
||||||
endSlash := false
|
|
||||||
if options.strictSlash && strings.HasSuffix(tpl, "/") {
|
|
||||||
tpl = tpl[:len(tpl)-1]
|
|
||||||
endSlash = true
|
|
||||||
}
|
|
||||||
varsN := make([]string, len(idxs)/2)
|
|
||||||
varsR := make([]*regexp.Regexp, len(idxs)/2)
|
|
||||||
pattern := bytes.NewBufferString("")
|
|
||||||
pattern.WriteByte('^')
|
|
||||||
reverse := bytes.NewBufferString("")
|
|
||||||
var end int
|
|
||||||
var err error
|
|
||||||
for i := 0; i < len(idxs); i += 2 {
|
|
||||||
// Set all values we are interested in.
|
|
||||||
raw := tpl[end:idxs[i]]
|
|
||||||
end = idxs[i+1]
|
|
||||||
parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
|
|
||||||
name := parts[0]
|
|
||||||
patt := defaultPattern
|
|
||||||
if len(parts) == 2 {
|
|
||||||
patt = parts[1]
|
|
||||||
}
|
|
||||||
// Name or pattern can't be empty.
|
|
||||||
if name == "" || patt == "" {
|
|
||||||
return nil, fmt.Errorf("mux: missing name or pattern in %q",
|
|
||||||
tpl[idxs[i]:end])
|
|
||||||
}
|
|
||||||
// Build the regexp pattern.
|
|
||||||
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
|
|
||||||
|
|
||||||
// Build the reverse template.
|
|
||||||
fmt.Fprintf(reverse, "%s%%s", raw)
|
|
||||||
|
|
||||||
// Append variable name and compiled pattern.
|
|
||||||
varsN[i/2] = name
|
|
||||||
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add the remaining.
|
|
||||||
raw := tpl[end:]
|
|
||||||
pattern.WriteString(regexp.QuoteMeta(raw))
|
|
||||||
if options.strictSlash {
|
|
||||||
pattern.WriteString("[/]?")
|
|
||||||
}
|
|
||||||
if typ == regexpTypeQuery {
|
|
||||||
// Add the default pattern if the query value is empty
|
|
||||||
if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
|
|
||||||
pattern.WriteString(defaultPattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typ != regexpTypePrefix {
|
|
||||||
pattern.WriteByte('$')
|
|
||||||
}
|
|
||||||
reverse.WriteString(raw)
|
|
||||||
if endSlash {
|
|
||||||
reverse.WriteByte('/')
|
|
||||||
}
|
|
||||||
// Compile full regexp.
|
|
||||||
reg, errCompile := regexp.Compile(pattern.String())
|
|
||||||
if errCompile != nil {
|
|
||||||
return nil, errCompile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for capturing groups which used to work in older versions
|
|
||||||
if reg.NumSubexp() != len(idxs)/2 {
|
|
||||||
panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
|
|
||||||
"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done!
|
|
||||||
return &routeRegexp{
|
|
||||||
template: template,
|
|
||||||
regexpType: typ,
|
|
||||||
options: options,
|
|
||||||
regexp: reg,
|
|
||||||
reverse: reverse.String(),
|
|
||||||
varsN: varsN,
|
|
||||||
varsR: varsR,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// routeRegexp stores a regexp to match a host or path and information to
|
|
||||||
// collect and validate route variables.
|
|
||||||
type routeRegexp struct {
|
|
||||||
// The unmodified template.
|
|
||||||
template string
|
|
||||||
// The type of match
|
|
||||||
regexpType regexpType
|
|
||||||
// Options for matching
|
|
||||||
options routeRegexpOptions
|
|
||||||
// Expanded regexp.
|
|
||||||
regexp *regexp.Regexp
|
|
||||||
// Reverse template.
|
|
||||||
reverse string
|
|
||||||
// Variable names.
|
|
||||||
varsN []string
|
|
||||||
// Variable regexps (validators).
|
|
||||||
varsR []*regexp.Regexp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match matches the regexp against the URL host or path.
|
|
||||||
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
|
||||||
if r.regexpType != regexpTypeHost {
|
|
||||||
if r.regexpType == regexpTypeQuery {
|
|
||||||
return r.matchQueryString(req)
|
|
||||||
}
|
|
||||||
path := req.URL.Path
|
|
||||||
if r.options.useEncodedPath {
|
|
||||||
path = req.URL.EscapedPath()
|
|
||||||
}
|
|
||||||
return r.regexp.MatchString(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.regexp.MatchString(getHost(req))
|
|
||||||
}
|
|
||||||
|
|
||||||
// url builds a URL part using the given values.
|
|
||||||
func (r *routeRegexp) url(values map[string]string) (string, error) {
|
|
||||||
urlValues := make([]interface{}, len(r.varsN))
|
|
||||||
for k, v := range r.varsN {
|
|
||||||
value, ok := values[v]
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("mux: missing route variable %q", v)
|
|
||||||
}
|
|
||||||
if r.regexpType == regexpTypeQuery {
|
|
||||||
value = url.QueryEscape(value)
|
|
||||||
}
|
|
||||||
urlValues[k] = value
|
|
||||||
}
|
|
||||||
rv := fmt.Sprintf(r.reverse, urlValues...)
|
|
||||||
if !r.regexp.MatchString(rv) {
|
|
||||||
// The URL is checked against the full regexp, instead of checking
|
|
||||||
// individual variables. This is faster but to provide a good error
|
|
||||||
// message, we check individual regexps if the URL doesn't match.
|
|
||||||
for k, v := range r.varsN {
|
|
||||||
if !r.varsR[k].MatchString(values[v]) {
|
|
||||||
return "", fmt.Errorf(
|
|
||||||
"mux: variable %q doesn't match, expected %q", values[v],
|
|
||||||
r.varsR[k].String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getURLQuery returns a single query parameter from a request URL.
|
|
||||||
// For a URL with foo=bar&baz=ding, we return only the relevant key
|
|
||||||
// value pair for the routeRegexp.
|
|
||||||
func (r *routeRegexp) getURLQuery(req *http.Request) string {
|
|
||||||
if r.regexpType != regexpTypeQuery {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
templateKey := strings.SplitN(r.template, "=", 2)[0]
|
|
||||||
for key, vals := range req.URL.Query() {
|
|
||||||
if key == templateKey && len(vals) > 0 {
|
|
||||||
return key + "=" + vals[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *routeRegexp) matchQueryString(req *http.Request) bool {
|
|
||||||
return r.regexp.MatchString(r.getURLQuery(req))
|
|
||||||
}
|
|
||||||
|
|
||||||
// braceIndices returns the first level curly brace indices from a string.
|
|
||||||
// It returns an error in case of unbalanced braces.
|
|
||||||
func braceIndices(s string) ([]int, error) {
|
|
||||||
var level, idx int
|
|
||||||
var idxs []int
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
switch s[i] {
|
|
||||||
case '{':
|
|
||||||
if level++; level == 1 {
|
|
||||||
idx = i
|
|
||||||
}
|
|
||||||
case '}':
|
|
||||||
if level--; level == 0 {
|
|
||||||
idxs = append(idxs, idx, i+1)
|
|
||||||
} else if level < 0 {
|
|
||||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if level != 0 {
|
|
||||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
|
||||||
}
|
|
||||||
return idxs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// varGroupName builds a capturing group name for the indexed variable.
|
|
||||||
func varGroupName(idx int) string {
|
|
||||||
return "v" + strconv.Itoa(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// routeRegexpGroup
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// routeRegexpGroup groups the route matchers that carry variables.
|
|
||||||
type routeRegexpGroup struct {
|
|
||||||
host *routeRegexp
|
|
||||||
path *routeRegexp
|
|
||||||
queries []*routeRegexp
|
|
||||||
}
|
|
||||||
|
|
||||||
// setMatch extracts the variables from the URL once a route matches.
|
|
||||||
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
|
||||||
// Store host variables.
|
|
||||||
if v.host != nil {
|
|
||||||
host := getHost(req)
|
|
||||||
matches := v.host.regexp.FindStringSubmatchIndex(host)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
extractVars(host, matches, v.host.varsN, m.Vars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path := req.URL.Path
|
|
||||||
if r.useEncodedPath {
|
|
||||||
path = req.URL.EscapedPath()
|
|
||||||
}
|
|
||||||
// Store path variables.
|
|
||||||
if v.path != nil {
|
|
||||||
matches := v.path.regexp.FindStringSubmatchIndex(path)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
extractVars(path, matches, v.path.varsN, m.Vars)
|
|
||||||
// Check if we should redirect.
|
|
||||||
if v.path.options.strictSlash {
|
|
||||||
p1 := strings.HasSuffix(path, "/")
|
|
||||||
p2 := strings.HasSuffix(v.path.template, "/")
|
|
||||||
if p1 != p2 {
|
|
||||||
u, _ := url.Parse(req.URL.String())
|
|
||||||
if p1 {
|
|
||||||
u.Path = u.Path[:len(u.Path)-1]
|
|
||||||
} else {
|
|
||||||
u.Path += "/"
|
|
||||||
}
|
|
||||||
m.Handler = http.RedirectHandler(u.String(), 301)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Store query string variables.
|
|
||||||
for _, q := range v.queries {
|
|
||||||
queryURL := q.getURLQuery(req)
|
|
||||||
matches := q.regexp.FindStringSubmatchIndex(queryURL)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
extractVars(queryURL, matches, q.varsN, m.Vars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getHost tries its best to return the request host.
|
|
||||||
func getHost(r *http.Request) string {
|
|
||||||
if r.URL.IsAbs() {
|
|
||||||
return r.URL.Host
|
|
||||||
}
|
|
||||||
host := r.Host
|
|
||||||
// Slice off any port information.
|
|
||||||
if i := strings.Index(host, ":"); i != -1 {
|
|
||||||
host = host[:i]
|
|
||||||
}
|
|
||||||
return host
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractVars(input string, matches []int, names []string, output map[string]string) {
|
|
||||||
for i, name := range names {
|
|
||||||
output[name] = input[matches[2*i+2]:matches[2*i+3]]
|
|
||||||
}
|
|
||||||
}
|
|
763
vendor/github.com/gorilla/mux/route.go
generated
vendored
763
vendor/github.com/gorilla/mux/route.go
generated
vendored
|
@ -1,763 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla 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 mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Route stores information to match a request and build URLs.
|
|
||||||
type Route struct {
|
|
||||||
// Parent where the route was registered (a Router).
|
|
||||||
parent parentRoute
|
|
||||||
// Request handler for the route.
|
|
||||||
handler http.Handler
|
|
||||||
// List of matchers.
|
|
||||||
matchers []matcher
|
|
||||||
// Manager for the variables from host and path.
|
|
||||||
regexp *routeRegexpGroup
|
|
||||||
// If true, when the path pattern is "/path/", accessing "/path" will
|
|
||||||
// redirect to the former and vice versa.
|
|
||||||
strictSlash bool
|
|
||||||
// If true, when the path pattern is "/path//to", accessing "/path//to"
|
|
||||||
// will not redirect
|
|
||||||
skipClean bool
|
|
||||||
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
|
|
||||||
useEncodedPath bool
|
|
||||||
// The scheme used when building URLs.
|
|
||||||
buildScheme string
|
|
||||||
// If true, this route never matches: it is only used to build URLs.
|
|
||||||
buildOnly bool
|
|
||||||
// The name used to build URLs.
|
|
||||||
name string
|
|
||||||
// Error resulted from building a route.
|
|
||||||
err error
|
|
||||||
|
|
||||||
buildVarsFunc BuildVarsFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// SkipClean reports whether path cleaning is enabled for this route via
|
|
||||||
// Router.SkipClean.
|
|
||||||
func (r *Route) SkipClean() bool {
|
|
||||||
return r.skipClean
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match matches the route against the request.
|
|
||||||
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
|
||||||
if r.buildOnly || r.err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var matchErr error
|
|
||||||
|
|
||||||
// Match everything.
|
|
||||||
for _, m := range r.matchers {
|
|
||||||
if matched := m.Match(req, match); !matched {
|
|
||||||
if _, ok := m.(methodMatcher); ok {
|
|
||||||
matchErr = ErrMethodMismatch
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
matchErr = nil
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matchErr != nil {
|
|
||||||
match.MatchErr = matchErr
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if match.MatchErr == ErrMethodMismatch {
|
|
||||||
// We found a route which matches request method, clear MatchErr
|
|
||||||
match.MatchErr = nil
|
|
||||||
// Then override the mis-matched handler
|
|
||||||
match.Handler = r.handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yay, we have a match. Let's collect some info about it.
|
|
||||||
if match.Route == nil {
|
|
||||||
match.Route = r
|
|
||||||
}
|
|
||||||
if match.Handler == nil {
|
|
||||||
match.Handler = r.handler
|
|
||||||
}
|
|
||||||
if match.Vars == nil {
|
|
||||||
match.Vars = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set variables.
|
|
||||||
if r.regexp != nil {
|
|
||||||
r.regexp.setMatch(req, match, r)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Route attributes
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// GetError returns an error resulted from building the route, if any.
|
|
||||||
func (r *Route) GetError() error {
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildOnly sets the route to never match: it is only used to build URLs.
|
|
||||||
func (r *Route) BuildOnly() *Route {
|
|
||||||
r.buildOnly = true
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Handler sets a handler for the route.
|
|
||||||
func (r *Route) Handler(handler http.Handler) *Route {
|
|
||||||
if r.err == nil {
|
|
||||||
r.handler = handler
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandlerFunc sets a handler function for the route.
|
|
||||||
func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
|
|
||||||
return r.Handler(http.HandlerFunc(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHandler returns the handler for the route, if any.
|
|
||||||
func (r *Route) GetHandler() http.Handler {
|
|
||||||
return r.handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Name sets the name for the route, used to build URLs.
|
|
||||||
// If the name was registered already it will be overwritten.
|
|
||||||
func (r *Route) Name(name string) *Route {
|
|
||||||
if r.name != "" {
|
|
||||||
r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
|
|
||||||
r.name, name)
|
|
||||||
}
|
|
||||||
if r.err == nil {
|
|
||||||
r.name = name
|
|
||||||
r.getNamedRoutes()[name] = r
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name for the route, if any.
|
|
||||||
func (r *Route) GetName() string {
|
|
||||||
return r.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Matchers
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// matcher types try to match a request.
|
|
||||||
type matcher interface {
|
|
||||||
Match(*http.Request, *RouteMatch) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// addMatcher adds a matcher to the route.
|
|
||||||
func (r *Route) addMatcher(m matcher) *Route {
|
|
||||||
if r.err == nil {
|
|
||||||
r.matchers = append(r.matchers, m)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// addRegexpMatcher adds a host or path matcher and builder to a route.
|
|
||||||
func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
|
|
||||||
if r.err != nil {
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
r.regexp = r.getRegexpGroup()
|
|
||||||
if typ == regexpTypePath || typ == regexpTypePrefix {
|
|
||||||
if len(tpl) > 0 && tpl[0] != '/' {
|
|
||||||
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
|
||||||
}
|
|
||||||
if r.regexp.path != nil {
|
|
||||||
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{
|
|
||||||
strictSlash: r.strictSlash,
|
|
||||||
useEncodedPath: r.useEncodedPath,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, q := range r.regexp.queries {
|
|
||||||
if err = uniqueVars(rr.varsN, q.varsN); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typ == regexpTypeHost {
|
|
||||||
if r.regexp.path != nil {
|
|
||||||
if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.regexp.host = rr
|
|
||||||
} else {
|
|
||||||
if r.regexp.host != nil {
|
|
||||||
if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typ == regexpTypeQuery {
|
|
||||||
r.regexp.queries = append(r.regexp.queries, rr)
|
|
||||||
} else {
|
|
||||||
r.regexp.path = rr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.addMatcher(rr)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headers --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// headerMatcher matches the request against header values.
|
|
||||||
type headerMatcher map[string]string
|
|
||||||
|
|
||||||
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
||||||
return matchMapWithString(m, r.Header, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headers adds a matcher for request header values.
|
|
||||||
// It accepts a sequence of key/value pairs to be matched. For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.Headers("Content-Type", "application/json",
|
|
||||||
// "X-Requested-With", "XMLHttpRequest")
|
|
||||||
//
|
|
||||||
// The above route will only match if both request header values match.
|
|
||||||
// If the value is an empty string, it will match any value if the key is set.
|
|
||||||
func (r *Route) Headers(pairs ...string) *Route {
|
|
||||||
if r.err == nil {
|
|
||||||
var headers map[string]string
|
|
||||||
headers, r.err = mapFromPairsToString(pairs...)
|
|
||||||
return r.addMatcher(headerMatcher(headers))
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// headerRegexMatcher matches the request against the route given a regex for the header
|
|
||||||
type headerRegexMatcher map[string]*regexp.Regexp
|
|
||||||
|
|
||||||
func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
||||||
return matchMapWithRegex(m, r.Header, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
|
|
||||||
// support. For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.HeadersRegexp("Content-Type", "application/(text|json)",
|
|
||||||
// "X-Requested-With", "XMLHttpRequest")
|
|
||||||
//
|
|
||||||
// The above route will only match if both the request header matches both regular expressions.
|
|
||||||
// If the value is an empty string, it will match any value if the key is set.
|
|
||||||
// Use the start and end of string anchors (^ and $) to match an exact value.
|
|
||||||
func (r *Route) HeadersRegexp(pairs ...string) *Route {
|
|
||||||
if r.err == nil {
|
|
||||||
var headers map[string]*regexp.Regexp
|
|
||||||
headers, r.err = mapFromPairsToRegex(pairs...)
|
|
||||||
return r.addMatcher(headerRegexMatcher(headers))
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Host adds a matcher for the URL host.
|
|
||||||
// It accepts a template with zero or more URL variables enclosed by {}.
|
|
||||||
// Variables can define an optional regexp pattern to be matched:
|
|
||||||
//
|
|
||||||
// - {name} matches anything until the next dot.
|
|
||||||
//
|
|
||||||
// - {name:pattern} matches the given regexp pattern.
|
|
||||||
//
|
|
||||||
// For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.Host("www.example.com")
|
|
||||||
// r.Host("{subdomain}.domain.com")
|
|
||||||
// r.Host("{subdomain:[a-z]+}.domain.com")
|
|
||||||
//
|
|
||||||
// Variable names must be unique in a given route. They can be retrieved
|
|
||||||
// calling mux.Vars(request).
|
|
||||||
func (r *Route) Host(tpl string) *Route {
|
|
||||||
r.err = r.addRegexpMatcher(tpl, regexpTypeHost)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatcherFunc ----------------------------------------------------------------
|
|
||||||
|
|
||||||
// MatcherFunc is the function signature used by custom matchers.
|
|
||||||
type MatcherFunc func(*http.Request, *RouteMatch) bool
|
|
||||||
|
|
||||||
// Match returns the match for a given request.
|
|
||||||
func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
|
|
||||||
return m(r, match)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatcherFunc adds a custom function to be used as request matcher.
|
|
||||||
func (r *Route) MatcherFunc(f MatcherFunc) *Route {
|
|
||||||
return r.addMatcher(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// methodMatcher matches the request against HTTP methods.
|
|
||||||
type methodMatcher []string
|
|
||||||
|
|
||||||
func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
||||||
return matchInArray(m, r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods adds a matcher for HTTP methods.
|
|
||||||
// It accepts a sequence of one or more methods to be matched, e.g.:
|
|
||||||
// "GET", "POST", "PUT".
|
|
||||||
func (r *Route) Methods(methods ...string) *Route {
|
|
||||||
for k, v := range methods {
|
|
||||||
methods[k] = strings.ToUpper(v)
|
|
||||||
}
|
|
||||||
return r.addMatcher(methodMatcher(methods))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Path adds a matcher for the URL path.
|
|
||||||
// It accepts a template with zero or more URL variables enclosed by {}. The
|
|
||||||
// template must start with a "/".
|
|
||||||
// Variables can define an optional regexp pattern to be matched:
|
|
||||||
//
|
|
||||||
// - {name} matches anything until the next slash.
|
|
||||||
//
|
|
||||||
// - {name:pattern} matches the given regexp pattern.
|
|
||||||
//
|
|
||||||
// For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.Path("/products/").Handler(ProductsHandler)
|
|
||||||
// r.Path("/products/{key}").Handler(ProductsHandler)
|
|
||||||
// r.Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
// Handler(ArticleHandler)
|
|
||||||
//
|
|
||||||
// Variable names must be unique in a given route. They can be retrieved
|
|
||||||
// calling mux.Vars(request).
|
|
||||||
func (r *Route) Path(tpl string) *Route {
|
|
||||||
r.err = r.addRegexpMatcher(tpl, regexpTypePath)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathPrefix -----------------------------------------------------------------
|
|
||||||
|
|
||||||
// PathPrefix adds a matcher for the URL path prefix. This matches if the given
|
|
||||||
// template is a prefix of the full URL path. See Route.Path() for details on
|
|
||||||
// the tpl argument.
|
|
||||||
//
|
|
||||||
// Note that it does not treat slashes specially ("/foobar/" will be matched by
|
|
||||||
// the prefix "/foo") so you may want to use a trailing slash here.
|
|
||||||
//
|
|
||||||
// Also note that the setting of Router.StrictSlash() has no effect on routes
|
|
||||||
// with a PathPrefix matcher.
|
|
||||||
func (r *Route) PathPrefix(tpl string) *Route {
|
|
||||||
r.err = r.addRegexpMatcher(tpl, regexpTypePrefix)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Queries adds a matcher for URL query values.
|
|
||||||
// It accepts a sequence of key/value pairs. Values may define variables.
|
|
||||||
// For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
|
|
||||||
//
|
|
||||||
// The above route will only match if the URL contains the defined queries
|
|
||||||
// values, e.g.: ?foo=bar&id=42.
|
|
||||||
//
|
|
||||||
// It the value is an empty string, it will match any value if the key is set.
|
|
||||||
//
|
|
||||||
// Variables can define an optional regexp pattern to be matched:
|
|
||||||
//
|
|
||||||
// - {name} matches anything until the next slash.
|
|
||||||
//
|
|
||||||
// - {name:pattern} matches the given regexp pattern.
|
|
||||||
func (r *Route) Queries(pairs ...string) *Route {
|
|
||||||
length := len(pairs)
|
|
||||||
if length%2 != 0 {
|
|
||||||
r.err = fmt.Errorf(
|
|
||||||
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i := 0; i < length; i += 2 {
|
|
||||||
if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schemes --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// schemeMatcher matches the request against URL schemes.
|
|
||||||
type schemeMatcher []string
|
|
||||||
|
|
||||||
func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
||||||
return matchInArray(m, r.URL.Scheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schemes adds a matcher for URL schemes.
|
|
||||||
// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
|
|
||||||
func (r *Route) Schemes(schemes ...string) *Route {
|
|
||||||
for k, v := range schemes {
|
|
||||||
schemes[k] = strings.ToLower(v)
|
|
||||||
}
|
|
||||||
if r.buildScheme == "" && len(schemes) > 0 {
|
|
||||||
r.buildScheme = schemes[0]
|
|
||||||
}
|
|
||||||
return r.addMatcher(schemeMatcher(schemes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildVarsFunc --------------------------------------------------------------
|
|
||||||
|
|
||||||
// BuildVarsFunc is the function signature used by custom build variable
|
|
||||||
// functions (which can modify route variables before a route's URL is built).
|
|
||||||
type BuildVarsFunc func(map[string]string) map[string]string
|
|
||||||
|
|
||||||
// BuildVarsFunc adds a custom function to be used to modify build variables
|
|
||||||
// before a route's URL is built.
|
|
||||||
func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
|
||||||
r.buildVarsFunc = f
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subrouter ------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Subrouter creates a subrouter for the route.
|
|
||||||
//
|
|
||||||
// It will test the inner routes only if the parent route matched. For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// s := r.Host("www.example.com").Subrouter()
|
|
||||||
// s.HandleFunc("/products/", ProductsHandler)
|
|
||||||
// s.HandleFunc("/products/{key}", ProductHandler)
|
|
||||||
// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
|
||||||
//
|
|
||||||
// Here, the routes registered in the subrouter won't be tested if the host
|
|
||||||
// doesn't match.
|
|
||||||
func (r *Route) Subrouter() *Router {
|
|
||||||
router := &Router{parent: r, strictSlash: r.strictSlash}
|
|
||||||
r.addMatcher(router)
|
|
||||||
return router
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// URL building
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// URL builds a URL for the route.
|
|
||||||
//
|
|
||||||
// It accepts a sequence of key/value pairs for the route variables. For
|
|
||||||
// example, given this route:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
|
||||||
// Name("article")
|
|
||||||
//
|
|
||||||
// ...a URL for it can be built using:
|
|
||||||
//
|
|
||||||
// url, err := r.Get("article").URL("category", "technology", "id", "42")
|
|
||||||
//
|
|
||||||
// ...which will return an url.URL with the following path:
|
|
||||||
//
|
|
||||||
// "/articles/technology/42"
|
|
||||||
//
|
|
||||||
// This also works for host variables:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.Host("{subdomain}.domain.com").
|
|
||||||
// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
|
||||||
// Name("article")
|
|
||||||
//
|
|
||||||
// // url.String() will be "http://news.domain.com/articles/technology/42"
|
|
||||||
// url, err := r.Get("article").URL("subdomain", "news",
|
|
||||||
// "category", "technology",
|
|
||||||
// "id", "42")
|
|
||||||
//
|
|
||||||
// All variables defined in the route are required, and their values must
|
|
||||||
// conform to the corresponding patterns.
|
|
||||||
func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have a host or path")
|
|
||||||
}
|
|
||||||
values, err := r.prepareVars(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var scheme, host, path string
|
|
||||||
queries := make([]string, 0, len(r.regexp.queries))
|
|
||||||
if r.regexp.host != nil {
|
|
||||||
if host, err = r.regexp.host.url(values); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
scheme = "http"
|
|
||||||
if s := r.getBuildScheme(); s != "" {
|
|
||||||
scheme = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.regexp.path != nil {
|
|
||||||
if path, err = r.regexp.path.url(values); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, q := range r.regexp.queries {
|
|
||||||
var query string
|
|
||||||
if query, err = q.url(values); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
queries = append(queries, query)
|
|
||||||
}
|
|
||||||
return &url.URL{
|
|
||||||
Scheme: scheme,
|
|
||||||
Host: host,
|
|
||||||
Path: path,
|
|
||||||
RawQuery: strings.Join(queries, "&"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLHost builds the host part of the URL for a route. See Route.URL().
|
|
||||||
//
|
|
||||||
// The route must have a host defined.
|
|
||||||
func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.host == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have a host")
|
|
||||||
}
|
|
||||||
values, err := r.prepareVars(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
host, err := r.regexp.host.url(values)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
u := &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: host,
|
|
||||||
}
|
|
||||||
if s := r.getBuildScheme(); s != "" {
|
|
||||||
u.Scheme = s
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLPath builds the path part of the URL for a route. See Route.URL().
|
|
||||||
//
|
|
||||||
// The route must have a path defined.
|
|
||||||
func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.path == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have a path")
|
|
||||||
}
|
|
||||||
values, err := r.prepareVars(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
path, err := r.regexp.path.url(values)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &url.URL{
|
|
||||||
Path: path,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPathTemplate returns the template used to build the
|
|
||||||
// route match.
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if the route does not define a path.
|
|
||||||
func (r *Route) GetPathTemplate() (string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return "", r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.path == nil {
|
|
||||||
return "", errors.New("mux: route doesn't have a path")
|
|
||||||
}
|
|
||||||
return r.regexp.path.template, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPathRegexp returns the expanded regular expression used to match route path.
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if the route does not define a path.
|
|
||||||
func (r *Route) GetPathRegexp() (string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return "", r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.path == nil {
|
|
||||||
return "", errors.New("mux: route does not have a path")
|
|
||||||
}
|
|
||||||
return r.regexp.path.regexp.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueriesRegexp returns the expanded regular expressions used to match the
|
|
||||||
// route queries.
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if the route does not have queries.
|
|
||||||
func (r *Route) GetQueriesRegexp() ([]string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.queries == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have queries")
|
|
||||||
}
|
|
||||||
var queries []string
|
|
||||||
for _, query := range r.regexp.queries {
|
|
||||||
queries = append(queries, query.regexp.String())
|
|
||||||
}
|
|
||||||
return queries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueriesTemplates returns the templates used to build the
|
|
||||||
// query matching.
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if the route does not define queries.
|
|
||||||
func (r *Route) GetQueriesTemplates() ([]string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.queries == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have queries")
|
|
||||||
}
|
|
||||||
var queries []string
|
|
||||||
for _, query := range r.regexp.queries {
|
|
||||||
queries = append(queries, query.template)
|
|
||||||
}
|
|
||||||
return queries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMethods returns the methods the route matches against
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if route does not have methods.
|
|
||||||
func (r *Route) GetMethods() ([]string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
for _, m := range r.matchers {
|
|
||||||
if methods, ok := m.(methodMatcher); ok {
|
|
||||||
return []string(methods), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("mux: route doesn't have methods")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHostTemplate returns the template used to build the
|
|
||||||
// route match.
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if the route does not define a host.
|
|
||||||
func (r *Route) GetHostTemplate() (string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return "", r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.host == nil {
|
|
||||||
return "", errors.New("mux: route doesn't have a host")
|
|
||||||
}
|
|
||||||
return r.regexp.host.template, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareVars converts the route variable pairs into a map. If the route has a
|
|
||||||
// BuildVarsFunc, it is invoked.
|
|
||||||
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
|
|
||||||
m, err := mapFromPairsToString(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return r.buildVars(m), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Route) buildVars(m map[string]string) map[string]string {
|
|
||||||
if r.parent != nil {
|
|
||||||
m = r.parent.buildVars(m)
|
|
||||||
}
|
|
||||||
if r.buildVarsFunc != nil {
|
|
||||||
m = r.buildVarsFunc(m)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// parentRoute
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// parentRoute allows routes to know about parent host and path definitions.
|
|
||||||
type parentRoute interface {
|
|
||||||
getBuildScheme() string
|
|
||||||
getNamedRoutes() map[string]*Route
|
|
||||||
getRegexpGroup() *routeRegexpGroup
|
|
||||||
buildVars(map[string]string) map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Route) getBuildScheme() string {
|
|
||||||
if r.buildScheme != "" {
|
|
||||||
return r.buildScheme
|
|
||||||
}
|
|
||||||
if r.parent != nil {
|
|
||||||
return r.parent.getBuildScheme()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNamedRoutes returns the map where named routes are registered.
|
|
||||||
func (r *Route) getNamedRoutes() map[string]*Route {
|
|
||||||
if r.parent == nil {
|
|
||||||
// During tests router is not always set.
|
|
||||||
r.parent = NewRouter()
|
|
||||||
}
|
|
||||||
return r.parent.getNamedRoutes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRegexpGroup returns regexp definitions from this route.
|
|
||||||
func (r *Route) getRegexpGroup() *routeRegexpGroup {
|
|
||||||
if r.regexp == nil {
|
|
||||||
if r.parent == nil {
|
|
||||||
// During tests router is not always set.
|
|
||||||
r.parent = NewRouter()
|
|
||||||
}
|
|
||||||
regexp := r.parent.getRegexpGroup()
|
|
||||||
if regexp == nil {
|
|
||||||
r.regexp = new(routeRegexpGroup)
|
|
||||||
} else {
|
|
||||||
// Copy.
|
|
||||||
r.regexp = &routeRegexpGroup{
|
|
||||||
host: regexp.host,
|
|
||||||
path: regexp.path,
|
|
||||||
queries: regexp.queries,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.regexp
|
|
||||||
}
|
|
19
vendor/github.com/gorilla/mux/test_helpers.go
generated
vendored
19
vendor/github.com/gorilla/mux/test_helpers.go
generated
vendored
|
@ -1,19 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla 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 mux
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
// SetURLVars sets the URL variables for the given request, to be accessed via
|
|
||||||
// mux.Vars for testing route behaviour. Arguments are not modified, a shallow
|
|
||||||
// copy is returned.
|
|
||||||
//
|
|
||||||
// This API should only be used for testing purposes; it provides a way to
|
|
||||||
// inject variables into the request context. Alternatively, URL variables
|
|
||||||
// can be set by making a route that captures the required variables,
|
|
||||||
// starting a server and sending the request to that server.
|
|
||||||
func SetURLVars(r *http.Request, val map[string]string) *http.Request {
|
|
||||||
return setVars(r, val)
|
|
||||||
}
|
|
19
vendor/github.com/gorilla/securecookie/.travis.yml
generated
vendored
19
vendor/github.com/gorilla/securecookie/.travis.yml
generated
vendored
|
@ -1,19 +0,0 @@
|
||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.3
|
|
||||||
- go: 1.4
|
|
||||||
- go: 1.5
|
|
||||||
- go: 1.6
|
|
||||||
- go: 1.7
|
|
||||||
- go: tip
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go vet $(go list ./... | grep -v /vendor/)
|
|
||||||
- go test -v -race ./...
|
|
27
vendor/github.com/gorilla/securecookie/LICENSE
generated
vendored
27
vendor/github.com/gorilla/securecookie/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 Rodrigo Moraes. 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.
|
|
80
vendor/github.com/gorilla/securecookie/README.md
generated
vendored
80
vendor/github.com/gorilla/securecookie/README.md
generated
vendored
|
@ -1,80 +0,0 @@
|
||||||
securecookie
|
|
||||||
============
|
|
||||||
[![GoDoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie) [![Build Status](https://travis-ci.org/gorilla/securecookie.png?branch=master)](https://travis-ci.org/gorilla/securecookie)
|
|
||||||
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/securecookie/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/securecookie?badge)
|
|
||||||
|
|
||||||
|
|
||||||
securecookie encodes and decodes authenticated and optionally encrypted
|
|
||||||
cookie values.
|
|
||||||
|
|
||||||
Secure cookies can't be forged, because their values are validated using HMAC.
|
|
||||||
When encrypted, the content is also inaccessible to malicious eyes. It is still
|
|
||||||
recommended that sensitive data not be stored in cookies, and that HTTPS be used
|
|
||||||
to prevent cookie [replay attacks](https://en.wikipedia.org/wiki/Replay_attack).
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
To use it, first create a new SecureCookie instance:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Hash keys should be at least 32 bytes long
|
|
||||||
var hashKey = []byte("very-secret")
|
|
||||||
// Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long.
|
|
||||||
// Shorter keys may weaken the encryption used.
|
|
||||||
var blockKey = []byte("a-lot-secret")
|
|
||||||
var s = securecookie.New(hashKey, blockKey)
|
|
||||||
```
|
|
||||||
|
|
||||||
The hashKey is required, used to authenticate the cookie value using HMAC.
|
|
||||||
It is recommended to use a key with 32 or 64 bytes.
|
|
||||||
|
|
||||||
The blockKey is optional, used to encrypt the cookie value -- set it to nil
|
|
||||||
to not use encryption. If set, the length must correspond to the block size
|
|
||||||
of the encryption algorithm. For AES, used by default, valid lengths are
|
|
||||||
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
|
||||||
|
|
||||||
Strong keys can be created using the convenience function GenerateRandomKey().
|
|
||||||
|
|
||||||
Once a SecureCookie instance is set, use it to encode a cookie value:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
value := map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
}
|
|
||||||
if encoded, err := s.Encode("cookie-name", value); err == nil {
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: "cookie-name",
|
|
||||||
Value: encoded,
|
|
||||||
Path: "/",
|
|
||||||
Secure: true,
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Later, use the same SecureCookie instance to decode and validate a cookie
|
|
||||||
value:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if cookie, err := r.Cookie("cookie-name"); err == nil {
|
|
||||||
value := make(map[string]string)
|
|
||||||
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
|
|
||||||
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We stored a map[string]string, but secure cookies can hold any value that
|
|
||||||
can be encoded using `encoding/gob`. To store custom types, they must be
|
|
||||||
registered first using gob.Register(). For basic types this is not needed;
|
|
||||||
it works out of the box. An optional JSON encoder that uses `encoding/json` is
|
|
||||||
available for types compatible with JSON.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
BSD licensed. See the LICENSE file for details.
|
|
61
vendor/github.com/gorilla/securecookie/doc.go
generated
vendored
61
vendor/github.com/gorilla/securecookie/doc.go
generated
vendored
|
@ -1,61 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla 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 securecookie encodes and decodes authenticated and optionally
|
|
||||||
encrypted cookie values.
|
|
||||||
|
|
||||||
Secure cookies can't be forged, because their values are validated using HMAC.
|
|
||||||
When encrypted, the content is also inaccessible to malicious eyes.
|
|
||||||
|
|
||||||
To use it, first create a new SecureCookie instance:
|
|
||||||
|
|
||||||
var hashKey = []byte("very-secret")
|
|
||||||
var blockKey = []byte("a-lot-secret")
|
|
||||||
var s = securecookie.New(hashKey, blockKey)
|
|
||||||
|
|
||||||
The hashKey is required, used to authenticate the cookie value using HMAC.
|
|
||||||
It is recommended to use a key with 32 or 64 bytes.
|
|
||||||
|
|
||||||
The blockKey is optional, used to encrypt the cookie value -- set it to nil
|
|
||||||
to not use encryption. If set, the length must correspond to the block size
|
|
||||||
of the encryption algorithm. For AES, used by default, valid lengths are
|
|
||||||
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
|
||||||
|
|
||||||
Strong keys can be created using the convenience function GenerateRandomKey().
|
|
||||||
|
|
||||||
Once a SecureCookie instance is set, use it to encode a cookie value:
|
|
||||||
|
|
||||||
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
value := map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
}
|
|
||||||
if encoded, err := s.Encode("cookie-name", value); err == nil {
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: "cookie-name",
|
|
||||||
Value: encoded,
|
|
||||||
Path: "/",
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Later, use the same SecureCookie instance to decode and validate a cookie
|
|
||||||
value:
|
|
||||||
|
|
||||||
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if cookie, err := r.Cookie("cookie-name"); err == nil {
|
|
||||||
value := make(map[string]string)
|
|
||||||
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
|
|
||||||
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
We stored a map[string]string, but secure cookies can hold any value that
|
|
||||||
can be encoded using encoding/gob. To store custom types, they must be
|
|
||||||
registered first using gob.Register(). For basic types this is not needed;
|
|
||||||
it works out of the box.
|
|
||||||
*/
|
|
||||||
package securecookie
|
|
25
vendor/github.com/gorilla/securecookie/fuzz.go
generated
vendored
25
vendor/github.com/gorilla/securecookie/fuzz.go
generated
vendored
|
@ -1,25 +0,0 @@
|
||||||
// +build gofuzz
|
|
||||||
|
|
||||||
package securecookie
|
|
||||||
|
|
||||||
var hashKey = []byte("very-secret12345")
|
|
||||||
var blockKey = []byte("a-lot-secret1234")
|
|
||||||
var s = New(hashKey, blockKey)
|
|
||||||
|
|
||||||
type Cookie struct {
|
|
||||||
B bool
|
|
||||||
I int
|
|
||||||
S string
|
|
||||||
}
|
|
||||||
|
|
||||||
func Fuzz(data []byte) int {
|
|
||||||
datas := string(data)
|
|
||||||
var c Cookie
|
|
||||||
if err := s.Decode("fuzz", datas, &c); err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if _, err := s.Encode("fuzz", c); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
646
vendor/github.com/gorilla/securecookie/securecookie.go
generated
vendored
646
vendor/github.com/gorilla/securecookie/securecookie.go
generated
vendored
|
@ -1,646 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla 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 securecookie
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/subtle"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/gob"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error is the interface of all errors returned by functions in this library.
|
|
||||||
type Error interface {
|
|
||||||
error
|
|
||||||
|
|
||||||
// IsUsage returns true for errors indicating the client code probably
|
|
||||||
// uses this library incorrectly. For example, the client may have
|
|
||||||
// failed to provide a valid hash key, or may have failed to configure
|
|
||||||
// the Serializer adequately for encoding value.
|
|
||||||
IsUsage() bool
|
|
||||||
|
|
||||||
// IsDecode returns true for errors indicating that a cookie could not
|
|
||||||
// be decoded and validated. Since cookies are usually untrusted
|
|
||||||
// user-provided input, errors of this type should be expected.
|
|
||||||
// Usually, the proper action is simply to reject the request.
|
|
||||||
IsDecode() bool
|
|
||||||
|
|
||||||
// IsInternal returns true for unexpected errors occurring in the
|
|
||||||
// securecookie implementation.
|
|
||||||
IsInternal() bool
|
|
||||||
|
|
||||||
// Cause, if it returns a non-nil value, indicates that this error was
|
|
||||||
// propagated from some underlying library. If this method returns nil,
|
|
||||||
// this error was raised directly by this library.
|
|
||||||
//
|
|
||||||
// Cause is provided principally for debugging/logging purposes; it is
|
|
||||||
// rare that application logic should perform meaningfully different
|
|
||||||
// logic based on Cause. See, for example, the caveats described on
|
|
||||||
// (MultiError).Cause().
|
|
||||||
Cause() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorType is a bitmask giving the error type(s) of an cookieError value.
|
|
||||||
type errorType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
usageError = errorType(1 << iota)
|
|
||||||
decodeError
|
|
||||||
internalError
|
|
||||||
)
|
|
||||||
|
|
||||||
type cookieError struct {
|
|
||||||
typ errorType
|
|
||||||
msg string
|
|
||||||
cause error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e cookieError) IsUsage() bool { return (e.typ & usageError) != 0 }
|
|
||||||
func (e cookieError) IsDecode() bool { return (e.typ & decodeError) != 0 }
|
|
||||||
func (e cookieError) IsInternal() bool { return (e.typ & internalError) != 0 }
|
|
||||||
|
|
||||||
func (e cookieError) Cause() error { return e.cause }
|
|
||||||
|
|
||||||
func (e cookieError) Error() string {
|
|
||||||
parts := []string{"securecookie: "}
|
|
||||||
if e.msg == "" {
|
|
||||||
parts = append(parts, "error")
|
|
||||||
} else {
|
|
||||||
parts = append(parts, e.msg)
|
|
||||||
}
|
|
||||||
if c := e.Cause(); c != nil {
|
|
||||||
parts = append(parts, " - caused by: ", c.Error())
|
|
||||||
}
|
|
||||||
return strings.Join(parts, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errGeneratingIV = cookieError{typ: internalError, msg: "failed to generate random iv"}
|
|
||||||
|
|
||||||
errNoCodecs = cookieError{typ: usageError, msg: "no codecs provided"}
|
|
||||||
errHashKeyNotSet = cookieError{typ: usageError, msg: "hash key is not set"}
|
|
||||||
errBlockKeyNotSet = cookieError{typ: usageError, msg: "block key is not set"}
|
|
||||||
errEncodedValueTooLong = cookieError{typ: usageError, msg: "the value is too long"}
|
|
||||||
|
|
||||||
errValueToDecodeTooLong = cookieError{typ: decodeError, msg: "the value is too long"}
|
|
||||||
errTimestampInvalid = cookieError{typ: decodeError, msg: "invalid timestamp"}
|
|
||||||
errTimestampTooNew = cookieError{typ: decodeError, msg: "timestamp is too new"}
|
|
||||||
errTimestampExpired = cookieError{typ: decodeError, msg: "expired timestamp"}
|
|
||||||
errDecryptionFailed = cookieError{typ: decodeError, msg: "the value could not be decrypted"}
|
|
||||||
errValueNotByte = cookieError{typ: decodeError, msg: "value not a []byte."}
|
|
||||||
errValueNotBytePtr = cookieError{typ: decodeError, msg: "value not a pointer to []byte."}
|
|
||||||
|
|
||||||
// ErrMacInvalid indicates that cookie decoding failed because the HMAC
|
|
||||||
// could not be extracted and verified. Direct use of this error
|
|
||||||
// variable is deprecated; it is public only for legacy compatibility,
|
|
||||||
// and may be privatized in the future, as it is rarely useful to
|
|
||||||
// distinguish between this error and other Error implementations.
|
|
||||||
ErrMacInvalid = cookieError{typ: decodeError, msg: "the value is not valid"}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Codec defines an interface to encode and decode cookie values.
|
|
||||||
type Codec interface {
|
|
||||||
Encode(name string, value interface{}) (string, error)
|
|
||||||
Decode(name, value string, dst interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new SecureCookie.
|
|
||||||
//
|
|
||||||
// hashKey is required, used to authenticate values using HMAC. Create it using
|
|
||||||
// GenerateRandomKey(). It is recommended to use a key with 32 or 64 bytes.
|
|
||||||
//
|
|
||||||
// blockKey is optional, used to encrypt values. Create it using
|
|
||||||
// GenerateRandomKey(). The key length must correspond to the block size
|
|
||||||
// of the encryption algorithm. For AES, used by default, valid lengths are
|
|
||||||
// 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
|
||||||
// The default encoder used for cookie serialization is encoding/gob.
|
|
||||||
//
|
|
||||||
// Note that keys created using GenerateRandomKey() are not automatically
|
|
||||||
// persisted. New keys will be created when the application is restarted, and
|
|
||||||
// previously issued cookies will not be able to be decoded.
|
|
||||||
func New(hashKey, blockKey []byte) *SecureCookie {
|
|
||||||
s := &SecureCookie{
|
|
||||||
hashKey: hashKey,
|
|
||||||
blockKey: blockKey,
|
|
||||||
hashFunc: sha256.New,
|
|
||||||
maxAge: 86400 * 30,
|
|
||||||
maxLength: 4096,
|
|
||||||
sz: GobEncoder{},
|
|
||||||
}
|
|
||||||
if hashKey == nil {
|
|
||||||
s.err = errHashKeyNotSet
|
|
||||||
}
|
|
||||||
if blockKey != nil {
|
|
||||||
s.BlockFunc(aes.NewCipher)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecureCookie encodes and decodes authenticated and optionally encrypted
|
|
||||||
// cookie values.
|
|
||||||
type SecureCookie struct {
|
|
||||||
hashKey []byte
|
|
||||||
hashFunc func() hash.Hash
|
|
||||||
blockKey []byte
|
|
||||||
block cipher.Block
|
|
||||||
maxLength int
|
|
||||||
maxAge int64
|
|
||||||
minAge int64
|
|
||||||
err error
|
|
||||||
sz Serializer
|
|
||||||
// For testing purposes, the function that returns the current timestamp.
|
|
||||||
// If not set, it will use time.Now().UTC().Unix().
|
|
||||||
timeFunc func() int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serializer provides an interface for providing custom serializers for cookie
|
|
||||||
// values.
|
|
||||||
type Serializer interface {
|
|
||||||
Serialize(src interface{}) ([]byte, error)
|
|
||||||
Deserialize(src []byte, dst interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// GobEncoder encodes cookie values using encoding/gob. This is the simplest
|
|
||||||
// encoder and can handle complex types via gob.Register.
|
|
||||||
type GobEncoder struct{}
|
|
||||||
|
|
||||||
// JSONEncoder encodes cookie values using encoding/json. Users who wish to
|
|
||||||
// encode complex types need to satisfy the json.Marshaller and
|
|
||||||
// json.Unmarshaller interfaces.
|
|
||||||
type JSONEncoder struct{}
|
|
||||||
|
|
||||||
// NopEncoder does not encode cookie values, and instead simply accepts a []byte
|
|
||||||
// (as an interface{}) and returns a []byte. This is particularly useful when
|
|
||||||
// you encoding an object upstream and do not wish to re-encode it.
|
|
||||||
type NopEncoder struct{}
|
|
||||||
|
|
||||||
// MaxLength restricts the maximum length, in bytes, for the cookie value.
|
|
||||||
//
|
|
||||||
// Default is 4096, which is the maximum value accepted by Internet Explorer.
|
|
||||||
func (s *SecureCookie) MaxLength(value int) *SecureCookie {
|
|
||||||
s.maxLength = value
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxAge restricts the maximum age, in seconds, for the cookie value.
|
|
||||||
//
|
|
||||||
// Default is 86400 * 30. Set it to 0 for no restriction.
|
|
||||||
func (s *SecureCookie) MaxAge(value int) *SecureCookie {
|
|
||||||
s.maxAge = int64(value)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinAge restricts the minimum age, in seconds, for the cookie value.
|
|
||||||
//
|
|
||||||
// Default is 0 (no restriction).
|
|
||||||
func (s *SecureCookie) MinAge(value int) *SecureCookie {
|
|
||||||
s.minAge = int64(value)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashFunc sets the hash function used to create HMAC.
|
|
||||||
//
|
|
||||||
// Default is crypto/sha256.New.
|
|
||||||
func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie {
|
|
||||||
s.hashFunc = f
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockFunc sets the encryption function used to create a cipher.Block.
|
|
||||||
//
|
|
||||||
// Default is crypto/aes.New.
|
|
||||||
func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCookie {
|
|
||||||
if s.blockKey == nil {
|
|
||||||
s.err = errBlockKeyNotSet
|
|
||||||
} else if block, err := f(s.blockKey); err == nil {
|
|
||||||
s.block = block
|
|
||||||
} else {
|
|
||||||
s.err = cookieError{cause: err, typ: usageError}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encoding sets the encoding/serialization method for cookies.
|
|
||||||
//
|
|
||||||
// Default is encoding/gob. To encode special structures using encoding/gob,
|
|
||||||
// they must be registered first using gob.Register().
|
|
||||||
func (s *SecureCookie) SetSerializer(sz Serializer) *SecureCookie {
|
|
||||||
s.sz = sz
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode encodes a cookie value.
|
|
||||||
//
|
|
||||||
// It serializes, optionally encrypts, signs with a message authentication code,
|
|
||||||
// and finally encodes the value.
|
|
||||||
//
|
|
||||||
// The name argument is the cookie name. It is stored with the encoded value.
|
|
||||||
// The value argument is the value to be encoded. It can be any value that can
|
|
||||||
// be encoded using the currently selected serializer; see SetSerializer().
|
|
||||||
//
|
|
||||||
// It is the client's responsibility to ensure that value, when encoded using
|
|
||||||
// the current serialization/encryption settings on s and then base64-encoded,
|
|
||||||
// is shorter than the maximum permissible length.
|
|
||||||
func (s *SecureCookie) Encode(name string, value interface{}) (string, error) {
|
|
||||||
if s.err != nil {
|
|
||||||
return "", s.err
|
|
||||||
}
|
|
||||||
if s.hashKey == nil {
|
|
||||||
s.err = errHashKeyNotSet
|
|
||||||
return "", s.err
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
var b []byte
|
|
||||||
// 1. Serialize.
|
|
||||||
if b, err = s.sz.Serialize(value); err != nil {
|
|
||||||
return "", cookieError{cause: err, typ: usageError}
|
|
||||||
}
|
|
||||||
// 2. Encrypt (optional).
|
|
||||||
if s.block != nil {
|
|
||||||
if b, err = encrypt(s.block, b); err != nil {
|
|
||||||
return "", cookieError{cause: err, typ: usageError}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b = encode(b)
|
|
||||||
// 3. Create MAC for "name|date|value". Extra pipe to be used later.
|
|
||||||
b = []byte(fmt.Sprintf("%s|%d|%s|", name, s.timestamp(), b))
|
|
||||||
mac := createMac(hmac.New(s.hashFunc, s.hashKey), b[:len(b)-1])
|
|
||||||
// Append mac, remove name.
|
|
||||||
b = append(b, mac...)[len(name)+1:]
|
|
||||||
// 4. Encode to base64.
|
|
||||||
b = encode(b)
|
|
||||||
// 5. Check length.
|
|
||||||
if s.maxLength != 0 && len(b) > s.maxLength {
|
|
||||||
return "", errEncodedValueTooLong
|
|
||||||
}
|
|
||||||
// Done.
|
|
||||||
return string(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode decodes a cookie value.
|
|
||||||
//
|
|
||||||
// It decodes, verifies a message authentication code, optionally decrypts and
|
|
||||||
// finally deserializes the value.
|
|
||||||
//
|
|
||||||
// The name argument is the cookie name. It must be the same name used when
|
|
||||||
// it was stored. The value argument is the encoded cookie value. The dst
|
|
||||||
// argument is where the cookie will be decoded. It must be a pointer.
|
|
||||||
func (s *SecureCookie) Decode(name, value string, dst interface{}) error {
|
|
||||||
if s.err != nil {
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
if s.hashKey == nil {
|
|
||||||
s.err = errHashKeyNotSet
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
// 1. Check length.
|
|
||||||
if s.maxLength != 0 && len(value) > s.maxLength {
|
|
||||||
return errValueToDecodeTooLong
|
|
||||||
}
|
|
||||||
// 2. Decode from base64.
|
|
||||||
b, err := decode([]byte(value))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// 3. Verify MAC. Value is "date|value|mac".
|
|
||||||
parts := bytes.SplitN(b, []byte("|"), 3)
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return ErrMacInvalid
|
|
||||||
}
|
|
||||||
h := hmac.New(s.hashFunc, s.hashKey)
|
|
||||||
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])-1]...)
|
|
||||||
if err = verifyMac(h, b, parts[2]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// 4. Verify date ranges.
|
|
||||||
var t1 int64
|
|
||||||
if t1, err = strconv.ParseInt(string(parts[0]), 10, 64); err != nil {
|
|
||||||
return errTimestampInvalid
|
|
||||||
}
|
|
||||||
t2 := s.timestamp()
|
|
||||||
if s.minAge != 0 && t1 > t2-s.minAge {
|
|
||||||
return errTimestampTooNew
|
|
||||||
}
|
|
||||||
if s.maxAge != 0 && t1 < t2-s.maxAge {
|
|
||||||
return errTimestampExpired
|
|
||||||
}
|
|
||||||
// 5. Decrypt (optional).
|
|
||||||
b, err = decode(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s.block != nil {
|
|
||||||
if b, err = decrypt(s.block, b); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 6. Deserialize.
|
|
||||||
if err = s.sz.Deserialize(b, dst); err != nil {
|
|
||||||
return cookieError{cause: err, typ: decodeError}
|
|
||||||
}
|
|
||||||
// Done.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// timestamp returns the current timestamp, in seconds.
|
|
||||||
//
|
|
||||||
// For testing purposes, the function that generates the timestamp can be
|
|
||||||
// overridden. If not set, it will return time.Now().UTC().Unix().
|
|
||||||
func (s *SecureCookie) timestamp() int64 {
|
|
||||||
if s.timeFunc == nil {
|
|
||||||
return time.Now().UTC().Unix()
|
|
||||||
}
|
|
||||||
return s.timeFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authentication -------------------------------------------------------------
|
|
||||||
|
|
||||||
// createMac creates a message authentication code (MAC).
|
|
||||||
func createMac(h hash.Hash, value []byte) []byte {
|
|
||||||
h.Write(value)
|
|
||||||
return h.Sum(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyMac verifies that a message authentication code (MAC) is valid.
|
|
||||||
func verifyMac(h hash.Hash, value []byte, mac []byte) error {
|
|
||||||
mac2 := createMac(h, value)
|
|
||||||
// Check that both MACs are of equal length, as subtle.ConstantTimeCompare
|
|
||||||
// does not do this prior to Go 1.4.
|
|
||||||
if len(mac) == len(mac2) && subtle.ConstantTimeCompare(mac, mac2) == 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ErrMacInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encryption -----------------------------------------------------------------
|
|
||||||
|
|
||||||
// encrypt encrypts a value using the given block in counter mode.
|
|
||||||
//
|
|
||||||
// A random initialization vector (http://goo.gl/zF67k) with the length of the
|
|
||||||
// block size is prepended to the resulting ciphertext.
|
|
||||||
func encrypt(block cipher.Block, value []byte) ([]byte, error) {
|
|
||||||
iv := GenerateRandomKey(block.BlockSize())
|
|
||||||
if iv == nil {
|
|
||||||
return nil, errGeneratingIV
|
|
||||||
}
|
|
||||||
// Encrypt it.
|
|
||||||
stream := cipher.NewCTR(block, iv)
|
|
||||||
stream.XORKeyStream(value, value)
|
|
||||||
// Return iv + ciphertext.
|
|
||||||
return append(iv, value...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt decrypts a value using the given block in counter mode.
|
|
||||||
//
|
|
||||||
// The value to be decrypted must be prepended by a initialization vector
|
|
||||||
// (http://goo.gl/zF67k) with the length of the block size.
|
|
||||||
func decrypt(block cipher.Block, value []byte) ([]byte, error) {
|
|
||||||
size := block.BlockSize()
|
|
||||||
if len(value) > size {
|
|
||||||
// Extract iv.
|
|
||||||
iv := value[:size]
|
|
||||||
// Extract ciphertext.
|
|
||||||
value = value[size:]
|
|
||||||
// Decrypt it.
|
|
||||||
stream := cipher.NewCTR(block, iv)
|
|
||||||
stream.XORKeyStream(value, value)
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
return nil, errDecryptionFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialization --------------------------------------------------------------
|
|
||||||
|
|
||||||
// Serialize encodes a value using gob.
|
|
||||||
func (e GobEncoder) Serialize(src interface{}) ([]byte, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
enc := gob.NewEncoder(buf)
|
|
||||||
if err := enc.Encode(src); err != nil {
|
|
||||||
return nil, cookieError{cause: err, typ: usageError}
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize decodes a value using gob.
|
|
||||||
func (e GobEncoder) Deserialize(src []byte, dst interface{}) error {
|
|
||||||
dec := gob.NewDecoder(bytes.NewBuffer(src))
|
|
||||||
if err := dec.Decode(dst); err != nil {
|
|
||||||
return cookieError{cause: err, typ: decodeError}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize encodes a value using encoding/json.
|
|
||||||
func (e JSONEncoder) Serialize(src interface{}) ([]byte, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
enc := json.NewEncoder(buf)
|
|
||||||
if err := enc.Encode(src); err != nil {
|
|
||||||
return nil, cookieError{cause: err, typ: usageError}
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize decodes a value using encoding/json.
|
|
||||||
func (e JSONEncoder) Deserialize(src []byte, dst interface{}) error {
|
|
||||||
dec := json.NewDecoder(bytes.NewReader(src))
|
|
||||||
if err := dec.Decode(dst); err != nil {
|
|
||||||
return cookieError{cause: err, typ: decodeError}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize passes a []byte through as-is.
|
|
||||||
func (e NopEncoder) Serialize(src interface{}) ([]byte, error) {
|
|
||||||
if b, ok := src.([]byte); ok {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errValueNotByte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize passes a []byte through as-is.
|
|
||||||
func (e NopEncoder) Deserialize(src []byte, dst interface{}) error {
|
|
||||||
if dat, ok := dst.(*[]byte); ok {
|
|
||||||
*dat = src
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errValueNotBytePtr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encoding -------------------------------------------------------------------
|
|
||||||
|
|
||||||
// encode encodes a value using base64.
|
|
||||||
func encode(value []byte) []byte {
|
|
||||||
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(value)))
|
|
||||||
base64.URLEncoding.Encode(encoded, value)
|
|
||||||
return encoded
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode decodes a cookie using base64.
|
|
||||||
func decode(value []byte) ([]byte, error) {
|
|
||||||
decoded := make([]byte, base64.URLEncoding.DecodedLen(len(value)))
|
|
||||||
b, err := base64.URLEncoding.Decode(decoded, value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, cookieError{cause: err, typ: decodeError, msg: "base64 decode failed"}
|
|
||||||
}
|
|
||||||
return decoded[:b], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// GenerateRandomKey creates a random key with the given length in bytes.
|
|
||||||
// On failure, returns nil.
|
|
||||||
//
|
|
||||||
// Callers should explicitly check for the possibility of a nil return, treat
|
|
||||||
// it as a failure of the system random number generator, and not continue.
|
|
||||||
func GenerateRandomKey(length int) []byte {
|
|
||||||
k := make([]byte, length)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, k); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
|
|
||||||
// CodecsFromPairs returns a slice of SecureCookie instances.
|
|
||||||
//
|
|
||||||
// It is a convenience function to create a list of codecs for key rotation. Note
|
|
||||||
// that the generated Codecs will have the default options applied: callers
|
|
||||||
// should iterate over each Codec and type-assert the underlying *SecureCookie to
|
|
||||||
// change these.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// codecs := securecookie.CodecsFromPairs(
|
|
||||||
// []byte("new-hash-key"),
|
|
||||||
// []byte("new-block-key"),
|
|
||||||
// []byte("old-hash-key"),
|
|
||||||
// []byte("old-block-key"),
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// // Modify each instance.
|
|
||||||
// for _, s := range codecs {
|
|
||||||
// if cookie, ok := s.(*securecookie.SecureCookie); ok {
|
|
||||||
// cookie.MaxAge(86400 * 7)
|
|
||||||
// cookie.SetSerializer(securecookie.JSONEncoder{})
|
|
||||||
// cookie.HashFunc(sha512.New512_256)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
func CodecsFromPairs(keyPairs ...[]byte) []Codec {
|
|
||||||
codecs := make([]Codec, len(keyPairs)/2+len(keyPairs)%2)
|
|
||||||
for i := 0; i < len(keyPairs); i += 2 {
|
|
||||||
var blockKey []byte
|
|
||||||
if i+1 < len(keyPairs) {
|
|
||||||
blockKey = keyPairs[i+1]
|
|
||||||
}
|
|
||||||
codecs[i/2] = New(keyPairs[i], blockKey)
|
|
||||||
}
|
|
||||||
return codecs
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeMulti encodes a cookie value using a group of codecs.
|
|
||||||
//
|
|
||||||
// The codecs are tried in order. Multiple codecs are accepted to allow
|
|
||||||
// key rotation.
|
|
||||||
//
|
|
||||||
// On error, may return a MultiError.
|
|
||||||
func EncodeMulti(name string, value interface{}, codecs ...Codec) (string, error) {
|
|
||||||
if len(codecs) == 0 {
|
|
||||||
return "", errNoCodecs
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors MultiError
|
|
||||||
for _, codec := range codecs {
|
|
||||||
encoded, err := codec.Encode(name, value)
|
|
||||||
if err == nil {
|
|
||||||
return encoded, nil
|
|
||||||
}
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
return "", errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeMulti decodes a cookie value using a group of codecs.
|
|
||||||
//
|
|
||||||
// The codecs are tried in order. Multiple codecs are accepted to allow
|
|
||||||
// key rotation.
|
|
||||||
//
|
|
||||||
// On error, may return a MultiError.
|
|
||||||
func DecodeMulti(name string, value string, dst interface{}, codecs ...Codec) error {
|
|
||||||
if len(codecs) == 0 {
|
|
||||||
return errNoCodecs
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors MultiError
|
|
||||||
for _, codec := range codecs {
|
|
||||||
err := codec.Decode(name, value, dst)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiError groups multiple errors.
|
|
||||||
type MultiError []error
|
|
||||||
|
|
||||||
func (m MultiError) IsUsage() bool { return m.any(func(e Error) bool { return e.IsUsage() }) }
|
|
||||||
func (m MultiError) IsDecode() bool { return m.any(func(e Error) bool { return e.IsDecode() }) }
|
|
||||||
func (m MultiError) IsInternal() bool { return m.any(func(e Error) bool { return e.IsInternal() }) }
|
|
||||||
|
|
||||||
// Cause returns nil for MultiError; there is no unique underlying cause in the
|
|
||||||
// general case.
|
|
||||||
//
|
|
||||||
// Note: we could conceivably return a non-nil Cause only when there is exactly
|
|
||||||
// one child error with a Cause. However, it would be brittle for client code
|
|
||||||
// to rely on the arity of causes inside a MultiError, so we have opted not to
|
|
||||||
// provide this functionality. Clients which really wish to access the Causes
|
|
||||||
// of the underlying errors are free to iterate through the errors themselves.
|
|
||||||
func (m MultiError) Cause() error { return nil }
|
|
||||||
|
|
||||||
func (m MultiError) Error() string {
|
|
||||||
s, n := "", 0
|
|
||||||
for _, e := range m {
|
|
||||||
if e != nil {
|
|
||||||
if n == 0 {
|
|
||||||
s = e.Error()
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch n {
|
|
||||||
case 0:
|
|
||||||
return "(0 errors)"
|
|
||||||
case 1:
|
|
||||||
return s
|
|
||||||
case 2:
|
|
||||||
return s + " (and 1 other error)"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// any returns true if any element of m is an Error for which pred returns true.
|
|
||||||
func (m MultiError) any(pred func(Error) bool) bool {
|
|
||||||
for _, e := range m {
|
|
||||||
if ourErr, ok := e.(Error); ok && pred(ourErr) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
22
vendor/github.com/gorilla/sessions/.travis.yml
generated
vendored
22
vendor/github.com/gorilla/sessions/.travis.yml
generated
vendored
|
@ -1,22 +0,0 @@
|
||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.3
|
|
||||||
- go: 1.4
|
|
||||||
- go: 1.5
|
|
||||||
- go: 1.6
|
|
||||||
- go: 1.7
|
|
||||||
- go: tip
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- # skip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go vet $(go list ./... | grep -v /vendor/)
|
|
||||||
- go test -v -race ./...
|
|
43
vendor/github.com/gorilla/sessions/AUTHORS
generated
vendored
43
vendor/github.com/gorilla/sessions/AUTHORS
generated
vendored
|
@ -1,43 +0,0 @@
|
||||||
# This is the official list of gorilla/sessions authors for copyright purposes.
|
|
||||||
#
|
|
||||||
# Please keep the list sorted.
|
|
||||||
|
|
||||||
Ahmadreza Zibaei <ahmadrezazibaei@hotmail.com>
|
|
||||||
Anton Lindström <lindztr@gmail.com>
|
|
||||||
Brian Jones <mojobojo@gmail.com>
|
|
||||||
Collin Stedman <kronion@users.noreply.github.com>
|
|
||||||
Deniz Eren <dee.116@gmail.com>
|
|
||||||
Dmitry Chestnykh <dmitry@codingrobots.com>
|
|
||||||
Dustin Oprea <myselfasunder@gmail.com>
|
|
||||||
Egon Elbre <egonelbre@gmail.com>
|
|
||||||
enumappstore <appstore@enumapps.com>
|
|
||||||
Geofrey Ernest <geofreyernest@live.com>
|
|
||||||
Google LLC (https://opensource.google.com/)
|
|
||||||
Jerry Saravia <SaraviaJ@gmail.com>
|
|
||||||
Jonathan Gillham <jonathan.gillham@gamil.com>
|
|
||||||
Justin Clift <justin@postgresql.org>
|
|
||||||
Justin Hellings <justin.hellings@gmail.com>
|
|
||||||
Kamil Kisiel <kamil@kamilkisiel.net>
|
|
||||||
Keiji Yoshida <yoshida.keiji.84@gmail.com>
|
|
||||||
kliron <kliron@gmail.com>
|
|
||||||
Kshitij Saraogi <KshitijSaraogi@gmail.com>
|
|
||||||
Lauris BH <lauris@nix.lv>
|
|
||||||
Lukas Rist <glaslos@gmail.com>
|
|
||||||
Mark Dain <ancarda@users.noreply.github.com>
|
|
||||||
Matt Ho <matt.ho@gmail.com>
|
|
||||||
Matt Silverlock <matt@eatsleeprepeat.net>
|
|
||||||
Mattias Wadman <mattias.wadman@gmail.com>
|
|
||||||
Michael Schuett <michaeljs1990@gmail.com>
|
|
||||||
Michael Stapelberg <stapelberg@users.noreply.github.com>
|
|
||||||
Mirco Zeiss <mirco.zeiss@gmail.com>
|
|
||||||
moraes <rodrigo.moraes@gmail.com>
|
|
||||||
nvcnvn <nguyen@open-vn.org>
|
|
||||||
pappz <zoltan.pmail@gmail.com>
|
|
||||||
Pontus Leitzler <leitzler@users.noreply.github.com>
|
|
||||||
QuaSoft <info@quasoft.net>
|
|
||||||
rcadena <robert.cadena@gmail.com>
|
|
||||||
rodrigo moraes <rodrigo.moraes@gmail.com>
|
|
||||||
Shawn Smith <shawnpsmith@gmail.com>
|
|
||||||
Taylor Hurt <taylor.a.hurt@gmail.com>
|
|
||||||
Tortuoise <sanyasinp@gmail.com>
|
|
||||||
Vitor De Mario <vitordemario@gmail.com>
|
|
27
vendor/github.com/gorilla/sessions/LICENSE
generated
vendored
27
vendor/github.com/gorilla/sessions/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012-2018 The Gorilla 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.
|
|
92
vendor/github.com/gorilla/sessions/README.md
generated
vendored
92
vendor/github.com/gorilla/sessions/README.md
generated
vendored
|
@ -1,92 +0,0 @@
|
||||||
sessions
|
|
||||||
========
|
|
||||||
[![GoDoc](https://godoc.org/github.com/gorilla/sessions?status.svg)](https://godoc.org/github.com/gorilla/sessions) [![Build Status](https://travis-ci.org/gorilla/sessions.svg?branch=master)](https://travis-ci.org/gorilla/sessions)
|
|
||||||
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/sessions/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/sessions?badge)
|
|
||||||
|
|
||||||
|
|
||||||
gorilla/sessions provides cookie and filesystem sessions and infrastructure for
|
|
||||||
custom session backends.
|
|
||||||
|
|
||||||
The key features are:
|
|
||||||
|
|
||||||
* Simple API: use it as an easy way to set signed (and optionally
|
|
||||||
encrypted) cookies.
|
|
||||||
* Built-in backends to store sessions in cookies or the filesystem.
|
|
||||||
* Flash messages: session values that last until read.
|
|
||||||
* Convenient way to switch session persistency (aka "remember me") and set
|
|
||||||
other attributes.
|
|
||||||
* Mechanism to rotate authentication and encryption keys.
|
|
||||||
* Multiple sessions per request, even using different backends.
|
|
||||||
* Interfaces and infrastructure for custom session backends: sessions from
|
|
||||||
different stores can be retrieved and batch-saved using a common API.
|
|
||||||
|
|
||||||
Let's start with an example that shows the sessions API in a nutshell:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get a session. We're ignoring the error resulted from decoding an
|
|
||||||
// existing session: Get() always returns a session, even if empty.
|
|
||||||
session, _ := store.Get(r, "session-name")
|
|
||||||
// Set some session values.
|
|
||||||
session.Values["foo"] = "bar"
|
|
||||||
session.Values[42] = 43
|
|
||||||
// Save it before we write to the response/return from the handler.
|
|
||||||
session.Save(r, w)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
First we initialize a session store calling `NewCookieStore()` and passing a
|
|
||||||
secret key used to authenticate the session. Inside the handler, we call
|
|
||||||
`store.Get()` to retrieve an existing session or create a new one. Then we set
|
|
||||||
some session values in session.Values, which is a `map[interface{}]interface{}`.
|
|
||||||
And finally we call `session.Save()` to save the session in the response.
|
|
||||||
|
|
||||||
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
|
|
||||||
with
|
|
||||||
[`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler)
|
|
||||||
or else you will leak memory! An easy way to do this is to wrap the top-level
|
|
||||||
mux when calling http.ListenAndServe:
|
|
||||||
|
|
||||||
```go
|
|
||||||
http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
|
|
||||||
```
|
|
||||||
|
|
||||||
The ClearHandler function is provided by the gorilla/context package.
|
|
||||||
|
|
||||||
More examples are available [on the Gorilla
|
|
||||||
website](http://www.gorillatoolkit.org/pkg/sessions).
|
|
||||||
|
|
||||||
## Store Implementations
|
|
||||||
|
|
||||||
Other implementations of the `sessions.Store` interface:
|
|
||||||
|
|
||||||
* [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB
|
|
||||||
* [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
|
|
||||||
* [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
|
|
||||||
* [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
|
|
||||||
* [github.com/savaki/dynastore](https://github.com/savaki/dynastore) - DynamoDB on AWS (Official AWS library)
|
|
||||||
* [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
|
|
||||||
* [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine
|
|
||||||
* [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
|
|
||||||
* [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
|
|
||||||
* [github.com/EnumApps/clustersqlstore](https://github.com/EnumApps/clustersqlstore) - MySQL Cluster
|
|
||||||
* [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
|
|
||||||
* [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
|
|
||||||
* [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
|
|
||||||
* [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
|
|
||||||
* [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
|
|
||||||
* [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite)
|
|
||||||
* [github.com/gernest/qlstore](https://github.com/gernest/qlstore) - ql
|
|
||||||
* [github.com/quasoft/memstore](https://github.com/quasoft/memstore) - In-memory implementation for use in unit tests
|
|
||||||
* [github.com/lafriks/xormstore](https://github.com/lafriks/xormstore) - XORM (MySQL, PostgreSQL, SQLite, Microsoft SQL Server, TiDB)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
BSD licensed. See the LICENSE file for details.
|
|
198
vendor/github.com/gorilla/sessions/doc.go
generated
vendored
198
vendor/github.com/gorilla/sessions/doc.go
generated
vendored
|
@ -1,198 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla 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 sessions provides cookie and filesystem sessions and
|
|
||||||
infrastructure for custom session backends.
|
|
||||||
|
|
||||||
The key features are:
|
|
||||||
|
|
||||||
* Simple API: use it as an easy way to set signed (and optionally
|
|
||||||
encrypted) cookies.
|
|
||||||
* Built-in backends to store sessions in cookies or the filesystem.
|
|
||||||
* Flash messages: session values that last until read.
|
|
||||||
* Convenient way to switch session persistency (aka "remember me") and set
|
|
||||||
other attributes.
|
|
||||||
* Mechanism to rotate authentication and encryption keys.
|
|
||||||
* Multiple sessions per request, even using different backends.
|
|
||||||
* Interfaces and infrastructure for custom session backends: sessions from
|
|
||||||
different stores can be retrieved and batch-saved using a common API.
|
|
||||||
|
|
||||||
Let's start with an example that shows the sessions API in a nutshell:
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get a session. Get() always returns a session, even if empty.
|
|
||||||
session, err := store.Get(r, "session-name")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set some session values.
|
|
||||||
session.Values["foo"] = "bar"
|
|
||||||
session.Values[42] = 43
|
|
||||||
// Save it before we write to the response/return from the handler.
|
|
||||||
session.Save(r, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
First we initialize a session store calling NewCookieStore() and passing a
|
|
||||||
secret key used to authenticate the session. Inside the handler, we call
|
|
||||||
store.Get() to retrieve an existing session or a new one. Then we set some
|
|
||||||
session values in session.Values, which is a map[interface{}]interface{}.
|
|
||||||
And finally we call session.Save() to save the session in the response.
|
|
||||||
|
|
||||||
Note that in production code, we should check for errors when calling
|
|
||||||
session.Save(r, w), and either display an error message or otherwise handle it.
|
|
||||||
|
|
||||||
Save must be called before writing to the response, otherwise the session
|
|
||||||
cookie will not be sent to the client.
|
|
||||||
|
|
||||||
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
|
|
||||||
with context.ClearHandler as or else you will leak memory! An easy way to do this
|
|
||||||
is to wrap the top-level mux when calling http.ListenAndServe:
|
|
||||||
|
|
||||||
http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
|
|
||||||
|
|
||||||
The ClearHandler function is provided by the gorilla/context package.
|
|
||||||
|
|
||||||
That's all you need to know for the basic usage. Let's take a look at other
|
|
||||||
options, starting with flash messages.
|
|
||||||
|
|
||||||
Flash messages are session values that last until read. The term appeared with
|
|
||||||
Ruby On Rails a few years back. When we request a flash message, it is removed
|
|
||||||
from the session. To add a flash, call session.AddFlash(), and to get all
|
|
||||||
flashes, call session.Flashes(). Here is an example:
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get a session.
|
|
||||||
session, err := store.Get(r, "session-name")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the previous flashes, if any.
|
|
||||||
if flashes := session.Flashes(); len(flashes) > 0 {
|
|
||||||
// Use the flash values.
|
|
||||||
} else {
|
|
||||||
// Set a new flash.
|
|
||||||
session.AddFlash("Hello, flash messages world!")
|
|
||||||
}
|
|
||||||
session.Save(r, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
Flash messages are useful to set information to be read after a redirection,
|
|
||||||
like after form submissions.
|
|
||||||
|
|
||||||
There may also be cases where you want to store a complex datatype within a
|
|
||||||
session, such as a struct. Sessions are serialised using the encoding/gob package,
|
|
||||||
so it is easy to register new datatypes for storage in sessions:
|
|
||||||
|
|
||||||
import(
|
|
||||||
"encoding/gob"
|
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Person struct {
|
|
||||||
FirstName string
|
|
||||||
LastName string
|
|
||||||
Email string
|
|
||||||
Age int
|
|
||||||
}
|
|
||||||
|
|
||||||
type M map[string]interface{}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
gob.Register(&Person{})
|
|
||||||
gob.Register(&M{})
|
|
||||||
}
|
|
||||||
|
|
||||||
As it's not possible to pass a raw type as a parameter to a function, gob.Register()
|
|
||||||
relies on us passing it a value of the desired type. In the example above we've passed
|
|
||||||
it a pointer to a struct and a pointer to a custom type representing a
|
|
||||||
map[string]interface. (We could have passed non-pointer values if we wished.) This will
|
|
||||||
then allow us to serialise/deserialise values of those types to and from our sessions.
|
|
||||||
|
|
||||||
Note that because session values are stored in a map[string]interface{}, there's
|
|
||||||
a need to type-assert data when retrieving it. We'll use the Person struct we registered above:
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
session, err := store.Get(r, "session-name")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve our struct and type-assert it
|
|
||||||
val := session.Values["person"]
|
|
||||||
var person = &Person{}
|
|
||||||
if person, ok := val.(*Person); !ok {
|
|
||||||
// Handle the case that it's not an expected type
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can use our person object
|
|
||||||
}
|
|
||||||
|
|
||||||
By default, session cookies last for a month. This is probably too long for
|
|
||||||
some cases, but it is easy to change this and other attributes during
|
|
||||||
runtime. Sessions can be configured individually or the store can be
|
|
||||||
configured and then all sessions saved using it will use that configuration.
|
|
||||||
We access session.Options or store.Options to set a new configuration. The
|
|
||||||
fields are basically a subset of http.Cookie fields. Let's change the
|
|
||||||
maximum age of a session to one week:
|
|
||||||
|
|
||||||
session.Options = &sessions.Options{
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: 86400 * 7,
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
Sometimes we may want to change authentication and/or encryption keys without
|
|
||||||
breaking existing sessions. The CookieStore supports key rotation, and to use
|
|
||||||
it you just need to set multiple authentication and encryption keys, in pairs,
|
|
||||||
to be tested in order:
|
|
||||||
|
|
||||||
var store = sessions.NewCookieStore(
|
|
||||||
[]byte("new-authentication-key"),
|
|
||||||
[]byte("new-encryption-key"),
|
|
||||||
[]byte("old-authentication-key"),
|
|
||||||
[]byte("old-encryption-key"),
|
|
||||||
)
|
|
||||||
|
|
||||||
New sessions will be saved using the first pair. Old sessions can still be
|
|
||||||
read because the first pair will fail, and the second will be tested. This
|
|
||||||
makes it easy to "rotate" secret keys and still be able to validate existing
|
|
||||||
sessions. Note: for all pairs the encryption key is optional; set it to nil
|
|
||||||
or omit it and and encryption won't be used.
|
|
||||||
|
|
||||||
Multiple sessions can be used in the same request, even with different
|
|
||||||
session backends. When this happens, calling Save() on each session
|
|
||||||
individually would be cumbersome, so we have a way to save all sessions
|
|
||||||
at once: it's sessions.Save(). Here's an example:
|
|
||||||
|
|
||||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get a session and set a value.
|
|
||||||
session1, _ := store.Get(r, "session-one")
|
|
||||||
session1.Values["foo"] = "bar"
|
|
||||||
// Get another session and set another value.
|
|
||||||
session2, _ := store.Get(r, "session-two")
|
|
||||||
session2.Values[42] = 43
|
|
||||||
// Save all sessions.
|
|
||||||
sessions.Save(r, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
This is possible because when we call Get() from a session store, it adds the
|
|
||||||
session to a common registry. Save() uses it to save all registered sessions.
|
|
||||||
*/
|
|
||||||
package sessions
|
|
6
vendor/github.com/gorilla/sessions/go.mod
generated
vendored
6
vendor/github.com/gorilla/sessions/go.mod
generated
vendored
|
@ -1,6 +0,0 @@
|
||||||
module "github.com/gorilla/sessions"
|
|
||||||
|
|
||||||
require (
|
|
||||||
"github.com/gorilla/context" v1.1.1
|
|
||||||
"github.com/gorilla/securecookie" v1.1.1
|
|
||||||
)
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue