Convert project to go mod from go dep
parent
433ea009f3
commit
578ab7b880
@ -1,63 +0,0 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/GeertJohan/go.rice"
|
|
||||||
packages = [".","embedded"]
|
|
||||||
revision = "c02ca9a983da5807ddf7d796784928f5be4afd09"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/daaku/go.zipexe"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "a5fe2436ffcb3236e175e5149162b41cd28bd27d"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/gorilla/context"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
|
|
||||||
version = "v1.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/gorilla/mux"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "bcd8bc72b08df0f70df986b97f95590779502d31"
|
|
||||||
version = "v1.4.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/gorilla/pat"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "cf955c3d1f2c27ee96f93e9738085c762ff5f49d"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/kardianos/osext"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "ae77be60afb1dcacde03767a8c37337fad28ac14"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pkg/errors"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
|
||||||
version = "v0.8.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/urfave/cli"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "0bdeddeeb0f650497d603c4ad7b20cfe685682f6"
|
|
||||||
version = "v1.19.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/urfave/negroni"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "fde5e16d32adc7ad637e9cd9ad21d4ebc6192535"
|
|
||||||
version = "v0.2.0"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "78a02c21c6ce8facc4d7831f21f7df96cc47a8fa4874713c14611e87cd8d7913"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
@ -1,42 +0,0 @@
|
|||||||
|
|
||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/GeertJohan/go.rice"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/gorilla/pat"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/pkg/errors"
|
|
||||||
version = "0.8.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/urfave/cli"
|
|
||||||
version = "1.19.1"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/urfave/negroni"
|
|
||||||
version = "0.2.0"
|
|
@ -0,0 +1,15 @@
|
|||||||
|
module git.buddy.wtf/buddy/what-is-my-ip
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da
|
||||||
|
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb // indirect
|
||||||
|
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f // indirect
|
||||||
|
github.com/gorilla/mux v1.4.0 // indirect
|
||||||
|
github.com/gorilla/pat v0.0.0-20160413041632-cf955c3d1f2c
|
||||||
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
|
||||||
|
github.com/pkg/errors v0.8.0
|
||||||
|
github.com/urfave/cli v1.19.1
|
||||||
|
github.com/urfave/negroni v0.2.0
|
||||||
|
)
|
@ -0,0 +1,18 @@
|
|||||||
|
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da h1:UVU3a9pRUyLdnBtn60WjRl0s4SEyJc2ChCY56OAR6wI=
|
||||||
|
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da/go.mod h1:DgrzXonpdQbfN3uYaGz1EG4Sbhyum/MMIn6Cphlh2bw=
|
||||||
|
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb h1:tUf55Po0vzOendQ7NWytcdK0VuzQmfAgvGBUOQvN0WA=
|
||||||
|
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb/go.mod h1:U0vRfAucUOohvdCxt5MWLF+TePIL0xbCkbKIiV8TQCE=
|
||||||
|
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f h1:9oNbS1z4rVpbnkHBdPZU4jo9bSmrLpII768arSyMFgk=
|
||||||
|
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/mux v1.4.0 h1:N6R8isjoRv7IcVVlf0cTBbo0UDc9V6ZXWEm0HQoQmLo=
|
||||||
|
github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gorilla/pat v0.0.0-20160413041632-cf955c3d1f2c h1:/dq3+XpxRH+cjiua5GyJKE80RXZfZbmT8ejIcNsb93Q=
|
||||||
|
github.com/gorilla/pat v0.0.0-20160413041632-cf955c3d1f2c/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
|
||||||
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||||
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/urfave/cli v1.19.1 h1:0mKm4ZoB74PxYmZVua162y1dGt1qc10MyymYRBf3lb8=
|
||||||
|
github.com/urfave/cli v1.19.1/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
|
github.com/urfave/negroni v0.2.0 h1:cadBY8/+9L/dTagBqV7N0l/SJiB4Wg+os5QdmaFY5Wg=
|
||||||
|
github.com/urfave/negroni v0.2.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
@ -1,8 +0,0 @@
|
|||||||
/example/example
|
|
||||||
/example/example.exe
|
|
||||||
/rice/rice
|
|
||||||
/rice/rice.exe
|
|
||||||
|
|
||||||
*.rice-box.go
|
|
||||||
*.rice-box.syso
|
|
||||||
.wercker
|
|
@ -1,19 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- master
|
|
||||||
- 1.x.x
|
|
||||||
- 1.8.x
|
|
||||||
- 1.7.x
|
|
||||||
- 1.6.x
|
|
||||||
- 1.5.x
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go get -t ./...
|
|
||||||
- env
|
|
||||||
- if [ "${TRAVIS_GO_VERSION%.*}" != "1.5" ]; then go get github.com/golang/lint/golint; fi
|
|
||||||
script:
|
|
||||||
- go build -x ./...
|
|
||||||
- go test -cover ./...
|
|
||||||
- go vet ./...
|
|
||||||
- if [ "${TRAVIS_GO_VERSION%.*}" != "1.5" ]; then golint .; fi
|
|
@ -1,4 +0,0 @@
|
|||||||
Geert-Johan Riemer <geertjohan@geertjohan.net>
|
|
||||||
Paul Maddox <paul.maddox@gmail.com>
|
|
||||||
Vincent Petithory <vincent.petithory@gmail.com>
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
|||||||
Copyright (c) 2013, Geert-Johan Riemer
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,151 +0,0 @@
|
|||||||
## go.rice
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/GeertJohan/go.rice.png)](https://travis-ci.org/GeertJohan/go.rice)
|
|
||||||
[![Godoc](https://img.shields.io/badge/godoc-go.rice-blue.svg?style=flat-square)](https://godoc.org/github.com/GeertJohan/go.rice)
|
|
||||||
|
|
||||||
go.rice is a [Go](http://golang.org) package that makes working with resources such as html,js,css,images and templates very easy. During development `go.rice` will load required files directly from disk. Upon deployment it is easy to add all resource files to a executable using the `rice` tool, without changing the source code for your package. go.rice provides several methods to add resources to a binary.
|
|
||||||
|
|
||||||
### What does it do?
|
|
||||||
The first thing go.rice does is finding the correct absolute path for your resource files. Say you are executing go binary in your home directory, but your `html-files` are located in `$GOPATH/src/yourApplication/html-files`. `go.rice` will lookup the correct path for that directory (relative to the location of yourApplication). The only thing you have to do is include the resources using `rice.FindBox("html-files")`.
|
|
||||||
|
|
||||||
This only works when the source is available to the machine executing the binary. Which is always the case when the binary was installed with `go get` or `go install`. It might occur that you wish to simply provide a binary, without source. The `rice` tool analyses source code and finds call's to `rice.FindBox(..)` and adds the required directories to the executable binary. There are several methods to add these resources. You can 'embed' by generating go source code, or append the resource to the executable as zip file. In both cases `go.rice` will detect the embedded or appended resources and load those, instead of looking up files from disk.
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
Use `go get` to install the package the `rice` tool.
|
|
||||||
```
|
|
||||||
go get github.com/GeertJohan/go.rice
|
|
||||||
go get github.com/GeertJohan/go.rice/rice
|
|
||||||
```
|
|
||||||
|
|
||||||
### Package usage
|
|
||||||
|
|
||||||
Import the package: `import "github.com/GeertJohan/go.rice"`
|
|
||||||
|
|
||||||
**Serving a static content folder over HTTP with a rice Box**
|
|
||||||
```go
|
|
||||||
http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox()))
|
|
||||||
http.ListenAndServe(":8080", nil)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Service a static content folder over HTTP at a non-root location**
|
|
||||||
```go
|
|
||||||
box := rice.MustFindBox("cssfiles")
|
|
||||||
cssFileServer := http.StripPrefix("/css/", http.FileServer(box.HTTPBox()))
|
|
||||||
http.Handle("/css/", cssFileServer)
|
|
||||||
http.ListenAndServe(":8080", nil)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note the *trailing slash* in `/css/` in both the call to
|
|
||||||
`http.StripPrefix` and `http.Handle`.
|
|
||||||
|
|
||||||
**Loading a template**
|
|
||||||
```go
|
|
||||||
// find a rice.Box
|
|
||||||
templateBox, err := rice.FindBox("example-templates")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// get file contents as string
|
|
||||||
templateString, err := templateBox.String("message.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// parse and execute the template
|
|
||||||
tmplMessage, err := template.New("message").Parse(templateString)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
tmplMessage.Execute(os.Stdout, map[string]string{"Message": "Hello, world!"})
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Never call `FindBox()` or `MustFindBox()` from an `init()` function, as the boxes might have not been loaded at that time.
|
|
||||||
|
|
||||||
### Tool usage
|
|
||||||
The `rice` tool lets you add the resources to a binary executable so the files are not loaded from the filesystem anymore. This creates a 'standalone' executable. There are several ways to add the resources to a binary, each has pro's and con's but all will work without requiring changes to the way you load the resources.
|
|
||||||
|
|
||||||
#### embed-go
|
|
||||||
**Embed resources by generating Go source code**
|
|
||||||
|
|
||||||
This method must be executed before building. It generates a single Go source file called *rice-box.go* for each package, that is compiled by the go compiler into the binary.
|
|
||||||
|
|
||||||
The downside with this option is that the generated go source files can become very large, which will slow down compilation and require lots of memory to compile.
|
|
||||||
|
|
||||||
Execute the following commands:
|
|
||||||
```
|
|
||||||
rice embed-go
|
|
||||||
go build
|
|
||||||
```
|
|
||||||
|
|
||||||
*A Note on Symbolic Links*: `embed-go` uses the `os.Walk` function
|
|
||||||
from the standard library. The `os.Walk` function does **not** follow
|
|
||||||
symbolic links. So, when creating a box, be aware that any symbolic
|
|
||||||
links inside your box's directory will not be followed. **However**,
|
|
||||||
if the box itself is a symbolic link, its actual location will be
|
|
||||||
resolved first and then walked. In summary, if your box location is a
|
|
||||||
symbolic link, it will be followed but none of the symbolic links in
|
|
||||||
the box will be followed.
|
|
||||||
|
|
||||||
#### embed-syso
|
|
||||||
**Embed resources by generating a coff .syso file and some .go source code**
|
|
||||||
|
|
||||||
** This method is experimental and should not be used for production systems just yet **
|
|
||||||
|
|
||||||
This method must be executed before building. It generates a COFF .syso file and Go source file that are compiled by the go compiler into the binary.
|
|
||||||
|
|
||||||
Execute the following commands:
|
|
||||||
```
|
|
||||||
rice embed-syso
|
|
||||||
go build
|
|
||||||
```
|
|
||||||
|
|
||||||
#### append
|
|
||||||
**Append resources to executable as zip file**
|
|
||||||
|
|
||||||
This method changes an already built executable. It appends the resources as zip file to the binary. It makes compilation a lot faster and can be used with large resource files.
|
|
||||||
|
|
||||||
Downsides for appending are that it requires `zip` to be installed and does not provide a working Seek method.
|
|
||||||
|
|
||||||
Run the following commands to create a standalone executable.
|
|
||||||
```
|
|
||||||
go build -o example
|
|
||||||
rice append --exec example
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note: requires zip command to be installed**
|
|
||||||
|
|
||||||
On windows, install zip from http://gnuwin32.sourceforge.net/packages/zip.htm or cygwin/msys toolsets.
|
|
||||||
|
|
||||||
#### Help information
|
|
||||||
Run `rice -h` for information about all options.
|
|
||||||
|
|
||||||
You can run the -h option for each sub-command, e.g. `rice append -h`.
|
|
||||||
|
|
||||||
### Order of precedence
|
|
||||||
When opening a new box, the rice package tries to locate the resources in the following order:
|
|
||||||
|
|
||||||
- embedded in generated go source
|
|
||||||
- appended as zip
|
|
||||||
- 'live' from filesystem
|
|
||||||
|
|
||||||
|
|
||||||
### License
|
|
||||||
This project is licensed under a Simplified BSD license. Please read the [LICENSE file][license].
|
|
||||||
|
|
||||||
### TODO & Development
|
|
||||||
This package is not completed yet. Though it already provides working embedding, some important featuers are still missing.
|
|
||||||
- implement Readdir() correctly on virtualDir
|
|
||||||
- in-code TODO's
|
|
||||||
- find boxes in imported packages
|
|
||||||
|
|
||||||
Less important stuff:
|
|
||||||
- idea, os/arch dependent embeds. rice checks if embedding file has _os_arch or build flags. If box is not requested by file without buildflags, then the buildflags are applied to the embed file.
|
|
||||||
|
|
||||||
### Package documentation
|
|
||||||
|
|
||||||
You will find package documentation at [godoc.org/github.com/GeertJohan/go.rice][godoc].
|
|
||||||
|
|
||||||
|
|
||||||
[license]: https://github.com/GeertJohan/go.rice/blob/master/LICENSE
|
|
||||||
[godoc]: http://godoc.org/github.com/GeertJohan/go.rice
|
|
@ -1,138 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/daaku/go.zipexe"
|
|
||||||
"github.com/kardianos/osext"
|
|
||||||
)
|
|
||||||
|
|
||||||
// appendedBox defines an appended box
|
|
||||||
type appendedBox struct {
|
|
||||||
Name string // box name
|
|
||||||
Files map[string]*appendedFile // appended files (*zip.File) by full path
|
|
||||||
}
|
|
||||||
|
|
||||||
type appendedFile struct {
|
|
||||||
zipFile *zip.File
|
|
||||||
dir bool
|
|
||||||
dirInfo *appendedDirInfo
|
|
||||||
children []*appendedFile
|
|
||||||
content []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendedBoxes is a public register of appendes boxes
|
|
||||||
var appendedBoxes = make(map[string]*appendedBox)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// find if exec is appended
|
|
||||||
thisFile, err := osext.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return // not appended or cant find self executable
|
|
||||||
}
|
|
||||||
closer, rd, err := zipexe.OpenCloser(thisFile)
|
|
||||||
if err != nil {
|
|
||||||
return // not appended
|
|
||||||
}
|
|
||||||
defer closer.Close()
|
|
||||||
|
|
||||||
for _, f := range rd.File {
|
|
||||||
// get box and file name from f.Name
|
|
||||||
fileParts := strings.SplitN(strings.TrimLeft(filepath.ToSlash(f.Name), "/"), "/", 2)
|
|
||||||
boxName := fileParts[0]
|
|
||||||
var fileName string
|
|
||||||
if len(fileParts) > 1 {
|
|
||||||
fileName = fileParts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// find box or create new one if doesn't exist
|
|
||||||
box := appendedBoxes[boxName]
|
|
||||||
if box == nil {
|
|
||||||
box = &appendedBox{
|
|
||||||
Name: boxName,
|
|
||||||
Files: make(map[string]*appendedFile),
|
|
||||||
}
|
|
||||||
appendedBoxes[boxName] = box
|
|
||||||
}
|
|
||||||
|
|
||||||
// create and add file to box
|
|
||||||
af := &appendedFile{
|
|
||||||
zipFile: f,
|
|
||||||
}
|
|
||||||
if f.Comment == "dir" {
|
|
||||||
af.dir = true
|
|
||||||
af.dirInfo = &appendedDirInfo{
|
|
||||||
name: filepath.Base(af.zipFile.Name),
|
|
||||||
//++ TODO: use zip modtime when that is set correctly: af.zipFile.ModTime()
|
|
||||||
time: time.Now(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// this is a file, we need it's contents so we can create a bytes.Reader when the file is opened
|
|
||||||
// make a new byteslice
|
|
||||||
af.content = make([]byte, af.zipFile.FileInfo().Size())
|
|
||||||
// ignore reading empty files from zip (empty file still is a valid file to be read though!)
|
|
||||||
if len(af.content) > 0 {
|
|
||||||
// open io.ReadCloser
|
|
||||||
rc, err := af.zipFile.Open()
|
|
||||||
if err != nil {
|
|
||||||
af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
|
|
||||||
// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
|
|
||||||
log.Printf("error opening appended file %s: %v", af.zipFile.Name, err)
|
|
||||||
} else {
|
|
||||||
_, err = rc.Read(af.content)
|
|
||||||
rc.Close()
|
|
||||||
if err != nil {
|
|
||||||
af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
|
|
||||||
// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
|
|
||||||
log.Printf("error reading data for appended file %s: %v", af.zipFile.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add appendedFile to box file list
|
|
||||||
box.Files[fileName] = af
|
|
||||||
|
|
||||||
// add to parent dir (if any)
|
|
||||||
dirName := filepath.Dir(fileName)
|
|
||||||
if dirName == "." {
|
|
||||||
dirName = ""
|
|
||||||
}
|
|
||||||
if fileName != "" { // don't make box root dir a child of itself
|
|
||||||
if dir := box.Files[dirName]; dir != nil {
|
|
||||||
dir.children = append(dir.children, af)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// implements os.FileInfo.
|
|
||||||
// used for Readdir()
|
|
||||||
type appendedDirInfo struct {
|
|
||||||
name string
|
|
||||||
time time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (adi *appendedDirInfo) Name() string {
|
|
||||||
return adi.name
|
|
||||||
}
|
|
||||||
func (adi *appendedDirInfo) Size() int64 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
func (adi *appendedDirInfo) Mode() os.FileMode {
|
|
||||||
return os.ModeDir
|
|
||||||
}
|
|
||||||
func (adi *appendedDirInfo) ModTime() time.Time {
|
|
||||||
return adi.time
|
|
||||||
}
|
|
||||||
func (adi *appendedDirInfo) IsDir() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
func (adi *appendedDirInfo) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,337 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Box abstracts a directory for resources/files.
|
|
||||||
// It can either load files from disk, or from embedded code (when `rice --embed` was ran).
|
|
||||||
type Box struct {
|
|
||||||
name string
|
|
||||||
absolutePath string
|
|
||||||
embed *embedded.EmbeddedBox
|
|
||||||
appendd *appendedBox
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultLocateOrder = []LocateMethod{LocateEmbedded, LocateAppended, LocateFS}
|
|
||||||
|
|
||||||
func findBox(name string, order []LocateMethod) (*Box, error) {
|
|
||||||
b := &Box{name: name}
|
|
||||||
|
|
||||||
// no support for absolute paths since gopath can be different on different machines.
|
|
||||||
// therefore, required box must be located relative to package requiring it.
|
|
||||||
if filepath.IsAbs(name) {
|
|
||||||
return nil, errors.New("given name/path is absolute")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for _, method := range order {
|
|
||||||
switch method {
|
|
||||||
case LocateEmbedded:
|
|
||||||
if embed := embedded.EmbeddedBoxes[name]; embed != nil {
|
|
||||||
b.embed = embed
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case LocateAppended:
|
|
||||||
appendedBoxName := strings.Replace(name, `/`, `-`, -1)
|
|
||||||
if appendd := appendedBoxes[appendedBoxName]; appendd != nil {
|
|
||||||
b.appendd = appendd
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case LocateFS:
|
|
||||||
// resolve absolute directory path
|
|
||||||
err := b.resolveAbsolutePathFromCaller()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// check if absolutePath exists on filesystem
|
|
||||||
info, err := os.Stat(b.absolutePath)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// check if absolutePath is actually a directory
|
|
||||||
if !info.IsDir() {
|
|
||||||
err = errors.New("given name/path is not a directory")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
case LocateWorkingDirectory:
|
|
||||||
// resolve absolute directory path
|
|
||||||
err := b.resolveAbsolutePathFromWorkingDirectory()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// check if absolutePath exists on filesystem
|
|
||||||
info, err := os.Stat(b.absolutePath)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// check if absolutePath is actually a directory
|
|
||||||
if !info.IsDir() {
|
|
||||||
err = errors.New("given name/path is not a directory")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
err = fmt.Errorf("could not locate box %q", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindBox returns a Box instance for given name.
|
|
||||||
// When the given name is a relative path, it's base path will be the calling pkg/cmd's source root.
|
|
||||||
// When the given name is absolute, it's absolute. derp.
|
|
||||||
// Make sure the path doesn't contain any sensitive information as it might be placed into generated go source (embedded).
|
|
||||||
func FindBox(name string) (*Box, error) {
|
|
||||||
return findBox(name, defaultLocateOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFindBox returns a Box instance for given name, like FindBox does.
|
|
||||||
// It does not return an error, instead it panics when an error occurs.
|
|
||||||
func MustFindBox(name string) *Box {
|
|
||||||
box, err := findBox(name, defaultLocateOrder)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return box
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is injected as a mutable function literal so that we can mock it out in
|
|
||||||
// tests and return a fixed test file.
|
|
||||||
var resolveAbsolutePathFromCaller = func(name string, nStackFrames int) (string, error) {
|
|
||||||
_, callingGoFile, _, ok := runtime.Caller(nStackFrames)
|
|
||||||
if !ok {
|
|
||||||
return "", errors.New("couldn't find caller on stack")
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve to proper path
|
|
||||||
pkgDir := filepath.Dir(callingGoFile)
|
|
||||||
// fix for go cover
|
|
||||||
const coverPath = "_test/_obj_test"
|
|
||||||
if !filepath.IsAbs(pkgDir) {
|
|
||||||
if i := strings.Index(pkgDir, coverPath); i >= 0 {
|
|
||||||
pkgDir = pkgDir[:i] + pkgDir[i+len(coverPath):] // remove coverPath
|
|
||||||
pkgDir = filepath.Join(os.Getenv("GOPATH"), "src", pkgDir) // make absolute
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filepath.Join(pkgDir, name), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Box) resolveAbsolutePathFromCaller() error {
|
|
||||||
path, err := resolveAbsolutePathFromCaller(b.name, 4)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.absolutePath = path
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Box) resolveAbsolutePathFromWorkingDirectory() error {
|
|
||||||
path, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.absolutePath = filepath.Join(path, b.name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmbedded indicates wether this box was embedded into the application
|
|
||||||
func (b *Box) IsEmbedded() bool {
|
|
||||||
return b.embed != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAppended indicates wether this box was appended to the application
|
|
||||||
func (b *Box) IsAppended() bool {
|
|
||||||
return b.appendd != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time returns how actual the box is.
|
|
||||||
// When the box is embedded, it's value is saved in the embedding code.
|
|
||||||
// When the box is live, this methods returns time.Now()
|
|
||||||
func (b *Box) Time() time.Time {
|
|
||||||
if b.IsEmbedded() {
|
|
||||||
return b.embed.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
//++ TODO: return time for appended box
|
|
||||||
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open opens a File from the box
|
|
||||||
// If there is an error, it will be of type *os.PathError.
|
|
||||||
func (b *Box) Open(name string) (*File, error) {
|
|
||||||
if Debug {
|
|
||||||
fmt.Printf("Open(%s)\n", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.IsEmbedded() {
|
|
||||||
if Debug {
|
|
||||||
fmt.Println("Box is embedded")
|
|
||||||
}
|
|
||||||
|
|
||||||
// trim prefix (paths are relative to box)
|
|
||||||
name = strings.TrimLeft(name, "/")
|
|
||||||
if Debug {
|
|
||||||
fmt.Printf("Trying %s\n", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// search for file
|
|
||||||
ef := b.embed.Files[name]
|
|
||||||
if ef == nil {
|
|
||||||
if Debug {
|
|
||||||
fmt.Println("Didn't find file in embed")
|
|
||||||
}
|
|
||||||
// file not found, try dir
|
|
||||||
ed := b.embed.Dirs[name]
|
|
||||||
if ed == nil {
|
|
||||||
if Debug {
|
|
||||||
fmt.Println("Didn't find dir in embed")
|
|
||||||
}
|
|
||||||
// dir not found, error out
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "open",
|
|
||||||
Path: name,
|
|
||||||
Err: os.ErrNotExist,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if Debug {
|
|
||||||
fmt.Println("Found dir. Returning virtual dir")
|
|
||||||
}
|
|
||||||
vd := newVirtualDir(ed)
|
|
||||||
return &File{virtualD: vd}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// box is embedded
|
|
||||||
if Debug {
|
|
||||||
fmt.Println("Found file. Returning virtual file")
|
|
||||||
}
|
|
||||||
vf := newVirtualFile(ef)
|
|
||||||
return &File{virtualF: vf}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.IsAppended() {
|
|
||||||
// trim prefix (paths are relative to box)
|
|
||||||
name = strings.TrimLeft(name, "/")
|
|
||||||
|
|
||||||
// search for file
|
|
||||||
appendedFile := b.appendd.Files[name]
|
|
||||||
if appendedFile == nil {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "open",
|
|
||||||
Path: name,
|
|
||||||
Err: os.ErrNotExist,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new file
|
|
||||||
f := &File{
|
|
||||||
appendedF: appendedFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this file is a directory, we want to be able to read and seek
|
|
||||||
if !appendedFile.dir {
|
|
||||||
// looks like malformed data in zip, error now
|
|
||||||
if appendedFile.content == nil {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "open",
|
|
||||||
Path: "name",
|
|
||||||
Err: errors.New("error reading data from zip file"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// create new bytes.Reader
|
|
||||||
f.appendedFileReader = bytes.NewReader(appendedFile.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// all done
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform os open
|
|
||||||
if Debug {
|
|
||||||
fmt.Printf("Using os.Open(%s)", filepath.Join(b.absolutePath, name))
|
|
||||||
}
|
|
||||||
file, err := os.Open(filepath.Join(b.absolutePath, name))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &File{realF: file}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the content of the file with given name as []byte.
|
|
||||||
func (b *Box) Bytes(name string) ([]byte, error) {
|
|
||||||
file, err := b.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
content, err := ioutil.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustBytes returns the content of the file with given name as []byte.
|
|
||||||
// panic's on error.
|
|
||||||
func (b *Box) MustBytes(name string) []byte {
|
|
||||||
bts, err := b.Bytes(name)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return bts
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the content of the file with given name as string.
|
|
||||||
func (b *Box) String(name string) (string, error) {
|
|
||||||
// check if box is embedded, optimized fast path
|
|
||||||
if b.IsEmbedded() {
|
|
||||||
// find file in embed
|
|
||||||
ef := b.embed.Files[name]
|
|
||||||
if ef == nil {
|
|
||||||
return "", os.ErrNotExist
|
|
||||||
}
|
|
||||||
// return as string
|
|
||||||
return ef.Content, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
bts, err := b.Bytes(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(bts), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustString returns the content of the file with given name as string.
|
|
||||||
// panic's on error.
|
|
||||||
func (b *Box) MustString(name string) string {
|
|
||||||
str, err := b.String(name)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the box
|
|
||||||
func (b *Box) Name() string {
|
|
||||||
return b.name
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
// LocateMethod defines how a box is located.
|
|
||||||
type LocateMethod int
|
|
||||||
|
|
||||||
const (
|
|
||||||
LocateFS = LocateMethod(iota) // Locate on the filesystem according to package path.
|
|
||||||
LocateAppended // Locate boxes appended to the executable.
|
|
||||||
LocateEmbedded // Locate embedded boxes.
|
|
||||||
LocateWorkingDirectory // Locate on the binary working directory
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config allows customizing the box lookup behavior.
|
|
||||||
type Config struct {
|
|
||||||
// LocateOrder defines the priority order that boxes are searched for. By
|
|
||||||
// default, the package global FindBox searches for embedded boxes first,
|
|
||||||
// then appended boxes, and then finally boxes on the filesystem. That
|
|
||||||
// search order may be customized by provided the ordered list here. Leaving
|
|
||||||
// out a particular method will omit that from the search space. For
|
|
||||||
// example, []LocateMethod{LocateEmbedded, LocateAppended} will never search
|
|
||||||
// the filesystem for boxes.
|
|
||||||
LocateOrder []LocateMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindBox searches for boxes using the LocateOrder of the config.
|
|
||||||
func (c *Config) FindBox(boxName string) (*Box, error) {
|
|
||||||
return findBox(boxName, c.LocateOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFindBox searches for boxes using the LocateOrder of the config, like
|
|
||||||
// FindBox does. It does not return an error, instead it panics when an error
|
|
||||||
// occurs.
|
|
||||||
func (c *Config) MustFindBox(boxName string) *Box {
|
|
||||||
box, err := findBox(boxName, c.LocateOrder)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return box
|
|
||||||
}
|
|
@ -1,136 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
)
|
|
||||||
|
|
||||||
// For all test code in this package, define a set of test boxes.
|
|
||||||
var eb1 *embedded.EmbeddedBox
|
|
||||||
var ab1, ab2 *appendedBox
|
|
||||||
var fsb1, fsb2, fsb3 string // paths to filesystem boxes
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Box1 exists in all three locations.
|
|
||||||
eb1 = &embedded.EmbeddedBox{Name: "box1"}
|
|
||||||
embedded.RegisterEmbeddedBox(eb1.Name, eb1)
|
|
||||||
ab1 = &appendedBox{Name: "box1"}
|
|
||||||
appendedBoxes["box1"] = ab1
|
|
||||||
fsb1, err = ioutil.TempDir("", "box1")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box2 exists in only appended and FS.
|
|
||||||
ab2 = &appendedBox{Name: "box2"}
|
|
||||||
appendedBoxes["box2"] = ab2
|
|
||||||
fsb2, err = ioutil.TempDir("", "box2")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box3 exists only on disk.
|
|
||||||
fsb3, err = ioutil.TempDir("", "box3")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also, replace the default filesystem lookup path to directly support the
|
|
||||||
// on-disk temp directories.
|
|
||||||
resolveAbsolutePathFromCaller = func(name string, n int) (string, error) {
|
|
||||||
if name == "box1" {
|
|
||||||
return fsb1, nil
|
|
||||||
} else if name == "box2" {
|
|
||||||
return fsb2, nil
|
|
||||||
} else if name == "box3" {
|
|
||||||
return fsb3, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("Unknown box name: %q", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultLookupOrder(t *testing.T) {
|
|
||||||
// Box1 exists in all three, so the default order should find the embedded.
|
|
||||||
b, err := FindBox("box1")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box1, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b.embed != eb1 {
|
|
||||||
t.Fatalf("Expected to find embedded box, but got %#v", b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box2 exists in appended and FS, so find the appended.
|
|
||||||
b2, err := FindBox("box2")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box2, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b2.appendd != ab2 {
|
|
||||||
t.Fatalf("Expected to find appended box, but got %#v", b2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box3 exists only on FS, so find it there.
|
|
||||||
b3, err := FindBox("box3")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box3, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b3.absolutePath != fsb3 {
|
|
||||||
t.Fatalf("Expected to find FS box, but got %#v", b3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigLocateOrder(t *testing.T) {
|
|
||||||
cfg := Config{LocateOrder: []LocateMethod{LocateFS, LocateAppended, LocateEmbedded}}
|
|
||||||
fsb := []string{fsb1, fsb2, fsb3}
|
|
||||||
// All 3 boxes have a FS backend, so we should always find that.
|
|
||||||
for i, boxName := range []string{"box1", "box2", "box3"} {
|
|
||||||
b, err := cfg.FindBox(boxName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find %q, got error: %v", boxName, err)
|
|
||||||
}
|
|
||||||
if b.absolutePath != fsb[i] {
|
|
||||||
t.Fatalf("Expected to find FS box, but got %#v", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.LocateOrder = []LocateMethod{LocateAppended, LocateFS, LocateEmbedded}
|
|
||||||
{
|
|
||||||
b, err := cfg.FindBox("box3")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box3, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b.absolutePath != fsb3 {
|
|
||||||
t.Fatalf("Expected to find FS box, but got %#v", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
b, err := cfg.FindBox("box2")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box2, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b.appendd != ab2 {
|
|
||||||
t.Fatalf("Expected to find appended box, but got %#v", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// What if we don't list all the locate methods?
|
|
||||||
cfg.LocateOrder = []LocateMethod{LocateEmbedded}
|
|
||||||
{
|
|
||||||
b, err := cfg.FindBox("box2")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected not to find box2, but something was found: %#v", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
b, err := cfg.FindBox("box1")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box2, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b.embed != eb1 {
|
|
||||||
t.Fatalf("Expected to find embedded box, but got %#v", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
// Debug can be set to true to enable debugging.
|
|
||||||
var Debug = false
|
|
@ -1,90 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
)
|
|
||||||
|
|
||||||
// re-type to make exported methods invisible to user (godoc)
|
|
||||||
// they're not required for the user
|
|
||||||
// embeddedDirInfo implements os.FileInfo
|
|
||||||
type embeddedDirInfo embedded.EmbeddedDir
|
|
||||||
|
|
||||||
// Name returns the base name of the directory
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) Name() string {
|
|
||||||
return ed.Filename
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size always returns 0
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) Size() int64 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns the file mode bits
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) Mode() os.FileMode {
|
|
||||||
return os.FileMode(0555 | os.ModeDir) // dr-xr-xr-x
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModTime returns the modification time
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) ModTime() time.Time {
|
|
||||||
return ed.DirModTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir returns the abbreviation for Mode().IsDir() (always true)
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) IsDir() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sys returns the underlying data source (always nil)
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-type to make exported methods invisible to user (godoc)
|
|
||||||
// they're not required for the user
|
|
||||||
// embeddedFileInfo implements os.FileInfo
|
|
||||||
type embeddedFileInfo embedded.EmbeddedFile
|
|
||||||
|
|
||||||
// Name returns the base name of the file
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) Name() string {
|
|
||||||
return ef.Filename
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the length in bytes for regular files; system-dependent for others
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) Size() int64 {
|
|
||||||
return int64(len(ef.Content))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns the file mode bits
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) Mode() os.FileMode {
|
|
||||||
return os.FileMode(0555) // r-xr-xr-x
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModTime returns the modification time
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) ModTime() time.Time {
|
|
||||||
return ef.FileModTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir returns the abbreviation for Mode().IsDir() (always false)
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) IsDir() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sys returns the underlying data source (always nil)
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
// Package embedded defines embedded data types that are shared between the go.rice package and generated code.
|
|
||||||
package embedded
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
EmbedTypeGo = 0
|
|
||||||
EmbedTypeSyso = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// EmbeddedBox defines an embedded box
|
|
||||||
type EmbeddedBox struct {
|
|
||||||
Name string // box name
|
|
||||||
Time time.Time // embed time
|
|
||||||
EmbedType int // kind of embedding
|
|
||||||
Files map[string]*EmbeddedFile // ALL embedded files by full path
|
|
||||||
Dirs map[string]*EmbeddedDir // ALL embedded dirs by full path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link creates the ChildDirs and ChildFiles links in all EmbeddedDir's
|
|
||||||
func (e *EmbeddedBox) Link() {
|
|
||||||
for path, ed := range e.Dirs {
|
|
||||||
fmt.Println(path)
|
|
||||||
ed.ChildDirs = make([]*EmbeddedDir, 0)
|
|
||||||
ed.ChildFiles = make([]*EmbeddedFile, 0)
|
|
||||||
}
|
|
||||||
for path, ed := range e.Dirs {
|
|
||||||
parentDirpath, _ := filepath.Split(path)
|
|
||||||
if strings.HasSuffix(parentDirpath, "/") {
|
|
||||||
parentDirpath = parentDirpath[:len(parentDirpath)-1]
|
|
||||||
}
|
|
||||||
parentDir := e.Dirs[parentDirpath]
|
|
||||||
if parentDir == nil {
|
|
||||||
panic("parentDir `" + parentDirpath + "` is missing in embedded box")
|
|
||||||
}
|
|
||||||
parentDir.ChildDirs = append(parentDir.ChildDirs, ed)
|
|
||||||
}
|
|
||||||
for path, ef := range e.Files {
|
|
||||||
dirpath, _ := filepath.Split(path)
|
|
||||||
if strings.HasSuffix(dirpath, "/") {
|
|
||||||
dirpath = dirpath[:len(dirpath)-1]
|
|
||||||
}
|
|
||||||
dir := e.Dirs[dirpath]
|
|
||||||
if dir == nil {
|
|
||||||
panic("dir `" + dirpath + "` is missing in embedded box")
|
|
||||||
}
|
|
||||||
dir.ChildFiles = append(dir.ChildFiles, ef)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmbeddedDir is instanced in the code generated by the rice tool and contains all necicary information about an embedded file
|
|
||||||
type EmbeddedDir struct {
|
|
||||||
Filename string
|
|
||||||
DirModTime time.Time
|
|
||||||
ChildDirs []*EmbeddedDir // direct childs, as returned by virtualDir.Readdir()
|
|
||||||
ChildFiles []*EmbeddedFile // direct childs, as returned by virtualDir.Readdir()
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmbeddedFile is instanced in the code generated by the rice tool and contains all necicary information about an embedded file
|
|
||||||
type EmbeddedFile struct {
|
|
||||||
Filename string // filename
|
|
||||||
FileModTime time.Time
|
|
||||||
Content string
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmbeddedBoxes is a public register of embedded boxes
|
|
||||||
var EmbeddedBoxes = make(map[string]*EmbeddedBox)
|
|
||||||
|
|
||||||
// RegisterEmbeddedBox registers an EmbeddedBox
|
|
||||||
func RegisterEmbeddedBox(name string, box *EmbeddedBox) {
|
|
||||||
if _, exists := EmbeddedBoxes[name]; exists {
|
|
||||||
panic(fmt.Sprintf("EmbeddedBox with name `%s` exists already", name))
|
|
||||||
}
|
|
||||||
EmbeddedBoxes[name] = box
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
test content
|
|
||||||
break
|
|
Binary file not shown.
Before Width: | Height: | Size: 52 KiB |
@ -1 +0,0 @@
|
|||||||
I have a message for you: {{.Message}}
|
|
@ -1,69 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
conf := rice.Config{
|
|
||||||
LocateOrder: []rice.LocateMethod{rice.LocateEmbedded, rice.LocateAppended, rice.LocateFS},
|
|
||||||
}
|
|
||||||
box, err := conf.FindBox("example-files")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error opening rice.Box: %s\n", err)
|
|
||||||
}
|
|
||||||
// spew.Dump(box)
|
|
||||||
|
|
||||||
contentString, err := box.String("file.txt")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("could not read file contents as string: %s\n", err)
|
|
||||||
}
|
|
||||||
log.Printf("Read some file contents as string:\n%s\n", contentString)
|
|
||||||
|
|
||||||
contentBytes, err := box.Bytes("file.txt")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("could not read file contents as byteSlice: %s\n", err)
|
|
||||||
}
|
|
||||||
log.Printf("Read some file contents as byteSlice:\n%s\n", hex.Dump(contentBytes))
|
|
||||||
|
|
||||||
file, err := box.Open("file.txt")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("could not open file: %s\n", err)
|
|
||||||
}
|
|
||||||
spew.Dump(file)
|
|
||||||
|
|
||||||
// find/create a rice.Box
|
|
||||||
templateBox, err := rice.FindBox("example-templates")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// get file contents as string
|
|
||||||
templateString, err := templateBox.String("message.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// parse and execute the template
|
|
||||||
tmplMessage, err := template.New("message").Parse(templateString)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
tmplMessage.Execute(os.Stdout, map[string]string{"Message": "Hello, world!"})
|
|
||||||
|
|
||||||
http.Handle("/", http.FileServer(box.HTTPBox()))
|
|
||||||
go func() {
|
|
||||||
fmt.Println("Serving files on :8080, press ctrl-C to exit")
|
|
||||||
err := http.ListenAndServe(":8080", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error serving files: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {}
|
|
||||||
}
|
|
@ -1,144 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// File implements the io.Reader, io.Seeker, io.Closer and http.File interfaces
|
|
||||||
type File struct {
|
|
||||||
// File abstracts file methods so the user doesn't see the difference between rice.virtualFile, rice.virtualDir and os.File
|
|
||||||
// TODO: maybe use internal File interface and four implementations: *os.File, appendedFile, virtualFile, virtualDir
|
|
||||||
|
|
||||||
// real file on disk
|
|
||||||
realF *os.File
|
|
||||||
|
|
||||||
// when embedded (go)
|
|
||||||
virtualF *virtualFile
|
|
||||||
virtualD *virtualDir
|
|
||||||
|
|
||||||
// when appended (zip)
|
|
||||||
appendedF *appendedFile
|
|
||||||
appendedFileReader *bytes.Reader
|
|
||||||
// TODO: is appendedFileReader subject of races? Might need a lock here..
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close is like (*os.File).Close()
|
|
||||||
// Visit http://golang.org/pkg/os/#File.Close for more information
|
|
||||||
func (f *File) Close() error {
|
|
||||||
if f.appendedF != nil {
|
|
||||||
if f.appendedFileReader == nil {
|
|
||||||
return errors.New("already closed")
|
|
||||||
}
|
|
||||||
f.appendedFileReader = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if f.virtualF != nil {
|
|
||||||
return f.virtualF.close()
|
|
||||||
}
|
|
||||||
if f.virtualD != nil {
|
|
||||||
return f.virtualD.close()
|
|
||||||
}
|
|
||||||
return f.realF.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat is like (*os.File).Stat()
|
|
||||||
// Visit http://golang.org/pkg/os/#File.Stat for more information
|
|
||||||
func (f *File) Stat() (os.FileInfo, error) {
|
|
||||||
if f.appendedF != nil {
|
|
||||||
if f.appendedF.dir {
|
|
||||||
return f.appendedF.dirInfo, nil
|
|
||||||
}
|
|
||||||
if f.appendedFileReader == nil {
|
|
||||||
return nil, errors.New("file is closed")
|
|
||||||
}
|
|
||||||
return f.appendedF.zipFile.FileInfo(), nil
|
|
||||||
}
|
|
||||||
if f.virtualF != nil {
|
|
||||||
return f.virtualF.stat()
|
|
||||||
}
|
|
||||||
if f.virtualD != nil {
|
|
||||||
return f.virtualD.stat()
|
|
||||||
}
|
|
||||||
return f.realF.Stat()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readdir is like (*os.File).Readdir()
|
|
||||||
// Visit http://golang.org/pkg/os/#File.Readdir for more information
|
|
||||||
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
|
|
||||||
if f.appendedF != nil {
|
|
||||||
if f.appendedF.dir {
|
|
||||||
fi := make([]os.FileInfo, 0, len(f.appendedF.children))
|
|
||||||
for _, childAppendedFile := range f.appendedF.children {
|
|
||||||
if childAppendedFile.dir {
|
|
||||||
fi = append(fi, childAppendedFile.dirInfo)
|
|
||||||
} else {
|
|
||||||
fi = append(fi, childAppendedFile.zipFile.FileInfo())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fi, nil
|
|
||||||
}
|
|
||||||
//++ TODO: is os.ErrInvalid the correct error for Readdir on file?
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
if f.virtualF != nil {
|
|
||||||
return f.virtualF.readdir(count)
|
|
||||||
}
|
|
||||||
if f.virtualD != nil {
|
|
||||||
return f.virtualD.readdir(count)
|
|
||||||
}
|
|
||||||
return f.realF.Readdir(count)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read is like (*os.File).Read()
|
|
||||||
// Visit http://golang.org/pkg/os/#File.Read for more information
|
|
||||||
func (f *File) Read(bts []byte) (int, error) {
|
|
||||||
if f.appendedF != nil {
|
|
||||||
if f.appendedFileReader == nil {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "read",
|
|
||||||
Path: filepath.Base(f.appendedF.zipFile.Name),
|
|
||||||
Err: errors.New("file is closed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if f.appendedF.dir {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "read",
|
|
||||||
Path: filepath.Base(f.appendedF.zipFile.Name),
|
|
||||||
Err: errors.New("is a directory"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f.appendedFileReader.Read(bts)
|
|
||||||
}
|
|
||||||
if f.virtualF != nil {
|
|
||||||
return f.virtualF.read(bts)
|
|
||||||
}
|
|
||||||
if f.virtualD != nil {
|
|
||||||
return f.virtualD.read(bts)
|
|
||||||
}
|
|
||||||
return f.realF.Read(bts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek is like (*os.File).Seek()
|
|
||||||
// Visit http://golang.org/pkg/os/#File.Seek for more information
|
|
||||||
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
|
||||||
if f.appendedF != nil {
|
|
||||||
if f.appendedFileReader == nil {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "seek",
|
|
||||||
Path: filepath.Base(f.appendedF.zipFile.Name),
|
|
||||||
Err: errors.New("file is closed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f.appendedFileReader.Seek(offset, whence)
|
|
||||||
}
|
|
||||||
if f.virtualF != nil {
|
|
||||||
return f.virtualF.seek(offset, whence)
|
|
||||||
}
|
|
||||||
if f.virtualD != nil {
|
|
||||||
return f.virtualD.seek(offset, whence)
|
|
||||||
}
|
|
||||||
return f.realF.Seek(offset, whence)
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTTPBox implements http.FileSystem which allows the use of Box with a http.FileServer.
|
|
||||||
// e.g.: http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox()))
|
|
||||||
type HTTPBox struct {
|
|
||||||
*Box
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPBox creates a new HTTPBox from an existing Box
|
|
||||||
func (b *Box) HTTPBox() *HTTPBox {
|
|
||||||
return &HTTPBox{b}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open returns a File using the http.File interface
|
|
||||||
func (hb *HTTPBox) Open(name string) (http.File, error) {
|
|
||||||
return hb.Box.Open(name)
|
|
||||||
}
|
|
@ -1,157 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
zipexe "github.com/daaku/go.zipexe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func operationAppend(pkgs []*build.Package) {
|
|
||||||
// create tmp zipfile
|
|
||||||
tmpZipfileName := filepath.Join(os.TempDir(), fmt.Sprintf("ricebox-%d-%s.zip", time.Now().Unix(), randomString(10)))
|
|
||||||
verbosef("Will create tmp zipfile: %s\n", tmpZipfileName)
|
|
||||||
tmpZipfile, err := os.Create(tmpZipfileName)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating tmp zipfile: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
tmpZipfile.Close()
|
|
||||||
os.Remove(tmpZipfileName)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// find abs path for binary file
|
|
||||||
binfileName, err := filepath.Abs(flags.Append.Executable)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error finding absolute path for executable to append: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
verbosef("Will append to file: %s\n", binfileName)
|
|
||||||
|
|
||||||
// check that command doesn't already have zip appended
|
|
||||||
if rd, _ := zipexe.Open(binfileName); rd != nil {
|
|
||||||
fmt.Printf("Cannot append to already appended executable. Please remove %s and build a fresh one.\n", binfileName)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// open binfile
|
|
||||||
binfile, err := os.OpenFile(binfileName, os.O_WRONLY, os.ModeAppend)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: unable to open executable file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer binfile.Close()
|
|
||||||
|
|
||||||
binfileInfo, err := binfile.Stat()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: unable to stat executable file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create zip.Writer
|
|
||||||
zipWriter := zip.NewWriter(tmpZipfile)
|
|
||||||
|
|
||||||
// write the zip offset into the zip data
|
|
||||||
zipWriter.SetOffset(binfileInfo.Size())
|
|
||||||
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
// find boxes for this command
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
|
|
||||||
// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
|
|
||||||
if len(boxMap) == 0 {
|
|
||||||
fmt.Printf("no calls to rice.FindBox() or rice.MustFindBox() found in import path `%s`\n", pkg.ImportPath)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
verbosef("\n")
|
|
||||||
|
|
||||||
for boxname := range boxMap {
|
|
||||||
appendedBoxName := strings.Replace(boxname, `/`, `-`, -1)
|
|
||||||
|
|
||||||
// walk box path's and insert files
|
|
||||||
boxPath := filepath.Clean(filepath.Join(pkg.Dir, boxname))
|
|
||||||
filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if info == nil {
|
|
||||||
fmt.Printf("Error: box \"%s\" not found on disk\n", path)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
// create zipFilename
|
|
||||||
zipFileName := filepath.Join(appendedBoxName, strings.TrimPrefix(path, boxPath))
|
|
||||||
// write directories as empty file with comment "dir"
|
|
||||||
if info.IsDir() {
|
|
||||||
_, err := zipWriter.CreateHeader(&zip.FileHeader{
|
|
||||||
Name: zipFileName,
|
|
||||||
Comment: "dir",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating dir in tmp zip: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// create zipFileWriter
|
|
||||||
zipFileHeader, err := zip.FileInfoHeader(info)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating zip FileHeader: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
zipFileHeader.Name = zipFileName
|
|
||||||
zipFileWriter, err := zipWriter.CreateHeader(zipFileHeader)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating file in tmp zip: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
srcFile, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error opening file to append: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
_, err = io.Copy(zipFileWriter, srcFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error copying file contents to zip: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
srcFile.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = zipWriter.Close()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error closing tmp zipfile: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tmpZipfile.Sync()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error syncing tmp zipfile: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
_, err = tmpZipfile.Seek(0, 0)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error seeking tmp zipfile: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
_, err = binfile.Seek(0, 2)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error seeking bin file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(binfile, tmpZipfile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error appending zipfile to executable: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func operationClean(pkg *build.Package) {
|
|
||||||
filepath.Walk(pkg.Dir, func(filename string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error walking pkg dir to clean files: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
verbosef("checking file '%s'\n", filename)
|
|
||||||
if filepath.Base(filename) == "rice-box.go" ||
|
|
||||||
strings.HasSuffix(filename, ".rice-box.go") ||
|
|
||||||
strings.HasSuffix(filename, ".rice-box.syso") {
|
|
||||||
err := os.Remove(filename)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error removing file (%s): %s\n", filename, err)
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
verbosef("removed file '%s'\n", filename)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,161 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"go/format"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const boxFilename = "rice-box.go"
|
|
||||||
|
|
||||||
func writeBoxesGo(pkg *build.Package, out io.Writer) error {
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
|
|
||||||
// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
|
|
||||||
if len(boxMap) == 0 {
|
|
||||||
fmt.Println("no calls to rice.FindBox() found")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
verbosef("\n")
|
|
||||||
|
|
||||||
var boxes []*boxDataType
|
|
||||||
|
|
||||||
for boxname := range boxMap {
|
|
||||||
// find path and filename for this box
|
|
||||||
boxPath := filepath.Join(pkg.Dir, boxname)
|
|
||||||
|
|
||||||
// Check to see if the path for the box is a symbolic link. If so, simply
|
|
||||||
// box what the symbolic link points to. Note: the filepath.Walk function
|
|
||||||
// will NOT follow any nested symbolic links. This only handles the case
|
|
||||||
// where the root of the box is a symbolic link.
|
|
||||||
symPath, serr := os.Readlink(boxPath)
|
|
||||||
if serr == nil {
|
|
||||||
boxPath = symPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// verbose info
|
|
||||||
verbosef("embedding box '%s' to '%s'\n", boxname, boxFilename)
|
|
||||||
|
|
||||||
// read box metadata
|
|
||||||
boxInfo, ierr := os.Stat(boxPath)
|
|
||||||
if ierr != nil {
|
|
||||||
return fmt.Errorf("Error: unable to access box at %s\n", boxPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create box datastructure (used by template)
|
|
||||||
box := &boxDataType{
|
|
||||||
BoxName: boxname,
|
|
||||||
UnixNow: boxInfo.ModTime().Unix(),
|
|
||||||
Files: make([]*fileDataType, 0),
|
|
||||||
Dirs: make(map[string]*dirDataType),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !boxInfo.IsDir() {
|
|
||||||
return fmt.Errorf("Error: Box %s must point to a directory but points to %s instead\n",
|
|
||||||
boxname, boxPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill box datastructure with file data
|
|
||||||
err := filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error walking box: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := strings.TrimPrefix(path, boxPath)
|
|
||||||
filename = strings.Replace(filename, "\\", "/", -1)
|
|
||||||
filename = strings.TrimPrefix(filename, "/")
|
|
||||||
if info.IsDir() {
|
|
||||||
dirData := &dirDataType{
|
|
||||||
Identifier: "dir" + nextIdentifier(),
|
|
||||||
FileName: filename,
|
|
||||||
ModTime: info.ModTime().Unix(),
|
|
||||||
ChildFiles: make([]*fileDataType, 0),
|
|
||||||
ChildDirs: make([]*dirDataType, 0),
|
|
||||||
}
|
|
||||||
verbosef("\tincludes dir: '%s'\n", dirData.FileName)
|
|
||||||
box.Dirs[dirData.FileName] = dirData
|
|
||||||
|
|
||||||
// add tree entry (skip for root, it'll create a recursion)
|
|
||||||
if dirData.FileName != "" {
|
|
||||||
pathParts := strings.Split(dirData.FileName, "/")
|
|
||||||
parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
|
|
||||||
parentDir.ChildDirs = append(parentDir.ChildDirs, dirData)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fileData := &fileDataType{
|
|
||||||
Identifier: "file" + nextIdentifier(),
|
|
||||||
FileName: filename,
|
|
||||||
ModTime: info.ModTime().Unix(),
|
|
||||||
}
|
|
||||||
verbosef("\tincludes file: '%s'\n", fileData.FileName)
|
|
||||||
fileData.Content, err = ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error reading file content while walking box: %s\n", err)
|
|
||||||
}
|
|
||||||
box.Files = append(box.Files, fileData)
|
|
||||||
|
|
||||||
// add tree entry
|
|
||||||
pathParts := strings.Split(fileData.FileName, "/")
|
|
||||||
parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
|
|
||||||
if parentDir == nil {
|
|
||||||
return fmt.Errorf("Error: parent of %s is not within the box\n", path)
|
|
||||||
}
|
|
||||||
parentDir.ChildFiles = append(parentDir.ChildFiles, fileData)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
boxes = append(boxes, box)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
embedSourceUnformated := bytes.NewBuffer(make([]byte, 0))
|
|
||||||
|
|
||||||
// execute template to buffer
|
|
||||||
err := tmplEmbeddedBox.Execute(
|
|
||||||
embedSourceUnformated,
|
|
||||||
embedFileDataType{pkg.Name, boxes},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error writing embedded box to file (template execute): %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// format the source code
|
|
||||||
embedSource, err := format.Source(embedSourceUnformated.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error formatting embedSource: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write source to file
|
|
||||||
_, err = io.Copy(out, bytes.NewBuffer(embedSource))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error writing embedSource to file: %s\n", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func operationEmbedGo(pkg *build.Package) {
|
|
||||||
// create go file for box
|
|
||||||
boxFile, err := os.Create(filepath.Join(pkg.Dir, boxFilename))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error creating embedded box file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer boxFile.Close()
|
|
||||||
|
|
||||||
err = writeBoxesGo(pkg, boxFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error creating embedded box file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,680 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type registeredDir struct {
|
|
||||||
Filename string
|
|
||||||
ModTime int
|
|
||||||
ChildFiles []*registeredFile
|
|
||||||
ChildDirs []*registeredDir
|
|
||||||
}
|
|
||||||
|
|
||||||
type registeredFile struct {
|
|
||||||
Filename string
|
|
||||||
ModTime int
|
|
||||||
Content string
|
|
||||||
}
|
|
||||||
|
|
||||||
type registeredBox struct {
|
|
||||||
Name string
|
|
||||||
Time int
|
|
||||||
// key is path
|
|
||||||
Dirs map[string]*registeredDir
|
|
||||||
// key is path
|
|
||||||
Files map[string]*registeredFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// isSimpleSelector returns true if expr is pkgName.ident
|
|
||||||
func isSimpleSelector(pkgName, ident string, expr ast.Expr) bool {
|
|
||||||
if sel, ok := expr.(*ast.SelectorExpr); ok {
|
|
||||||
if pkgIdent, ok := sel.X.(*ast.Ident); ok && pkgIdent.Name == pkgName && sel.Sel != nil && sel.Sel.Name == ident {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIdent(ident string, expr ast.Expr) bool {
|
|
||||||
if expr, ok := expr.(*ast.Ident); ok && expr.Name == ident {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIdentName(expr ast.Expr) (string, bool) {
|
|
||||||
if expr, ok := expr.(*ast.Ident); ok {
|
|
||||||
return expr.Name, true
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKey(expr *ast.KeyValueExpr) string {
|
|
||||||
if ident, ok := expr.Key.(*ast.Ident); ok {
|
|
||||||
return ident.Name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseModTime parses a time.Unix call, and returns the unix time.
|
|
||||||
func parseModTime(expr ast.Expr) (int, error) {
|
|
||||||
if expr, ok := expr.(*ast.CallExpr); ok {
|
|
||||||
if !isSimpleSelector("time", "Unix", expr.Fun) {
|
|
||||||
return 0, fmt.Errorf("ModTime is not time.Unix: %#v", expr.Fun)
|
|
||||||
}
|
|
||||||
if len(expr.Args) == 0 {
|
|
||||||
return 0, fmt.Errorf("not enough args to time.Unix")
|
|
||||||
}
|
|
||||||
arg0 := expr.Args[0]
|
|
||||||
if lit, ok := arg0.(*ast.BasicLit); ok && lit.Kind == token.INT {
|
|
||||||
return strconv.Atoi(lit.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("not time.Unix: %#v", expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseString(expr ast.Expr) (string, error) {
|
|
||||||
if expr, ok := expr.(*ast.CallExpr); ok && isIdent("string", expr.Fun) && len(expr.Args) == 1 {
|
|
||||||
return parseString(expr.Args[0])
|
|
||||||
}
|
|
||||||
if lit, ok := expr.(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
|
||||||
return strconv.Unquote(lit.Value)
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("not string: %#v", expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDir parses an embedded.EmbeddedDir literal.
|
|
||||||
// It can be either a variable name or a composite literal.
|
|
||||||
// Returns nil if the literal is not embedded.EmbeddedDir.
|
|
||||||
func parseDir(expr ast.Expr, dirs map[string]*registeredDir, files map[string]*registeredFile) (*registeredDir, []error) {
|
|
||||||
|
|
||||||
if varName, ok := getIdentName(expr); ok {
|
|
||||||
dir, ok := dirs[varName]
|
|
||||||
if !ok {
|
|
||||||
return nil, []error{fmt.Errorf("unknown variable %v", varName)}
|
|
||||||
}
|
|
||||||
return dir, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if !ok {
|
|
||||||
return nil, []error{fmt.Errorf("dir is not a composite literal: %#v", expr)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors []error
|
|
||||||
if !isSimpleSelector("embedded", "EmbeddedDir", lit.Type) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
ret := ®isteredDir{}
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
if el, ok := el.(*ast.KeyValueExpr); ok {
|
|
||||||
key := getKey(el)
|
|
||||||
if key == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "DirModTime":
|
|
||||||
var err error
|
|
||||||
ret.ModTime, err = parseModTime(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("DirModTime %s", err))
|
|
||||||
}
|
|
||||||
case "Filename":
|
|
||||||
var err error
|
|
||||||
ret.Filename, err = parseString(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("Filename %s", err))
|
|
||||||
}
|
|
||||||
case "ChildDirs":
|
|
||||||
var errors2 []error
|
|
||||||
ret.ChildDirs, errors2 = parseDirsSlice(el.Value, dirs, files)
|
|
||||||
errors = append(errors, errors2...)
|
|
||||||
case "ChildFiles":
|
|
||||||
var errors2 []error
|
|
||||||
ret.ChildFiles, errors2 = parseFilesSlice(el.Value, files)
|
|
||||||
errors = append(errors, errors2...)
|
|
||||||
default:
|
|
||||||
errors = append(errors, fmt.Errorf("Unknown field: %v: %#v", key, el.Value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseFile parses an embedded.EmbeddedFile literal.
|
|
||||||
// It can be either a variable name or a composite literal.
|
|
||||||
// Returns nil if the literal is not embedded.EmbeddedFile.
|
|
||||||
func parseFile(expr ast.Expr, files map[string]*registeredFile) (*registeredFile, []error) {
|
|
||||||
if varName, ok := getIdentName(expr); ok {
|
|
||||||
file, ok := files[varName]
|
|
||||||
if !ok {
|
|
||||||
return nil, []error{fmt.Errorf("unknown variable %v", varName)}
|
|
||||||
}
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if !ok {
|
|
||||||
return nil, []error{fmt.Errorf("file is not a composite literal: %#v", expr)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors []error
|
|
||||||
if !isSimpleSelector("embedded", "EmbeddedFile", lit.Type) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
ret := ®isteredFile{}
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
if el, ok := el.(*ast.KeyValueExpr); ok {
|
|
||||||
key := getKey(el)
|
|
||||||
if key == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "FileModTime":
|
|
||||||
var err error
|
|
||||||
ret.ModTime, err = parseModTime(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("DirModTime %s", err))
|
|
||||||
}
|
|
||||||
case "Filename":
|
|
||||||
var err error
|
|
||||||
ret.Filename, err = parseString(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("Filename %s", err))
|
|
||||||
}
|
|
||||||
case "Content":
|
|
||||||
var err error
|
|
||||||
ret.Content, err = parseString(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("Content %s", err))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
errors = append(errors, fmt.Errorf("Unknown field: %v: %#v", key, el.Value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, errors
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRegistration(lit *ast.CompositeLit, dirs map[string]*registeredDir, files map[string]*registeredFile) (*registeredBox, []error) {
|
|
||||||
var errors []error
|
|
||||||
if !isSimpleSelector("embedded", "EmbeddedBox", lit.Type) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
ret := ®isteredBox{
|
|
||||||
Dirs: make(map[string]*registeredDir),
|
|
||||||
Files: make(map[string]*registeredFile),
|
|
||||||
}
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
if el, ok := el.(*ast.KeyValueExpr); ok {
|
|
||||||
key := getKey(el)
|
|
||||||
if key == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "Time":
|
|
||||||
var err error
|
|
||||||
ret.Time, err = parseModTime(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("Time %s", err))
|
|
||||||
}
|
|
||||||
case "Name":
|
|
||||||
var err error
|
|
||||||
ret.Name, err = parseString(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("Name %s", err))
|
|
||||||
}
|
|
||||||
case "Dirs":
|
|
||||||
var errors2 []error
|
|
||||||
ret.Dirs, errors2 = parseDirsMap(el.Value, dirs, files)
|
|
||||||
errors = append(errors, errors2...)
|
|
||||||
case "Files":
|
|
||||||
var errors2 []error
|
|
||||||
ret.Files, errors2 = parseFilesMap(el.Value, files)
|
|
||||||
errors = append(errors, errors2...)
|
|
||||||
default:
|
|
||||||
errors = append(errors, fmt.Errorf("Unknown field: %v: %#v", key, el.Value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, errors
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDirsSlice(expr ast.Expr, dirs map[string]*registeredDir, files map[string]*registeredFile) (childDirs []*registeredDir, errors []error) {
|
|
||||||
valid := false
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
if arrType, ok := lit.Type.(*ast.ArrayType); ok {
|
|
||||||
if star, ok := arrType.Elt.(*ast.StarExpr); ok {
|
|
||||||
if isSimpleSelector("embedded", "EmbeddedDir", star.X) {
|
|
||||||
valid = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return nil, []error{fmt.Errorf("not a []*embedded.EmbeddedDir: %#v", expr)}
|
|
||||||
}
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
child, childErrors := parseDir(el, dirs, files)
|
|
||||||
errors = append(errors, childErrors...)
|
|
||||||
childDirs = append(childDirs, child)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFilesSlice(expr ast.Expr, files map[string]*registeredFile) (childFiles []*registeredFile, errors []error) {
|
|
||||||
valid := false
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
if arrType, ok := lit.Type.(*ast.ArrayType); ok {
|
|
||||||
if star, ok := arrType.Elt.(*ast.StarExpr); ok {
|
|
||||||
if isSimpleSelector("embedded", "EmbeddedFile", star.X) {
|
|
||||||
valid = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return nil, []error{fmt.Errorf("not a []*embedded.EmbeddedFile: %#v", expr)}
|
|
||||||
}
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
child, childErrors := parseFile(el, files)
|
|
||||||
errors = append(errors, childErrors...)
|
|
||||||
childFiles = append(childFiles, child)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDirsMap(expr ast.Expr, dirs map[string]*registeredDir, files map[string]*registeredFile) (childDirs map[string]*registeredDir, errors []error) {
|
|
||||||
valid := false
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
if mapType, ok := lit.Type.(*ast.MapType); ok {
|
|
||||||
if star, ok := mapType.Value.(*ast.StarExpr); ok {
|
|
||||||
if isSimpleSelector("embedded", "EmbeddedDir", star.X) && isIdent("string", mapType.Key) {
|
|
||||||
valid = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return nil, []error{fmt.Errorf("not a map[string]*embedded.EmbeddedDir: %#v", expr)}
|
|
||||||
}
|
|
||||||
childDirs = make(map[string]*registeredDir)
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
kv, ok := el.(*ast.KeyValueExpr)
|
|
||||||
if !ok {
|
|
||||||
errors = append(errors, fmt.Errorf("not a KeyValueExpr: %#v", el))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key, err := parseString(kv.Key)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("key %s", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
child, childErrors := parseDir(kv.Value, dirs, files)
|
|
||||||
errors = append(errors, childErrors...)
|
|
||||||
childDirs[key] = child
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFilesMap(expr ast.Expr, files map[string]*registeredFile) (childFiles map[string]*registeredFile, errors []error) {
|
|
||||||
valid := false
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
if mapType, ok := lit.Type.(*ast.MapType); ok {
|
|
||||||
if star, ok := mapType.Value.(*ast.StarExpr); ok {
|
|
||||||
if isSimpleSelector("embedded", "EmbeddedFile", star.X) && isIdent("string", mapType.Key) {
|
|
||||||
valid = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return nil, []error{fmt.Errorf("not a map[string]*embedded.EmbeddedFile: %#v", expr)}
|
|
||||||
}
|
|
||||||
childFiles = make(map[string]*registeredFile)
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
kv, ok := el.(*ast.KeyValueExpr)
|
|
||||||
if !ok {
|
|
||||||
errors = append(errors, fmt.Errorf("not a KeyValueExpr: %#v", el))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key, err := parseString(kv.Key)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("key %s", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
child, childErrors := parseFile(kv.Value, files)
|
|
||||||
errors = append(errors, childErrors...)
|
|
||||||
childFiles[key] = child
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpoint returns the expression expr points to
|
|
||||||
// if expr is a & unary expression.
|
|
||||||
func unpoint(expr ast.Expr) ast.Expr {
|
|
||||||
if expr, ok := expr.(*ast.UnaryExpr); ok {
|
|
||||||
if expr.Op == token.AND {
|
|
||||||
return expr.X
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateBox(t *testing.T, box *registeredBox, files []sourceFile) {
|
|
||||||
dirsToBeChecked := make(map[string]struct{})
|
|
||||||
filesToBeChecked := make(map[string]string)
|
|
||||||
for _, file := range files {
|
|
||||||
if !strings.HasPrefix(file.Name, box.Name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pathParts := strings.Split(file.Name, "/")
|
|
||||||
dirs := pathParts[:len(pathParts)-1]
|
|
||||||
dirPath := ""
|
|
||||||
for _, dir := range dirs {
|
|
||||||
if dir != box.Name {
|
|
||||||
dirPath = path.Join(dirPath, dir)
|
|
||||||
}
|
|
||||||
dirsToBeChecked[dirPath] = struct{}{}
|
|
||||||
}
|
|
||||||
filesToBeChecked[path.Join(dirPath, pathParts[len(pathParts)-1])] = string(file.Contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(box.Files) != len(filesToBeChecked) {
|
|
||||||
t.Errorf("box %v has incorrect number of files; expected %v, got %v", box.Name, len(filesToBeChecked), len(box.Files))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(box.Dirs) != len(dirsToBeChecked) {
|
|
||||||
t.Errorf("box %v has incorrect number of dirs; expected %v, got %v", box.Name, len(dirsToBeChecked), len(box.Dirs))
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, content := range filesToBeChecked {
|
|
||||||
f, ok := box.Files[name]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("file %v not present in box %v", name, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if f.Filename != name {
|
|
||||||
t.Errorf("box %v: filename mismatch: key: %v; Filename: %v", box.Name, name, f.Filename)
|
|
||||||
}
|
|
||||||
if f.Content != content {
|
|
||||||
t.Errorf("box %v: file %v content does not match: got %v, expected %v", box.Name, name, f.Content, content)
|
|
||||||
}
|
|
||||||
dirPath, _ := path.Split(name)
|
|
||||||
dirPath = strings.TrimSuffix(dirPath, "/")
|
|
||||||
dir, ok := box.Dirs[dirPath]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("directory %v not present in box %v", dirPath, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
found := false
|
|
||||||
for _, file := range dir.ChildFiles {
|
|
||||||
if file == f {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
t.Errorf("file %v not found in directory %v in box %v", name, dirPath, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for name := range dirsToBeChecked {
|
|
||||||
d, ok := box.Dirs[name]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("directory %v not present in box %v", name, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if d.Filename != name {
|
|
||||||
t.Errorf("box %v: filename mismatch: key: %v; Filename: %v", box.Name, name, d.Filename)
|
|
||||||
}
|
|
||||||
if name != "" {
|
|
||||||
dirPath, _ := path.Split(name)
|
|
||||||
dirPath = strings.TrimSuffix(dirPath, "/")
|
|
||||||
dir, ok := box.Dirs[dirPath]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("directory %v not present in box %v", dirPath, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
found := false
|
|
||||||
for _, dir := range dir.ChildDirs {
|
|
||||||
if dir == d {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
t.Errorf("directory %v not found in directory %v in box %v", name, dirPath, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmbedGo(t *testing.T) {
|
|
||||||
sourceFiles := []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rice.MustFindBox("foo")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/test1.txt",
|
|
||||||
[]byte(`This is test 1`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/test2.txt",
|
|
||||||
[]byte(`This is test 2`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/bar/test1.txt",
|
|
||||||
[]byte(`This is test 1 in bar`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/bar/baz/test1.txt",
|
|
||||||
[]byte(`This is test 1 in bar/baz`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/bar/baz/backtick`.txt",
|
|
||||||
[]byte(`Backtick filename`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/bar/baz/\"quote\".txt",
|
|
||||||
[]byte(`double quoted filename`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/bar/baz/'quote'.txt",
|
|
||||||
[]byte(`single quoted filename`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/`/`/`.txt",
|
|
||||||
[]byte(`Backticks everywhere!`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/new\nline",
|
|
||||||
[]byte("File with newline in name. Yes, this is possible."),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", sourceFiles)
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
|
|
||||||
err = writeBoxesGo(pkg, &buffer)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("Generated file: \n%s", buffer.String())
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, "rice-box.go"), &buffer, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var initFunc *ast.FuncDecl
|
|
||||||
for _, decl := range f.Decls {
|
|
||||||
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Name != nil && decl.Name.Name == "init" {
|
|
||||||
initFunc = decl
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if initFunc == nil {
|
|
||||||
t.Fatal("init function not found in generated file")
|
|
||||||
}
|
|
||||||
if initFunc.Body == nil {
|
|
||||||
t.Fatal("init function has no body in generated file")
|
|
||||||
}
|
|
||||||
var registrations []*ast.CallExpr
|
|
||||||
directories := make(map[string]*registeredDir)
|
|
||||||
files := make(map[string]*registeredFile)
|
|
||||||
_ = directories
|
|
||||||
_ = files
|
|
||||||
for _, stmt := range initFunc.Body.List {
|
|
||||||
if stmt, ok := stmt.(*ast.ExprStmt); ok {
|
|
||||||
if call, ok := stmt.X.(*ast.CallExpr); ok {
|
|
||||||
registrations = append(registrations, call)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if stmt, ok := stmt.(*ast.AssignStmt); ok {
|
|
||||||
for i, rhs := range stmt.Rhs {
|
|
||||||
// Rhs can be EmbeddedDir or EmbeddedFile.
|
|
||||||
var literal *ast.CompositeLit
|
|
||||||
literal, ok := unpoint(rhs).(*ast.CompositeLit)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if lhs, ok := stmt.Lhs[i].(*ast.Ident); ok {
|
|
||||||
// variable
|
|
||||||
edir, direrrs := parseDir(literal, directories, files)
|
|
||||||
efile, fileerrs := parseFile(literal, files)
|
|
||||||
abort := false
|
|
||||||
for _, err := range direrrs {
|
|
||||||
t.Error("error while parsing dir: ", err)
|
|
||||||
abort = true
|
|
||||||
}
|
|
||||||
for _, err := range fileerrs {
|
|
||||||
t.Error("error while parsing file: ", err)
|
|
||||||
abort = true
|
|
||||||
}
|
|
||||||
if abort {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if edir == nil && efile == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if edir != nil {
|
|
||||||
directories[lhs.Name] = edir
|
|
||||||
} else {
|
|
||||||
files[lhs.Name] = efile
|
|
||||||
}
|
|
||||||
} else if lhs, ok := stmt.Lhs[i].(*ast.SelectorExpr); ok {
|
|
||||||
selName, ok := getIdentName(lhs.Sel)
|
|
||||||
if !ok || selName != "ChildDirs" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
varName, ok := getIdentName(lhs.X)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("cannot parse ChildDirs assignment: %#v", lhs)
|
|
||||||
}
|
|
||||||
dir, ok := directories[varName]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("variable %v not found", varName)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors []error
|
|
||||||
dir.ChildDirs, errors = parseDirsSlice(rhs, directories, files)
|
|
||||||
|
|
||||||
abort := false
|
|
||||||
for _, err := range errors {
|
|
||||||
t.Errorf("error parsing child dirs: %s", err)
|
|
||||||
abort = true
|
|
||||||
}
|
|
||||||
if abort {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(registrations) == 0 {
|
|
||||||
t.Fatal("could not find registration of embedded box")
|
|
||||||
}
|
|
||||||
|
|
||||||
boxes := make(map[string]*registeredBox)
|
|
||||||
|
|
||||||
for _, call := range registrations {
|
|
||||||
if isSimpleSelector("embedded", "RegisterEmbeddedBox", call.Fun) {
|
|
||||||
if len(call.Args) != 2 {
|
|
||||||
t.Fatalf("incorrect arguments to embedded.RegisterEmbeddedBox: %#v", call.Args)
|
|
||||||
}
|
|
||||||
boxArg := unpoint(call.Args[1])
|
|
||||||
name, err := parseString(call.Args[0])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("first argument to embedded.RegisterEmbeddedBox incorrect: %s", err)
|
|
||||||
}
|
|
||||||
boxLit, ok := boxArg.(*ast.CompositeLit)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("second argument to embedded.RegisterEmbeddedBox is not a composite literal: %#v", boxArg)
|
|
||||||
}
|
|
||||||
abort := false
|
|
||||||
box, errors := parseRegistration(boxLit, directories, files)
|
|
||||||
for _, err := range errors {
|
|
||||||
t.Error("error while parsing box: ", err)
|
|
||||||
abort = true
|
|
||||||
}
|
|
||||||
if abort {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if box == nil {
|
|
||||||
t.Fatalf("second argument to embedded.RegisterEmbeddedBox is not an embedded.EmbeddedBox: %#v", boxArg)
|
|
||||||
}
|
|
||||||
if box.Name != name {
|
|
||||||
t.Fatalf("first argument to embedded.RegisterEmbeddedBox is not the same as the name in the second argument: %v, %#v", name, boxArg)
|
|
||||||
}
|
|
||||||
boxes[name] = box
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that all boxes are present.
|
|
||||||
if _, ok := boxes["foo"]; !ok {
|
|
||||||
t.Error("box \"foo\" not found")
|
|
||||||
}
|
|
||||||
for _, box := range boxes {
|
|
||||||
validateBox(t, box, sourceFiles)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,204 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
"github.com/akavel/rsrc/coff"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sizedReader struct {
|
|
||||||
*bytes.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sizedReader) Size() int64 {
|
|
||||||
return int64(s.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmplEmbeddedSysoHelper *template.Template
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
tmplEmbeddedSysoHelper, err = template.New("embeddedSysoHelper").Parse(`package {{.Package}}
|
|
||||||
// ############# GENERATED CODE #####################
|
|
||||||
// ## This file was generated by the rice tool.
|
|
||||||
// ## Do not edit unless you know what you're doing.
|
|
||||||
// ##################################################
|
|
||||||
|
|
||||||
// extern char _bricebox_{{.Symname}}[], _ericebox_{{.Symname}};
|
|
||||||
// int get_{{.Symname}}_length() {
|
|
||||||
// return &_ericebox_{{.Symname}} - _bricebox_{{.Symname}};
|
|
||||||
// }
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ptr := unsafe.Pointer(&C._bricebox_{{.Symname}})
|
|
||||||
bts := C.GoBytes(ptr, C.get_{{.Symname}}_length())
|
|
||||||
embeddedBox := &embedded.EmbeddedBox{}
|
|
||||||
err := gob.NewDecoder(bytes.NewReader(bts)).Decode(embeddedBox)
|
|
||||||
if err != nil {
|
|
||||||
panic("error decoding embedded box: "+err.Error())
|
|
||||||
}
|
|
||||||
embeddedBox.Link()
|
|
||||||
embedded.RegisterEmbeddedBox(embeddedBox.Name, embeddedBox)
|
|
||||||
}`)
|
|
||||||
if err != nil {
|
|
||||||
panic("could not parse template embeddedSysoHelper: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type embeddedSysoHelperData struct {
|
|
||||||
Package string
|
|
||||||
Symname string
|
|
||||||
}
|
|
||||||
|
|
||||||
func operationEmbedSyso(pkg *build.Package) {
|
|
||||||
|
|
||||||
regexpSynameReplacer := regexp.MustCompile(`[^a-z0-9_]`)
|
|
||||||
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
|
|
||||||
// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
|
|
||||||
if len(boxMap) == 0 {
|
|
||||||
fmt.Println("no calls to rice.FindBox() found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
verbosef("\n")
|
|
||||||
|
|
||||||
for boxname := range boxMap {
|
|
||||||
// find path and filename for this box
|
|
||||||
boxPath := filepath.Join(pkg.Dir, boxname)
|
|
||||||
boxFilename := strings.Replace(boxname, "/", "-", -1)
|
|
||||||
boxFilename = strings.Replace(boxFilename, "..", "back", -1)
|
|
||||||
boxFilename = strings.Replace(boxFilename, ".", "-", -1)
|
|
||||||
|
|
||||||
// verbose info
|
|
||||||
verbosef("embedding box '%s'\n", boxname)
|
|
||||||
verbosef("\tto file %s\n", boxFilename)
|
|
||||||
|
|
||||||
// read box metadata
|
|
||||||
boxInfo, ierr := os.Stat(boxPath)
|
|
||||||
if ierr != nil {
|
|
||||||
fmt.Printf("Error: unable to access box at %s\n", boxPath)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create box datastructure (used by template)
|
|
||||||
box := &embedded.EmbeddedBox{
|
|
||||||
Name: boxname,
|
|
||||||
Time: boxInfo.ModTime(),
|
|
||||||
EmbedType: embedded.EmbedTypeSyso,
|
|
||||||
Files: make(map[string]*embedded.EmbeddedFile),
|
|
||||||
Dirs: make(map[string]*embedded.EmbeddedDir),
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill box datastructure with file data
|
|
||||||
filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error walking box: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := strings.TrimPrefix(path, boxPath)
|
|
||||||
filename = strings.Replace(filename, "\\", "/", -1)
|
|
||||||
filename = strings.TrimPrefix(filename, "/")
|
|
||||||
if info.IsDir() {
|
|
||||||
embeddedDir := &embedded.EmbeddedDir{
|
|
||||||
Filename: filename,
|
|
||||||
DirModTime: info.ModTime(),
|
|
||||||
}
|
|
||||||
verbosef("\tincludes dir: '%s'\n", embeddedDir.Filename)
|
|
||||||
box.Dirs[embeddedDir.Filename] = embeddedDir
|
|
||||||
|
|
||||||
// add tree entry (skip for root, it'll create a recursion)
|
|
||||||
if embeddedDir.Filename != "" {
|
|
||||||
pathParts := strings.Split(embeddedDir.Filename, "/")
|
|
||||||
parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
|
|
||||||
parentDir.ChildDirs = append(parentDir.ChildDirs, embeddedDir)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
embeddedFile := &embedded.EmbeddedFile{
|
|
||||||
Filename: filename,
|
|
||||||
FileModTime: info.ModTime(),
|
|
||||||
Content: "",
|
|
||||||
}
|
|
||||||
verbosef("\tincludes file: '%s'\n", embeddedFile.Filename)
|
|
||||||
contentBytes, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error reading file content while walking box: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
embeddedFile.Content = string(contentBytes)
|
|
||||||
box.Files[embeddedFile.Filename] = embeddedFile
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// encode embedded box to gob file
|
|
||||||
boxGobBuf := &bytes.Buffer{}
|
|
||||||
err := gob.NewEncoder(boxGobBuf).Encode(box)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error encoding box to gob: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
verbosef("gob-encoded embeddedBox is %d bytes large\n", boxGobBuf.Len())
|
|
||||||
|
|
||||||
// write coff
|
|
||||||
symname := regexpSynameReplacer.ReplaceAllString(boxname, "_")
|
|
||||||
createCoffSyso(boxname, symname, "386", boxGobBuf.Bytes())
|
|
||||||
createCoffSyso(boxname, symname, "amd64", boxGobBuf.Bytes())
|
|
||||||
|
|
||||||
// write go
|
|
||||||
sysoHelperData := embeddedSysoHelperData{
|
|
||||||
Package: pkg.Name,
|
|
||||||
Symname: symname,
|
|
||||||
}
|
|
||||||
fileSysoHelper, err := os.Create(boxFilename + ".rice-box.go")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error creating syso helper: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
err = tmplEmbeddedSysoHelper.Execute(fileSysoHelper, sysoHelperData)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error executing tmplEmbeddedSysoHelper: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createCoffSyso(boxFilename string, symname string, arch string, data []byte) {
|
|
||||||
boxCoff := coff.NewRDATA()
|
|
||||||
switch arch {
|
|
||||||
case "386":
|
|
||||||
case "amd64":
|
|
||||||
boxCoff.FileHeader.Machine = 0x8664
|
|
||||||
default:
|
|
||||||
panic("invalid arch")
|
|
||||||
}
|
|
||||||
boxCoff.AddData("_bricebox_"+symname, sizedReader{bytes.NewReader(data)})
|
|
||||||
boxCoff.AddData("_ericebox_"+symname, io.NewSectionReader(strings.NewReader("\000\000"), 0, 2)) // TODO: why? copied from rsrc, which copied it from as-generated
|
|
||||||
boxCoff.Freeze()
|
|
||||||
err := writeCoff(boxCoff, boxFilename+"_"+arch+".rice-box.syso")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error writing %s coff/.syso: %v\n", arch, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func badArgument(fileset *token.FileSet, p token.Pos) {
|
|
||||||
pos := fileset.Position(p)
|
|
||||||
filename := pos.Filename
|
|
||||||
base, err := os.Getwd()
|
|
||||||
if err == nil {
|
|
||||||
rpath, perr := filepath.Rel(base, pos.Filename)
|
|
||||||
if perr == nil {
|
|
||||||
filename = rpath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg := fmt.Sprintf("%s:%d: Error: found call to rice.FindBox, "+
|
|
||||||
"but argument must be a string literal.\n", filename, pos.Line)
|
|
||||||
fmt.Println(msg)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findBoxes(pkg *build.Package) map[string]bool {
|
|
||||||
// create map of boxes to embed
|
|
||||||
var boxMap = make(map[string]bool)
|
|
||||||
|
|
||||||
// create one list of files for this package
|
|
||||||
filenames := make([]string, 0, len(pkg.GoFiles)+len(pkg.CgoFiles))
|
|
||||||
filenames = append(filenames, pkg.GoFiles...)
|
|
||||||
filenames = append(filenames, pkg.CgoFiles...)
|
|
||||||
|
|
||||||
// loop over files, search for rice.FindBox(..) calls
|
|
||||||
for _, filename := range filenames {
|
|
||||||
// find full filepath
|
|
||||||
fullpath := filepath.Join(pkg.Dir, filename)
|
|
||||||
if strings.HasSuffix(filename, "rice-box.go") {
|
|
||||||
// Ignore *.rice-box.go files
|
|
||||||
verbosef("skipping file %q\n", fullpath)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
verbosef("scanning file %q\n", fullpath)
|
|
||||||
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
f, err := parser.ParseFile(fset, fullpath, nil, 0)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var riceIsImported bool
|
|
||||||
ricePkgName := "rice"
|
|
||||||
for _, imp := range f.Imports {
|
|
||||||
if strings.HasSuffix(imp.Path.Value, "go.rice\"") {
|
|
||||||
if imp.Name != nil {
|
|
||||||
ricePkgName = imp.Name.Name
|
|
||||||
}
|
|
||||||
riceIsImported = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !riceIsImported {
|
|
||||||
// Rice wasn't imported, so we won't find a box.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ricePkgName == "_" {
|
|
||||||
// Rice pkg is unnamed, so we won't find a box.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspect AST, looking for calls to (Must)?FindBox.
|
|
||||||
// First parameter of the func must be a basic literal.
|
|
||||||
// Identifiers won't be resolved.
|
|
||||||
var nextIdentIsBoxFunc bool
|
|
||||||
var nextBasicLitParamIsBoxName bool
|
|
||||||
var boxCall token.Pos
|
|
||||||
var variableToRemember string
|
|
||||||
var validVariablesForBoxes map[string]bool = make(map[string]bool)
|
|
||||||
|
|
||||||
ast.Inspect(f, func(node ast.Node) bool {
|
|
||||||
if node == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch x := node.(type) {
|
|
||||||
// this case fixes the var := func() style assignments, not assignments to vars declared separately from the assignment.
|
|
||||||
case *ast.AssignStmt:
|
|
||||||
var assign = node.(*ast.AssignStmt)
|
|
||||||
name, found := assign.Lhs[0].(*ast.Ident)
|
|
||||||
if found {
|
|
||||||
variableToRemember = name.Name
|
|
||||||
composite, first := assign.Rhs[0].(*ast.CompositeLit)
|
|
||||||
if first {
|
|
||||||
riceSelector, second := composite.Type.(*ast.SelectorExpr)
|
|
||||||
|
|
||||||
if second {
|
|
||||||
callCorrect := riceSelector.Sel.Name == "Config"
|
|
||||||
packageName, third := riceSelector.X.(*ast.Ident)
|
|
||||||
|
|
||||||
if third && callCorrect && packageName.Name == ricePkgName {
|
|
||||||
validVariablesForBoxes[name.Name] = true
|
|
||||||
verbosef("\tfound variable, saving to scan for boxes: %q\n", name.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ast.Ident:
|
|
||||||
if nextIdentIsBoxFunc || ricePkgName == "." {
|
|
||||||
nextIdentIsBoxFunc = false
|
|
||||||
if x.Name == "FindBox" || x.Name == "MustFindBox" {
|
|
||||||
nextBasicLitParamIsBoxName = true
|
|
||||||
boxCall = x.Pos()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if x.Name == ricePkgName || validVariablesForBoxes[x.Name] {
|
|
||||||
nextIdentIsBoxFunc = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ast.BasicLit:
|
|
||||||
if nextBasicLitParamIsBoxName {
|
|
||||||
if x.Kind == token.STRING {
|
|
||||||
nextBasicLitParamIsBoxName = false
|
|
||||||
// trim "" or ``
|
|
||||||
name := x.Value[1 : len(x.Value)-1]
|
|
||||||
boxMap[name] = true
|
|
||||||
verbosef("\tfound box %q\n", name)
|
|
||||||
} else {
|
|
||||||
badArgument(fset, boxCall)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if nextIdentIsBoxFunc {
|
|
||||||
nextIdentIsBoxFunc = false
|
|
||||||
}
|
|
||||||
if nextBasicLitParamIsBoxName {
|
|
||||||
badArgument(fset, boxCall)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return boxMap
|
|
||||||
}
|
|
@ -1,302 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sourceFile struct {
|
|
||||||
Name string
|
|
||||||
Contents []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectBoxes(expected []string, actual map[string]bool) error {
|
|
||||||
if len(expected) != len(actual) {
|
|
||||||
return fmt.Errorf("expected %v, got %v", expected, actual)
|
|
||||||
}
|
|
||||||
for _, box := range expected {
|
|
||||||
if _, ok := actual[box]; !ok {
|
|
||||||
return fmt.Errorf("expected %v, got %v", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setUpTestPkg(pkgName string, files []sourceFile) (*build.Package, func(), error) {
|
|
||||||
temp, err := ioutil.TempDir("", "go.rice-test")
|
|
||||||
if err != nil {
|
|
||||||
return nil, func() {}, err
|
|
||||||
}
|
|
||||||
cleanup := func() {
|
|
||||||
os.RemoveAll(temp)
|
|
||||||
}
|
|
||||||
dir := filepath.Join(temp, pkgName)
|
|
||||||
if err := os.Mkdir(dir, 0770); err != nil {
|
|
||||||
return nil, cleanup, err
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
|
||||||
fullPath := filepath.Join(dir, f.Name)
|
|
||||||
if err := os.MkdirAll(filepath.Dir(fullPath), 0770); err != nil {
|
|
||||||
return nil, cleanup, err
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(fullPath, f.Contents, 0660); err != nil {
|
|
||||||
return nil, cleanup, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pkg, err := build.ImportDir(dir, 0)
|
|
||||||
return pkg, cleanup, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindOneBox(t *testing.T) {
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rice.MustFindBox("foo")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedBoxes := []string{"foo"}
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
if err := expectBoxes(expectedBoxes, boxMap); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindOneBoxViaVariable(t *testing.T) {
|
|
||||||
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
conf := rice.Config{
|
|
||||||
LocateOrder: []rice.LocateMethod{rice.LocateEmbedded, rice.LocateAppended, rice.LocateFS},
|
|
||||||
}
|
|
||||||
conf.MustFindBox("foo")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedBoxes := []string{"foo"}
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
if err := expectBoxes(expectedBoxes, boxMap); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindMultipleBoxes(t *testing.T) {
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rice.MustFindBox("foo")
|
|
||||||
rice.MustFindBox("bar")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedBoxes := []string{"foo", "bar"}
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
if err := expectBoxes(expectedBoxes, boxMap); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoBoxFoundIfRiceNotImported(t *testing.T) {
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package main
|
|
||||||
type fakerice struct {}
|
|
||||||
|
|
||||||
func (fr fakerice) FindBox(s string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rice := fakerice{}
|
|
||||||
rice.FindBox("foo")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
if _, ok := boxMap["foo"]; ok {
|
|
||||||
t.Errorf("Unexpected box %q was found", "foo")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnrelatedBoxesAreNotFound(t *testing.T) {
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package foobar
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakerice struct {}
|
|
||||||
|
|
||||||
func (fr fakerice) FindBox(s string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindBox(s string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadBoxes() {
|
|
||||||
rice := fakerice{}
|
|
||||||
rice.FindBox("foo")
|
|
||||||
|
|
||||||
FindBox("bar")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
for _, box := range []string{"foo", "bar"} {
|
|
||||||
if _, ok := boxMap[box]; ok {
|
|
||||||
t.Errorf("Unexpected box %q was found", box)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMixGoodAndBadBoxes(t *testing.T) {
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes1.go",
|
|
||||||
[]byte(`package foobar
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakerice struct {}
|
|
||||||
|
|
||||||
func (fr fakerice) FindBox(s string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindBox(s string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadBoxes1() {
|
|
||||||
rice := fakerice{}
|
|
||||||
rice.FindBox("foo")
|
|
||||||
|
|
||||||
FindBox("bar")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"boxes2.go",
|
|
||||||
[]byte(`package foobar
|
|
||||||
|
|
||||||
import (
|
|
||||||
noodles "github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoadBoxes2() {
|
|
||||||
FindBox("baz")
|
|
||||||
noodles.FindBox("veggies")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"boxes3.go",
|
|
||||||
[]byte(`package foobar
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoadBoxes3() {
|
|
||||||
rice.FindBox("fish")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"boxes4.go",
|
|
||||||
[]byte(`package foobar
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoadBoxes3() {
|
|
||||||
MustFindBox("chicken")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
for _, box := range []string{"foo", "bar", "baz"} {
|
|
||||||
if _, ok := boxMap[box]; ok {
|
|
||||||
t.Errorf("Unexpected box %q was found", box)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, box := range []string{"veggies", "fish", "chicken"} {
|
|
||||||
if _, ok := boxMap[box]; !ok {
|
|
||||||
t.Errorf("Expected box %q not found", box)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
goflags "github.com/jessevdk/go-flags" // rename import to `goflags` (file scope) so we can use `var flags` (package scope)
|
|
||||||
)
|
|
||||||
|
|
||||||
// flags
|
|
||||||
var flags struct {
|
|
||||||
Verbose bool `long:"verbose" short:"v" description:"Show verbose debug information"`
|
|
||||||
ImportPaths []string `long:"import-path" short:"i" description:"Import path(s) to use. Using PWD when left empty. Specify multiple times for more import paths to append"`
|
|
||||||
|
|
||||||
Append struct {
|
|
||||||
Executable string `long:"exec" description:"Executable to append" required:"true"`
|
|
||||||
} `command:"append"`
|
|
||||||
|
|
||||||
EmbedGo struct{} `command:"embed-go" alias:"embed"`
|
|
||||||
EmbedSyso struct{} `command:"embed-syso"`
|
|
||||||
Clean struct{} `command:"clean"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// flags parser
|
|
||||||
var flagsParser *goflags.Parser
|
|
||||||
|
|
||||||
// initFlags parses the given flags.
|
|
||||||
// when the user asks for help (-h or --help): the application exists with status 0
|
|
||||||
// when unexpected flags is given: the application exits with status 1
|
|
||||||
func parseArguments() {
|
|
||||||
// create flags parser in global var, for flagsParser.Active.Name (operation)
|
|
||||||
flagsParser = goflags.NewParser(&flags, goflags.Default)
|
|
||||||
|
|
||||||
// parse flags
|
|
||||||
args, err := flagsParser.Parse()
|
|
||||||
if err != nil {
|
|
||||||
// assert the err to be a flags.Error
|
|
||||||
flagError := err.(*goflags.Error)
|
|
||||||
if flagError.Type == goflags.ErrHelp {
|
|
||||||
// user asked for help on flags.
|
|
||||||
// program can exit successfully
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
if flagError.Type == goflags.ErrUnknownFlag {
|
|
||||||
fmt.Println("Use --help to view available options.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if flagError.Type == goflags.ErrRequired {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Error parsing flags: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// error on left-over arguments
|
|
||||||
if len(args) > 0 {
|
|
||||||
fmt.Printf("Unexpected arguments: %s\nUse --help to view available options.", args)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// default ImportPath to pwd when not set
|
|
||||||
if len(flags.ImportPaths) == 0 {
|
|
||||||
pwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error getting pwd: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
verbosef("using pwd as import path\n")
|
|
||||||
// find non-absolute path for this pwd
|
|
||||||
pkg, err := build.ImportDir(pwd, build.FindOnly)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error using current directory as import path: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
flags.ImportPaths = append(flags.ImportPaths, pkg.ImportPath)
|
|
||||||
verbosef("using import paths: %s\n", flags.ImportPaths)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.incremental"
|
|
||||||
)
|
|
||||||
|
|
||||||
var identifierCount incremental.Uint64
|
|
||||||
|
|
||||||
func nextIdentifier() string {
|
|
||||||
num := identifierCount.Next()
|
|
||||||
return strconv.FormatUint(num, 36) // 0123456789abcdefghijklmnopqrstuvwxyz
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// parser arguments
|
|
||||||
parseArguments()
|
|
||||||
|
|
||||||
// find package for path
|
|
||||||
var pkgs []*build.Package
|
|
||||||
for _, importPath := range flags.ImportPaths {
|
|
||||||
pkg := pkgForPath(importPath)
|
|
||||||
pkgs = append(pkgs, pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// switch on the operation to perform
|
|
||||||
switch flagsParser.Active.Name {
|
|
||||||
case "embed", "embed-go":
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
operationEmbedGo(pkg)
|
|
||||||
}
|
|
||||||
case "embed-syso":
|
|
||||||
log.Println("WARNING: embedding .syso is experimental..")
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
operationEmbedSyso(pkg)
|
|
||||||
}
|
|
||||||
case "append":
|
|
||||||
operationAppend(pkgs)
|
|
||||||
case "clean":
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
operationClean(pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// all done
|
|
||||||
verbosef("\n")
|
|
||||||
verbosef("rice finished successfully\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function to get *build.Package for given path
|
|
||||||
func pkgForPath(path string) *build.Package {
|
|
||||||
// get pwd for relative imports
|
|
||||||
pwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error getting pwd (required for relative imports): %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// read full package information
|
|
||||||
pkg, err := build.Import(path, pwd, 0)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error reading package: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
func verbosef(format string, stuff ...interface{}) {
|
|
||||||
if flags.Verbose {
|
|
||||||
log.Printf(format, stuff...)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
var tmplEmbeddedBox *template.Template
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// parse embedded box template
|
|
||||||
tmplEmbeddedBox, err = template.New("embeddedBox").Parse(`package {{.Package}}
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
{{range .Boxes}}
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
// define files
|
|
||||||
{{range .Files}}{{.Identifier}} := &embedded.EmbeddedFile{
|
|
||||||
Filename: {{.FileName | printf "%q"}},
|
|
||||||
FileModTime: time.Unix({{.ModTime}}, 0),
|
|
||||||
Content: string({{.Content | printf "%q"}}),
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// define dirs
|
|
||||||
{{range .Dirs}}{{.Identifier}} := &embedded.EmbeddedDir{
|
|
||||||
Filename: {{.FileName | printf "%q"}},
|
|
||||||
DirModTime: time.Unix({{.ModTime}}, 0),
|
|
||||||
ChildFiles: []*embedded.EmbeddedFile{
|
|
||||||
{{range .ChildFiles}}{{.Identifier}}, // {{.FileName | printf "%q"}}
|
|
||||||
{{end}}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// link ChildDirs
|
|
||||||
{{range .Dirs}}{{.Identifier}}.ChildDirs = []*embedded.EmbeddedDir{
|
|
||||||
{{range .ChildDirs}}{{.Identifier}}, // {{.FileName | printf "%q"}}
|
|
||||||
{{end}}
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// register embeddedBox
|
|
||||||
embedded.RegisterEmbeddedBox(` + "`" + `{{.BoxName}}` + "`" + `, &embedded.EmbeddedBox{
|
|
||||||
Name: ` + "`" + `{{.BoxName}}` + "`" + `,
|
|
||||||
Time: time.Unix({{.UnixNow}}, 0),
|
|
||||||
Dirs: map[string]*embedded.EmbeddedDir{
|
|
||||||
{{range .Dirs}}{{.FileName | printf "%q"}}: {{.Identifier}},
|
|
||||||
{{end}}
|
|
||||||
},
|
|
||||||
Files: map[string]*embedded.EmbeddedFile{
|
|
||||||
{{range .Files}}{{.FileName | printf "%q"}}: {{.Identifier}},
|
|
||||||
{{end}}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
{{end}}`)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error parsing embedded box template: %s\n", err)
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type embedFileDataType struct {
|
|
||||||
Package string
|
|
||||||
Boxes []*boxDataType
|
|
||||||
}
|
|
||||||
|
|
||||||
type boxDataType struct {
|
|
||||||
BoxName string
|
|
||||||
UnixNow int64
|
|
||||||
Files []*fileDataType
|
|
||||||
Dirs map[string]*dirDataType
|
|
||||||
}
|
|
||||||
|
|
||||||
type fileDataType struct {
|
|
||||||
Identifier string
|
|
||||||
FileName string
|
|
||||||
Content []byte
|
|
||||||
ModTime int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type dirDataType struct {
|
|
||||||
Identifier string
|
|
||||||
FileName string
|
|
||||||
Content []byte
|
|
||||||
ModTime int64
|
|
||||||
ChildDirs []*dirDataType
|
|
||||||
ChildFiles []*fileDataType
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// randomString generates a pseudo-random alpha-numeric string with given length.
|
|
||||||
func randomString(length int) string {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
k := make([]rune, length)
|
|
||||||
for i := 0; i < length; i++ {
|
|
||||||
c := rand.Intn(35)
|
|
||||||
if c < 10 {
|
|
||||||
c += 48 // numbers (0-9) (0+48 == 48 == '0', 9+48 == 57 == '9')
|
|
||||||
} else {
|
|
||||||
c += 87 // lower case alphabets (a-z) (10+87 == 97 == 'a', 35+87 == 122 = 'z')
|
|
||||||
}
|
|
||||||
k[i] = rune(c)
|
|
||||||
}
|
|
||||||
return string(k)
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/akavel/rsrc/binutil"
|
|
||||||
"github.com/akavel/rsrc/coff"
|
|
||||||
)
|
|
||||||
|
|
||||||
// copied from github.com/akavel/rsrc
|
|
||||||
// LICENSE: MIT
|
|
||||||
// Copyright 2013-2014 The rsrc Authors. (https://github.com/akavel/rsrc/blob/master/AUTHORS)
|
|
||||||
func writeCoff(coff *coff.Coff, fnameout string) error {
|
|
||||||
out, err := os.Create(fnameout)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
w := binutil.Writer{W: out}
|
|
||||||
|
|
||||||
// write the resulting file to disk
|
|
||||||
binutil.Walk(coff, func(v reflect.Value, path string) error {
|
|
||||||
if binutil.Plain(v.Kind()) {
|
|
||||||
w.WriteLE(v.Interface())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
vv, ok := v.Interface().(binutil.SizedReader)
|
|
||||||
if ok {
|
|
||||||
w.WriteFromSized(vv)
|
|
||||||
return binutil.WALK_SKIP
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if w.Err != nil {
|
|
||||||
return fmt.Errorf("Error writing output file: %s", w.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
// SortByName allows an array of os.FileInfo objects
|
|
||||||
// to be easily sorted by filename using sort.Sort(SortByName(array))
|
|
||||||
type SortByName []os.FileInfo
|
|
||||||
|
|
||||||
func (f SortByName) Len() int { return len(f) }
|
|
||||||
func (f SortByName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
|
||||||
func (f SortByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
|
||||||
|
|
||||||
// SortByModified allows an array of os.FileInfo objects
|
|
||||||
// to be easily sorted by modified date using sort.Sort(SortByModified(array))
|
|
||||||
type SortByModified []os.FileInfo
|
|
||||||
|
|
||||||
func (f SortByModified) Len() int { return len(f) }
|
|
||||||
func (f SortByModified) Less(i, j int) bool { return f[i].ModTime().Unix() > f[j].ModTime().Unix() }
|
|
||||||
func (f SortByModified) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
|
@ -1,252 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
)
|
|
||||||
|
|
||||||
//++ TODO: IDEA: merge virtualFile and virtualDir, this decreases work done by rice.File
|
|
||||||
|
|
||||||
// Error indicating some function is not implemented yet (but available to satisfy an interface)
|
|
||||||
var ErrNotImplemented = errors.New("not implemented yet")
|
|
||||||
|
|
||||||
// virtualFile is a 'stateful' virtual file.
|
|
||||||
// virtualFile wraps an *EmbeddedFile for a call to Box.Open() and virtualizes 'read cursor' (offset) and 'closing'.
|
|
||||||
// virtualFile is only internally visible and should be exposed through rice.File
|
|
||||||
type virtualFile struct {
|
|
||||||
*embedded.EmbeddedFile // the actual embedded file, embedded to obtain methods
|
|
||||||
offset int64 // read position on the virtual file
|
|
||||||
closed bool // closed when true
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new virtualFile for given EmbeddedFile
|
|
||||||
func newVirtualFile(ef *embedded.EmbeddedFile) *virtualFile {
|
|
||||||
vf := &virtualFile{
|
|
||||||
EmbeddedFile: ef,
|
|
||||||
offset: 0,
|
|
||||||
closed: false,
|
|
||||||
}
|
|
||||||
return vf
|
|
||||||
}
|
|
||||||
|
|
||||||
//++ TODO check for nil pointers in all these methods. When so: return os.PathError with Err: os.ErrInvalid
|
|
||||||
|
|
||||||
func (vf *virtualFile) close() error {
|
|
||||||
if vf.closed {
|
|
||||||
return &os.PathError{
|
|
||||||
Op: "close",
|
|
||||||
Path: vf.EmbeddedFile.Filename,
|
|
||||||
Err: errors.New("already closed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vf.EmbeddedFile = nil
|
|
||||||
vf.closed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vf *virtualFile) stat() (os.FileInfo, error) {
|
|
||||||
if vf.closed {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "stat",
|
|
||||||
Path: vf.EmbeddedFile.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (*embeddedFileInfo)(vf.EmbeddedFile), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vf *virtualFile) readdir(count int) ([]os.FileInfo, error) {
|
|
||||||
if vf.closed {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "readdir",
|
|
||||||
Path: vf.EmbeddedFile.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//TODO: return proper error for a readdir() call on a file
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vf *virtualFile) read(bts []byte) (int, error) {
|
|
||||||
if vf.closed {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "read",
|
|
||||||
Path: vf.EmbeddedFile.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end := vf.offset + int64(len(bts))
|
|
||||||
|
|
||||||
if end >= int64(len(vf.Content)) {
|
|
||||||
// end of file, so return what we have + EOF
|
|
||||||
n := copy(bts, vf.Content[vf.offset:])
|
|
||||||
vf.offset = 0
|
|
||||||
return n, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
n := copy(bts, vf.Content[vf.offset:end])
|
|
||||||
vf.offset += int64(n)
|
|
||||||
return n, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vf *virtualFile) seek(offset int64, whence int) (int64, error) {
|
|
||||||
if vf.closed {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "seek",
|
|
||||||
Path: vf.EmbeddedFile.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var e error
|
|
||||||
|
|
||||||
//++ TODO: check if this is correct implementation for seek
|
|
||||||
switch whence {
|
|
||||||
case os.SEEK_SET:
|
|
||||||
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
|
||||||
vf.offset = offset
|
|
||||||
case os.SEEK_CUR:
|
|
||||||
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
|
||||||
vf.offset += offset
|
|
||||||
case os.SEEK_END:
|
|
||||||
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
|
||||||
vf.offset = int64(len(vf.EmbeddedFile.Content)) - offset
|
|
||||||
}
|
|
||||||
|
|
||||||
if e != nil {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "seek",
|
|
||||||
Path: vf.Filename,
|
|
||||||
Err: e,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return vf.offset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// virtualDir is a 'stateful' virtual directory.
|
|
||||||
// virtualDir wraps an *EmbeddedDir for a call to Box.Open() and virtualizes 'closing'.
|
|
||||||
// virtualDir is only internally visible and should be exposed through rice.File
|
|
||||||
type virtualDir struct {
|
|
||||||
*embedded.EmbeddedDir
|
|
||||||
offset int // readdir position on the directory
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new virtualDir for given EmbeddedDir
|
|
||||||
func newVirtualDir(ed *embedded.EmbeddedDir) *virtualDir {
|
|
||||||
vd := &virtualDir{
|
|
||||||
EmbeddedDir: ed,
|
|
||||||
offset: 0,
|
|
||||||
closed: false,
|
|
||||||
}
|
|
||||||
return vd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vd *virtualDir) close() error {
|
|
||||||
//++ TODO: needs sync mutex?
|
|
||||||
if vd.closed {
|
|
||||||
return &os.PathError{
|
|
||||||
Op: "close",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("already closed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vd.closed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vd *virtualDir) stat() (os.FileInfo, error) {
|
|
||||||
if vd.closed {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "stat",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (*embeddedDirInfo)(vd.EmbeddedDir), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vd *virtualDir) readdir(n int) (fi []os.FileInfo, err error) {
|
|
||||||
|
|
||||||
if vd.closed {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "readdir",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build up the array of our contents
|
|
||||||
var files []os.FileInfo
|
|
||||||
|
|
||||||
// Add the child directories
|
|
||||||
for _, child := range vd.ChildDirs {
|
|
||||||
child.Filename = filepath.Base(child.Filename)
|
|
||||||
files = append(files, (*embeddedDirInfo)(child))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the child files
|
|
||||||
for _, child := range vd.ChildFiles {
|
|
||||||
child.Filename = filepath.Base(child.Filename)
|
|
||||||
files = append(files, (*embeddedFileInfo)(child))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort it by filename (lexical order)
|
|
||||||
sort.Sort(SortByName(files))
|
|
||||||
|
|
||||||
// Return all contents if that's what is requested
|
|
||||||
if n <= 0 {
|
|
||||||
vd.offset = 0
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user has requested past the end of our list
|
|
||||||
// return what we can and send an EOF
|
|
||||||
if vd.offset+n >= len(files) {
|
|
||||||
offset := vd.offset
|
|
||||||
vd.offset = 0
|
|
||||||
return files[offset:], io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
offset := vd.offset
|
|
||||||
vd.offset += n
|
|
||||||
return files[offset : offset+n], nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vd *virtualDir) read(bts []byte) (int, error) {
|
|
||||||
if vd.closed {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "read",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "read",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("is a directory"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vd *virtualDir) seek(offset int64, whence int) (int64, error) {
|
|
||||||
if vd.closed {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "seek",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "seek",
|
|
||||||
Path: vd.Filename,
|
|
||||||
Err: errors.New("is a directory"),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Walk is like filepath.Walk()
|
|
||||||
// Visit http://golang.org/pkg/path/filepath/#Walk for more information
|
|
||||||
func (b *Box) Walk(path string, walkFn filepath.WalkFunc) error {
|
|
||||||
|
|
||||||
pathFile, err := b.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer pathFile.Close()
|
|
||||||
|
|
||||||
pathInfo, err := pathFile.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.IsAppended() || b.IsEmbedded() {
|
|
||||||
return b.walk(path, pathInfo, walkFn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't have any embedded or appended box so use live filesystem mode
|
|
||||||
return filepath.Walk(b.absolutePath+string(os.PathSeparator)+path, func(path string, info os.FileInfo, err error) error {
|
|
||||||
|
|
||||||
// Strip out the box name from the returned paths
|
|
||||||
path = strings.TrimPrefix(path, b.absolutePath+string(os.PathSeparator))
|
|
||||||
return walkFn(path, info, err)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// walk recursively descends path.
|
|
||||||
// See walk() in $GOROOT/src/pkg/path/filepath/path.go
|
|
||||||
func (b *Box) walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
|
||||||
|
|
||||||
err := walkFn(path, info, nil)
|
|
||||||
if err != nil {
|
|
||||||
if info.IsDir() && err == filepath.SkipDir {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
names, err := b.readDirNames(path)
|
|
||||||
if err != nil {
|
|
||||||
return walkFn(path, info, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, name := range names {
|
|
||||||
|
|
||||||
filename := filepath.Join(path, name)
|
|
||||||
fileObject, err := b.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fileObject.Close()
|
|
||||||
|
|
||||||
fileInfo, err := fileObject.Stat()
|
|
||||||
if err != nil {
|
|
||||||
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = b.walk(filename, fileInfo, walkFn)
|
|
||||||
if err != nil {
|
|
||||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// readDirNames reads the directory named by path and returns a sorted list of directory entries.
|
|
||||||
// See readDirNames() in $GOROOT/pkg/path/filepath/path.go
|
|
||||||
func (b *Box) readDirNames(path string) ([]string, error) {
|
|
||||||
|
|
||||||
f, err := b.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
stat, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stat.IsDir() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
infos, err := f.Readdir(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var names []string
|
|
||||||
|
|
||||||
for _, info := range infos {
|
|
||||||
names = append(names, info.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(names)
|
|
||||||
return names, nil
|
|
||||||
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright © 2012-2015 Carlos Castillo
|
|
||||||
|
|
||||||
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.
|
|
@ -1,5 +0,0 @@
|
|||||||
go.zipexe
|
|
||||||
=========
|
|
||||||
|
|
||||||
This module was taken as-is from https://github.com/cookieo9/resources-go.
|
|
||||||
Documentation: https://godoc.org/github.com/daaku/go.zipexe
|
|
@ -1,142 +0,0 @@
|
|||||||
// Package zipexe attempts to open an executable binary file as a zip file.
|
|
||||||
package zipexe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"debug/elf"
|
|
||||||
"debug/macho"
|
|
||||||
"debug/pe"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Opens a zip file by path.
|
|
||||||
func Open(path string) (*zip.Reader, error) {
|
|
||||||
_, rd, err := OpenCloser(path)
|
|
||||||
return rd, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenCloser is like Open but returns an additional Closer to avoid leaking open files.
|
|
||||||
func OpenCloser(path string) (io.Closer, *zip.Reader, error) {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
finfo, err := file.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
zr, err := NewReader(file, finfo.Size())
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return file, zr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open a zip file, specially handling various binaries that may have been
|
|
||||||
// augmented with zip data.
|
|
||||||
func NewReader(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
|
||||||
handlers := []func(io.ReaderAt, int64) (*zip.Reader, error){
|
|
||||||
zip.NewReader,
|
|
||||||
zipExeReaderMacho,
|
|
||||||
zipExeReaderElf,
|
|
||||||
zipExeReaderPe,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, handler := range handlers {
|
|
||||||
zfile, err := handler(rda, size)
|
|
||||||
if err == nil {
|
|
||||||
return zfile, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("Couldn't Open As Executable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// zipExeReaderMacho treats the file as a Mach-O binary
|
|
||||||
// (Mac OS X / Darwin executable) and attempts to find a zip archive.
|
|
||||||
func zipExeReaderMacho(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
|
||||||
file, err := macho.NewFile(rda)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var max int64
|
|
||||||
for _, load := range file.Loads {
|
|
||||||
seg, ok := load.(*macho.Segment)
|
|
||||||
if ok {
|
|
||||||
// Check if the segment contains a zip file
|
|
||||||
if zfile, err := zip.NewReader(seg, int64(seg.Filesz)); err == nil {
|
|
||||||
return zfile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise move end of file pointer
|
|
||||||
end := int64(seg.Offset + seg.Filesz)
|
|
||||||
if end > max {
|
|
||||||
max = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No zip file within binary, try appended to end
|
|
||||||
section := io.NewSectionReader(rda, max, size-max)
|
|
||||||
return zip.NewReader(section, section.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
// zipExeReaderPe treats the file as a Portable Exectuable binary
|
|
||||||
// (Windows executable) and attempts to find a zip archive.
|
|
||||||
func zipExeReaderPe(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
|
||||||
file, err := pe.NewFile(rda)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var max int64
|
|
||||||
for _, sec := range file.Sections {
|
|
||||||
// Check if this section has a zip file
|
|
||||||
if zfile, err := zip.NewReader(sec, int64(sec.Size)); err == nil {
|
|
||||||
return zfile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise move end of file pointer
|
|
||||||
end := int64(sec.Offset + sec.Size)
|
|
||||||
if end > max {
|
|
||||||
max = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No zip file within binary, try appended to end
|
|
||||||
section := io.NewSectionReader(rda, max, size-max)
|
|
||||||
return zip.NewReader(section, section.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
// zipExeReaderElf treats the file as a ELF binary
|
|
||||||
// (linux/BSD/etc... executable) and attempts to find a zip archive.
|
|
||||||
func zipExeReaderElf(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
|
||||||
file, err := elf.NewFile(rda)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var max int64
|
|
||||||
for _, sect := range file.Sections {
|
|
||||||
if sect.Type == elf.SHT_NOBITS {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this section has a zip file
|
|
||||||
if zfile, err := zip.NewReader(sect, int64(sect.Size)); err == nil {
|
|
||||||
return zfile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise move end of file pointer
|
|
||||||
end := int64(sect.Offset + sect.Size)
|
|
||||||
if end > max {
|
|
||||||
max = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No zip file within binary, try appended to end
|
|
||||||
section := io.NewSectionReader(rda, max, size-max)
|
|
||||||
return zip.NewReader(section, section.Size())
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.3
|
|
||||||
- go: 1.4
|
|
||||||
- go: 1.5
|
|
||||||
- go: 1.6
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go get golang.org/x/tools/cmd/vet
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go tool vet .
|
|
||||||
- go test -v -race ./...
|
|
@ -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.
|
|
@ -1,7 +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.
|
|
||||||
|
|
||||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,161 +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"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type keyType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
key1 keyType = iota
|
|
||||||
key2
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestContext(t *testing.T) {
|
|
||||||
assertEqual := func(val interface{}, exp interface{}) {
|
|
||||||
if val != exp {
|
|
||||||
t.Errorf("Expected %v, got %v.", exp, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
|
||||||
emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
|
||||||
|
|
||||||
// Get()
|
|
||||||
assertEqual(Get(r, key1), nil)
|
|
||||||
|
|
||||||
// Set()
|
|
||||||
Set(r, key1, "1")
|
|
||||||
assertEqual(Get(r, key1), "1")
|
|
||||||
assertEqual(len(data[r]), 1)
|
|
||||||
|
|
||||||
Set(r, key2, "2")
|
|
||||||
assertEqual(Get(r, key2), "2")
|
|
||||||
assertEqual(len(data[r]), 2)
|
|
||||||
|
|
||||||
//GetOk
|
|
||||||
value, ok := GetOk(r, key1)
|
|
||||||
assertEqual(value, "1")
|
|
||||||
assertEqual(ok, true)
|
|
||||||
|
|
||||||
value, ok = GetOk(r, "not exists")
|
|
||||||
assertEqual(value, nil)
|
|
||||||
assertEqual(ok, false)
|
|
||||||
|
|
||||||
Set(r, "nil value", nil)
|
|
||||||
value, ok = GetOk(r, "nil value")
|
|
||||||
assertEqual(value, nil)
|
|
||||||
assertEqual(ok, true)
|
|
||||||
|
|
||||||
// GetAll()
|
|
||||||
values := GetAll(r)
|
|
||||||
assertEqual(len(values), 3)
|
|
||||||
|
|
||||||
// GetAll() for empty request
|
|
||||||
values = GetAll(emptyR)
|
|
||||||
if values != nil {
|
|
||||||
t.Error("GetAll didn't return nil value for invalid request")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllOk()
|
|
||||||
values, ok = GetAllOk(r)
|
|
||||||
assertEqual(len(values), 3)
|
|
||||||
assertEqual(ok, true)
|
|
||||||
|
|
||||||
// GetAllOk() for empty request
|
|
||||||
values, ok = GetAllOk(emptyR)
|
|
||||||
assertEqual(value, nil)
|
|
||||||
assertEqual(ok, false)
|
|
||||||
|
|
||||||
// Delete()
|
|
||||||
Delete(r, key1)
|
|
||||||
assertEqual(Get(r, key1), nil)
|
|
||||||
assertEqual(len(data[r]), 2)
|
|
||||||
|
|
||||||
Delete(r, key2)
|
|
||||||
assertEqual(Get(r, key2), nil)
|
|
||||||
assertEqual(len(data[r]), 1)
|
|
||||||
|
|
||||||
// Clear()
|
|
||||||
Clear(r)
|
|
||||||
assertEqual(len(data), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) {
|
|
||||||
<-wait
|
|
||||||
for i := 0; i < iterations; i++ {
|
|
||||||
Get(r, key)
|
|
||||||
}
|
|
||||||
done <- struct{}{}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) {
|
|
||||||
<-wait
|
|
||||||
for i := 0; i < iterations; i++ {
|
|
||||||
Set(r, key, value)
|
|
||||||
}
|
|
||||||
done <- struct{}{}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) {
|
|
||||||
|
|
||||||
b.StopTimer()
|
|
||||||
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
|
||||||
done := make(chan struct{})
|
|
||||||
b.StartTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
wait := make(chan struct{})
|
|
||||||
|
|
||||||
for i := 0; i < numReaders; i++ {
|
|
||||||
go parallelReader(r, "test", iterations, wait, done)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < numWriters; i++ {
|
|
||||||
go parallelWriter(r, "test", "123", iterations, wait, done)
|
|
||||||
}
|
|
||||||
|
|
||||||
close(wait)
|
|
||||||
|
|
||||||
for i := 0; i < numReaders+numWriters; i++ {
|
|
||||||
<-done
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMutexSameReadWrite1(b *testing.B) {
|
|
||||||
benchmarkMutex(b, 1, 1, 32)
|
|
||||||
}
|
|
||||||
func BenchmarkMutexSameReadWrite2(b *testing.B) {
|
|
||||||
benchmarkMutex(b, 2, 2, 32)
|
|
||||||
}
|
|
||||||
func BenchmarkMutexSameReadWrite4(b *testing.B) {
|
|
||||||
benchmarkMutex(b, 4, 4, 32)
|
|
||||||
}
|
|
||||||
func BenchmarkMutex1(b *testing.B) {
|
|
||||||
benchmarkMutex(b, 2, 8, 32)
|
|
||||||
}
|
|
||||||
func BenchmarkMutex2(b *testing.B) {
|
|
||||||
benchmarkMutex(b, 16, 4, 64)
|
|
||||||
}
|
|
||||||
func BenchmarkMutex3(b *testing.B) {
|
|
||||||
benchmarkMutex(b, 1, 2, 128)
|
|
||||||
}
|
|
||||||
func BenchmarkMutex4(b *testing.B) {
|
|
||||||
benchmarkMutex(b, 128, 32, 256)
|
|
||||||
}
|
|
||||||
func BenchmarkMutex5(b *testing.B) {
|
|
||||||
benchmarkMutex(b, 1024, 2048, 64)
|
|
||||||
}
|
|
||||||
func BenchmarkMutex6(b *testing.B) {
|
|
||||||
benchmarkMutex(b, 2048, 1024, 512)
|
|
||||||
}
|
|
@ -1,82 +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.
|
|
||||||
|
|
||||||
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
|
|
@ -1,22 +0,0 @@
|
|||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.2
|
|
||||||
- go: 1.3
|
|
||||||
- go: 1.4
|
|
||||||
- go: 1.5
|
|
||||||
- go: 1.6
|
|
||||||
- go: 1.7
|
|
||||||
- go: 1.8
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- # Skip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go tool vet .
|
|
||||||
- go test -v -race ./...
|
|
@ -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.
|
|
@ -1,351 +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 and paths 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)
|
|
||||||
* [Listing Routes](#listing-routes)
|
|
||||||
* [Static Files](#static-files)
|
|
||||||
* [Registered URLs](#registered-urls)
|
|
||||||
* [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")
|
|
||||||
```
|
|
||||||
|
|
||||||
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)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Listing Routes
|
|
||||||
|
|
||||||
Routes on a mux can be listed using the Router.Walk method—useful for generating documentation:
|
|
||||||
|
|
||||||
```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.Methods("POST").HandleFunc("/products", handler)
|
|
||||||
r.Methods("GET").HandleFunc("/articles", handler)
|
|
||||||
r.Methods("GET", "PUT").HandleFunc("/articles/{id}", handler)
|
|
||||||
r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
|
||||||
t, err := route.GetPathTemplate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// p will contain regular expression is compatible with regular expression in Perl, Python, and other languages.
|
|
||||||
// for instance the regular expression for path '/articles/{id}' will be '^/articles/(?P<v0>[^/]+)$'
|
|
||||||
p, err := route.GetPathRegexp()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m, err := route.GetMethods()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(strings.Join(m, ","), t, p)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
http.Handle("/", r)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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 variables:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.Host("{subdomain}.domain.com").
|
|
||||||
Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
HandlerFunc(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. 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")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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.
|
|
@ -1,49 +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"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkMux(b *testing.B) {
|
|
||||||
router := new(Router)
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {}
|
|
||||||
router.HandleFunc("/v1/{v1}", handler)
|
|
||||||
|
|
||||||
request, _ := http.NewRequest("GET", "/v1/anything", nil)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
router.ServeHTTP(nil, request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMuxAlternativeInRegexp(b *testing.B) {
|
|
||||||
router := new(Router)
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {}
|
|
||||||
router.HandleFunc("/v1/{v1:(?:a|b)}", handler)
|
|
||||||
|
|
||||||
requestA, _ := http.NewRequest("GET", "/v1/a", nil)
|
|
||||||
requestB, _ := http.NewRequest("GET", "/v1/b", nil)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
router.ServeHTTP(nil, requestA)
|
|
||||||
router.ServeHTTP(nil, requestB)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkManyPathVariables(b *testing.B) {
|
|
||||||
router := new(Router)
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {}
|
|
||||||
router.HandleFunc("/v1/{v1}/{v2}/{v3}/{v4}/{v5}", handler)
|
|
||||||
|
|
||||||
matchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4/5", nil)
|
|
||||||
notMatchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4", nil)
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
router.ServeHTTP(nil, matchingRequest)
|
|
||||||
router.ServeHTTP(recorder, notMatchingRequest)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gorilla/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tests that the context is cleared or not cleared properly depending on
|
|
||||||
// the configuration of the router
|
|
||||||
func TestKeepContext(t *testing.T) {
|
|
||||||
func1 := func(w http.ResponseWriter, r *http.Request) {}
|
|
||||||
|
|
||||||
r := NewRouter()
|
|
||||||
r.HandleFunc("/", func1).Name("func1")
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "http://localhost/", nil)
|
|
||||||
context.Set(req, "t", 1)
|
|
||||||
|
|
||||||
res := new(http.ResponseWriter)
|
|
||||||
r.ServeHTTP(*res, req)
|
|
||||||
|
|
||||||
if _, ok := context.GetOk(req, "t"); ok {
|
|
||||||
t.Error("Context should have been cleared at end of request")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.KeepContext = true
|
|
||||||
|
|
||||||
req, _ = http.NewRequest("GET", "http://localhost/", nil)
|
|
||||||
context.Set(req, "t", 1)
|
|
||||||
|
|
||||||
r.ServeHTTP(*res, req)
|
|
||||||
if _, ok := context.GetOk(req, "t"); !ok {
|
|
||||||
t.Error("Context should NOT have been cleared at end of request")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
// +build go1.7
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNativeContextMiddleware(t *testing.T) {
|
|
||||||
withTimeout := func(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
h.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
r := NewRouter()
|
|
||||||
r.Handle("/path/{foo}", withTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := Vars(r)
|
|
||||||
if vars["foo"] != "bar" {
|
|
||||||
t.Fatal("Expected foo var to be set")
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
|
|
||||||
rec := NewRecorder()
|
|
||||||
req := newRequest("GET", "/path/bar")
|
|
||||||
r.ServeHTTP(rec, req)
|
|
||||||
}
|
|
@ -1,240 +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 and paths 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 variables:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.Host("{subdomain}.domain.com").
|
|
||||||
Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
HandlerFunc(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. 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")
|
|
||||||
*/
|
|
||||||
package mux
|
|
@ -1,542 +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"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match matches registered routes against the request.
|
|
||||||
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
|
||||||
for _, route := range r.routes {
|
|
||||||
if route.Match(req, match) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closest match for a router (includes sub-routers)
|
|
||||||
if r.NotFoundHandler != nil {
|
|
||||||
match.Handler = r.NotFoundHandler
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
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 = getPath(req)
|
|
||||||
}
|
|
||||||
// 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 {
|
|
||||||
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 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.
|
|
||||||
//
|
|
||||||
// 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".
|
|
||||||
// This behavior has the drawback of needing to match routes against
|
|
||||||
// r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
|
|
||||||
// to r.URL.Path will not affect routing when this flag is on and thus may
|
|
||||||
// induce unintended behavior.
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
err := h.walk(walkFn, ancestors)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// getPath returns the escaped path if possible; doing what URL.EscapedPath()
|
|
||||||
// which was added in go1.5 does
|
|
||||||
func getPath(req *http.Request) string {
|
|
||||||
if req.RequestURI != "" {
|
|
||||||
// Extract the path from RequestURI (which is escaped unlike URL.Path)
|
|
||||||
// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
|
|
||||||
// for < 1.5 server side workaround
|
|
||||||
// http://localhost/path/here?v=1 -> /path/here
|
|
||||||
path := req.RequestURI
|
|
||||||
path = strings.TrimPrefix(path, req.URL.Scheme+`://`)
|
|
||||||
path = strings.TrimPrefix(path, req.URL.Host)
|
|
||||||
if i := strings.LastIndex(path, "?"); i > -1 {
|
|
||||||
path = path[:i]
|
|
||||||
}
|
|
||||||
if i := strings.LastIndex(path, "#"); i > -1 {
|
|
||||||
path = path[:i]
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
return req.URL.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 paramers 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
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,710 +0,0 @@
|
|||||||
// Old tests ported to Go1. This is a mess. Want to drop it one day.
|
|
||||||
|
|
||||||
// Copyright 2011 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"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// ResponseRecorder
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Copyright 2009 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.
|
|
||||||
|
|
||||||
// ResponseRecorder is an implementation of http.ResponseWriter that
|
|
||||||
// records its mutations for later inspection in tests.
|
|
||||||
type ResponseRecorder struct {
|
|
||||||
Code int // the HTTP response code from WriteHeader
|
|
||||||
HeaderMap http.Header // the HTTP response headers
|
|
||||||
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
|
||||||
Flushed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRecorder returns an initialized ResponseRecorder.
|
|
||||||
func NewRecorder() *ResponseRecorder {
|
|
||||||
return &ResponseRecorder{
|
|
||||||
HeaderMap: make(http.Header),
|
|
||||||
Body: new(bytes.Buffer),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header returns the response headers.
|
|
||||||
func (rw *ResponseRecorder) Header() http.Header {
|
|
||||||
return rw.HeaderMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write always succeeds and writes to rw.Body, if not nil.
|
|
||||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
|
||||||
if rw.Body != nil {
|
|
||||||
rw.Body.Write(buf)
|
|
||||||
}
|
|
||||||
if rw.Code == 0 {
|
|
||||||
rw.Code = http.StatusOK
|
|
||||||
}
|
|
||||||
return len(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeader sets rw.Code.
|
|
||||||
func (rw *ResponseRecorder) WriteHeader(code int) {
|
|
||||||
rw.Code = code
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush sets rw.Flushed to true.
|
|
||||||
func (rw *ResponseRecorder) Flush() {
|
|
||||||
rw.Flushed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
func TestRouteMatchers(t *testing.T) {
|
|
||||||
var scheme, host, path, query, method string
|
|
||||||
var headers map[string]string
|
|
||||||
var resultVars map[bool]map[string]string
|
|
||||||
|
|
||||||
router := NewRouter()
|
|
||||||
router.NewRoute().Host("{var1}.google.com").
|
|
||||||
Path("/{var2:[a-z]+}/{var3:[0-9]+}").
|
|
||||||
Queries("foo", "bar").
|
|
||||||
Methods("GET").
|
|
||||||
Schemes("https").
|
|
||||||
Headers("x-requested-with", "XMLHttpRequest")
|
|
||||||
router.NewRoute().Host("www.{var4}.com").
|
|
||||||
PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
|
|
||||||
Queries("baz", "ding").
|
|
||||||
Methods("POST").
|
|
||||||
Schemes("http").
|
|
||||||
Headers("Content-Type", "application/json")
|
|
||||||
|
|
||||||
reset := func() {
|
|
||||||
// Everything match.
|
|
||||||
scheme = "https"
|
|
||||||
host = "www.google.com"
|
|
||||||
path = "/product/42"
|
|
||||||
query = "?foo=bar"
|
|
||||||
method = "GET"
|
|
||||||
headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
|
|
||||||
resultVars = map[bool]map[string]string{
|
|
||||||
true: {"var1": "www", "var2": "product", "var3": "42"},
|
|
||||||
false: {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reset2 := func() {
|
|
||||||
// Everything match.
|
|
||||||
scheme = "http"
|
|
||||||
host = "www.google.com"
|
|
||||||
path = "/foo/product/42/path/that/is/ignored"
|
|
||||||
query = "?baz=ding"
|
|
||||||
method = "POST"
|
|
||||||
headers = map[string]string{"Content-Type": "application/json"}
|
|
||||||
resultVars = map[bool]map[string]string{
|
|
||||||
true: {"var4": "google", "var5": "product", "var6": "42"},
|
|
||||||
false: {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match := func(shouldMatch bool) {
|
|
||||||
url := scheme + "://" + host + path + query
|
|
||||||
request, _ := http.NewRequest(method, url, nil)
|
|
||||||
for key, value := range headers {
|
|
||||||
request.Header.Add(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var routeMatch RouteMatch
|
|
||||||
matched := router.Match(request, &routeMatch)
|
|
||||||
if matched != shouldMatch {
|
|
||||||
// Need better messages. :)
|
|
||||||
if matched {
|
|
||||||
t.Errorf("Should match.")
|
|
||||||
} else {
|
|
||||||
t.Errorf("Should not match.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matched {
|
|
||||||
currentRoute := routeMatch.Route
|
|
||||||
if currentRoute == nil {
|
|
||||||
t.Errorf("Expected a current route.")
|
|
||||||
}
|
|
||||||
vars := routeMatch.Vars
|
|
||||||
expectedVars := resultVars[shouldMatch]
|
|
||||||
if len(vars) != len(expectedVars) {
|
|
||||||
t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
|
|
||||||
}
|
|
||||||
for name, value := range vars {
|
|
||||||
if expectedVars[name] != value {
|
|
||||||
t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1st route --------------------------------------------------------------
|
|
||||||
|
|
||||||
// Everything match.
|
|
||||||
reset()
|
|
||||||
match(true)
|
|
||||||
|
|
||||||
// Scheme doesn't match.
|
|
||||||
reset()
|
|
||||||
scheme = "http"
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Host doesn't match.
|
|
||||||
reset()
|
|
||||||
host = "www.mygoogle.com"
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Path doesn't match.
|
|
||||||
reset()
|
|
||||||
path = "/product/notdigits"
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Query doesn't match.
|
|
||||||
reset()
|
|
||||||
query = "?foo=baz"
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Method doesn't match.
|
|
||||||
reset()
|
|
||||||
method = "POST"
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Header doesn't match.
|
|
||||||
reset()
|
|
||||||
headers = map[string]string{}
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Everything match, again.
|
|
||||||
reset()
|
|
||||||
match(true)
|
|
||||||
|
|
||||||
// 2nd route --------------------------------------------------------------
|
|
||||||
|
|
||||||
// Everything match.
|
|
||||||
reset2()
|
|
||||||
match(true)
|
|
||||||
|
|
||||||
// Scheme doesn't match.
|
|
||||||
reset2()
|
|
||||||
scheme = "https"
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Host doesn't match.
|
|
||||||
reset2()
|
|
||||||
host = "sub.google.com"
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Path doesn't match.
|
|
||||||
reset2()
|
|
||||||
path = "/bar/product/42"
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Query doesn't match.
|
|
||||||
reset2()
|
|
||||||
query = "?foo=baz"
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Method doesn't match.
|
|
||||||
reset2()
|
|
||||||
method = "GET"
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Header doesn't match.
|
|
||||||
reset2()
|
|
||||||
headers = map[string]string{}
|
|
||||||
match(false)
|
|
||||||
|
|
||||||
// Everything match, again.
|
|
||||||
reset2()
|
|
||||||
match(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
type headerMatcherTest struct {
|
|
||||||
matcher headerMatcher
|
|
||||||
headers map[string]string
|
|
||||||
result bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var headerMatcherTests = []headerMatcherTest{
|
|
||||||
{
|
|
||||||
matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
|
|
||||||
headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
|
|
||||||
result: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
|
|
||||||
headers: map[string]string{"X-Requested-With": "anything"},
|
|
||||||
result: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
|
|
||||||
headers: map[string]string{},
|
|
||||||
result: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type hostMatcherTest struct {
|
|
||||||
matcher *Route
|
|
||||||
url string
|
|
||||||
vars map[string]string
|
|
||||||
result bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var hostMatcherTests = []hostMatcherTest{
|
|
||||||
{
|
|
||||||
matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
|
|
||||||
url: "http://abc.def.ghi/",
|
|
||||||
vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
|
|
||||||
result: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
|
|
||||||
url: "http://a.b.c/",
|
|
||||||
vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
|
|
||||||
result: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type methodMatcherTest struct {
|
|
||||||
matcher methodMatcher
|
|
||||||
method string
|
|
||||||
result bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var methodMatcherTests = []methodMatcherTest{
|
|
||||||
{
|
|
||||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
|
||||||
method: "GET",
|
|
||||||
result: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
|
||||||
method: "POST",
|
|
||||||
result: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
|
||||||
method: "PUT",
|
|
||||||
result: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
|
||||||
method: "DELETE",
|
|
||||||
result: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type pathMatcherTest struct {
|
|
||||||
matcher *Route
|
|
||||||
url string
|
|
||||||
vars map[string]string
|
|
||||||
result bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var pathMatcherTests = []pathMatcherTest{
|
|
||||||
{
|
|
||||||
matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
|
|
||||||
url: "http://localhost:8080/123/456/789",
|
|
||||||
vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
|
|
||||||
result: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
|
|
||||||
url: "http://localhost:8080/1/2/3",
|
|
||||||
vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
|
|
||||||
result: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type schemeMatcherTest struct {
|
|
||||||
matcher schemeMatcher
|
|
||||||
url string
|
|
||||||
result bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var schemeMatcherTests = []schemeMatcherTest{
|
|
||||||
{
|
|
||||||
matcher: schemeMatcher([]string{"http", "https"}),
|
|
||||||
url: "http://localhost:8080/",
|
|
||||||
result: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: schemeMatcher([]string{"http", "https"}),
|
|
||||||
url: "https://localhost:8080/",
|
|
||||||
result: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: schemeMatcher([]string{"https"}),
|
|
||||||
url: "http://localhost:8080/",
|
|
||||||
result: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: schemeMatcher([]string{"http"}),
|
|
||||||
url: "https://localhost:8080/",
|
|
||||||
result: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type urlBuildingTest struct {
|
|
||||||
route *Route
|
|
||||||
vars []string
|
|
||||||
url string
|
|
||||||
}
|
|
||||||
|
|
||||||
var urlBuildingTests = []urlBuildingTest{
|
|
||||||
{
|
|
||||||
route: new(Route).Host("foo.domain.com"),
|
|
||||||
vars: []string{},
|
|
||||||
url: "http://foo.domain.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: new(Route).Host("{subdomain}.domain.com"),
|
|
||||||
vars: []string{"subdomain", "bar"},
|
|
||||||
url: "http://bar.domain.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: new(Route).Host("foo.domain.com").Path("/articles"),
|
|
||||||
vars: []string{},
|
|
||||||
url: "http://foo.domain.com/articles",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: new(Route).Path("/articles"),
|
|
||||||
vars: []string{},
|
|
||||||
url: "/articles",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
|
|
||||||
vars: []string{"category", "technology", "id", "42"},
|
|
||||||
url: "/articles/technology/42",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
|
|
||||||
vars: []string{"subdomain", "foo", "category", "technology", "id", "42"},
|
|
||||||
url: "http://foo.domain.com/articles/technology/42",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHeaderMatcher(t *testing.T) {
|
|
||||||
for _, v := range headerMatcherTests {
|
|
||||||
request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
|
||||||
for key, value := range v.headers {
|
|
||||||
request.Header.Add(key, value)
|
|
||||||
}
|
|
||||||
var routeMatch RouteMatch
|
|
||||||
result := v.matcher.Match(request, &routeMatch)
|
|
||||||
if result != v.result {
|
|
||||||
if v.result {
|
|
||||||
t.Errorf("%#v: should match %v.", v.matcher, request.Header)
|
|
||||||
} else {
|
|
||||||
t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHostMatcher(t *testing.T) {
|
|
||||||
for _, v := range hostMatcherTests {
|
|
||||||
request, _ := http.NewRequest("GET", v.url, nil)
|
|
||||||
var routeMatch RouteMatch
|
|
||||||
result := v.matcher.Match(request, &routeMatch)
|
|
||||||
vars := routeMatch.Vars
|
|
||||||
if result != v.result {
|
|
||||||
if v.result {
|
|
||||||
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
|
||||||
} else {
|
|
||||||
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if result {
|
|
||||||
if len(vars) != len(v.vars) {
|
|
||||||
t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
|
|
||||||
}
|
|
||||||
for name, value := range vars {
|
|
||||||
if v.vars[name] != value {
|
|
||||||
t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(vars) != 0 {
|
|
||||||
t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMethodMatcher(t *testing.T) {
|
|
||||||
for _, v := range methodMatcherTests {
|
|
||||||
request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
|
|
||||||
var routeMatch RouteMatch
|
|
||||||
result := v.matcher.Match(request, &routeMatch)
|
|
||||||
if result != v.result {
|
|
||||||
if v.result {
|
|
||||||
t.Errorf("%#v: should match %v.", v.matcher, v.method)
|
|
||||||
} else {
|
|
||||||
t.Errorf("%#v: should not match %v.", v.matcher, v.method)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathMatcher(t *testing.T) {
|
|
||||||
for _, v := range pathMatcherTests {
|
|
||||||
request, _ := http.NewRequest("GET", v.url, nil)
|
|
||||||
var routeMatch RouteMatch
|
|
||||||
result := v.matcher.Match(request, &routeMatch)
|
|
||||||
vars := routeMatch.Vars
|
|
||||||
if result != v.result {
|
|
||||||
if v.result {
|
|
||||||
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
|
||||||
} else {
|
|
||||||
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if result {
|
|
||||||
if len(vars) != len(v.vars) {
|
|
||||||
t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
|
|
||||||
}
|
|
||||||
for name, value := range vars {
|
|
||||||
if v.vars[name] != value {
|
|
||||||
t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(vars) != 0 {
|
|
||||||
t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSchemeMatcher(t *testing.T) {
|
|
||||||
for _, v := range schemeMatcherTests {
|
|
||||||
request, _ := http.NewRequest("GET", v.url, nil)
|
|
||||||
var routeMatch RouteMatch
|
|
||||||
result := v.matcher.Match(request, &routeMatch)
|
|
||||||
if result != v.result {
|
|
||||||
if v.result {
|
|
||||||
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
|
||||||
} else {
|
|
||||||
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUrlBuilding(t *testing.T) {
|
|
||||||
|
|
||||||
for _, v := range urlBuildingTests {
|
|
||||||
u, _ := v.route.URL(v.vars...)
|
|
||||||
url := u.String()
|
|
||||||
if url != v.url {
|
|
||||||
t.Errorf("expected %v, got %v", v.url, url)
|
|
||||||
/*
|
|
||||||
reversePath := ""
|
|
||||||
reverseHost := ""
|
|
||||||
if v.route.pathTemplate != nil {
|
|
||||||
reversePath = v.route.pathTemplate.Reverse
|
|
||||||
}
|
|
||||||
if v.route.hostTemplate != nil {
|
|
||||||
reverseHost = v.route.hostTemplate.Reverse
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost)
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
}
|
|
||||||
|
|
||||||
router := NewRouter()
|
|
||||||
router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
|
|
||||||
|
|
||||||
url, _ := router.Get("article").URL("category", "technology", "id", "42")
|
|
||||||
expected := "/articles/technology/42"
|
|
||||||
if url.String() != expected {
|
|
||||||
t.Errorf("Expected %v, got %v", expected, url.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMatchedRouteName(t *testing.T) {
|
|
||||||
routeName := "stock"
|
|
||||||
router := NewRouter()
|
|
||||||
route := router.NewRoute().Path("/products/").Name(routeName)
|
|
||||||
|
|
||||||
url := "http://www.example.com/products/"
|
|
||||||
request, _ := http.NewRequest("GET", url, nil)
|
|
||||||
var rv RouteMatch
|
|
||||||
ok := router.Match(request, &rv)
|
|
||||||
|
|
||||||
if !ok || rv.Route != route {
|
|
||||||
t.Errorf("Expected same route, got %+v.", rv.Route)
|
|
||||||
}
|
|
||||||
|
|
||||||
retName := rv.Route.GetName()
|
|
||||||
if retName != routeName {
|
|
||||||
t.Errorf("Expected %q, got %q.", routeName, retName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubRouting(t *testing.T) {
|
|
||||||
// Example from docs.
|
|
||||||
router := NewRouter()
|
|
||||||
subrouter := router.NewRoute().Host("www.example.com").Subrouter()
|
|
||||||
route := subrouter.NewRoute().Path("/products/").Name("products")
|
|
||||||
|
|
||||||
url := "http://www.example.com/products/"
|
|
||||||
request, _ := http.NewRequest("GET", url, nil)
|
|
||||||
var rv RouteMatch
|
|
||||||
ok := router.Match(request, &rv)
|
|
||||||
|
|
||||||
if !ok || rv.Route != route {
|
|
||||||
t.Errorf("Expected same route, got %+v.", rv.Route)
|
|
||||||
}
|
|
||||||
|
|
||||||
u, _ := router.Get("products").URL()
|
|
||||||
builtURL := u.String()
|
|
||||||
// Yay, subroute aware of the domain when building!
|
|
||||||
if builtURL != url {
|
|
||||||
t.Errorf("Expected %q, got %q.", url, builtURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableNames(t *testing.T) {
|
|
||||||
route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
|
|
||||||
if route.err == nil {
|
|
||||||
t.Errorf("Expected error for duplicated variable names")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRedirectSlash(t *testing.T) {
|
|
||||||
var route *Route
|
|
||||||
var routeMatch RouteMatch
|
|
||||||
r := NewRouter()
|
|
||||||
|
|
||||||
r.StrictSlash(false)
|
|
||||||
route = r.NewRoute()
|
|
||||||
if route.strictSlash != false {
|
|
||||||
t.Errorf("Expected false redirectSlash.")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.StrictSlash(true)
|
|
||||||
route = r.NewRoute()
|
|
||||||
if route.strictSlash != true {
|
|
||||||
t.Errorf("Expected true redirectSlash.")
|
|
||||||
}
|
|
||||||
|
|
||||||
route = new(Route)
|
|
||||||
route.strictSlash = true
|
|
||||||
route.Path("/{arg1}/{arg2:[0-9]+}/")
|
|
||||||
request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
|
|
||||||
routeMatch = RouteMatch{}
|
|
||||||
_ = route.Match(request, &routeMatch)
|
|
||||||
vars := routeMatch.Vars
|
|
||||||
if vars["arg1"] != "foo" {
|
|
||||||
t.Errorf("Expected foo.")
|
|
||||||
}
|
|
||||||
if vars["arg2"] != "123" {
|
|
||||||
t.Errorf("Expected 123.")
|
|
||||||
}
|
|
||||||
rsp := NewRecorder()
|
|
||||||
routeMatch.Handler.ServeHTTP(rsp, request)
|
|
||||||
if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
|
|
||||||
t.Errorf("Expected redirect header.")
|
|
||||||
}
|
|
||||||
|
|
||||||
route = new(Route)
|
|
||||||
route.strictSlash = true
|
|
||||||
route.Path("/{arg1}/{arg2:[0-9]+}")
|
|
||||||
request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
|
|
||||||
routeMatch = RouteMatch{}
|
|
||||||
_ = route.Match(request, &routeMatch)
|
|
||||||
vars = routeMatch.Vars
|
|
||||||
if vars["arg1"] != "foo" {
|
|
||||||
t.Errorf("Expected foo.")
|
|
||||||
}
|
|
||||||
if vars["arg2"] != "123" {
|
|
||||||
t.Errorf("Expected 123.")
|
|
||||||
}
|
|
||||||
rsp = NewRecorder()
|
|
||||||
routeMatch.Handler.ServeHTTP(rsp, request)
|
|
||||||
if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
|
|
||||||
t.Errorf("Expected redirect header.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for the new regexp library, still not available in stable Go.
|
|
||||||
func TestNewRegexp(t *testing.T) {
|
|
||||||
var p *routeRegexp
|
|
||||||
var matches []string
|
|
||||||
|
|
||||||
tests := map[string]map[string][]string{
|
|
||||||
"/{foo:a{2}}": {
|
|
||||||
"/a": nil,
|
|
||||||
"/aa": {"aa"},
|
|
||||||
"/aaa": nil,
|
|
||||||
"/aaaa": nil,
|
|
||||||
},
|
|
||||||
"/{foo:a{2,}}": {
|
|
||||||
"/a": nil,
|
|
||||||
"/aa": {"aa"},
|
|
||||||
"/aaa": {"aaa"},
|
|
||||||
"/aaaa": {"aaaa"},
|
|
||||||
},
|
|
||||||
"/{foo:a{2,3}}": {
|
|
||||||
"/a": nil,
|
|
||||||
"/aa": {"aa"},
|
|
||||||
"/aaa": {"aaa"},
|
|
||||||
"/aaaa": nil,
|
|
||||||
},
|
|
||||||
"/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
|
|
||||||
"/a": nil,
|
|
||||||
"/ab": nil,
|
|
||||||
"/abc": nil,
|
|
||||||
"/abcd": nil,
|
|
||||||
"/abc/ab": {"abc", "ab"},
|
|
||||||
"/abc/abc": nil,
|
|
||||||
"/abcd/ab": nil,
|
|
||||||
},
|
|
||||||
`/{foo:\w{3,}}/{bar:\d{2,}}`: {
|
|
||||||
"/a": nil,
|
|
||||||
"/ab": nil,
|
|
||||||
"/abc": nil,
|
|
||||||
"/abc/1": nil,
|
|
||||||
"/abc/12": {"abc", "12"},
|
|
||||||
"/abcd/12": {"abcd", "12"},
|
|
||||||
"/abcd/123": {"abcd", "123"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for pattern, paths := range tests {
|
|
||||||
p, _ = newRouteRegexp(pattern, false, false, false, false, false)
|
|
||||||
for path, result := range paths {
|
|
||||||
matches = p.regexp.FindStringSubmatch(path)
|
|
||||||
if result == nil {
|
|
||||||
if matches != nil {
|
|
||||||
t.Errorf("%v should not match %v.", pattern, path)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(matches) != len(result)+1 {
|
|
||||||
t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
|
|
||||||
} else {
|
|
||||||
for k, v := range result {
|
|
||||||
if matches[k+1] != v {
|
|
||||||
t.Errorf("Expected %v, got %v.", v, matches[k+1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,323 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*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 matchQuery {
|
|
||||||
defaultPattern = "[^?&]*"
|
|
||||||
} else if matchHost {
|
|
||||||
defaultPattern = "[^.]+"
|
|
||||||
matchPrefix = false
|
|
||||||
}
|
|
||||||
// Only match strict slash if not matching
|
|
||||||
if matchPrefix || matchHost || matchQuery {
|
|
||||||
strictSlash = false
|
|
||||||
}
|
|
||||||
// Set a flag for strictSlash.
|
|
||||||
endSlash := false
|
|
||||||
if 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 strictSlash {
|
|
||||||
pattern.WriteString("[/]?")
|
|
||||||
}
|
|
||||||
if matchQuery {
|
|
||||||
// Add the default pattern if the query value is empty
|
|
||||||
if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
|
|
||||||
pattern.WriteString(defaultPattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !matchPrefix {
|
|
||||||
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,
|
|
||||||
matchHost: matchHost,
|
|
||||||
matchQuery: matchQuery,
|
|
||||||
strictSlash: strictSlash,
|
|
||||||
useEncodedPath: useEncodedPath,
|
|
||||||
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
|
|
||||||
// True for host match, false for path or query string match.
|
|
||||||
matchHost bool
|
|
||||||
// True for query string match, false for path and host match.
|
|
||||||
matchQuery bool
|
|
||||||
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
|
|
||||||
strictSlash bool
|
|
||||||
// Determines whether to use encoded path from getPath function or unencoded
|
|
||||||
// req.URL.Path for path matching
|
|
||||||
useEncodedPath bool
|
|
||||||
// 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.matchHost {
|
|
||||||
if r.matchQuery {
|
|
||||||
return r.matchQueryString(req)
|
|
||||||
}
|
|
||||||
path := req.URL.Path
|
|
||||||
if r.useEncodedPath {
|
|
||||||
path = getPath(req)
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
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.matchQuery {
|
|
||||||
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 = getPath(req)
|
|
||||||
}
|
|
||||||
// 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.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]]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,677 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
// Match everything.
|
|
||||||
for _, m := range r.matchers {
|
|
||||||
if matched := m.Match(req, match); !matched {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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, matchHost, matchPrefix, matchQuery bool) error {
|
|
||||||
if r.err != nil {
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
r.regexp = r.getRegexpGroup()
|
|
||||||
if !matchHost && !matchQuery {
|
|
||||||
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, matchHost, matchPrefix, matchQuery, r.strictSlash, 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 matchHost {
|
|
||||||
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 matchQuery {
|
|
||||||
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.
|
|
||||||
// It the value is an empty string, it will match any value if the key is set.
|
|
||||||
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, true, false, false)
|
|
||||||
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, false, false, false)
|
|
||||||
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, false, true, false)
|
|
||||||
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], false, false, true); 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
|
|
||||||
if r.regexp.host != nil {
|
|
||||||
if host, err = r.regexp.host.url(values); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
scheme = "http"
|
|
||||||
if r.buildScheme != "" {
|
|
||||||
scheme = r.buildScheme
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.regexp.path != nil {
|
|
||||||
if path, err = r.regexp.path.url(values); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &url.URL{
|
|
||||||
Scheme: scheme,
|
|
||||||
Host: host,
|
|
||||||
Path: path,
|
|
||||||
}, 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 r.buildScheme != "" {
|
|
||||||
u.Scheme = r.buildScheme
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 empty list 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, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
getNamedRoutes() map[string]*Route
|
|
||||||
getRegexpGroup() *routeRegexpGroup
|
|
||||||
buildVars(map[string]string) map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.3
|
|
||||||
- go: 1.4
|
|
||||||
- go: 1.5
|
|
||||||
- go: 1.6
|
|
||||||
- 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 ./...
|
|
@ -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.
|
|
@ -1,70 +0,0 @@
|
|||||||
pat
|
|
||||||
===
|
|
||||||
[![GoDoc](https://godoc.org/github.com/gorilla/pat?status.svg)](https://godoc.org/github.com/gorilla/pat)
|
|
||||||
[![Build Status](https://travis-ci.org/gorilla/pat.svg?branch=master)](https://travis-ci.org/gorilla/pat)
|
|
||||||
|
|
||||||
### How to use?
|
|
||||||
|
|
||||||
pat is pretty simple. The documentation lives [here](http://www.gorillatoolkit.org/pkg/pat).
|
|
||||||
|
|
||||||
### Install
|
|
||||||
With a properly configured Go toolchain:
|
|
||||||
```sh
|
|
||||||
go get github.com/gorilla/pat
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
Here's an example of a RESTful api:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/pat"
|
|
||||||
)
|
|
||||||
|
|
||||||
func homeHandler(wr http.ResponseWriter, req *http.Request) {
|
|
||||||
wr.WriteHeader(http.StatusOK)
|
|
||||||
wr.Write([]byte("Yay! We're home, Jim!"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAllTheThings(wr http.ResponseWriter, req *http.Request) {
|
|
||||||
wr.WriteHeader(http.StatusOK)
|
|
||||||
wr.Write([]byte("Look, Jim! Get all the things!"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func putOneThing(wr http.ResponseWriter, req *http.Request) {
|
|
||||||
wr.WriteHeader(http.StatusOK)
|
|
||||||
wr.Write([]byte("Look, Jim! Put one thing!"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteOneThing(wr http.ResponseWriter, req *http.Request) {
|
|
||||||
wr.WriteHeader(http.StatusOK)
|
|
||||||
wr.Write([]byte("Look, Jim! Delete one thing!"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
router := pat.New()
|
|
||||||
|
|
||||||
router.Get("/things", getAllTheThings)
|
|
||||||
|
|
||||||
router.Put("/things/{id}", putOneThing)
|
|
||||||
router.Delete("/things/{id}", deleteOneThing)
|
|
||||||
|
|
||||||
router.Get("/", homeHandler)
|
|
||||||
|
|
||||||
http.Handle("/", router)
|
|
||||||
|
|
||||||
log.Print("Listening on 127.0.0.1:8000...")
|
|
||||||
log.Fatal(http.ListenAndServe(":8000", nil))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Notice how the routes descend? That's because Pat will take the first route
|
|
||||||
that matches. For your own testing, take the line ```router.Get("/",
|
|
||||||
homeHandler)``` and put it above the other routes and run the example. When you
|
|
||||||
try to curl any of the routes, you'll only get what the homeHandler returns.
|
|
||||||
Design your routes carefully.
|
|
@ -1,67 +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 gorilla/pat is a request router and dispatcher with a pat-like
|
|
||||||
interface. It is an alternative to gorilla/mux that showcases how it can
|
|
||||||
be used as a base for different API flavors. Package pat is documented at:
|
|
||||||
|
|
||||||
http://godoc.org/github.com/bmizerany/pat
|
|
||||||
|
|
||||||
Let's start registering a couple of URL paths and handlers:
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := pat.New()
|
|
||||||
r.Get("/products", ProductsHandler)
|
|
||||||
r.Get("/articles", ArticlesHandler)
|
|
||||||
r.Get("/", HomeHandler)
|
|
||||||
http.Handle("/", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
Here we register three routes mapping URL paths to handlers. This is
|
|
||||||
equivalent to how http.HandleFunc() works: if an incoming GET request matches
|
|
||||||
one of the paths, the corresponding handler is called passing
|
|
||||||
(http.ResponseWriter, *http.Request) as parameters.
|
|
||||||
|
|
||||||
Note: gorilla/pat matches path prefixes, so you must register the most
|
|
||||||
specific paths first.
|
|
||||||
|
|
||||||
Note: differently from pat, these methods accept a handler function, and not an
|
|
||||||
http.Handler. We think this is shorter and more convenient. To set an
|
|
||||||
http.Handler, use the Add() method.
|
|
||||||
|
|
||||||
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 := pat.New()
|
|
||||||
r.Get("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
|
||||||
r.Get("/articles/{category}/", ArticlesCategoryHandler)
|
|
||||||
r.Get("/products/{key}", ProductHandler)
|
|
||||||
|
|
||||||
The names are used to create a map of route variables which are stored in the
|
|
||||||
URL query, prefixed by a colon:
|
|
||||||
|
|
||||||
category := req.URL.Query().Get(":category")
|
|
||||||
|
|
||||||
As in the gorilla/mux package, other matchers can be added to the registered
|
|
||||||
routes and URLs can be reversed as well. To build a URL for a route, first
|
|
||||||
add a name to it:
|
|
||||||
|
|
||||||
r.Get("/products/{key}", ProductHandler).Name("product")
|
|
||||||
|
|
||||||
Then you can get it using the name and generate a URL:
|
|
||||||
|
|
||||||
url, err := r.GetRoute("product").URL("key", "transmogrifier")
|
|
||||||
|
|
||||||
...and the result will be a url.URL with the following path:
|
|
||||||
|
|
||||||
"/products/transmogrifier"
|
|
||||||
|
|
||||||
Check the mux documentation for more details about URL building and extra
|
|
||||||
matchers:
|
|
||||||
|
|
||||||
http://gorilla-web.appspot.com/pkg/mux/
|
|
||||||
*/
|
|
||||||
package pat
|
|
@ -1,126 +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 pat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gorilla/context"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
// New returns a new router.
|
|
||||||
func New() *Router {
|
|
||||||
return &Router{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Router is a request router that implements a pat-like API.
|
|
||||||
//
|
|
||||||
// pat docs: http://godoc.org/github.com/bmizerany/pat
|
|
||||||
type Router struct {
|
|
||||||
mux.Router
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add registers a pattern with a handler for the given request method.
|
|
||||||
func (r *Router) Add(meth, pat string, h http.Handler) *mux.Route {
|
|
||||||
return r.NewRoute().PathPrefix(pat).Handler(h).Methods(meth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options registers a pattern with a handler for OPTIONS requests.
|
|
||||||
func (r *Router) Options(pat string, h http.HandlerFunc) *mux.Route {
|
|
||||||
return r.Add("OPTIONS", pat, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete registers a pattern with a handler for DELETE requests.
|
|
||||||
func (r *Router) Delete(pat string, h http.HandlerFunc) *mux.Route {
|
|
||||||
return r.Add("DELETE", pat, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head registers a pattern with a handler for HEAD requests.
|
|
||||||
func (r *Router) Head(pat string, h http.HandlerFunc) *mux.Route {
|
|
||||||
return r.Add("HEAD", pat, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get registers a pattern with a handler for GET requests.
|
|
||||||
func (r *Router) Get(pat string, h http.HandlerFunc) *mux.Route {
|
|
||||||
return r.Add("GET", pat, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post registers a pattern with a handler for POST requests.
|
|
||||||
func (r *Router) Post(pat string, h http.HandlerFunc) *mux.Route {
|
|
||||||
return r.Add("POST", pat, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put registers a pattern with a handler for PUT requests.
|
|
||||||
func (r *Router) Put(pat string, h http.HandlerFunc) *mux.Route {
|
|
||||||
return r.Add("PUT", pat, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Patch registers a pattern with a handler for PATCH requests.
|
|
||||||
func (r *Router) Patch(pat string, h http.HandlerFunc) *mux.Route {
|
|
||||||
return r.Add("PATCH", pat, h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP dispatches the handler registered in the matched route.
|
|
||||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
// Clean path to canonical form and redirect.
|
|
||||||
if p := cleanPath(req.URL.Path); p != req.URL.Path {
|
|
||||||
w.Header().Set("Location", p)
|
|
||||||
w.WriteHeader(http.StatusMovedPermanently)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var match mux.RouteMatch
|
|
||||||
var handler http.Handler
|
|
||||||
if matched := r.Match(req, &match); matched {
|
|
||||||
handler = match.Handler
|
|
||||||
registerVars(req, match.Vars)
|
|
||||||
}
|
|
||||||
if handler == nil {
|
|
||||||
if r.NotFoundHandler == nil {
|
|
||||||
r.NotFoundHandler = http.NotFoundHandler()
|
|
||||||
}
|
|
||||||
handler = r.NotFoundHandler
|
|
||||||
}
|
|
||||||
if !r.KeepContext {
|
|
||||||
defer context.Clear(req)
|
|
||||||
}
|
|
||||||
handler.ServeHTTP(w, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerVars adds the matched route variables to the URL query.
|
|
||||||
func registerVars(r *http.Request, vars map[string]string) {
|
|
||||||
parts, i := make([]string, len(vars)), 0
|
|
||||||
for key, value := range vars {
|
|
||||||
parts[i] = url.QueryEscape(":"+key) + "=" + url.QueryEscape(value)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
q := strings.Join(parts, "&")
|
|
||||||
if r.URL.RawQuery == "" {
|
|
||||||
r.URL.RawQuery = q
|
|
||||||
} else {
|
|
||||||
r.URL.RawQuery += "&" + q
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
@ -1,64 +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 pat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func myHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMatch(t *testing.T, meth, pat, path string, ok bool, vars map[string]string) {
|
|
||||||
r := New()
|
|
||||||
switch meth {
|
|
||||||
case "OPTIONS":
|
|
||||||
r.Options(pat, myHandler)
|
|
||||||
case "DELETE":
|
|
||||||
r.Delete(pat, myHandler)
|
|
||||||
case "HEAD":
|
|
||||||
r.Head(pat, myHandler)
|
|
||||||
case "GET":
|
|
||||||
r.Get(pat, myHandler)
|
|
||||||
case "POST":
|
|
||||||
r.Post(pat, myHandler)
|
|
||||||
case "PUT":
|
|
||||||
r.Put(pat, myHandler)
|
|
||||||
case "PATCH":
|
|
||||||
r.Patch(pat, myHandler)
|
|
||||||
}
|
|
||||||
req, _ := http.NewRequest(meth, "http://localhost"+path, nil)
|
|
||||||
m := mux.RouteMatch{}
|
|
||||||
if r.Match(req, &m) != ok {
|
|
||||||
if ok {
|
|
||||||
t.Errorf("Expected request to %q to match %q", path, pat)
|
|
||||||
} else {
|
|
||||||
t.Errorf("Expected request to %q to not match %q", path, pat)
|
|
||||||
}
|
|
||||||
} else if ok && vars != nil {
|
|
||||||
registerVars(req, m.Vars)
|
|
||||||
q := req.URL.Query()
|
|
||||||
for k, v := range vars {
|
|
||||||
if q.Get(k) != v {
|
|
||||||
t.Errorf("Variable missing: %q (value: %q)", k, q.Get(k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPatMatch(t *testing.T) {
|
|
||||||
testMatch(t, "OPTIONS", "/foo/{name}", "/foo/bar", true, map[string]string{":name": "bar"})
|
|
||||||
testMatch(t, "DELETE", "/foo/{name}", "/foo/bar", true, map[string]string{":name": "bar"})
|
|
||||||
testMatch(t, "HEAD", "/foo/{name}", "/foo/bar", true, map[string]string{":name": "bar"})
|
|
||||||
testMatch(t, "GET", "/foo/{name}", "/foo/bar/baz", true, map[string]string{":name": "bar"})
|
|
||||||
testMatch(t, "POST", "/foo/{name}/baz", "/foo/bar/baz", true, map[string]string{":name": "bar"})
|
|
||||||
testMatch(t, "PUT", "/foo/{name}/baz", "/foo/bar/baz/ding", true, map[string]string{":name": "bar"})
|
|
||||||
testMatch(t, "GET", "/foo/x{name}", "/foo/xbar", true, map[string]string{":name": "bar"})
|
|
||||||
testMatch(t, "GET", "/foo/x{name}", "/foo/xbar/baz", true, map[string]string{":name": "bar"})
|
|
||||||
testMatch(t, "PATCH", "/foo/x{name}", "/foo/xbar/baz", true, map[string]string{":name": "bar"})
|
|
||||||
}
|
|
@ -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.
|
|
@ -1,21 +0,0 @@
|
|||||||
### Extensions to the "os" package.
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/kardianos/osext?status.svg)](https://godoc.org/github.com/kardianos/osext)
|
|
||||||
|
|
||||||
## Find the current Executable and ExecutableFolder.
|
|
||||||
|
|
||||||
As of go1.8 the Executable function may be found in `os`. The Executable function
|
|
||||||
in the std lib `os` package is used if available.
|
|
||||||
|
|
||||||
There is sometimes utility in finding the current executable file
|
|
||||||
that is running. This can be used for upgrading the current executable
|
|
||||||
or finding resources located relative to the executable file. Both
|
|
||||||
working directory and the os.Args[0] value are arbitrary and cannot
|
|
||||||
be relied on; os.Args[0] can be "faked".
|
|
||||||
|
|
||||||
Multi-platform and supports:
|
|
||||||
* Linux
|
|
||||||
* OS X
|
|
||||||
* Windows
|
|
||||||
* Plan 9
|
|
||||||
* BSDs.
|
|
@ -1,33 +0,0 @@
|
|||||||
// Copyright 2012 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.
|
|
||||||
|
|
||||||
// Extensions to the standard "os" package.
|
|
||||||
package osext // import "github.com/kardianos/osext"
|
|
||||||
|
|
||||||
import "path/filepath"
|
|
||||||
|
|
||||||
var cx, ce = executableClean()
|
|
||||||
|
|
||||||
func executableClean() (string, error) {
|
|
||||||
p, err := executable()
|
|
||||||
return filepath.Clean(p), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Executable returns an absolute path that can be used to
|
|
||||||
// re-invoke the current program.
|
|
||||||
// It may not be valid after the current program exits.
|
|
||||||
func Executable() (string, error) {
|
|
||||||
return cx, ce
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns same path as Executable, returns just the folder
|
|
||||||
// path. Excludes the executable name and any trailing slash.
|
|
||||||
func ExecutableFolder() (string, error) {
|
|
||||||
p, err := Executable()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Dir(p), nil
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
//+build go1.8,!openbsd
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
func executable() (string, error) {
|
|
||||||
return os.Executable()
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//+build !go1.8
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func executable() (string, error) {
|
|
||||||
f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
return syscall.Fd2path(int(f.Fd()))
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.8,android !go1.8,linux !go1.8,netbsd !go1.8,solaris !go1.8,dragonfly
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func executable() (string, error) {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux", "android":
|
|
||||||
const deletedTag = " (deleted)"
|
|
||||||
execpath, err := os.Readlink("/proc/self/exe")
|
|
||||||
if err != nil {
|
|
||||||
return execpath, err
|
|
||||||
}
|
|
||||||
execpath = strings.TrimSuffix(execpath, deletedTag)
|
|
||||||
execpath = strings.TrimPrefix(execpath, deletedTag)
|
|
||||||
return execpath, nil
|
|
||||||
case "netbsd":
|
|
||||||
return os.Readlink("/proc/curproc/exe")
|
|
||||||
case "dragonfly":
|
|
||||||
return os.Readlink("/proc/curproc/file")
|
|
||||||
case "solaris":
|
|
||||||
return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
|
|
||||||
}
|
|
||||||
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.8,darwin !go1.8,freebsd openbsd
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var initCwd, initCwdErr = os.Getwd()
|
|
||||||
|
|
||||||
func executable() (string, error) {
|
|
||||||
var mib [4]int32
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "freebsd":
|
|
||||||
mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
|
|
||||||
case "darwin":
|
|
||||||
mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
|
|
||||||
case "openbsd":
|
|
||||||
mib = [4]int32{1 /* CTL_KERN */, 55 /* KERN_PROC_ARGS */, int32(os.Getpid()), 1 /* KERN_PROC_ARGV */}
|
|
||||||
}
|
|
||||||
|
|
||||||
n := uintptr(0)
|
|
||||||
// Get length.
|
|
||||||
_, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
|
|
||||||
if errNum != 0 {
|
|
||||||
return "", errNum
|
|
||||||
}
|
|
||||||
if n == 0 { // This shouldn't happen.
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
buf := make([]byte, n)
|
|
||||||
_, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
|
|
||||||
if errNum != 0 {
|
|
||||||
return "", errNum
|
|
||||||
}
|
|
||||||
if n == 0 { // This shouldn't happen.
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var execPath string
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "openbsd":
|
|
||||||
// buf now contains **argv, with pointers to each of the C-style
|
|
||||||
// NULL terminated arguments.
|
|
||||||
var args []string
|
|
||||||
argv := uintptr(unsafe.Pointer(&buf[0]))
|
|
||||||
Loop:
|
|
||||||
for {
|
|
||||||
argp := *(**[1 << 20]byte)(unsafe.Pointer(argv))
|
|
||||||
if argp == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for i := 0; uintptr(i) < n; i++ {
|
|
||||||
// we don't want the full arguments list
|
|
||||||
if string(argp[i]) == " " {
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
if argp[i] != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
args = append(args, string(argp[:i]))
|
|
||||||
n -= uintptr(i)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if n < unsafe.Sizeof(argv) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
argv += unsafe.Sizeof(argv)
|
|
||||||
n -= unsafe.Sizeof(argv)
|
|
||||||
}
|
|
||||||
execPath = args[0]
|
|
||||||
// There is no canonical way to get an executable path on
|
|
||||||
// OpenBSD, so check PATH in case we are called directly
|
|
||||||
if execPath[0] != '/' && execPath[0] != '.' {
|
|
||||||
execIsInPath, err := exec.LookPath(execPath)
|
|
||||||
if err == nil {
|
|
||||||
execPath = execIsInPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
for i, v := range buf {
|
|
||||||
if v == 0 {
|
|
||||||
buf = buf[:i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
execPath = string(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
// execPath will not be empty due to above checks.
|
|
||||||
// Try to get the absolute path if the execPath is not rooted.
|
|
||||||
if execPath[0] != '/' {
|
|
||||||
execPath, err = getAbs(execPath)
|
|
||||||
if err != nil {
|
|
||||||
return execPath, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// For darwin KERN_PROCARGS may return the path to a symlink rather than the
|
|
||||||
// actual executable.
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
if execPath, err = filepath.EvalSymlinks(execPath); err != nil {
|
|
||||||
return execPath, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return execPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAbs(execPath string) (string, error) {
|
|
||||||
if initCwdErr != nil {
|
|
||||||
return execPath, initCwdErr
|
|
||||||
}
|
|
||||||
// The execPath may begin with a "../" or a "./" so clean it first.
|
|
||||||
// Join the two paths, trailing and starting slashes undetermined, so use
|
|
||||||
// the generic Join function.
|
|
||||||
return filepath.Join(initCwd, filepath.Clean(execPath)), nil
|
|
||||||
}
|
|
@ -1,203 +0,0 @@
|
|||||||
// Copyright 2012 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 darwin linux freebsd netbsd windows openbsd
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE"
|
|
||||||
|
|
||||||
executableEnvValueMatch = "match"
|
|
||||||
executableEnvValueDelete = "delete"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPrintExecutable(t *testing.T) {
|
|
||||||
ef, err := Executable()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Executable failed: %v", err)
|
|
||||||
}
|
|
||||||
t.Log("Executable:", ef)
|
|
||||||
}
|
|
||||||
func TestPrintExecutableFolder(t *testing.T) {
|
|
||||||
ef, err := ExecutableFolder()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
|
||||||
}
|
|
||||||
t.Log("Executable Folder:", ef)
|
|
||||||
}
|
|
||||||
func TestExecutableFolder(t *testing.T) {
|
|
||||||
ef, err := ExecutableFolder()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
|
||||||
}
|
|
||||||
if ef[len(ef)-1] == filepath.Separator {
|
|
||||||
t.Fatal("ExecutableFolder ends with a trailing slash.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestExecutableMatch(t *testing.T) {
|
|
||||||
ep, err := Executable()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Executable failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fullpath to be of the form "dir/prog".
|
|
||||||
dir := filepath.Dir(filepath.Dir(ep))
|
|
||||||
fullpath, err := filepath.Rel(dir, ep)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("filepath.Rel: %v", err)
|
|
||||||
}
|
|
||||||
// Make child start with a relative program path.
|
|
||||||
// Alter argv[0] for child to verify getting real path without argv[0].
|
|
||||||
cmd := &exec.Cmd{
|
|
||||||
Dir: dir,
|
|
||||||
Path: fullpath,
|
|
||||||
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)},
|
|
||||||
}
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("exec(self) failed: %v", err)
|
|
||||||
}
|
|
||||||
outs := string(out)
|
|
||||||
if !filepath.IsAbs(outs) {
|
|
||||||
t.Fatalf("Child returned %q, want an absolute path", out)
|
|
||||||
}
|
|
||||||
if !sameFile(outs, ep) {
|
|
||||||
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExecutableDelete(t *testing.T) {
|
|
||||||
if runtime.GOOS != "linux" {
|
|
||||||
t.Skip()
|
|
||||||
}
|
|
||||||
fpath, err := Executable()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Executable failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, w := io.Pipe()
|
|
||||||
stderrBuff := &bytes.Buffer{}
|
|
||||||
stdoutBuff := &bytes.Buffer{}
|
|
||||||
cmd := &exec.Cmd{
|
|
||||||
Path: fpath,
|
|
||||||
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)},
|
|
||||||
Stdin: r,
|
|
||||||
Stderr: stderrBuff,
|
|
||||||
Stdout: stdoutBuff,
|
|
||||||
}
|
|
||||||
err = cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("exec(self) start failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempPath := fpath + "_copy"
|
|
||||||
_ = os.Remove(tempPath)
|
|
||||||
|
|
||||||
err = copyFile(tempPath, fpath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("copy file failed: %v", err)
|
|
||||||
}
|
|
||||||
err = os.Remove(fpath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("remove running test file failed: %v", err)
|
|
||||||
}
|
|
||||||
err = os.Rename(tempPath, fpath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("rename copy to previous name failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte{0})
|
|
||||||
w.Close()
|
|
||||||
|
|
||||||
err = cmd.Wait()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("exec wait failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
childPath := stderrBuff.String()
|
|
||||||
if !filepath.IsAbs(childPath) {
|
|
||||||
t.Fatalf("Child returned %q, want an absolute path", childPath)
|
|
||||||
}
|
|
||||||
if !sameFile(childPath, fpath) {
|
|
||||||
t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameFile(fn1, fn2 string) bool {
|
|
||||||
fi1, err := os.Stat(fn1)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fi2, err := os.Stat(fn2)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return os.SameFile(fi1, fi2)
|
|
||||||
}
|
|
||||||
func copyFile(dest, src string) error {
|
|
||||||
df, err := os.Create(dest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer df.Close()
|
|
||||||
|
|
||||||
sf, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sf.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(df, sf)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
env := os.Getenv(executableEnvVar)
|
|
||||||
switch env {
|
|
||||||
case "":
|
|
||||||
os.Exit(m.Run())
|
|
||||||
case executableEnvValueMatch:
|
|
||||||
// First chdir to another path.
|
|
||||||
dir := "/"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
dir = filepath.VolumeName(".")
|
|
||||||
}
|
|
||||||
os.Chdir(dir)
|
|
||||||
if ep, err := Executable(); err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, ep)
|
|
||||||
}
|
|
||||||
case executableEnvValueDelete:
|
|
||||||
bb := make([]byte, 1)
|
|
||||||
var err error
|
|
||||||
n, err := os.Stdin.Read(bb)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
if n != 1 {
|
|
||||||
fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
if ep, err := Executable(); err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//+build !go1.8
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unicode/utf16"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
kernel = syscall.MustLoadDLL("kernel32.dll")
|
|
||||||
getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetModuleFileName() with hModule = NULL
|
|
||||||
func executable() (exePath string, err error) {
|
|
||||||
return getModuleFileName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getModuleFileName() (string, error) {
|
|
||||||
var n uint32
|
|
||||||
b := make([]uint16, syscall.MAX_PATH)
|
|
||||||
size := uint32(len(b))
|
|
||||||
|
|
||||||
r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
|
|
||||||
n = uint32(r0)
|
|
||||||
if n == 0 {
|
|
||||||
return "", e1
|
|
||||||
}
|
|
||||||
return string(utf16.Decode(b[0:n])), nil
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
||||||
*.prof
|
|
@ -1,11 +0,0 @@
|
|||||||
language: go
|
|
||||||
go_import_path: github.com/pkg/errors
|
|
||||||
go:
|
|
||||||
- 1.4.3
|
|
||||||
- 1.5.4
|
|
||||||
- 1.6.2
|
|
||||||
- 1.7.1
|
|
||||||
- tip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test -v ./...
|
|
@ -1,23 +0,0 @@
|
|||||||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
|
||||||
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.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,52 +0,0 @@
|
|||||||
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors)
|
|
||||||
|
|
||||||
Package errors provides simple error handling primitives.
|
|
||||||
|
|
||||||
`go get github.com/pkg/errors`
|
|
||||||
|
|
||||||
The traditional error handling idiom in Go is roughly akin to
|
|
||||||
```go
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
```
|
|
||||||
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
|
||||||
|
|
||||||
## Adding context to an error
|
|
||||||
|
|
||||||
The errors.Wrap function returns a new error that adds context to the original error. For example
|
|
||||||
```go
|
|
||||||
_, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "read failed")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
## Retrieving the cause of an error
|
|
||||||
|
|
||||||
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
|
||||||
```go
|
|
||||||
type causer interface {
|
|
||||||
Cause() error
|
|
||||||
}
|
|
||||||
```
|
|
||||||
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
|
||||||
```go
|
|
||||||
switch err := errors.Cause(err).(type) {
|
|
||||||
case *MyError:
|
|
||||||
// handle specifically
|
|
||||||
default:
|
|
||||||
// unknown error
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
|
||||||
|
|
||||||
Before proposing a change, please discuss your change by raising an issue.
|
|
||||||
|
|
||||||
## Licence
|
|
||||||
|
|
||||||
BSD-2-Clause
|
|
@ -1,32 +0,0 @@
|
|||||||
version: build-{build}.{branch}
|
|
||||||
|
|
||||||
clone_folder: C:\gopath\src\github.com\pkg\errors
|
|
||||||
shallow_clone: true # for startup speed
|
|
||||||
|
|
||||||
environment:
|
|
||||||
GOPATH: C:\gopath
|
|
||||||
|
|
||||||
platform:
|
|
||||||
- x64
|
|
||||||
|
|
||||||
# http://www.appveyor.com/docs/installed-software
|
|
||||||
install:
|
|
||||||
# some helpful output for debugging builds
|
|
||||||
- go version
|
|
||||||
- go env
|
|
||||||
# pre-installed MinGW at C:\MinGW is 32bit only
|
|
||||||
# but MSYS2 at C:\msys64 has mingw64
|
|
||||||
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
|
||||||
- gcc --version
|
|
||||||
- g++ --version
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- go install -v ./...
|
|
||||||
|
|
||||||
test_script:
|
|
||||||
- set PATH=C:\gopath\bin;%PATH%
|
|
||||||
- go test -v ./...
|
|
||||||
|
|
||||||
#artifacts:
|
|
||||||
# - path: '%GOPATH%\bin\*.exe'
|
|
||||||
deploy: off
|
|
@ -1,59 +0,0 @@
|
|||||||
// +build go1.7
|
|
||||||
|
|
||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
stderrors "errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func noErrors(at, depth int) error {
|
|
||||||
if at >= depth {
|
|
||||||
return stderrors.New("no error")
|
|
||||||
}
|
|
||||||
return noErrors(at+1, depth)
|
|
||||||
}
|
|
||||||
func yesErrors(at, depth int) error {
|
|
||||||
if at >= depth {
|
|
||||||
return New("ye error")
|
|
||||||
}
|
|
||||||
return yesErrors(at+1, depth)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkErrors(b *testing.B) {
|
|
||||||
var toperr error
|
|
||||||
type run struct {
|
|
||||||
stack int
|
|
||||||
std bool
|
|
||||||
}
|
|
||||||
runs := []run{
|
|
||||||
{10, false},
|
|
||||||
{10, true},
|
|
||||||
{100, false},
|
|
||||||
{100, true},
|
|
||||||
{1000, false},
|
|
||||||
{1000, true},
|
|
||||||
}
|
|
||||||
for _, r := range runs {
|
|
||||||
part := "pkg/errors"
|
|
||||||
if r.std {
|
|
||||||
part = "errors"
|
|
||||||
}
|
|
||||||
name := fmt.Sprintf("%s-stack-%d", part, r.stack)
|
|
||||||
b.Run(name, func(b *testing.B) {
|
|
||||||
var err error
|
|
||||||
f := yesErrors
|
|
||||||
if r.std {
|
|
||||||
f = noErrors
|
|
||||||
}
|
|
||||||
b.ReportAllocs()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
err = f(0, r.stack)
|
|
||||||
}
|
|
||||||
b.StopTimer()
|
|
||||||
toperr = err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,269 +0,0 @@
|
|||||||
// Package errors provides simple error handling primitives.
|
|
||||||
//
|
|
||||||
// The traditional error handling idiom in Go is roughly akin to
|
|
||||||
//
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// which applied recursively up the call stack results in error reports
|
|
||||||
// without context or debugging information. The errors package allows
|
|
||||||
// programmers to add context to the failure path in their code in a way
|
|
||||||
// that does not destroy the original value of the error.
|
|
||||||
//
|
|
||||||
// Adding context to an error
|
|
||||||
//
|
|
||||||
// The errors.Wrap function returns a new error that adds context to the
|
|
||||||
// original error by recording a stack trace at the point Wrap is called,
|
|
||||||
// and the supplied message. For example
|
|
||||||
//
|
|
||||||
// _, err := ioutil.ReadAll(r)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.Wrap(err, "read failed")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// If additional control is required the errors.WithStack and errors.WithMessage
|
|
||||||
// functions destructure errors.Wrap into its component operations of annotating
|
|
||||||
// an error with a stack trace and an a message, respectively.
|
|
||||||
//
|
|
||||||
// Retrieving the cause of an error
|
|
||||||
//
|
|
||||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
|
||||||
// preceding error. Depending on the nature of the error it may be necessary
|
|
||||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
|
||||||
// for inspection. Any error value which implements this interface
|
|
||||||
//
|
|
||||||
// type causer interface {
|
|
||||||
// Cause() error
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
|
||||||
// the topmost error which does not implement causer, which is assumed to be
|
|
||||||
// the original cause. For example:
|
|
||||||
//
|
|
||||||
// switch err := errors.Cause(err).(type) {
|
|
||||||
// case *MyError:
|
|
||||||
// // handle specifically
|
|
||||||
// default:
|
|
||||||
// // unknown error
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// causer interface is not exported by this package, but is considered a part
|
|
||||||
// of stable public API.
|
|
||||||
//
|
|
||||||
// Formatted printing of errors
|
|
||||||
//
|
|
||||||
// All error values returned from this package implement fmt.Formatter and can
|
|
||||||
// be formatted by the fmt package. The following verbs are supported
|
|
||||||
//
|
|
||||||
// %s print the error. If the error has a Cause it will be
|
|
||||||
// printed recursively
|
|
||||||
// %v see %s
|
|
||||||
// %+v extended format. Each Frame of the error's StackTrace will
|
|
||||||
// be printed in detail.
|
|
||||||
//
|
|
||||||
// Retrieving the stack trace of an error or wrapper
|
|
||||||
//
|
|
||||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
|
||||||
// invoked. This information can be retrieved with the following interface.
|
|
||||||
//
|
|
||||||
// type stackTracer interface {
|
|
||||||
// StackTrace() errors.StackTrace
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Where errors.StackTrace is defined as
|
|
||||||
//
|
|
||||||
// type StackTrace []Frame
|
|
||||||
//
|
|
||||||
// The Frame type represents a call site in the stack trace. Frame supports
|
|
||||||
// the fmt.Formatter interface that can be used for printing information about
|
|
||||||
// the stack trace of this error. For example:
|
|
||||||
//
|
|
||||||
// if err, ok := err.(stackTracer); ok {
|
|
||||||
// for _, f := range err.StackTrace() {
|
|
||||||
// fmt.Printf("%+s:%d", f)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// stackTracer interface is not exported by this package, but is considered a part
|
|
||||||
// of stable public API.
|
|
||||||
//
|
|
||||||
// See the documentation for Frame.Format for more details.
|
|
||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// New returns an error with the supplied message.
|
|
||||||
// New also records the stack trace at the point it was called.
|
|
||||||
func New(message string) error {
|
|
||||||
return &fundamental{
|
|
||||||
msg: message,
|
|
||||||
stack: callers(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf formats according to a format specifier and returns the string
|
|
||||||
// as a value that satisfies error.
|
|
||||||
// Errorf also records the stack trace at the point it was called.
|
|
||||||
func Errorf(format string, args ...interface{}) error {
|
|
||||||
return &fundamental{
|
|
||||||
msg: fmt.Sprintf(format, args...),
|
|
||||||
stack: callers(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fundamental is an error that has a message and a stack, but no caller.
|
|
||||||
type fundamental struct {
|
|
||||||
msg string
|
|
||||||
*stack
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fundamental) Error() string { return f.msg }
|
|
||||||
|
|
||||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
|
||||||
switch verb {
|
|
||||||
case 'v':
|
|
||||||
if s.Flag('+') {
|
|
||||||
io.WriteString(s, f.msg)
|
|
||||||
f.stack.Format(s, verb)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case 's':
|
|
||||||
io.WriteString(s, f.msg)
|
|
||||||
case 'q':
|
|
||||||
fmt.Fprintf(s, "%q", f.msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
|
||||||
// If err is nil, WithStack returns nil.
|
|
||||||
func WithStack(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &withStack{
|
|
||||||
err,
|
|
||||||
callers(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type withStack struct {
|
|
||||||
error
|
|
||||||
*stack
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *withStack) Cause() error { return w.error }
|
|
||||||
|
|
||||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
|
||||||
switch verb {
|
|
||||||
case 'v':
|
|
||||||
if s.Flag('+') {
|
|
||||||
fmt.Fprintf(s, "%+v", w.Cause())
|
|
||||||
w.stack.Format(s, verb)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case 's':
|
|
||||||
io.WriteString(s, w.Error())
|
|
||||||
case 'q':
|
|
||||||
fmt.Fprintf(s, "%q", w.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap returns an error annotating err with a stack trace
|
|
||||||
// at the point Wrap is called, and the supplied message.
|
|
||||||
// If err is nil, Wrap returns nil.
|
|
||||||
func Wrap(err error, message string) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = &withMessage{
|
|
||||||
cause: err,
|
|
||||||
msg: message,
|
|
||||||
}
|
|
||||||
return &withStack{
|
|
||||||
err,
|
|
||||||
callers(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapf returns an error annotating err with a stack trace
|
|
||||||
// at the point Wrapf is call, and the format specifier.
|
|
||||||
// If err is nil, Wrapf returns nil.
|
|
||||||
func Wrapf(err error, format string, args ...interface{}) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = &withMessage{
|
|
||||||
cause: err,
|
|
||||||
msg: fmt.Sprintf(format, args...),
|
|
||||||
}
|
|
||||||
return &withStack{
|
|
||||||
err,
|
|
||||||
callers(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMessage annotates err with a new message.
|
|
||||||
// If err is nil, WithMessage returns nil.
|
|
||||||
func WithMessage(err error, message string) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &withMessage{
|
|
||||||
cause: err,
|
|
||||||
msg: message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type withMessage struct {
|
|
||||||
cause error
|
|
||||||
msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
|
||||||
func (w *withMessage) Cause() error { return w.cause }
|
|
||||||
|
|
||||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
|
||||||
switch verb {
|
|
||||||
case 'v':
|
|
||||||
if s.Flag('+') {
|
|
||||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
|
||||||
io.WriteString(s, w.msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case 's', 'q':
|
|
||||||
io.WriteString(s, w.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cause returns the underlying cause of the error, if possible.
|
|
||||||
// An error value has a cause if it implements the following
|
|
||||||
// interface:
|
|
||||||
//
|
|
||||||
// type causer interface {
|
|
||||||
// Cause() error
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// If the error does not implement Cause, the original error will
|
|
||||||
// be returned. If the error is nil, nil will be returned without further
|
|
||||||
// investigation.
|
|
||||||
func Cause(err error) error {
|
|
||||||
type causer interface {
|
|
||||||
Cause() error
|
|
||||||
}
|
|
||||||
|
|
||||||
for err != nil {
|
|
||||||
cause, ok := err.(causer)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
err = cause.Cause()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,226 +0,0 @@
|
|||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err string
|
|
||||||
want error
|
|
||||||
}{
|
|
||||||
{"", fmt.Errorf("")},
|
|
||||||
{"foo", fmt.Errorf("foo")},
|
|
||||||
{"foo", New("foo")},
|
|
||||||
{"string with format specifiers: %v", errors.New("string with format specifiers: %v")},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := New(tt.err)
|
|
||||||
if got.Error() != tt.want.Error() {
|
|
||||||
t.Errorf("New.Error(): got: %q, want %q", got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapNil(t *testing.T) {
|
|
||||||
got := Wrap(nil, "no error")
|
|
||||||
if got != nil {
|
|
||||||
t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
message string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{io.EOF, "read error", "read error: EOF"},
|
|
||||||
{Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := Wrap(tt.err, tt.message).Error()
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type nilError struct{}
|
|
||||||
|
|
||||||
func (nilError) Error() string { return "nil error" }
|
|
||||||
|
|
||||||
func TestCause(t *testing.T) {
|
|
||||||
x := New("error")
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
want error
|
|
||||||
}{{
|
|
||||||
// nil error is nil
|
|
||||||
err: nil,
|
|
||||||
want: nil,
|
|
||||||
}, {
|
|
||||||
// explicit nil error is nil
|
|
||||||
err: (error)(nil),
|
|
||||||
want: nil,
|
|
||||||
}, {
|
|
||||||
// typed nil is nil
|
|
||||||
err: (*nilError)(nil),
|
|
||||||
want: (*nilError)(nil),
|
|
||||||
}, {
|
|
||||||
// uncaused error is unaffected
|
|
||||||
err: io.EOF,
|
|
||||||
want: io.EOF,
|
|
||||||
}, {
|
|
||||||
// caused error returns cause
|
|
||||||
err: Wrap(io.EOF, "ignored"),
|
|
||||||
want: io.EOF,
|
|
||||||
}, {
|
|
||||||
err: x, // return from errors.New
|
|
||||||
want: x,
|
|
||||||
}, {
|
|
||||||
WithMessage(nil, "whoops"),
|
|
||||||
nil,
|
|
||||||
}, {
|
|
||||||
WithMessage(io.EOF, "whoops"),
|
|
||||||
io.EOF,
|
|
||||||
}, {
|
|
||||||
WithStack(nil),
|
|
||||||
nil,
|
|
||||||
}, {
|
|
||||||
WithStack(io.EOF),
|
|
||||||
io.EOF,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
got := Cause(tt.err)
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapfNil(t *testing.T) {
|
|
||||||
got := Wrapf(nil, "no error")
|
|
||||||
if got != nil {
|
|
||||||
t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapf(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
message string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{io.EOF, "read error", "read error: EOF"},
|
|
||||||
{Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"},
|
|
||||||
{Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := Wrapf(tt.err, tt.message).Error()
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorf(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{Errorf("read error without format specifiers"), "read error without format specifiers"},
|
|
||||||
{Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := tt.err.Error()
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithStackNil(t *testing.T) {
|
|
||||||
got := WithStack(nil)
|
|
||||||
if got != nil {
|
|
||||||
t.Errorf("WithStack(nil): got %#v, expected nil", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithStack(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{io.EOF, "EOF"},
|
|
||||||
{WithStack(io.EOF), "EOF"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := WithStack(tt.err).Error()
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithMessageNil(t *testing.T) {
|
|
||||||
got := WithMessage(nil, "no error")
|
|
||||||
if got != nil {
|
|
||||||
t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithMessage(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
message string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{io.EOF, "read error", "read error: EOF"},
|
|
||||||
{WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := WithMessage(tt.err, tt.message).Error()
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// errors.New, etc values are not expected to be compared by value
|
|
||||||
// but the change in errors#27 made them incomparable. Assert that
|
|
||||||
// various kinds of errors have a functional equality operator, even
|
|
||||||
// if the result of that equality is always false.
|
|
||||||
func TestErrorEquality(t *testing.T) {
|
|
||||||
vals := []error{
|
|
||||||
nil,
|
|
||||||
io.EOF,
|
|
||||||
errors.New("EOF"),
|
|
||||||
New("EOF"),
|
|
||||||
Errorf("EOF"),
|
|
||||||
Wrap(io.EOF, "EOF"),
|
|
||||||
Wrapf(io.EOF, "EOF%d", 2),
|
|
||||||
WithMessage(nil, "whoops"),
|
|
||||||
WithMessage(io.EOF, "whoops"),
|
|
||||||
WithStack(io.EOF),
|
|
||||||
WithStack(nil),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range vals {
|
|
||||||
for j := range vals {
|
|
||||||
_ = vals[i] == vals[j] // mustn't panic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,205 +0,0 @@
|
|||||||
package errors_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleNew() {
|
|
||||||
err := errors.New("whoops")
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
// Output: whoops
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleNew_printf() {
|
|
||||||
err := errors.New("whoops")
|
|
||||||
fmt.Printf("%+v", err)
|
|
||||||
|
|
||||||
// Example output:
|
|
||||||
// whoops
|
|
||||||
// github.com/pkg/errors_test.ExampleNew_printf
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:17
|
|
||||||
// testing.runExample
|
|
||||||
// /home/dfc/go/src/testing/example.go:114
|
|
||||||
// testing.RunExamples
|
|
||||||
// /home/dfc/go/src/testing/example.go:38
|
|
||||||
// testing.(*M).Run
|
|
||||||
// /home/dfc/go/src/testing/testing.go:744
|
|
||||||
// main.main
|
|
||||||
// /github.com/pkg/errors/_test/_testmain.go:106
|
|
||||||
// runtime.main
|
|
||||||
// /home/dfc/go/src/runtime/proc.go:183
|
|
||||||
// runtime.goexit
|
|
||||||
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWithMessage() {
|
|
||||||
cause := errors.New("whoops")
|
|
||||||
err := errors.WithMessage(cause, "oh noes")
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
// Output: oh noes: whoops
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWithStack() {
|
|
||||||
cause := errors.New("whoops")
|
|
||||||
err := errors.WithStack(cause)
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
// Output: whoops
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWithStack_printf() {
|
|
||||||
cause := errors.New("whoops")
|
|
||||||
err := errors.WithStack(cause)
|
|
||||||
fmt.Printf("%+v", err)
|
|
||||||
|
|
||||||
// Example Output:
|
|
||||||
// whoops
|
|
||||||
// github.com/pkg/errors_test.ExampleWithStack_printf
|
|
||||||
// /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55
|
|
||||||
// testing.runExample
|
|
||||||
// /usr/lib/go/src/testing/example.go:114
|
|
||||||
// testing.RunExamples
|
|
||||||
// /usr/lib/go/src/testing/example.go:38
|
|
||||||
// testing.(*M).Run
|
|
||||||
// /usr/lib/go/src/testing/testing.go:744
|
|
||||||
// main.main
|
|
||||||
// github.com/pkg/errors/_test/_testmain.go:106
|
|
||||||
// runtime.main
|
|
||||||
// /usr/lib/go/src/runtime/proc.go:183
|
|
||||||
// runtime.goexit
|
|
||||||
// /usr/lib/go/src/runtime/asm_amd64.s:2086
|
|
||||||
// github.com/pkg/errors_test.ExampleWithStack_printf
|
|
||||||
// /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56
|
|
||||||
// testing.runExample
|
|
||||||
// /usr/lib/go/src/testing/example.go:114
|
|
||||||
// testing.RunExamples
|
|
||||||
// /usr/lib/go/src/testing/example.go:38
|
|
||||||
// testing.(*M).Run
|
|
||||||
// /usr/lib/go/src/testing/testing.go:744
|
|
||||||
// main.main
|
|
||||||
// github.com/pkg/errors/_test/_testmain.go:106
|
|
||||||
// runtime.main
|
|
||||||
// /usr/lib/go/src/runtime/proc.go:183
|
|
||||||
// runtime.goexit
|
|
||||||
// /usr/lib/go/src/runtime/asm_amd64.s:2086
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWrap() {
|
|
||||||
cause := errors.New("whoops")
|
|
||||||
err := errors.Wrap(cause, "oh noes")
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
// Output: oh noes: whoops
|
|
||||||
}
|
|
||||||
|
|
||||||
func fn() error {
|
|
||||||
e1 := errors.New("error")
|
|
||||||
e2 := errors.Wrap(e1, "inner")
|
|
||||||
e3 := errors.Wrap(e2, "middle")
|
|
||||||
return errors.Wrap(e3, "outer")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleCause() {
|
|
||||||
err := fn()
|
|
||||||
fmt.Println(err)
|
|
||||||
fmt.Println(errors.Cause(err))
|
|
||||||
|
|
||||||
// Output: outer: middle: inner: error
|
|
||||||
// error
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWrap_extended() {
|
|
||||||
err := fn()
|
|
||||||
fmt.Printf("%+v\n", err)
|
|
||||||
|
|
||||||
// Example output:
|
|
||||||
// error
|
|
||||||
// github.com/pkg/errors_test.fn
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:47
|
|
||||||
// github.com/pkg/errors_test.ExampleCause_printf
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:63
|
|
||||||
// testing.runExample
|
|
||||||
// /home/dfc/go/src/testing/example.go:114
|
|
||||||
// testing.RunExamples
|
|
||||||
// /home/dfc/go/src/testing/example.go:38
|
|
||||||
// testing.(*M).Run
|
|
||||||
// /home/dfc/go/src/testing/testing.go:744
|
|
||||||
// main.main
|
|
||||||
// /github.com/pkg/errors/_test/_testmain.go:104
|
|
||||||
// runtime.main
|
|
||||||
// /home/dfc/go/src/runtime/proc.go:183
|
|
||||||
// runtime.goexit
|
|
||||||
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
|
||||||
// github.com/pkg/errors_test.fn
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner
|
|
||||||
// github.com/pkg/errors_test.fn
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle
|
|
||||||
// github.com/pkg/errors_test.fn
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWrapf() {
|
|
||||||
cause := errors.New("whoops")
|
|
||||||
err := errors.Wrapf(cause, "oh noes #%d", 2)
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
// Output: oh noes #2: whoops
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleErrorf_extended() {
|
|
||||||
err := errors.Errorf("whoops: %s", "foo")
|
|
||||||
fmt.Printf("%+v", err)
|
|
||||||
|
|
||||||
// Example output:
|
|
||||||
// whoops: foo
|
|
||||||
// github.com/pkg/errors_test.ExampleErrorf
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:101
|
|
||||||
// testing.runExample
|
|
||||||
// /home/dfc/go/src/testing/example.go:114
|
|
||||||
// testing.RunExamples
|
|
||||||
// /home/dfc/go/src/testing/example.go:38
|
|
||||||
// testing.(*M).Run
|
|
||||||
// /home/dfc/go/src/testing/testing.go:744
|
|
||||||
// main.main
|
|
||||||
// /github.com/pkg/errors/_test/_testmain.go:102
|
|
||||||
// runtime.main
|
|
||||||
// /home/dfc/go/src/runtime/proc.go:183
|
|
||||||
// runtime.goexit
|
|
||||||
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_stackTrace() {
|
|
||||||
type stackTracer interface {
|
|
||||||
StackTrace() errors.StackTrace
|
|
||||||
}
|
|
||||||
|
|
||||||
err, ok := errors.Cause(fn()).(stackTracer)
|
|
||||||
if !ok {
|
|
||||||
panic("oops, err does not implement stackTracer")
|
|
||||||
}
|
|
||||||
|
|
||||||
st := err.StackTrace()
|
|
||||||
fmt.Printf("%+v", st[0:2]) // top two frames
|
|
||||||
|
|
||||||
// Example output:
|
|
||||||
// github.com/pkg/errors_test.fn
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:47
|
|
||||||
// github.com/pkg/errors_test.Example_stackTrace
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:127
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleCause_printf() {
|
|
||||||
err := errors.Wrap(func() error {
|
|
||||||
return func() error {
|
|
||||||
return errors.Errorf("hello %s", fmt.Sprintf("world"))
|
|
||||||
}()
|
|
||||||
}(), "failed")
|
|
||||||
|
|
||||||
fmt.Printf("%v", err)
|
|
||||||
|
|
||||||
// Output: failed: hello world
|
|
||||||
}
|
|
@ -1,535 +0,0 @@
|
|||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFormatNew(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
New("error"),
|
|
||||||
"%s",
|
|
||||||
"error",
|
|
||||||
}, {
|
|
||||||
New("error"),
|
|
||||||
"%v",
|
|
||||||
"error",
|
|
||||||
}, {
|
|
||||||
New("error"),
|
|
||||||
"%+v",
|
|
||||||
"error\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatNew\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:26",
|
|
||||||
}, {
|
|
||||||
New("error"),
|
|
||||||
"%q",
|
|
||||||
`"error"`,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatErrorf(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
Errorf("%s", "error"),
|
|
||||||
"%s",
|
|
||||||
"error",
|
|
||||||
}, {
|
|
||||||
Errorf("%s", "error"),
|
|
||||||
"%v",
|
|
||||||
"error",
|
|
||||||
}, {
|
|
||||||
Errorf("%s", "error"),
|
|
||||||
"%+v",
|
|
||||||
"error\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatErrorf\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:56",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatWrap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
Wrap(New("error"), "error2"),
|
|
||||||
"%s",
|
|
||||||
"error2: error",
|
|
||||||
}, {
|
|
||||||
Wrap(New("error"), "error2"),
|
|
||||||
"%v",
|
|
||||||
"error2: error",
|
|
||||||
}, {
|
|
||||||
Wrap(New("error"), "error2"),
|
|
||||||
"%+v",
|
|
||||||
"error\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatWrap\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:82",
|
|
||||||
}, {
|
|
||||||
Wrap(io.EOF, "error"),
|
|
||||||
"%s",
|
|
||||||
"error: EOF",
|
|
||||||
}, {
|
|
||||||
Wrap(io.EOF, "error"),
|
|
||||||
"%v",
|
|
||||||
"error: EOF",
|
|
||||||
}, {
|
|
||||||
Wrap(io.EOF, "error"),
|
|
||||||
"%+v",
|
|
||||||
"EOF\n" +
|
|
||||||
"error\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatWrap\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:96",
|
|
||||||
}, {
|
|
||||||
Wrap(Wrap(io.EOF, "error1"), "error2"),
|
|
||||||
"%+v",
|
|
||||||
"EOF\n" +
|
|
||||||
"error1\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatWrap\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:103\n",
|
|
||||||
}, {
|
|
||||||
Wrap(New("error with space"), "context"),
|
|
||||||
"%q",
|
|
||||||
`"context: error with space"`,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatWrapf(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
Wrapf(io.EOF, "error%d", 2),
|
|
||||||
"%s",
|
|
||||||
"error2: EOF",
|
|
||||||
}, {
|
|
||||||
Wrapf(io.EOF, "error%d", 2),
|
|
||||||
"%v",
|
|
||||||
"error2: EOF",
|
|
||||||
}, {
|
|
||||||
Wrapf(io.EOF, "error%d", 2),
|
|
||||||
"%+v",
|
|
||||||
"EOF\n" +
|
|
||||||
"error2\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatWrapf\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:134",
|
|
||||||
}, {
|
|
||||||
Wrapf(New("error"), "error%d", 2),
|
|
||||||
"%s",
|
|
||||||
"error2: error",
|
|
||||||
}, {
|
|
||||||
Wrapf(New("error"), "error%d", 2),
|
|
||||||
"%v",
|
|
||||||
"error2: error",
|
|
||||||
}, {
|
|
||||||
Wrapf(New("error"), "error%d", 2),
|
|
||||||
"%+v",
|
|
||||||
"error\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatWrapf\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:149",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatWithStack(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want []string
|
|
||||||
}{{
|
|
||||||
WithStack(io.EOF),
|
|
||||||
"%s",
|
|
||||||
[]string{"EOF"},
|
|
||||||
}, {
|
|
||||||
WithStack(io.EOF),
|
|
||||||
"%v",
|
|
||||||
[]string{"EOF"},
|
|
||||||
}, {
|
|
||||||
WithStack(io.EOF),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:175"},
|
|
||||||
}, {
|
|
||||||
WithStack(New("error")),
|
|
||||||
"%s",
|
|
||||||
[]string{"error"},
|
|
||||||
}, {
|
|
||||||
WithStack(New("error")),
|
|
||||||
"%v",
|
|
||||||
[]string{"error"},
|
|
||||||
}, {
|
|
||||||
WithStack(New("error")),
|
|
||||||
"%+v",
|
|
||||||
[]string{"error",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:189",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:189"},
|
|
||||||
}, {
|
|
||||||
WithStack(WithStack(io.EOF)),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:197",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:197"},
|
|
||||||
}, {
|
|
||||||
WithStack(WithStack(Wrapf(io.EOF, "message"))),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF",
|
|
||||||
"message",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:205",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:205",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:205"},
|
|
||||||
}, {
|
|
||||||
WithStack(Errorf("error%d", 1)),
|
|
||||||
"%+v",
|
|
||||||
[]string{"error1",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:216",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:216"},
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatWithMessage(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want []string
|
|
||||||
}{{
|
|
||||||
WithMessage(New("error"), "error2"),
|
|
||||||
"%s",
|
|
||||||
[]string{"error2: error"},
|
|
||||||
}, {
|
|
||||||
WithMessage(New("error"), "error2"),
|
|
||||||
"%v",
|
|
||||||
[]string{"error2: error"},
|
|
||||||
}, {
|
|
||||||
WithMessage(New("error"), "error2"),
|
|
||||||
"%+v",
|
|
||||||
[]string{
|
|
||||||
"error",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:244",
|
|
||||||
"error2"},
|
|
||||||
}, {
|
|
||||||
WithMessage(io.EOF, "addition1"),
|
|
||||||
"%s",
|
|
||||||
[]string{"addition1: EOF"},
|
|
||||||
}, {
|
|
||||||
WithMessage(io.EOF, "addition1"),
|
|
||||||
"%v",
|
|
||||||
[]string{"addition1: EOF"},
|
|
||||||
}, {
|
|
||||||
WithMessage(io.EOF, "addition1"),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF", "addition1"},
|
|
||||||
}, {
|
|
||||||
WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
|
|
||||||
"%v",
|
|
||||||
[]string{"addition2: addition1: EOF"},
|
|
||||||
}, {
|
|
||||||
WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF", "addition1", "addition2"},
|
|
||||||
}, {
|
|
||||||
Wrap(WithMessage(io.EOF, "error1"), "error2"),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF", "error1", "error2",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:272"},
|
|
||||||
}, {
|
|
||||||
WithMessage(Errorf("error%d", 1), "error2"),
|
|
||||||
"%+v",
|
|
||||||
[]string{"error1",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:278",
|
|
||||||
"error2"},
|
|
||||||
}, {
|
|
||||||
WithMessage(WithStack(io.EOF), "error"),
|
|
||||||
"%+v",
|
|
||||||
[]string{
|
|
||||||
"EOF",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:285",
|
|
||||||
"error"},
|
|
||||||
}, {
|
|
||||||
WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"),
|
|
||||||
"%+v",
|
|
||||||
[]string{
|
|
||||||
"EOF",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:293",
|
|
||||||
"inside-error",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:293",
|
|
||||||
"outside-error"},
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatGeneric(t *testing.T) {
|
|
||||||
starts := []struct {
|
|
||||||
err error
|
|
||||||
want []string
|
|
||||||
}{
|
|
||||||
{New("new-error"), []string{
|
|
||||||
"new-error",
|
|
||||||
"github.com/pkg/errors.TestFormatGeneric\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:315"},
|
|
||||||
}, {Errorf("errorf-error"), []string{
|
|
||||||
"errorf-error",
|
|
||||||
"github.com/pkg/errors.TestFormatGeneric\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:319"},
|
|
||||||
}, {errors.New("errors-new-error"), []string{
|
|
||||||
"errors-new-error"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
wrappers := []wrapper{
|
|
||||||
{
|
|
||||||
func(err error) error { return WithMessage(err, "with-message") },
|
|
||||||
[]string{"with-message"},
|
|
||||||
}, {
|
|
||||||
func(err error) error { return WithStack(err) },
|
|
||||||
[]string{
|
|
||||||
"github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" +
|
|
||||||
".+/github.com/pkg/errors/format_test.go:333",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
func(err error) error { return Wrap(err, "wrap-error") },
|
|
||||||
[]string{
|
|
||||||
"wrap-error",
|
|
||||||
"github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" +
|
|
||||||
".+/github.com/pkg/errors/format_test.go:339",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
func(err error) error { return Wrapf(err, "wrapf-error%d", 1) },
|
|
||||||
[]string{
|
|
||||||
"wrapf-error1",
|
|
||||||
"github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" +
|
|
||||||
".+/github.com/pkg/errors/format_test.go:346",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for s := range starts {
|
|
||||||
err := starts[s].err
|
|
||||||
want := starts[s].want
|
|
||||||
testFormatCompleteCompare(t, s, err, "%+v", want, false)
|
|
||||||
testGenericRecursive(t, err, want, wrappers, 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
|
|
||||||
got := fmt.Sprintf(format, arg)
|
|
||||||
gotLines := strings.SplitN(got, "\n", -1)
|
|
||||||
wantLines := strings.SplitN(want, "\n", -1)
|
|
||||||
|
|
||||||
if len(wantLines) > len(gotLines) {
|
|
||||||
t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, w := range wantLines {
|
|
||||||
match, err := regexp.MatchString(w, gotLines[i])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var stackLineR = regexp.MustCompile(`\.`)
|
|
||||||
|
|
||||||
// parseBlocks parses input into a slice, where:
|
|
||||||
// - incase entry contains a newline, its a stacktrace
|
|
||||||
// - incase entry contains no newline, its a solo line.
|
|
||||||
//
|
|
||||||
// Detecting stack boundaries only works incase the WithStack-calls are
|
|
||||||
// to be found on the same line, thats why it is optionally here.
|
|
||||||
//
|
|
||||||
// Example use:
|
|
||||||
//
|
|
||||||
// for _, e := range blocks {
|
|
||||||
// if strings.ContainsAny(e, "\n") {
|
|
||||||
// // Match as stack
|
|
||||||
// } else {
|
|
||||||
// // Match as line
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
func parseBlocks(input string, detectStackboundaries bool) ([]string, error) {
|
|
||||||
var blocks []string
|
|
||||||
|
|
||||||
stack := ""
|
|
||||||
wasStack := false
|
|
||||||
lines := map[string]bool{} // already found lines
|
|
||||||
|
|
||||||
for _, l := range strings.Split(input, "\n") {
|
|
||||||
isStackLine := stackLineR.MatchString(l)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case !isStackLine && wasStack:
|
|
||||||
blocks = append(blocks, stack, l)
|
|
||||||
stack = ""
|
|
||||||
lines = map[string]bool{}
|
|
||||||
case isStackLine:
|
|
||||||
if wasStack {
|
|
||||||
// Detecting two stacks after another, possible cause lines match in
|
|
||||||
// our tests due to WithStack(WithStack(io.EOF)) on same line.
|
|
||||||
if detectStackboundaries {
|
|
||||||
if lines[l] {
|
|
||||||
if len(stack) == 0 {
|
|
||||||
return nil, errors.New("len of block must not be zero here")
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks = append(blocks, stack)
|
|
||||||
stack = l
|
|
||||||
lines = map[string]bool{l: true}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stack = stack + "\n" + l
|
|
||||||
} else {
|
|
||||||
stack = l
|
|
||||||
}
|
|
||||||
lines[l] = true
|
|
||||||
case !isStackLine && !wasStack:
|
|
||||||
blocks = append(blocks, l)
|
|
||||||
default:
|
|
||||||
return nil, errors.New("must not happen")
|
|
||||||
}
|
|
||||||
|
|
||||||
wasStack = isStackLine
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use up stack
|
|
||||||
if stack != "" {
|
|
||||||
blocks = append(blocks, stack)
|
|
||||||
}
|
|
||||||
return blocks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) {
|
|
||||||
gotStr := fmt.Sprintf(format, arg)
|
|
||||||
|
|
||||||
got, err := parseBlocks(gotStr, detectStackBoundaries)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(got) != len(want) {
|
|
||||||
t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q",
|
|
||||||
n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range got {
|
|
||||||
if strings.ContainsAny(want[i], "\n") {
|
|
||||||
// Match as stack
|
|
||||||
match, err := regexp.MatchString(want[i], got[i])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n",
|
|
||||||
n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Match as message
|
|
||||||
if got[i] != want[i] {
|
|
||||||
t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrapper struct {
|
|
||||||
wrap func(err error) error
|
|
||||||
want []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func prettyBlocks(blocks []string, prefix ...string) string {
|
|
||||||
var out []string
|
|
||||||
|
|
||||||
for _, b := range blocks {
|
|
||||||
out = append(out, fmt.Sprintf("%v", b))
|
|
||||||
}
|
|
||||||
|
|
||||||
return " " + strings.Join(out, "\n ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) {
|
|
||||||
if len(beforeWant) == 0 {
|
|
||||||
panic("beforeWant must not be empty")
|
|
||||||
}
|
|
||||||
for _, w := range list {
|
|
||||||
if len(w.want) == 0 {
|
|
||||||
panic("want must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := w.wrap(beforeErr)
|
|
||||||
|
|
||||||
// Copy required cause append(beforeWant, ..) modified beforeWant subtly.
|
|
||||||
beforeCopy := make([]string, len(beforeWant))
|
|
||||||
copy(beforeCopy, beforeWant)
|
|
||||||
|
|
||||||
beforeWant := beforeCopy
|
|
||||||
last := len(beforeWant) - 1
|
|
||||||
var want []string
|
|
||||||
|
|
||||||
// Merge two stacks behind each other.
|
|
||||||
if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") {
|
|
||||||
want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...)
|
|
||||||
} else {
|
|
||||||
want = append(beforeWant, w.want...)
|
|
||||||
}
|
|
||||||
|
|
||||||
testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false)
|
|
||||||
if maxDepth > 0 {
|
|
||||||
testGenericRecursive(t, err, want, list, maxDepth-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,178 +0,0 @@
|
|||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"path"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Frame represents a program counter inside a stack frame.
|
|
||||||
type Frame uintptr
|
|
||||||
|
|
||||||
// pc returns the program counter for this frame;
|
|
||||||
// multiple frames may have the same PC value.
|
|
||||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
|
||||||
|
|
||||||
// file returns the full path to the file that contains the
|
|
||||||
// function for this Frame's pc.
|
|
||||||
func (f Frame) file() string {
|
|
||||||
fn := runtime.FuncForPC(f.pc())
|
|
||||||
if fn == nil {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
file, _ := fn.FileLine(f.pc())
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
|
|
||||||
// line returns the line number of source code of the
|
|
||||||
// function for this Frame's pc.
|
|
||||||
func (f Frame) line() int {
|
|
||||||
fn := runtime.FuncForPC(f.pc())
|
|
||||||
if fn == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
_, line := fn.FileLine(f.pc())
|
|
||||||
return line
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format formats the frame according to the fmt.Formatter interface.
|
|
||||||
//
|
|
||||||
// %s source file
|
|
||||||
// %d source line
|
|
||||||
// %n function name
|
|
||||||
// %v equivalent to %s:%d
|
|
||||||
//
|
|
||||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
|
||||||
//
|
|
||||||
// %+s path of source file relative to the compile time GOPATH
|
|
||||||
// %+v equivalent to %+s:%d
|
|
||||||
func (f Frame) Format(s fmt.State, verb rune) {
|
|
||||||
switch verb {
|
|
||||||
case 's':
|
|
||||||
switch {
|
|
||||||
case s.Flag('+'):
|
|
||||||
pc := f.pc()
|
|
||||||
fn := runtime.FuncForPC(pc)
|
|
||||||
if fn == nil {
|
|
||||||
io.WriteString(s, "unknown")
|
|
||||||
} else {
|
|
||||||
file, _ := fn.FileLine(pc)
|
|
||||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
io.WriteString(s, path.Base(f.file()))
|
|
||||||
}
|
|
||||||
case 'd':
|
|
||||||
fmt.Fprintf(s, "%d", f.line())
|
|
||||||
case 'n':
|
|
||||||
name := runtime.FuncForPC(f.pc()).Name()
|
|
||||||
io.WriteString(s, funcname(name))
|
|
||||||
case 'v':
|
|
||||||
f.Format(s, 's')
|
|
||||||
io.WriteString(s, ":")
|
|
||||||
f.Format(s, 'd')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
|
||||||
type StackTrace []Frame
|
|
||||||
|
|
||||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
|
||||||
switch verb {
|
|
||||||
case 'v':
|
|
||||||
switch {
|
|
||||||
case s.Flag('+'):
|
|
||||||
for _, f := range st {
|
|
||||||
fmt.Fprintf(s, "\n%+v", f)
|
|
||||||
}
|
|
||||||
case s.Flag('#'):
|
|
||||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(s, "%v", []Frame(st))
|
|
||||||
}
|
|
||||||
case 's':
|
|
||||||
fmt.Fprintf(s, "%s", []Frame(st))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stack represents a stack of program counters.
|
|
||||||
type stack []uintptr
|
|
||||||
|
|
||||||
func (s *stack) Format(st fmt.State, verb rune) {
|
|
||||||
switch verb {
|
|
||||||
case 'v':
|
|
||||||
switch {
|
|
||||||
case st.Flag('+'):
|
|
||||||
for _, pc := range *s {
|
|
||||||
f := Frame(pc)
|
|
||||||
fmt.Fprintf(st, "\n%+v", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stack) StackTrace() StackTrace {
|
|
||||||
f := make([]Frame, len(*s))
|
|
||||||
for i := 0; i < len(f); i++ {
|
|
||||||
f[i] = Frame((*s)[i])
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func callers() *stack {
|
|
||||||
const depth = 32
|
|
||||||
var pcs [depth]uintptr
|
|
||||||
n := runtime.Callers(3, pcs[:])
|
|
||||||
var st stack = pcs[0:n]
|
|
||||||
return &st
|
|
||||||
}
|
|
||||||
|
|
||||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
|
||||||
func funcname(name string) string {
|
|
||||||
i := strings.LastIndex(name, "/")
|
|
||||||
name = name[i+1:]
|
|
||||||
i = strings.Index(name, ".")
|
|
||||||
return name[i+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func trimGOPATH(name, file string) string {
|
|
||||||
// Here we want to get the source file path relative to the compile time
|
|
||||||
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
|
||||||
// GOPATH at runtime, but we can infer the number of path segments in the
|
|
||||||
// GOPATH. We note that fn.Name() returns the function name qualified by
|
|
||||||
// the import path, which does not include the GOPATH. Thus we can trim
|
|
||||||
// segments from the beginning of the file path until the number of path
|
|
||||||
// separators remaining is one more than the number of path separators in
|
|
||||||
// the function name. For example, given:
|
|
||||||
//
|
|
||||||
// GOPATH /home/user
|
|
||||||
// file /home/user/src/pkg/sub/file.go
|
|
||||||
// fn.Name() pkg/sub.Type.Method
|
|
||||||
//
|
|
||||||
// We want to produce:
|
|
||||||
//
|
|
||||||
// pkg/sub/file.go
|
|
||||||
//
|
|
||||||
// From this we can easily see that fn.Name() has one less path separator
|
|
||||||
// than our desired output. We count separators from the end of the file
|
|
||||||
// path until it finds two more than in the function name and then move
|
|
||||||
// one character forward to preserve the initial path segment without a
|
|
||||||
// leading separator.
|
|
||||||
const sep = "/"
|
|
||||||
goal := strings.Count(name, sep) + 2
|
|
||||||
i := len(file)
|
|
||||||
for n := 0; n < goal; n++ {
|
|
||||||
i = strings.LastIndex(file[:i], sep)
|
|
||||||
if i == -1 {
|
|
||||||
// not enough separators found, set i so that the slice expression
|
|
||||||
// below leaves file unmodified
|
|
||||||
i = -len(sep)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// get back to 0 or trim the leading separator
|
|
||||||
file = file[i+len(sep):]
|
|
||||||
return file
|
|
||||||
}
|
|
@ -1,292 +0,0 @@
|
|||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var initpc, _, _, _ = runtime.Caller(0)
|
|
||||||
|
|
||||||
func TestFrameLine(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
Frame
|
|
||||||
want int
|
|
||||||
}{{
|
|
||||||
Frame(initpc),
|
|
||||||
9,
|
|
||||||
}, {
|
|
||||||
func() Frame {
|
|
||||||
var pc, _, _, _ = runtime.Caller(0)
|
|
||||||
return Frame(pc)
|
|
||||||
}(),
|
|
||||||
20,
|
|
||||||
}, {
|
|
||||||
func() Frame {
|
|
||||||
var pc, _, _, _ = runtime.Caller(1)
|
|
||||||
return Frame(pc)
|
|
||||||
}(),
|
|
||||||
28,
|
|
||||||
}, {
|
|
||||||
Frame(0), // invalid PC
|
|
||||||
0,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := tt.Frame.line()
|
|
||||||
want := tt.want
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type X struct{}
|
|
||||||
|
|
||||||
func (x X) val() Frame {
|
|
||||||
var pc, _, _, _ = runtime.Caller(0)
|
|
||||||
return Frame(pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *X) ptr() Frame {
|
|
||||||
var pc, _, _, _ = runtime.Caller(0)
|
|
||||||
return Frame(pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFrameFormat(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
Frame
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
Frame(initpc),
|
|
||||||
"%s",
|
|
||||||
"stack_test.go",
|
|
||||||
}, {
|
|
||||||
Frame(initpc),
|
|
||||||
"%+s",
|
|
||||||
"github.com/pkg/errors.init\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go",
|
|
||||||
}, {
|
|
||||||
Frame(0),
|
|
||||||
"%s",
|
|
||||||
"unknown",
|
|
||||||
}, {
|
|
||||||
Frame(0),
|
|
||||||
"%+s",
|
|
||||||
"unknown",
|
|
||||||
}, {
|
|
||||||
Frame(initpc),
|
|
||||||
"%d",
|
|
||||||
"9",
|
|
||||||
}, {
|
|
||||||
Frame(0),
|
|
||||||
"%d",
|
|
||||||
"0",
|
|
||||||
}, {
|
|
||||||
Frame(initpc),
|
|
||||||
"%n",
|
|
||||||
"init",
|
|
||||||
}, {
|
|
||||||
func() Frame {
|
|
||||||
var x X
|
|
||||||
return x.ptr()
|
|
||||||
}(),
|
|
||||||
"%n",
|
|
||||||
`\(\*X\).ptr`,
|
|
||||||
}, {
|
|
||||||
func() Frame {
|
|
||||||
var x X
|
|
||||||
return x.val()
|
|
||||||
}(),
|
|
||||||
"%n",
|
|
||||||
"X.val",
|
|
||||||
}, {
|
|
||||||
Frame(0),
|
|
||||||
"%n",
|
|
||||||
"",
|
|
||||||
}, {
|
|
||||||
Frame(initpc),
|
|
||||||
"%v",
|
|
||||||
"stack_test.go:9",
|
|
||||||
}, {
|
|
||||||
Frame(initpc),
|
|
||||||
"%+v",
|
|
||||||
"github.com/pkg/errors.init\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:9",
|
|
||||||
}, {
|
|
||||||
Frame(0),
|
|
||||||
"%v",
|
|
||||||
"unknown:0",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.Frame, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFuncname(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name, want string
|
|
||||||
}{
|
|
||||||
{"", ""},
|
|
||||||
{"runtime.main", "main"},
|
|
||||||
{"github.com/pkg/errors.funcname", "funcname"},
|
|
||||||
{"funcname", "funcname"},
|
|
||||||
{"io.copyBuffer", "copyBuffer"},
|
|
||||||
{"main.(*R).Write", "(*R).Write"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := funcname(tt.name)
|
|
||||||
want := tt.want
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrimGOPATH(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
Frame
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
Frame(initpc),
|
|
||||||
"github.com/pkg/errors/stack_test.go",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
pc := tt.Frame.pc()
|
|
||||||
fn := runtime.FuncForPC(pc)
|
|
||||||
file, _ := fn.FileLine(pc)
|
|
||||||
got := trimGOPATH(fn.Name(), file)
|
|
||||||
testFormatRegexp(t, i, got, "%s", tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStackTrace(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
want []string
|
|
||||||
}{{
|
|
||||||
New("ooh"), []string{
|
|
||||||
"github.com/pkg/errors.TestStackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:172",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Wrap(New("ooh"), "ahh"), []string{
|
|
||||||
"github.com/pkg/errors.TestStackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:177", // this is the stack of Wrap, not New
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Cause(Wrap(New("ooh"), "ahh")), []string{
|
|
||||||
"github.com/pkg/errors.TestStackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:182", // this is the stack of New
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
func() error { return New("ooh") }(), []string{
|
|
||||||
`github.com/pkg/errors.(func·009|TestStackTrace.func1)` +
|
|
||||||
"\n\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New
|
|
||||||
"github.com/pkg/errors.TestStackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New's caller
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Cause(func() error {
|
|
||||||
return func() error {
|
|
||||||
return Errorf("hello %s", fmt.Sprintf("world"))
|
|
||||||
}()
|
|
||||||
}()), []string{
|
|
||||||
`github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` +
|
|
||||||
"\n\t.+/github.com/pkg/errors/stack_test.go:196", // this is the stack of Errorf
|
|
||||||
`github.com/pkg/errors.(func·011|TestStackTrace.func2)` +
|
|
||||||
"\n\t.+/github.com/pkg/errors/stack_test.go:197", // this is the stack of Errorf's caller
|
|
||||||
"github.com/pkg/errors.TestStackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
for i, tt := range tests {
|
|
||||||
x, ok := tt.err.(interface {
|
|
||||||
StackTrace() StackTrace
|
|
||||||
})
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
st := x.StackTrace()
|
|
||||||
for j, want := range tt.want {
|
|
||||||
testFormatRegexp(t, i, st[j], "%+v", want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stackTrace() StackTrace {
|
|
||||||
const depth = 8
|
|
||||||
var pcs [depth]uintptr
|
|
||||||
n := runtime.Callers(1, pcs[:])
|
|
||||||
var st stack = pcs[0:n]
|
|
||||||
return st.StackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStackTraceFormat(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
StackTrace
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
nil,
|
|
||||||
"%s",
|
|
||||||
`\[\]`,
|
|
||||||
}, {
|
|
||||||
nil,
|
|
||||||
"%v",
|
|
||||||
`\[\]`,
|
|
||||||
}, {
|
|
||||||
nil,
|
|
||||||
"%+v",
|
|
||||||
"",
|
|
||||||
}, {
|
|
||||||
nil,
|
|
||||||
"%#v",
|
|
||||||
`\[\]errors.Frame\(nil\)`,
|
|
||||||
}, {
|
|
||||||
make(StackTrace, 0),
|
|
||||||
"%s",
|
|
||||||
`\[\]`,
|
|
||||||
}, {
|
|
||||||
make(StackTrace, 0),
|
|
||||||
"%v",
|
|
||||||
`\[\]`,
|
|
||||||
}, {
|
|
||||||
make(StackTrace, 0),
|
|
||||||
"%+v",
|
|
||||||
"",
|
|
||||||
}, {
|
|
||||||
make(StackTrace, 0),
|
|
||||||
"%#v",
|
|
||||||
`\[\]errors.Frame{}`,
|
|
||||||
}, {
|
|
||||||
stackTrace()[:2],
|
|
||||||
"%s",
|
|
||||||
`\[stack_test.go stack_test.go\]`,
|
|
||||||
}, {
|
|
||||||
stackTrace()[:2],
|
|
||||||
"%v",
|
|
||||||
`\[stack_test.go:225 stack_test.go:272\]`,
|
|
||||||
}, {
|
|
||||||
stackTrace()[:2],
|
|
||||||
"%+v",
|
|
||||||
"\n" +
|
|
||||||
"github.com/pkg/errors.stackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:225\n" +
|
|
||||||
"github.com/pkg/errors.TestStackTraceFormat\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:276",
|
|
||||||
}, {
|
|
||||||
stackTrace()[:2],
|
|
||||||
"%#v",
|
|
||||||
`\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
*.coverprofile
|
|
||||||
node_modules/
|
|
@ -1,39 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- node_modules
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.2.x
|
|
||||||
- 1.3.x
|
|
||||||
- 1.4.2
|
|
||||||
- 1.5.x
|
|
||||||
- 1.6.x
|
|
||||||
- 1.7.x
|
|
||||||
- master
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: master
|
|
||||||
include:
|
|
||||||
- go: 1.6.x
|
|
||||||
os: osx
|
|
||||||
- go: 1.7.x
|
|
||||||
os: osx
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- go get github.com/urfave/gfmrun/... || true
|
|
||||||
- go get golang.org/x/tools/... || true
|
|
||||||
- if [ ! -f node_modules/.bin/markdown-toc ] ; then
|
|
||||||
npm install markdown-toc ;
|
|
||||||
fi
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ./runtests gen
|
|
||||||
- ./runtests vet
|
|
||||||
- ./runtests test
|
|
||||||
- ./runtests gfmrun
|
|
||||||
- ./runtests toc
|
|
@ -1,392 +0,0 @@
|
|||||||
# Change Log
|
|
||||||
|
|
||||||
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
## [1.19.1] - 2016-11-21
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as
|
|
||||||
the `Action` for a command would cause it to error rather than calling the
|
|
||||||
function. Should not have a affected declarative cases using `func(c
|
|
||||||
*cli.Context) err)`.
|
|
||||||
- Shell completion now handles the case where the user specifies
|
|
||||||
`--generate-bash-completion` immediately after a flag that takes an argument.
|
|
||||||
Previously it call the application with `--generate-bash-completion` as the
|
|
||||||
flag value.
|
|
||||||
|
|
||||||
## [1.19.0] - 2016-11-19
|
|
||||||
### Added
|
|
||||||
- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`)
|
|
||||||
- A `Description` field was added to `App` for a more detailed description of
|
|
||||||
the application (similar to the existing `Description` field on `Command`)
|
|
||||||
- Flag type code generation via `go generate`
|
|
||||||
- Write to stderr and exit 1 if action returns non-nil error
|
|
||||||
- Added support for TOML to the `altsrc` loader
|
|
||||||
- `SkipArgReorder` was added to allow users to skip the argument reordering.
|
|
||||||
This is useful if you want to consider all "flags" after an argument as
|
|
||||||
arguments rather than flags (the default behavior of the stdlib `flag`
|
|
||||||
library). This is backported functionality from the [removal of the flag
|
|
||||||
reordering](https://github.com/urfave/cli/pull/398) in the unreleased version
|
|
||||||
2
|
|
||||||
- For formatted errors (those implementing `ErrorFormatter`), the errors will
|
|
||||||
be formatted during output. Compatible with `pkg/errors`.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Raise minimum tested/supported Go version to 1.2+
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Consider empty environment variables as set (previously environment variables
|
|
||||||
with the equivalent of `""` would be skipped rather than their value used).
|
|
||||||
- Return an error if the value in a given environment variable cannot be parsed
|
|
||||||
as the flag type. Previously these errors were silently swallowed.
|
|
||||||
- Print full error when an invalid flag is specified (which includes the invalid flag)
|
|
||||||
- `App.Writer` defaults to `stdout` when `nil`
|
|
||||||
- If no action is specified on a command or app, the help is now printed instead of `panic`ing
|
|
||||||
- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized)
|
|
||||||
- Correctly show help message if `-h` is provided to a subcommand
|
|
||||||
- `context.(Global)IsSet` now respects environment variables. Previously it
|
|
||||||
would return `false` if a flag was specified in the environment rather than
|
|
||||||
as an argument
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
|
||||||
- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This
|
|
||||||
fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well
|
|
||||||
as `altsrc` where Go would complain that the types didn't match
|
|
||||||
|
|
||||||
## [1.18.1] - 2016-08-28
|
|
||||||
### Fixed
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported)
|
|
||||||
|
|
||||||
## [1.18.0] - 2016-06-27
|
|
||||||
### Added
|
|
||||||
- `./runtests` test runner with coverage tracking by default
|
|
||||||
- testing on OS X
|
|
||||||
- testing on Windows
|
|
||||||
- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Use spaces for alignment in help/usage output instead of tabs, making the
|
|
||||||
output alignment consistent regardless of tab width
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Printing of command aliases in help text
|
|
||||||
- Printing of visible flags for both struct and struct pointer flags
|
|
||||||
- Display the `help` subcommand when using `CommandCategories`
|
|
||||||
- No longer swallows `panic`s that occur within the `Action`s themselves when
|
|
||||||
detecting the signature of the `Action` field
|
|
||||||
|
|
||||||
## [1.17.1] - 2016-08-28
|
|
||||||
### Fixed
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
|
||||||
|
|
||||||
## [1.17.0] - 2016-05-09
|
|
||||||
### Added
|
|
||||||
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
|
|
||||||
- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool`
|
|
||||||
- Support for hiding commands by setting `Hidden: true` -- this will hide the
|
|
||||||
commands in help output
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
|
|
||||||
quoted in help text output.
|
|
||||||
- All flag types now include `(default: {value})` strings following usage when a
|
|
||||||
default value can be (reasonably) detected.
|
|
||||||
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
|
|
||||||
with non-slice flag types
|
|
||||||
- Apps now exit with a code of 3 if an unknown subcommand is specified
|
|
||||||
(previously they printed "No help topic for...", but still exited 0. This
|
|
||||||
makes it easier to script around apps built using `cli` since they can trust
|
|
||||||
that a 0 exit code indicated a successful execution.
|
|
||||||
- cleanups based on [Go Report Card
|
|
||||||
feedback](https://goreportcard.com/report/github.com/urfave/cli)
|
|
||||||
|
|
||||||
## [1.16.1] - 2016-08-28
|
|
||||||
### Fixed
|
|
||||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
|
||||||
|
|
||||||
## [1.16.0] - 2016-05-02
|
|
||||||
### Added
|
|
||||||
- `Hidden` field on all flag struct types to omit from generated help text
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from
|
|
||||||
generated help text via the `Hidden` field
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- handling of error values in `HandleAction` and `HandleExitCoder`
|
|
||||||
|
|
||||||
## [1.15.0] - 2016-04-30
|
|
||||||
### Added
|
|
||||||
- This file!
|
|
||||||
- Support for placeholders in flag usage strings
|
|
||||||
- `App.Metadata` map for arbitrary data/state management
|
|
||||||
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
|
|
||||||
parsing.
|
|
||||||
- Support for nested lookup of dot-delimited keys in structures loaded from
|
|
||||||
YAML.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- The `App.Action` and `Command.Action` now prefer a return signature of
|
|
||||||
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil
|
|
||||||
`error` is returned, there may be two outcomes:
|
|
||||||
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called
|
|
||||||
automatically
|
|
||||||
- Else the error is bubbled up and returned from `App.Run`
|
|
||||||
- Specifying an `Action` with the legacy return signature of
|
|
||||||
`func(*cli.Context)` will produce a deprecation message to stderr
|
|
||||||
- Specifying an `Action` that is not a `func` type will produce a non-zero exit
|
|
||||||
from `App.Run`
|
|
||||||
- Specifying an `Action` func that has an invalid (input) signature will
|
|
||||||
produce a non-zero exit from `App.Run`
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
- <a name="deprecated-cli-app-runandexitonerror"></a>
|
|
||||||
`cli.App.RunAndExitOnError`, which should now be done by returning an error
|
|
||||||
that fulfills `cli.ExitCoder` to `cli.App.Run`.
|
|
||||||
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for
|
|
||||||
`cli.App.Action` of `func(*cli.Context)`, which should now have a return
|
|
||||||
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Added missing `*cli.Context.GlobalFloat64` method
|
|
||||||
|
|
||||||
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Codebeat badge
|
|
||||||
- Support for categorization via `CategorizedHelp` and `Categories` on app.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Ensure version is not shown in help text when `HideVersion` set.
|
|
||||||
|
|
||||||
## [1.13.0] - 2016-03-06 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- YAML file input support.
|
|
||||||
- `NArg` method on context.
|
|
||||||
|
|
||||||
## [1.12.0] - 2016-02-17 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Custom usage error handling.
|
|
||||||
- Custom text support in `USAGE` section of help output.
|
|
||||||
- Improved help messages for empty strings.
|
|
||||||
- AppVeyor CI configuration.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Removed `panic` from default help printer func.
|
|
||||||
- De-duping and optimizations.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Correctly handle `Before`/`After` at command level when no subcommands.
|
|
||||||
- Case of literal `-` argument causing flag reordering.
|
|
||||||
- Environment variable hints on Windows.
|
|
||||||
- Docs updates.
|
|
||||||
|
|
||||||
## [1.11.1] - 2015-12-21 (backfilled 2016-04-25)
|
|
||||||
### Changed
|
|
||||||
- Use `path.Base` in `Name` and `HelpName`
|
|
||||||
- Export `GetName` on flag types.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Flag parsing when skipping is enabled.
|
|
||||||
- Test output cleanup.
|
|
||||||
- Move completion check to account for empty input case.
|
|
||||||
|
|
||||||
## [1.11.0] - 2015-11-15 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Destination scan support for flags.
|
|
||||||
- Testing against `tip` in Travis CI config.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Go version in Travis CI config.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Removed redundant tests.
|
|
||||||
- Use correct example naming in tests.
|
|
||||||
|
|
||||||
## [1.10.2] - 2015-10-29 (backfilled 2016-04-25)
|
|
||||||
### Fixed
|
|
||||||
- Remove unused var in bash completion.
|
|
||||||
|
|
||||||
## [1.10.1] - 2015-10-21 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Coverage and reference logos in README.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Use specified values in help and version parsing.
|
|
||||||
- Only display app version and help message once.
|
|
||||||
|
|
||||||
## [1.10.0] - 2015-10-06 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- More tests for existing functionality.
|
|
||||||
- `ArgsUsage` at app and command level for help text flexibility.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Honor `HideHelp` and `HideVersion` in `App.Run`.
|
|
||||||
- Remove juvenile word from README.
|
|
||||||
|
|
||||||
## [1.9.0] - 2015-09-08 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- `FullName` on command with accompanying help output update.
|
|
||||||
- Set default `$PROG` in bash completion.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Docs formatting.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Removed self-referential imports in tests.
|
|
||||||
|
|
||||||
## [1.8.0] - 2015-06-30 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Support for `Copyright` at app level.
|
|
||||||
- `Parent` func at context level to walk up context lineage.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Global flag processing at top level.
|
|
||||||
|
|
||||||
## [1.7.1] - 2015-06-11 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Aggregate errors from `Before`/`After` funcs.
|
|
||||||
- Doc comments on flag structs.
|
|
||||||
- Include non-global flags when checking version and help.
|
|
||||||
- Travis CI config updates.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Ensure slice type flags have non-nil values.
|
|
||||||
- Collect global flags from the full command hierarchy.
|
|
||||||
- Docs prose.
|
|
||||||
|
|
||||||
## [1.7.0] - 2015-05-03 (backfilled 2016-04-25)
|
|
||||||
### Changed
|
|
||||||
- `HelpPrinter` signature includes output writer.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Specify go 1.1+ in docs.
|
|
||||||
- Set `Writer` when running command as app.
|
|
||||||
|
|
||||||
## [1.6.0] - 2015-03-23 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Multiple author support.
|
|
||||||
- `NumFlags` at context level.
|
|
||||||
- `Aliases` at command level.
|
|
||||||
|
|
||||||
### Deprecated
|
|
||||||
- `ShortName` at command level.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Subcommand help output.
|
|
||||||
- Backward compatible support for deprecated `Author` and `Email` fields.
|
|
||||||
- Docs regarding `Names`/`Aliases`.
|
|
||||||
|
|
||||||
## [1.5.0] - 2015-02-20 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- `After` hook func support at app and command level.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Use parsed context when running command as subcommand.
|
|
||||||
- Docs prose.
|
|
||||||
|
|
||||||
## [1.4.1] - 2015-01-09 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Support for hiding `-h / --help` flags, but not `help` subcommand.
|
|
||||||
- Stop flag parsing after `--`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Help text for generic flags to specify single value.
|
|
||||||
- Use double quotes in output for defaults.
|
|
||||||
- Use `ParseInt` instead of `ParseUint` for int environment var values.
|
|
||||||
- Use `0` as base when parsing int environment var values.
|
|
||||||
|
|
||||||
## [1.4.0] - 2014-12-12 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Support for environment variable lookup "cascade".
|
|
||||||
- Support for `Stdout` on app for output redirection.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Print command help instead of app help in `ShowCommandHelp`.
|
|
||||||
|
|
||||||
## [1.3.1] - 2014-11-13 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- Docs and example code updates.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Default `-v / --version` flag made optional.
|
|
||||||
|
|
||||||
## [1.3.0] - 2014-08-10 (backfilled 2016-04-25)
|
|
||||||
### Added
|
|
||||||
- `FlagNames` at context level.
|
|
||||||
- Exposed `VersionPrinter` var for more control over version output.
|
|
||||||
- Zsh completion hook.
|
|
||||||
- `AUTHOR` section in default app help template.
|
|
||||||
- Contribution guidelines.
|
|
||||||
- `DurationFlag` type.
|
|
||||||
|
|
||||||
## [1.2.0] - 2014-08-02
|
|
||||||
### Added
|
|
||||||
- Support for environment variable defaults on flags plus tests.
|
|
||||||
|
|
||||||
## [1.1.0] - 2014-07-15
|
|
||||||
### Added
|
|
||||||
- Bash completion.
|
|
||||||
- Optional hiding of built-in help command.
|
|
||||||
- Optional skipping of flag parsing at command level.
|
|
||||||
- `Author`, `Email`, and `Compiled` metadata on app.
|
|
||||||
- `Before` hook func support at app and command level.
|
|
||||||
- `CommandNotFound` func support at app level.
|
|
||||||
- Command reference available on context.
|
|
||||||
- `GenericFlag` type.
|
|
||||||
- `Float64Flag` type.
|
|
||||||
- `BoolTFlag` type.
|
|
||||||
- `IsSet` flag helper on context.
|
|
||||||
- More flag lookup funcs at context level.
|
|
||||||
- More tests & docs.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Help template updates to account for presence/absence of flags.
|
|
||||||
- Separated subcommand help template.
|
|
||||||
- Exposed `HelpPrinter` var for more control over help output.
|
|
||||||
|
|
||||||
## [1.0.0] - 2013-11-01
|
|
||||||
### Added
|
|
||||||
- `help` flag in default app flag set and each command flag set.
|
|
||||||
- Custom handling of argument parsing errors.
|
|
||||||
- Command lookup by name at app level.
|
|
||||||
- `StringSliceFlag` type and supporting `StringSlice` type.
|
|
||||||
- `IntSliceFlag` type and supporting `IntSlice` type.
|
|
||||||
- Slice type flag lookups by name at context level.
|
|
||||||
- Export of app and command help functions.
|
|
||||||
- More tests & docs.
|
|
||||||
|
|
||||||
## 0.1.0 - 2013-07-22
|
|
||||||
### Added
|
|
||||||
- Initial implementation.
|
|
||||||
|
|
||||||
[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD
|
|
||||||
[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0
|
|
||||||
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
|
|
||||||
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
|
|
||||||
[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0
|
|
||||||
[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0
|
|
||||||
[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0
|
|
||||||
[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0
|
|
||||||
[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1
|
|
||||||
[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0
|
|
||||||
[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2
|
|
||||||
[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1
|
|
||||||
[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0
|
|
||||||
[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0
|
|
||||||
[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0
|
|
||||||
[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1
|
|
||||||
[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0
|
|
||||||
[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0
|
|
||||||
[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0
|
|
||||||
[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1
|
|
||||||
[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0
|
|
||||||
[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1
|
|
||||||
[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0
|
|
||||||
[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0
|
|
||||||
[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0
|
|
||||||
[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0
|
|
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016 Jeremy Saenz & Contributors
|
|
||||||
|
|
||||||
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.
|
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
|||||||
package altsrc
|
|
||||||
|
|
||||||
//go:generate python ../generate-flag-types altsrc -i ../flag-types.json -o flag_generated.go
|
|
@ -1,261 +0,0 @@
|
|||||||
package altsrc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FlagInputSourceExtension is an extension interface of cli.Flag that
|
|
||||||
// allows a value to be set on the existing parsed flags.
|
|
||||||
type FlagInputSourceExtension interface {
|
|
||||||
cli.Flag
|
|
||||||
ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyInputSourceValues iterates over all provided flags and
|
|
||||||
// executes ApplyInputSourceValue on flags implementing the
|
|
||||||
// FlagInputSourceExtension interface to initialize these flags
|
|
||||||
// to an alternate input source.
|
|
||||||
func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error {
|
|
||||||
for _, f := range flags {
|
|
||||||
inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension)
|
|
||||||
if isType {
|
|
||||||
err := inputSourceExtendedFlag.ApplyInputSourceValue(context, inputSourceContext)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitInputSource is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
|
|
||||||
// input source based on the func provided. If there is no error it will then apply the new input source to any flags
|
|
||||||
// that are supported by the input source
|
|
||||||
func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc {
|
|
||||||
return func(context *cli.Context) error {
|
|
||||||
inputSource, err := createInputSource()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApplyInputSourceValues(context, inputSource, flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
|
|
||||||
// input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is
|
|
||||||
// no error it will then apply the new input source to any flags that are supported by the input source
|
|
||||||
func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc {
|
|
||||||
return func(context *cli.Context) error {
|
|
||||||
inputSource, err := createInputSource(context)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApplyInputSourceValues(context, inputSource, flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a generic value to the flagSet if required
|
|
||||||
func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
|
||||||
if f.set != nil {
|
|
||||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
|
|
||||||
value, err := isc.Generic(f.GenericFlag.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if value != nil {
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
f.set.Set(f.Name, value.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a StringSlice value to the flagSet if required
|
|
||||||
func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
|
||||||
if f.set != nil {
|
|
||||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
|
|
||||||
value, err := isc.StringSlice(f.StringSliceFlag.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if value != nil {
|
|
||||||
var sliceValue cli.StringSlice = value
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
underlyingFlag := f.set.Lookup(f.Name)
|
|
||||||
if underlyingFlag != nil {
|
|
||||||
underlyingFlag.Value = &sliceValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a IntSlice value if required
|
|
||||||
func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
|
||||||
if f.set != nil {
|
|
||||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
|
|
||||||
value, err := isc.IntSlice(f.IntSliceFlag.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if value != nil {
|
|
||||||
var sliceValue cli.IntSlice = value
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
underlyingFlag := f.set.Lookup(f.Name)
|
|
||||||
if underlyingFlag != nil {
|
|
||||||
underlyingFlag.Value = &sliceValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a Bool value to the flagSet if required
|
|
||||||
func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
|
||||||
if f.set != nil {
|
|
||||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
|
|
||||||
value, err := isc.Bool(f.BoolFlag.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if value {
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
f.set.Set(f.Name, strconv.FormatBool(value))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a BoolT value to the flagSet if required
|
|
||||||
func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
|
||||||
if f.set != nil {
|
|
||||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
|
|
||||||
value, err := isc.BoolT(f.BoolTFlag.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !value {
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
f.set.Set(f.Name, strconv.FormatBool(value))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a String value to the flagSet if required
|
|
||||||
func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
|
||||||
if f.set != nil {
|
|
||||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
|
|
||||||
value, err := isc.String(f.StringFlag.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if value != "" {
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
f.set.Set(f.Name, value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a int value to the flagSet if required
|
|
||||||
func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
|
||||||
if f.set != nil {
|
|
||||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
|
|
||||||
value, err := isc.Int(f.IntFlag.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if value > 0 {
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
f.set.Set(f.Name, strconv.FormatInt(int64(value), 10))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a Duration value to the flagSet if required
|
|
||||||
func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
|
||||||
if f.set != nil {
|
|
||||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
|
|
||||||
value, err := isc.Duration(f.DurationFlag.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if value > 0 {
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
f.set.Set(f.Name, value.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyInputSourceValue applies a Float64 value to the flagSet if required
|
|
||||||
func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
|
||||||
if f.set != nil {
|
|
||||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
|
|
||||||
value, err := isc.Float64(f.Float64Flag.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if value > 0 {
|
|
||||||
floatStr := float64ToString(value)
|
|
||||||
eachName(f.Name, func(name string) {
|
|
||||||
f.set.Set(f.Name, floatStr)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isEnvVarSet(envVars string) bool {
|
|
||||||
for _, envVar := range strings.Split(envVars, ",") {
|
|
||||||
envVar = strings.TrimSpace(envVar)
|
|
||||||
if _, ok := syscall.Getenv(envVar); ok {
|
|
||||||
// TODO: Can't use this for bools as
|
|
||||||
// set means that it was true or false based on
|
|
||||||
// Bool flag type, should work for other types
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func float64ToString(f float64) string {
|
|
||||||
return fmt.Sprintf("%v", f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func eachName(longName string, fn func(string)) {
|
|
||||||
parts := strings.Split(longName, ",")
|
|
||||||
for _, name := range parts {
|
|
||||||
name = strings.Trim(name, " ")
|
|
||||||
fn(name)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,347 +0,0 @@
|
|||||||
package altsrc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WARNING: This file is generated!
|
|
||||||
|
|
||||||
// BoolFlag is the flag type that wraps cli.BoolFlag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type BoolFlag struct {
|
|
||||||
cli.BoolFlag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBoolFlag creates a new BoolFlag
|
|
||||||
func NewBoolFlag(fl cli.BoolFlag) *BoolFlag {
|
|
||||||
return &BoolFlag{BoolFlag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped BoolFlag.Apply
|
|
||||||
func (f *BoolFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.BoolFlag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped BoolFlag.ApplyWithError
|
|
||||||
func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.BoolFlag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolTFlag is the flag type that wraps cli.BoolTFlag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type BoolTFlag struct {
|
|
||||||
cli.BoolTFlag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBoolTFlag creates a new BoolTFlag
|
|
||||||
func NewBoolTFlag(fl cli.BoolTFlag) *BoolTFlag {
|
|
||||||
return &BoolTFlag{BoolTFlag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped BoolTFlag.Apply
|
|
||||||
func (f *BoolTFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.BoolTFlag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped BoolTFlag.ApplyWithError
|
|
||||||
func (f *BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.BoolTFlag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DurationFlag is the flag type that wraps cli.DurationFlag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type DurationFlag struct {
|
|
||||||
cli.DurationFlag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDurationFlag creates a new DurationFlag
|
|
||||||
func NewDurationFlag(fl cli.DurationFlag) *DurationFlag {
|
|
||||||
return &DurationFlag{DurationFlag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped DurationFlag.Apply
|
|
||||||
func (f *DurationFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.DurationFlag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped DurationFlag.ApplyWithError
|
|
||||||
func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.DurationFlag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64Flag is the flag type that wraps cli.Float64Flag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type Float64Flag struct {
|
|
||||||
cli.Float64Flag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFloat64Flag creates a new Float64Flag
|
|
||||||
func NewFloat64Flag(fl cli.Float64Flag) *Float64Flag {
|
|
||||||
return &Float64Flag{Float64Flag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped Float64Flag.Apply
|
|
||||||
func (f *Float64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.Float64Flag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped Float64Flag.ApplyWithError
|
|
||||||
func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.Float64Flag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenericFlag is the flag type that wraps cli.GenericFlag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type GenericFlag struct {
|
|
||||||
cli.GenericFlag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGenericFlag creates a new GenericFlag
|
|
||||||
func NewGenericFlag(fl cli.GenericFlag) *GenericFlag {
|
|
||||||
return &GenericFlag{GenericFlag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped GenericFlag.Apply
|
|
||||||
func (f *GenericFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.GenericFlag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped GenericFlag.ApplyWithError
|
|
||||||
func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.GenericFlag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Flag is the flag type that wraps cli.Int64Flag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type Int64Flag struct {
|
|
||||||
cli.Int64Flag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInt64Flag creates a new Int64Flag
|
|
||||||
func NewInt64Flag(fl cli.Int64Flag) *Int64Flag {
|
|
||||||
return &Int64Flag{Int64Flag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped Int64Flag.Apply
|
|
||||||
func (f *Int64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.Int64Flag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped Int64Flag.ApplyWithError
|
|
||||||
func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.Int64Flag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntFlag is the flag type that wraps cli.IntFlag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type IntFlag struct {
|
|
||||||
cli.IntFlag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIntFlag creates a new IntFlag
|
|
||||||
func NewIntFlag(fl cli.IntFlag) *IntFlag {
|
|
||||||
return &IntFlag{IntFlag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped IntFlag.Apply
|
|
||||||
func (f *IntFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.IntFlag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped IntFlag.ApplyWithError
|
|
||||||
func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.IntFlag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type IntSliceFlag struct {
|
|
||||||
cli.IntSliceFlag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIntSliceFlag creates a new IntSliceFlag
|
|
||||||
func NewIntSliceFlag(fl cli.IntSliceFlag) *IntSliceFlag {
|
|
||||||
return &IntSliceFlag{IntSliceFlag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped IntSliceFlag.Apply
|
|
||||||
func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.IntSliceFlag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped IntSliceFlag.ApplyWithError
|
|
||||||
func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.IntSliceFlag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type Int64SliceFlag struct {
|
|
||||||
cli.Int64SliceFlag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInt64SliceFlag creates a new Int64SliceFlag
|
|
||||||
func NewInt64SliceFlag(fl cli.Int64SliceFlag) *Int64SliceFlag {
|
|
||||||
return &Int64SliceFlag{Int64SliceFlag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped Int64SliceFlag.Apply
|
|
||||||
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.Int64SliceFlag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped Int64SliceFlag.ApplyWithError
|
|
||||||
func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.Int64SliceFlag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringFlag is the flag type that wraps cli.StringFlag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type StringFlag struct {
|
|
||||||
cli.StringFlag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStringFlag creates a new StringFlag
|
|
||||||
func NewStringFlag(fl cli.StringFlag) *StringFlag {
|
|
||||||
return &StringFlag{StringFlag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped StringFlag.Apply
|
|
||||||
func (f *StringFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.StringFlag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped StringFlag.ApplyWithError
|
|
||||||
func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.StringFlag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type StringSliceFlag struct {
|
|
||||||
cli.StringSliceFlag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStringSliceFlag creates a new StringSliceFlag
|
|
||||||
func NewStringSliceFlag(fl cli.StringSliceFlag) *StringSliceFlag {
|
|
||||||
return &StringSliceFlag{StringSliceFlag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped StringSliceFlag.Apply
|
|
||||||
func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.StringSliceFlag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped StringSliceFlag.ApplyWithError
|
|
||||||
func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.StringSliceFlag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64Flag is the flag type that wraps cli.Uint64Flag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type Uint64Flag struct {
|
|
||||||
cli.Uint64Flag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUint64Flag creates a new Uint64Flag
|
|
||||||
func NewUint64Flag(fl cli.Uint64Flag) *Uint64Flag {
|
|
||||||
return &Uint64Flag{Uint64Flag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped Uint64Flag.Apply
|
|
||||||
func (f *Uint64Flag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.Uint64Flag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped Uint64Flag.ApplyWithError
|
|
||||||
func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.Uint64Flag.ApplyWithError(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UintFlag is the flag type that wraps cli.UintFlag to allow
|
|
||||||
// for other values to be specified
|
|
||||||
type UintFlag struct {
|
|
||||||
cli.UintFlag
|
|
||||||
set *flag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUintFlag creates a new UintFlag
|
|
||||||
func NewUintFlag(fl cli.UintFlag) *UintFlag {
|
|
||||||
return &UintFlag{UintFlag: fl, set: nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped UintFlag.Apply
|
|
||||||
func (f *UintFlag) Apply(set *flag.FlagSet) {
|
|
||||||
f.set = set
|
|
||||||
f.UintFlag.Apply(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
||||||
// wrapped UintFlag.ApplyWithError
|
|
||||||
func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error {
|
|
||||||
f.set = set
|
|
||||||
return f.UintFlag.ApplyWithError(set)
|
|
||||||
}
|
|
@ -1,336 +0,0 @@
|
|||||||
package altsrc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testApplyInputSource struct {
|
|
||||||
Flag FlagInputSourceExtension
|
|
||||||
FlagName string
|
|
||||||
FlagSetName string
|
|
||||||
Expected string
|
|
||||||
ContextValueString string
|
|
||||||
ContextValue flag.Value
|
|
||||||
EnvVarValue string
|
|
||||||
EnvVarName string
|
|
||||||
MapValue interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenericApplyInputSourceValue(t *testing.T) {
|
|
||||||
v := &Parser{"abc", "def"}
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: v,
|
|
||||||
})
|
|
||||||
expect(t, v, c.Generic("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
|
|
||||||
p := &Parser{"abc", "def"}
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: &Parser{"efg", "hig"},
|
|
||||||
ContextValueString: p.String(),
|
|
||||||
})
|
|
||||||
expect(t, p, c.Generic("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}, EnvVar: "TEST"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: &Parser{"efg", "hij"},
|
|
||||||
EnvVarName: "TEST",
|
|
||||||
EnvVarValue: "abc,def",
|
|
||||||
})
|
|
||||||
expect(t, &Parser{"abc", "def"}, c.Generic("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringSliceApplyInputSourceValue(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: []string{"hello", "world"},
|
|
||||||
})
|
|
||||||
expect(t, c.StringSlice("test"), []string{"hello", "world"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: []string{"hello", "world"},
|
|
||||||
ContextValueString: "ohno",
|
|
||||||
})
|
|
||||||
expect(t, c.StringSlice("test"), []string{"ohno"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: []string{"hello", "world"},
|
|
||||||
EnvVarName: "TEST",
|
|
||||||
EnvVarValue: "oh,no",
|
|
||||||
})
|
|
||||||
expect(t, c.StringSlice("test"), []string{"oh", "no"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntSliceApplyInputSourceValue(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: []int{1, 2},
|
|
||||||
})
|
|
||||||
expect(t, c.IntSlice("test"), []int{1, 2})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: []int{1, 2},
|
|
||||||
ContextValueString: "3",
|
|
||||||
})
|
|
||||||
expect(t, c.IntSlice("test"), []int{3})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: []int{1, 2},
|
|
||||||
EnvVarName: "TEST",
|
|
||||||
EnvVarValue: "3,4",
|
|
||||||
})
|
|
||||||
expect(t, c.IntSlice("test"), []int{3, 4})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoolApplyInputSourceMethodSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: true,
|
|
||||||
})
|
|
||||||
expect(t, true, c.Bool("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: false,
|
|
||||||
ContextValueString: "true",
|
|
||||||
})
|
|
||||||
expect(t, true, c.Bool("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: false,
|
|
||||||
EnvVarName: "TEST",
|
|
||||||
EnvVarValue: "true",
|
|
||||||
})
|
|
||||||
expect(t, true, c.Bool("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoolTApplyInputSourceMethodSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: false,
|
|
||||||
})
|
|
||||||
expect(t, false, c.BoolT("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoolTApplyInputSourceMethodContextSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: true,
|
|
||||||
ContextValueString: "false",
|
|
||||||
})
|
|
||||||
expect(t, false, c.BoolT("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoolTApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test", EnvVar: "TEST"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: true,
|
|
||||||
EnvVarName: "TEST",
|
|
||||||
EnvVarValue: "false",
|
|
||||||
})
|
|
||||||
expect(t, false, c.BoolT("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringApplyInputSourceMethodSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewStringFlag(cli.StringFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: "hello",
|
|
||||||
})
|
|
||||||
expect(t, "hello", c.String("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewStringFlag(cli.StringFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: "hello",
|
|
||||||
ContextValueString: "goodbye",
|
|
||||||
})
|
|
||||||
expect(t, "goodbye", c.String("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: "hello",
|
|
||||||
EnvVarName: "TEST",
|
|
||||||
EnvVarValue: "goodbye",
|
|
||||||
})
|
|
||||||
expect(t, "goodbye", c.String("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntApplyInputSourceMethodSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewIntFlag(cli.IntFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: 15,
|
|
||||||
})
|
|
||||||
expect(t, 15, c.Int("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntApplyInputSourceMethodContextSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewIntFlag(cli.IntFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: 15,
|
|
||||||
ContextValueString: "7",
|
|
||||||
})
|
|
||||||
expect(t, 7, c.Int("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: 15,
|
|
||||||
EnvVarName: "TEST",
|
|
||||||
EnvVarValue: "12",
|
|
||||||
})
|
|
||||||
expect(t, 12, c.Int("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDurationApplyInputSourceMethodSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: time.Duration(30 * time.Second),
|
|
||||||
})
|
|
||||||
expect(t, time.Duration(30*time.Second), c.Duration("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: time.Duration(30 * time.Second),
|
|
||||||
ContextValueString: time.Duration(15 * time.Second).String(),
|
|
||||||
})
|
|
||||||
expect(t, time.Duration(15*time.Second), c.Duration("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: time.Duration(30 * time.Second),
|
|
||||||
EnvVarName: "TEST",
|
|
||||||
EnvVarValue: time.Duration(15 * time.Second).String(),
|
|
||||||
})
|
|
||||||
expect(t, time.Duration(15*time.Second), c.Duration("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: 1.3,
|
|
||||||
})
|
|
||||||
expect(t, 1.3, c.Float64("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: 1.3,
|
|
||||||
ContextValueString: fmt.Sprintf("%v", 1.4),
|
|
||||||
})
|
|
||||||
expect(t, 1.4, c.Float64("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
|
||||||
c := runTest(t, testApplyInputSource{
|
|
||||||
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}),
|
|
||||||
FlagName: "test",
|
|
||||||
MapValue: 1.3,
|
|
||||||
EnvVarName: "TEST",
|
|
||||||
EnvVarValue: fmt.Sprintf("%v", 1.4),
|
|
||||||
})
|
|
||||||
expect(t, 1.4, c.Float64("test"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
|
|
||||||
inputSource := &MapInputSource{valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}}
|
|
||||||
set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError)
|
|
||||||
c := cli.NewContext(nil, set, nil)
|
|
||||||
if test.EnvVarName != "" && test.EnvVarValue != "" {
|
|
||||||
os.Setenv(test.EnvVarName, test.EnvVarValue)
|
|
||||||
defer os.Setenv(test.EnvVarName, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
test.Flag.Apply(set)
|
|
||||||
if test.ContextValue != nil {
|
|
||||||
flag := set.Lookup(test.FlagName)
|
|
||||||
flag.Value = test.ContextValue
|
|
||||||
}
|
|
||||||
if test.ContextValueString != "" {
|
|
||||||
set.Set(test.FlagName, test.ContextValueString)
|
|
||||||
}
|
|
||||||
test.Flag.ApplyInputSourceValue(c, inputSource)
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
type Parser [2]string
|
|
||||||
|
|
||||||
func (p *Parser) Set(value string) error {
|
|
||||||
parts := strings.Split(value, ",")
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return fmt.Errorf("invalid format")
|
|
||||||
}
|
|
||||||
|
|
||||||
(*p)[0] = parts[0]
|
|
||||||
(*p)[1] = parts[1]
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) String() string {
|
|
||||||
return fmt.Sprintf("%s,%s", p[0], p[1])
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package altsrc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func expect(t *testing.T, a interface{}, b interface{}) {
|
|
||||||
if !reflect.DeepEqual(b, a) {
|
|
||||||
t.Errorf("Expected %#v (type %v) - Got %#v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func refute(t *testing.T, a interface{}, b interface{}) {
|
|
||||||
if a == b {
|
|
||||||
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package altsrc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InputSourceContext is an interface used to allow
|
|
||||||
// other input sources to be implemented as needed.
|
|
||||||
type InputSourceContext interface {
|
|
||||||
Int(name string) (int, error)
|
|
||||||
Duration(name string) (time.Duration, error)
|
|
||||||
Float64(name string) (float64, error)
|
|
||||||
String(name string) (string, error)
|
|
||||||
StringSlice(name string) ([]string, error)
|
|
||||||
IntSlice(name string) ([]int, error)
|
|
||||||
Generic(name string) (cli.Generic, error)
|
|
||||||
Bool(name string) (bool, error)
|
|
||||||
BoolT(name string) (bool, error)
|
|
||||||
}
|
|
@ -1,248 +0,0 @@
|
|||||||
package altsrc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MapInputSource implements InputSourceContext to return
|
|
||||||
// data from the map that is loaded.
|
|
||||||
type MapInputSource struct {
|
|
||||||
valueMap map[interface{}]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nestedVal checks if the name has '.' delimiters.
|
|
||||||
// If so, it tries to traverse the tree by the '.' delimited sections to find
|
|
||||||
// a nested value for the key.
|
|
||||||
func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool) {
|
|
||||||
if sections := strings.Split(name, "."); len(sections) > 1 {
|
|
||||||
node := tree
|
|
||||||
for _, section := range sections[:len(sections)-1] {
|
|
||||||
if child, ok := node[section]; !ok {
|
|
||||||
return nil, false
|
|
||||||
} else {
|
|
||||||
if ctype, ok := child.(map[interface{}]interface{}); !ok {
|
|
||||||
return nil, false
|
|
||||||
} else {
|
|
||||||
node = ctype
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if val, ok := node[sections[len(sections)-1]]; ok {
|
|
||||||
return val, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int returns an int from the map if it exists otherwise returns 0
|
|
||||||
func (fsm *MapInputSource) Int(name string) (int, error) {
|
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := otherGenericValue.(int)
|
|
||||||
if !isType {
|
|
||||||
return 0, incorrectTypeForFlagError(name, "int", otherGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.(int)
|
|
||||||
if !isType {
|
|
||||||
return 0, incorrectTypeForFlagError(name, "int", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duration returns a duration from the map if it exists otherwise returns 0
|
|
||||||
func (fsm *MapInputSource) Duration(name string) (time.Duration, error) {
|
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := otherGenericValue.(time.Duration)
|
|
||||||
if !isType {
|
|
||||||
return 0, incorrectTypeForFlagError(name, "duration", otherGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.(time.Duration)
|
|
||||||
if !isType {
|
|
||||||
return 0, incorrectTypeForFlagError(name, "duration", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64 returns an float64 from the map if it exists otherwise returns 0
|
|
||||||
func (fsm *MapInputSource) Float64(name string) (float64, error) {
|
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := otherGenericValue.(float64)
|
|
||||||
if !isType {
|
|
||||||
return 0, incorrectTypeForFlagError(name, "float64", otherGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.(float64)
|
|
||||||
if !isType {
|
|
||||||
return 0, incorrectTypeForFlagError(name, "float64", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string from the map if it exists otherwise returns an empty string
|
|
||||||
func (fsm *MapInputSource) String(name string) (string, error) {
|
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := otherGenericValue.(string)
|
|
||||||
if !isType {
|
|
||||||
return "", incorrectTypeForFlagError(name, "string", otherGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.(string)
|
|
||||||
if !isType {
|
|
||||||
return "", incorrectTypeForFlagError(name, "string", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringSlice returns an []string from the map if it exists otherwise returns nil
|
|
||||||
func (fsm *MapInputSource) StringSlice(name string) ([]string, error) {
|
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := otherGenericValue.([]string)
|
|
||||||
if !isType {
|
|
||||||
return nil, incorrectTypeForFlagError(name, "[]string", otherGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.([]string)
|
|
||||||
if !isType {
|
|
||||||
return nil, incorrectTypeForFlagError(name, "[]string", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntSlice returns an []int from the map if it exists otherwise returns nil
|
|
||||||
func (fsm *MapInputSource) IntSlice(name string) ([]int, error) {
|
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := otherGenericValue.([]int)
|
|
||||||
if !isType {
|
|
||||||
return nil, incorrectTypeForFlagError(name, "[]int", otherGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.([]int)
|
|
||||||
if !isType {
|
|
||||||
return nil, incorrectTypeForFlagError(name, "[]int", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic returns an cli.Generic from the map if it exists otherwise returns nil
|
|
||||||
func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) {
|
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := otherGenericValue.(cli.Generic)
|
|
||||||
if !isType {
|
|
||||||
return nil, incorrectTypeForFlagError(name, "cli.Generic", otherGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.(cli.Generic)
|
|
||||||
if !isType {
|
|
||||||
return nil, incorrectTypeForFlagError(name, "cli.Generic", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool returns an bool from the map otherwise returns false
|
|
||||||
func (fsm *MapInputSource) Bool(name string) (bool, error) {
|
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := otherGenericValue.(bool)
|
|
||||||
if !isType {
|
|
||||||
return false, incorrectTypeForFlagError(name, "bool", otherGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.(bool)
|
|
||||||
if !isType {
|
|
||||||
return false, incorrectTypeForFlagError(name, "bool", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolT returns an bool from the map otherwise returns true
|
|
||||||
func (fsm *MapInputSource) BoolT(name string) (bool, error) {
|
|
||||||
otherGenericValue, exists := fsm.valueMap[name]
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := otherGenericValue.(bool)
|
|
||||||
if !isType {
|
|
||||||
return true, incorrectTypeForFlagError(name, "bool", otherGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
|
||||||
if exists {
|
|
||||||
otherValue, isType := nestedGenericValue.(bool)
|
|
||||||
if !isType {
|
|
||||||
return true, incorrectTypeForFlagError(name, "bool", nestedGenericValue)
|
|
||||||
}
|
|
||||||
return otherValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error {
|
|
||||||
valueType := reflect.TypeOf(value)
|
|
||||||
valueTypeName := ""
|
|
||||||
if valueType != nil {
|
|
||||||
valueTypeName = valueType.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("Mismatched type for flag '%s'. Expected '%s' but actual is '%s'", name, expectedTypeName, valueTypeName)
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue