mirror of https://github.com/liamg/aminal.git
package default font into the binary
This commit is contained in:
parent
b2442fed9e
commit
aa42e702d4
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
engines:
|
||||||
|
golint:
|
||||||
|
enabled: true
|
||||||
|
checks:
|
||||||
|
GoLint/Naming/MixedCaps:
|
||||||
|
enabled: false
|
||||||
|
govet:
|
||||||
|
enabled: true
|
||||||
|
gofmt:
|
||||||
|
enabled: true
|
||||||
|
fixme:
|
||||||
|
enabled: true
|
||||||
|
ratings:
|
||||||
|
paths:
|
||||||
|
- "**.go"
|
||||||
|
exclude_paths:
|
||||||
|
- "**/*_test.go"
|
||||||
|
- "*_test.go"
|
||||||
|
- "fixtures/"
|
|
@ -0,0 +1,33 @@
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
doc
|
||||||
|
tmp
|
||||||
|
pkg
|
||||||
|
*.gem
|
||||||
|
*.pid
|
||||||
|
coverage
|
||||||
|
coverage.data
|
||||||
|
build/*
|
||||||
|
*.pbxuser
|
||||||
|
*.mode1v3
|
||||||
|
.svn
|
||||||
|
profile
|
||||||
|
.console_history
|
||||||
|
.sass-cache/*
|
||||||
|
.rake_tasks~
|
||||||
|
*.log.lck
|
||||||
|
solr/
|
||||||
|
.jhw-cache/
|
||||||
|
jhw.*
|
||||||
|
*.sublime*
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
generated/
|
||||||
|
.vendor/
|
||||||
|
bin/*
|
||||||
|
gin-bin
|
||||||
|
/packr_darwin_amd64
|
||||||
|
/packr_linux_amd64
|
||||||
|
.vscode/
|
||||||
|
debug.test
|
||||||
|
.grifter/
|
|
@ -0,0 +1,25 @@
|
||||||
|
builds:
|
||||||
|
-
|
||||||
|
goos:
|
||||||
|
- darwin
|
||||||
|
- linux
|
||||||
|
- windows
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
main: ./packr/main.go
|
||||||
|
binary: packr
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
name_template: 'checksums.txt'
|
||||||
|
snapshot:
|
||||||
|
name_template: "{{ .Tag }}-next"
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- '^docs:'
|
||||||
|
- '^test:'
|
||||||
|
brew:
|
||||||
|
github:
|
||||||
|
owner: gobuffalo
|
||||||
|
name: homebrew-tap
|
|
@ -0,0 +1,16 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.9
|
||||||
|
- "1.10"
|
||||||
|
- "1.11"
|
||||||
|
- tip
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: 'tip'
|
||||||
|
|
||||||
|
script:
|
||||||
|
- make ci-test
|
|
@ -0,0 +1,8 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
Copyright (c) 2016 Mark Bates
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,36 @@
|
||||||
|
TAGS ?= "sqlite"
|
||||||
|
GO_BIN ?= go
|
||||||
|
|
||||||
|
install: deps
|
||||||
|
packr
|
||||||
|
$(GO_BIN) install -v .
|
||||||
|
|
||||||
|
deps:
|
||||||
|
$(GO_BIN) get github.com/gobuffalo/packr/packr
|
||||||
|
$(GO_BIN) get -tags ${TAGS} -t ./...
|
||||||
|
|
||||||
|
build: deps
|
||||||
|
packr
|
||||||
|
$(GO_BIN) build -v .
|
||||||
|
|
||||||
|
test:
|
||||||
|
packr
|
||||||
|
$(GO_BIN) test -tags ${TAGS} ./...
|
||||||
|
|
||||||
|
ci-test: deps
|
||||||
|
$(GO_BIN) test -tags ${TAGS} -race ./...
|
||||||
|
|
||||||
|
lint:
|
||||||
|
gometalinter --vendor ./... --deadline=1m --skip=internal
|
||||||
|
|
||||||
|
update:
|
||||||
|
$(GO_BIN) get -u
|
||||||
|
$(GO_BIN) mod tidy
|
||||||
|
packr
|
||||||
|
make test
|
||||||
|
|
||||||
|
release-test:
|
||||||
|
$(GO_BIN) test -tags ${TAGS} -race ./...
|
||||||
|
|
||||||
|
release:
|
||||||
|
release -y -f version.go
|
|
@ -0,0 +1,196 @@
|
||||||
|
# packr
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/gobuffalo/packr)
|
||||||
|
|
||||||
|
Packr is a simple solution for bundling static assets inside of Go binaries. Most importantly it does it in a way that is friendly to developers while they are developing.
|
||||||
|
|
||||||
|
## Intro Video
|
||||||
|
|
||||||
|
To get an idea of the what and why of packr, please enjoy this short video: [https://vimeo.com/219863271](https://vimeo.com/219863271).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ go get -u github.com/gobuffalo/packr/...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### In Code
|
||||||
|
|
||||||
|
The first step in using Packr is to create a new box. A box represents a folder on disk. Once you have a box you can get `string` or `[]byte` representations of the file.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// set up a new box by giving it a (relative) path to a folder on disk:
|
||||||
|
box := packr.NewBox("./templates")
|
||||||
|
|
||||||
|
// Get the string representation of a file:
|
||||||
|
html := box.String("index.html")
|
||||||
|
|
||||||
|
// Get the string representation of a file, or an error if it doesn't exist:
|
||||||
|
html, err := box.MustString("index.html")
|
||||||
|
|
||||||
|
// Get the []byte representation of a file:
|
||||||
|
html := box.Bytes("index.html")
|
||||||
|
|
||||||
|
// Get the []byte representation of a file, or an error if it doesn't exist:
|
||||||
|
html, err := box.MustBytes("index.html")
|
||||||
|
```
|
||||||
|
|
||||||
|
### What is a Box?
|
||||||
|
|
||||||
|
A box represents a folder, and any sub-folders, on disk that you want to have access to in your binary. When compiling a binary using the `packr` CLI the contents of the folder will be converted into Go files that can be compiled inside of a "standard" go binary. Inside of the compiled binary the files will be read from memory. When working locally the files will be read directly off of disk. This is a seamless switch that doesn't require any special attention on your part.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
Assume the follow directory structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
├── main.go
|
||||||
|
└── templates
|
||||||
|
├── admin
|
||||||
|
│ └── index.html
|
||||||
|
└── index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
The following program will read the `./templates/admin/index.html` file and print it out.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gobuffalo/packr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
box := packr.NewBox("./templates")
|
||||||
|
|
||||||
|
s := box.String("admin/index.html")
|
||||||
|
fmt.Println(s)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Made Easy
|
||||||
|
|
||||||
|
In order to get static files into a Go binary, those files must first be converted to Go code. To do that, Packr, ships with a few tools to help build binaries. See below.
|
||||||
|
|
||||||
|
During development, however, it is painful to have to keep running a tool to compile those files.
|
||||||
|
|
||||||
|
Packr uses the following resolution rules when looking for a file:
|
||||||
|
|
||||||
|
1. Look for the file in-memory (inside a Go binary)
|
||||||
|
1. Look for the file on disk (during development)
|
||||||
|
|
||||||
|
Because Packr knows how to fall through to the file system, developers don't need to worry about constantly compiling their static files into a binary. They can work unimpeded.
|
||||||
|
|
||||||
|
Packr takes file resolution a step further. When declaring a new box you use a relative path, `./templates`. When Packr recieves this call it calculates out the absolute path to that directory. By doing this it means you can be guaranteed that Packr can find your files correctly, even if you're not running in the directory that the box was created in. This helps with the problem of testing, where Go changes the `pwd` for each package, making relative paths difficult to work with. This is not a problem when using Packr.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage with HTTP
|
||||||
|
|
||||||
|
A box implements the [`http.FileSystem`](https://golang.org/pkg/net/http/#FileSystemhttps://golang.org/pkg/net/http/#FileSystem) interface, meaning it can be used to serve static files.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gobuffalo/packr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
box := packr.NewBox("./templates")
|
||||||
|
|
||||||
|
http.Handle("/", http.FileServer(box))
|
||||||
|
http.ListenAndServe(":3000", nil)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Building a Binary (the easy way)
|
||||||
|
|
||||||
|
When it comes time to build, or install, your Go binary, simply use `packr build` or `packr install` just as you would `go build` or `go install`. All flags for the `go` tool are supported and everything works the way you expect, the only difference is your static assets are now bundled in the generated binary. If you want more control over how this happens, looking at the following section on building binaries (the hard way).
|
||||||
|
|
||||||
|
## Building a Binary (the hard way)
|
||||||
|
|
||||||
|
Before you build your Go binary, run the `packr` command first. It will look for all the boxes in your code and then generate `.go` files that pack the static files into bytes that can be bundled into the Go binary.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ packr
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run your `go build command` like normal.
|
||||||
|
|
||||||
|
*NOTE*: It is not recommended to check-in these generated `-packr.go` files. They can be large, and can easily become out of date if not careful. It is recommended that you always run `packr clean` after running the `packr` tool.
|
||||||
|
|
||||||
|
#### Cleaning Up
|
||||||
|
|
||||||
|
When you're done it is recommended that you run the `packr clean` command. This will remove all of the generated files that Packr created for you.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ packr clean
|
||||||
|
```
|
||||||
|
|
||||||
|
Why do you want to do this? Packr first looks to the information stored in these generated files, if the information isn't there it looks to disk. This makes it easy to work with in development.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Building/Moving a portable release
|
||||||
|
|
||||||
|
When it comes to building multiple releases you typically want that release to be built in a specific directory.
|
||||||
|
|
||||||
|
For example: `./releases`
|
||||||
|
|
||||||
|
However, because passing a `.go` file requires absolute paths, we must compile the release in the appropriate absolute path.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GOOS=linux GOARCH=amd64 packr build
|
||||||
|
```
|
||||||
|
|
||||||
|
Now your `project_name` binary will be built at the root of your project dir. Great!
|
||||||
|
|
||||||
|
All that is left to do is to move that binary to your release dir:
|
||||||
|
|
||||||
|
Linux/macOS/Windows (bash)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mv ./project_name ./releases
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows (cmd):
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
move ./project_name ./releases
|
||||||
|
```
|
||||||
|
|
||||||
|
Powershell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Move-Item -Path .\project_name -Destination .\releases\
|
||||||
|
```
|
||||||
|
|
||||||
|
If you _target_ for Windows when building don't forget that it's `project_name.exe`
|
||||||
|
|
||||||
|
Now you can make multiple releases and all of your needed static files will be available!
|
||||||
|
|
||||||
|
#### Summing it up:
|
||||||
|
|
||||||
|
Example Script for building to 3 common targets:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GOOS=darwin GOARCH=amd64 packr build && mv ./project_name ./releases/darwin-project_name \
|
||||||
|
&& GOOS=linux GOARCH=amd64 packr build && mv ./project_name ./releases/linux-project_name \
|
||||||
|
&& GOOS=windows GOARCH=386 packr build && mv ./project_name.exe ./releases/project_name.exe \
|
||||||
|
&& packr clean
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
The `packr` command passes all arguments down to the underlying `go` command, this includes the `-v` flag to print out `go build` information. Packr looks for the `-v` flag, and will turn on its own verbose logging. This is very useful for trying to understand what the `packr` command is doing when it is run.
|
|
@ -0,0 +1,204 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrResOutsideBox gets returned in case of the requested resources being outside the box
|
||||||
|
ErrResOutsideBox = errors.New("Can't find a resource outside the box")
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewBox returns a Box that can be used to
|
||||||
|
// retrieve files from either disk or the embedded
|
||||||
|
// binary.
|
||||||
|
func NewBox(path string) Box {
|
||||||
|
var cd string
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
_, filename, _, _ := runtime.Caller(1)
|
||||||
|
cd = filepath.Dir(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this little hack courtesy of the `-cover` flag!!
|
||||||
|
cov := filepath.Join("_test", "_obj_test")
|
||||||
|
cd = strings.Replace(cd, string(filepath.Separator)+cov, "", 1)
|
||||||
|
if !filepath.IsAbs(cd) && cd != "" {
|
||||||
|
cd = filepath.Join(GoPath(), "src", cd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Box{
|
||||||
|
Path: path,
|
||||||
|
callingDir: cd,
|
||||||
|
data: map[string][]byte{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Box represent a folder on a disk you want to
|
||||||
|
// have access to in the built Go binary.
|
||||||
|
type Box struct {
|
||||||
|
Path string
|
||||||
|
callingDir string
|
||||||
|
data map[string][]byte
|
||||||
|
directories map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddString converts t to a byteslice and delegates to AddBytes to add to b.data
|
||||||
|
func (b Box) AddString(path string, t string) {
|
||||||
|
b.AddBytes(path, []byte(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBytes sets t in b.data by the given path
|
||||||
|
func (b Box) AddBytes(path string, t []byte) {
|
||||||
|
b.data[path] = t
|
||||||
|
}
|
||||||
|
|
||||||
|
// String of the file asked for or an empty string.
|
||||||
|
func (b Box) String(name string) string {
|
||||||
|
return string(b.Bytes(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustString returns either the string of the requested
|
||||||
|
// file or an error if it can not be found.
|
||||||
|
func (b Box) MustString(name string) (string, error) {
|
||||||
|
bb, err := b.MustBytes(name)
|
||||||
|
return string(bb), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes of the file asked for or an empty byte slice.
|
||||||
|
func (b Box) Bytes(name string) []byte {
|
||||||
|
bb, _ := b.MustBytes(name)
|
||||||
|
return bb
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustBytes returns either the byte slice of the requested
|
||||||
|
// file or an error if it can not be found.
|
||||||
|
func (b Box) MustBytes(name string) ([]byte, error) {
|
||||||
|
f, err := b.find(name)
|
||||||
|
if err == nil {
|
||||||
|
bb := &bytes.Buffer{}
|
||||||
|
bb.ReadFrom(f)
|
||||||
|
return bb.Bytes(), err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has returns true if the resource exists in the box
|
||||||
|
func (b Box) Has(name string) bool {
|
||||||
|
_, err := b.find(name)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Box) decompress(bb []byte) []byte {
|
||||||
|
reader, err := gzip.NewReader(bytes.NewReader(bb))
|
||||||
|
if err != nil {
|
||||||
|
return bb
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return bb
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Box) find(name string) (File, error) {
|
||||||
|
if bb, ok := b.data[name]; ok {
|
||||||
|
return newVirtualFile(name, bb), nil
|
||||||
|
}
|
||||||
|
if b.directories == nil {
|
||||||
|
b.indexDirectories()
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanName := filepath.ToSlash(filepath.Clean(name))
|
||||||
|
// Ensure name is not outside the box
|
||||||
|
if strings.HasPrefix(cleanName, "../") {
|
||||||
|
return nil, ErrResOutsideBox
|
||||||
|
}
|
||||||
|
// Absolute name is considered as relative to the box root
|
||||||
|
cleanName = strings.TrimPrefix(cleanName, "/")
|
||||||
|
|
||||||
|
// Try to get the resource from the box
|
||||||
|
if _, ok := data[b.Path]; ok {
|
||||||
|
if bb, ok := data[b.Path][cleanName]; ok {
|
||||||
|
bb = b.decompress(bb)
|
||||||
|
return newVirtualFile(cleanName, bb), nil
|
||||||
|
}
|
||||||
|
if _, ok := b.directories[cleanName]; ok {
|
||||||
|
return newVirtualDir(cleanName), nil
|
||||||
|
}
|
||||||
|
if filepath.Ext(cleanName) != "" {
|
||||||
|
// The Handler created by http.FileSystem checks for those errors and
|
||||||
|
// returns http.StatusNotFound instead of http.StatusInternalServerError.
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found in the box virtual fs, try to get it from the file system
|
||||||
|
cleanName = filepath.FromSlash(cleanName)
|
||||||
|
p := filepath.Join(b.callingDir, b.Path, cleanName)
|
||||||
|
return fileFor(p, cleanName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns a File using the http.File interface
|
||||||
|
func (b Box) Open(name string) (http.File, error) {
|
||||||
|
return b.find(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List shows "What's in the box?"
|
||||||
|
func (b Box) List() []string {
|
||||||
|
var keys []string
|
||||||
|
|
||||||
|
if b.data == nil || len(b.data) == 0 {
|
||||||
|
b.Walk(func(path string, info File) error {
|
||||||
|
finfo, _ := info.FileInfo()
|
||||||
|
if !finfo.IsDir() {
|
||||||
|
keys = append(keys, finfo.Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for k := range b.data {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Box) indexDirectories() {
|
||||||
|
b.directories = map[string]bool{}
|
||||||
|
if _, ok := data[b.Path]; ok {
|
||||||
|
for name := range data[b.Path] {
|
||||||
|
prefix, _ := path.Split(name)
|
||||||
|
// Even on Windows the suffix appears to be a /
|
||||||
|
prefix = strings.TrimSuffix(prefix, "/")
|
||||||
|
b.directories[prefix] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileFor(p string, name string) (File, error) {
|
||||||
|
fi, err := os.Stat(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
return newVirtualDir(p), nil
|
||||||
|
}
|
||||||
|
if bb, err := ioutil.ReadFile(p); err == nil {
|
||||||
|
return newVirtualFile(name, bb), nil
|
||||||
|
}
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Box_String(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
s := testBox.String("hello.txt")
|
||||||
|
r.Equal("hello world!", strings.TrimSpace(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Box_MustString(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
_, err := testBox.MustString("idontexist.txt")
|
||||||
|
r.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Box_Bytes(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
s := testBox.Bytes("hello.txt")
|
||||||
|
r.Equal([]byte("hello world!"), bytes.TrimSpace(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Box_MustBytes(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
_, err := testBox.MustBytes("idontexist.txt")
|
||||||
|
r.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Box_Has(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
r.True(testBox.Has("hello.txt"))
|
||||||
|
r.False(testBox.Has("idontexist.txt"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_List_Virtual(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
mustHave := []string{"a", "b", "c", "d/a"}
|
||||||
|
actual := virtualBox.List()
|
||||||
|
sort.Strings(actual)
|
||||||
|
r.Equal(mustHave, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_List_Physical(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
mustHave := osPaths("foo/a.txt", "foo/bar/b.txt", "goodbye.txt", "hello.txt", "index.html")
|
||||||
|
actual := testBox.List()
|
||||||
|
r.Equal(mustHave, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Outside_Box(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
f, err := ioutil.TempFile("", "")
|
||||||
|
r.NoError(err)
|
||||||
|
defer os.RemoveAll(f.Name())
|
||||||
|
_, err = testBox.MustString(f.Name())
|
||||||
|
r.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Box_find(t *testing.T) {
|
||||||
|
box := NewBox("./example")
|
||||||
|
|
||||||
|
onWindows := runtime.GOOS == "windows"
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
found bool
|
||||||
|
}{
|
||||||
|
{"assets/app.css", true},
|
||||||
|
{"assets\\app.css", onWindows},
|
||||||
|
{"foo/bar.baz", false},
|
||||||
|
{"bar", true},
|
||||||
|
{"bar/sub", true},
|
||||||
|
{"bar/foo", false},
|
||||||
|
{"bar/sub/sub.html", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range table {
|
||||||
|
t.Run(tt.name, func(st *testing.T) {
|
||||||
|
r := require.New(st)
|
||||||
|
_, err := box.find(tt.name)
|
||||||
|
if tt.found {
|
||||||
|
r.True(box.Has(tt.name))
|
||||||
|
r.NoError(err)
|
||||||
|
} else {
|
||||||
|
r.False(box.Has(tt.name))
|
||||||
|
r.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Virtual_Directory_Not_Found(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
_, err := virtualBox.find("d")
|
||||||
|
r.NoError(err)
|
||||||
|
_, err = virtualBox.find("does-not-exist")
|
||||||
|
r.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AddString(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
_, err := virtualBox.find("string")
|
||||||
|
r.Error(err)
|
||||||
|
|
||||||
|
virtualBox.AddString("string", "hello")
|
||||||
|
|
||||||
|
_, err = virtualBox.find("string")
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal("hello", virtualBox.String("string"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AddBytes(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
_, err := virtualBox.find("bytes")
|
||||||
|
r.Error(err)
|
||||||
|
|
||||||
|
virtualBox.AddBytes("bytes", []byte("hello"))
|
||||||
|
|
||||||
|
_, err = virtualBox.find("bytes")
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal("hello", virtualBox.String("bytes"))
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type box struct {
|
||||||
|
Name string
|
||||||
|
Files []file
|
||||||
|
compress bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *box) Walk(root string) error {
|
||||||
|
root, err := filepath.EvalSymlinks(root)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(root); err != nil {
|
||||||
|
// return nil
|
||||||
|
return errors.Errorf("could not find folder for box: %s", root)
|
||||||
|
}
|
||||||
|
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if info == nil || info.IsDir() || strings.HasSuffix(info.Name(), "-packr.go") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
name := strings.Replace(path, root+string(os.PathSeparator), "", 1)
|
||||||
|
name = strings.Replace(name, "\\", "/", -1)
|
||||||
|
f := file{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugLog("packing file %s\n", f.Name)
|
||||||
|
|
||||||
|
bb, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
if b.compress {
|
||||||
|
bb, err = compressFile(bb)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bb, err = json.Marshal(bb)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
f.Contents = strings.Replace(string(bb), "\"", "\\\"", -1)
|
||||||
|
|
||||||
|
DebugLog("packed file %s\n", f.Name)
|
||||||
|
b.Files = append(b.Files, f)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func compressFile(bb []byte) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := gzip.NewWriter(&buf)
|
||||||
|
_, err := writer.Write(bb)
|
||||||
|
if err != nil {
|
||||||
|
return bb, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
return bb, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DebugLog func(string, ...interface{})
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
DebugLog = func(string, ...interface{}) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var invalidFilePattern = regexp.MustCompile(`(_test|-packr).go$`)
|
||||||
|
|
||||||
|
// Builder scans folders/files looking for `packr.NewBox` and then compiling
|
||||||
|
// the required static files into `<package-name>-packr.go` files so they can
|
||||||
|
// be built into Go binaries.
|
||||||
|
type Builder struct {
|
||||||
|
context.Context
|
||||||
|
RootPath string
|
||||||
|
IgnoredBoxes []string
|
||||||
|
IgnoredFolders []string
|
||||||
|
pkgs map[string]pkg
|
||||||
|
moot *sync.Mutex
|
||||||
|
Compress bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the builder.
|
||||||
|
func (b *Builder) Run() error {
|
||||||
|
wg := &errgroup.Group{}
|
||||||
|
root, err := filepath.EvalSymlinks(b.RootPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if info == nil {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
base := strings.ToLower(filepath.Base(path))
|
||||||
|
if strings.HasPrefix(base, "_") {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
for _, f := range b.IgnoredFolders {
|
||||||
|
if strings.ToLower(f) == base {
|
||||||
|
if info.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
wg.Go(func() error {
|
||||||
|
return b.process(path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
if err := wg.Wait(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return b.dump()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) dump() error {
|
||||||
|
for _, p := range b.pkgs {
|
||||||
|
name := filepath.Join(p.Dir, "a_"+p.Name+"-packr.go")
|
||||||
|
f, err := os.Create(name)
|
||||||
|
defer f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
t, err := template.New("").Parse(tmpl)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
err = t.Execute(f, p)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) process(path string) error {
|
||||||
|
ext := filepath.Ext(path)
|
||||||
|
if ext != ".go" || invalidFilePattern.MatchString(path) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := newVisitor(path)
|
||||||
|
if err := v.Run(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pk := pkg{
|
||||||
|
Dir: filepath.Dir(path),
|
||||||
|
Boxes: []box{},
|
||||||
|
Name: v.Package,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range v.Boxes {
|
||||||
|
var ignored bool
|
||||||
|
for _, i := range b.IgnoredBoxes {
|
||||||
|
if n == i {
|
||||||
|
// this is an ignored box
|
||||||
|
ignored = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ignored {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bx := &box{
|
||||||
|
Name: n,
|
||||||
|
Files: []file{},
|
||||||
|
compress: b.Compress,
|
||||||
|
}
|
||||||
|
DebugLog("building box %s\n", bx.Name)
|
||||||
|
p := filepath.Join(pk.Dir, bx.Name)
|
||||||
|
if err := bx.Walk(p); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
if len(bx.Files) > 0 {
|
||||||
|
pk.Boxes = append(pk.Boxes, *bx)
|
||||||
|
}
|
||||||
|
DebugLog("built box %s with %q\n", bx.Name, bx.Files)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pk.Boxes) > 0 {
|
||||||
|
b.addPkg(pk)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) addPkg(p pkg) {
|
||||||
|
b.moot.Lock()
|
||||||
|
defer b.moot.Unlock()
|
||||||
|
if _, ok := b.pkgs[p.Name]; !ok {
|
||||||
|
b.pkgs[p.Name] = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pp := b.pkgs[p.Name]
|
||||||
|
pp.Boxes = append(pp.Boxes, p.Boxes...)
|
||||||
|
b.pkgs[p.Name] = pp
|
||||||
|
}
|
||||||
|
|
||||||
|
// New Builder with a given context and path
|
||||||
|
func New(ctx context.Context, path string) *Builder {
|
||||||
|
return &Builder{
|
||||||
|
Context: ctx,
|
||||||
|
RootPath: path,
|
||||||
|
IgnoredBoxes: []string{},
|
||||||
|
IgnoredFolders: []string{"vendor", ".git", "node_modules", ".idea"},
|
||||||
|
pkgs: map[string]pkg{},
|
||||||
|
moot: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gobuffalo/packr"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Builder_Run(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
root := filepath.Join("..", "example")
|
||||||
|
Clean(root)
|
||||||
|
defer Clean(root)
|
||||||
|
|
||||||
|
exPackr := filepath.Join(root, "a_example-packr.go")
|
||||||
|
r.False(fileExists(exPackr))
|
||||||
|
|
||||||
|
fooPackr := filepath.Join(root, "foo", "a_foo-packr.go")
|
||||||
|
r.False(fileExists(fooPackr))
|
||||||
|
|
||||||
|
b := New(context.Background(), root)
|
||||||
|
err := b.Run()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
r.True(fileExists(exPackr))
|
||||||
|
r.True(fileExists(fooPackr))
|
||||||
|
|
||||||
|
bb, err := ioutil.ReadFile(exPackr)
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("./assets", "app.css", "\"Ym9ke`)))
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("./assets", "app.js", "\"YWxlcn`)))
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("./templates", "index.html", "\"PCFET0NUWVBF`)))
|
||||||
|
|
||||||
|
bb, err = ioutil.ReadFile(fooPackr)
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("../assets", "app.css", "\"Ym9keS`)))
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("../assets", "app.js", "\"YWxlcn`)))
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("../templates", "index.html", "\"PCFET0NUW`)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Builder_Run_Compress(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
root := filepath.Join("..", "example")
|
||||||
|
defer Clean(root)
|
||||||
|
|
||||||
|
exPackr := filepath.Join(root, "a_example-packr.go")
|
||||||
|
r.False(fileExists(exPackr))
|
||||||
|
|
||||||
|
fooPackr := filepath.Join(root, "foo", "a_foo-packr.go")
|
||||||
|
r.False(fileExists(fooPackr))
|
||||||
|
|
||||||
|
b := New(context.Background(), root)
|
||||||
|
b.Compress = true
|
||||||
|
err := b.Run()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
r.True(fileExists(exPackr))
|
||||||
|
r.True(fileExists(fooPackr))
|
||||||
|
|
||||||
|
bb, err := ioutil.ReadFile(exPackr)
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("./assets", "app.css", "\"H4sIAAAAAAAA/0rKT`)))
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("./assets", "app.js", "\"H4sIAAAAAAAA/0rMSS`)))
|
||||||
|
|
||||||
|
bb, err = ioutil.ReadFile(fooPackr)
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("../assets", "app.css", "\"H4sIAAAAAAAA/0rKT`)))
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("../assets", "app.js", "\"H4sIAAAAAAAA/0rMSS`)))
|
||||||
|
r.True(bytes.Contains(bb, []byte(`packr.PackJSONBytes("../templates", "index.html", "\"H4sIAAAAAAAA`)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Binary_Builds(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
defer os.Chdir(pwd)
|
||||||
|
|
||||||
|
root := "../example"
|
||||||
|
defer Clean(root)
|
||||||
|
defer os.RemoveAll(filepath.Join(root, "bin"))
|
||||||
|
|
||||||
|
b := New(context.Background(), root)
|
||||||
|
err := b.Run()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
os.Chdir(root)
|
||||||
|
cmd := exec.Command(packr.GoBin(), "build", "-v", "-o", "bin/example")
|
||||||
|
err = cmd.Run()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
r.True(fileExists("bin/example"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExists(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return err == nil
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clean up an *-packr.go files
|
||||||
|
func Clean(root string) {
|
||||||
|
root, _ = filepath.EvalSymlinks(root)
|
||||||
|
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
base := filepath.Base(path)
|
||||||
|
if base == ".git" || base == "vendor" || base == "node_modules" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
if info == nil || info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.Contains(base, "-packr.go") {
|
||||||
|
err := os.Remove(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
type file struct {
|
||||||
|
Name string
|
||||||
|
Contents string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f file) String() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
type pkg struct {
|
||||||
|
Name string
|
||||||
|
Dir string
|
||||||
|
Boxes []box
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
var tmpl = `// Code generated by github.com/gobuffalo/packr. DO NOT EDIT.
|
||||||
|
|
||||||
|
package {{.Name}}
|
||||||
|
|
||||||
|
import "github.com/gobuffalo/packr"
|
||||||
|
|
||||||
|
// You can use the "packr clean" command to clean up this,
|
||||||
|
// and any other packr generated files.
|
||||||
|
func init() {
|
||||||
|
{{- range $box := .Boxes }}
|
||||||
|
{{- range .Files }}
|
||||||
|
packr.PackJSONBytes("{{$box.Name}}", "{{.Name}}", "{{.Contents}}")
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,248 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io/ioutil"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type visitor struct {
|
||||||
|
Path string
|
||||||
|
Package string
|
||||||
|
Boxes []string
|
||||||
|
Errors []error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVisitor(path string) *visitor {
|
||||||
|
return &visitor{
|
||||||
|
Path: path,
|
||||||
|
Boxes: []string{},
|
||||||
|
Errors: []error{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) Run() error {
|
||||||
|
b, err := ioutil.ReadFile(v.Path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
file, err := parser.ParseFile(fset, v.Path, string(b), parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Package = file.Name.Name
|
||||||
|
ast.Walk(v, file)
|
||||||
|
|
||||||
|
m := map[string]string{}
|
||||||
|
for _, s := range v.Boxes {
|
||||||
|
m[s] = s
|
||||||
|
}
|
||||||
|
v.Boxes = []string{}
|
||||||
|
for k := range m {
|
||||||
|
v.Boxes = append(v.Boxes, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(v.Boxes)
|
||||||
|
|
||||||
|
if len(v.Errors) > 0 {
|
||||||
|
s := make([]string, len(v.Errors))
|
||||||
|
for i, e := range v.Errors {
|
||||||
|
s[i] = e.Error()
|
||||||
|
}
|
||||||
|
return errors.New(strings.Join(s, "\n"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) Visit(node ast.Node) ast.Visitor {
|
||||||
|
if node == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if err := v.eval(node); err != nil {
|
||||||
|
v.Errors = append(v.Errors, err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) eval(node ast.Node) error {
|
||||||
|
switch t := node.(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
return v.evalExpr(t)
|
||||||
|
case *ast.Ident:
|
||||||
|
return v.evalIdent(t)
|
||||||
|
case *ast.GenDecl:
|
||||||
|
for _, n := range t.Specs {
|
||||||
|
if err := v.eval(n); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
if t.Body == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, b := range t.Body.List {
|
||||||
|
if err := v.evalStmt(b); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case *ast.ValueSpec:
|
||||||
|
for _, e := range t.Values {
|
||||||
|
if err := v.evalExpr(e); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) evalStmt(stmt ast.Stmt) error {
|
||||||
|
switch t := stmt.(type) {
|
||||||
|
case *ast.ExprStmt:
|
||||||
|
return v.evalExpr(t.X)
|
||||||
|
case *ast.AssignStmt:
|
||||||
|
for _, e := range t.Rhs {
|
||||||
|
if err := v.evalArgs(e); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) evalExpr(expr ast.Expr) error {
|
||||||
|
switch t := expr.(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
if t.Fun == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, a := range t.Args {
|
||||||
|
switch at := a.(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
if sel, ok := t.Fun.(*ast.SelectorExpr); ok {
|
||||||
|
return v.evalSelector(at, sel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := v.evalArgs(at); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
case *ast.CompositeLit:
|
||||||
|
for _, e := range at.Elts {
|
||||||
|
if err := v.evalExpr(e); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ft, ok := t.Fun.(*ast.SelectorExpr); ok {
|
||||||
|
return v.evalSelector(t, ft)
|
||||||
|
}
|
||||||
|
case *ast.KeyValueExpr:
|
||||||
|
return v.evalExpr(t.Value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) evalArgs(expr ast.Expr) error {
|
||||||
|
switch at := expr.(type) {
|
||||||
|
case *ast.CompositeLit:
|
||||||
|
for _, e := range at.Elts {
|
||||||
|
if err := v.evalExpr(e); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// case *ast.BasicLit:
|
||||||
|
// fmt.Println("evalArgs", at.Value)
|
||||||
|
// v.addBox(at.Value)
|
||||||
|
case *ast.CallExpr:
|
||||||
|
if at.Fun == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch st := at.Fun.(type) {
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
if err := v.evalSelector(at, st); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
case *ast.Ident:
|
||||||
|
return v.evalIdent(st)
|
||||||
|
}
|
||||||
|
for _, a := range at.Args {
|
||||||
|
if err := v.evalArgs(a); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) evalSelector(expr *ast.CallExpr, sel *ast.SelectorExpr) error {
|
||||||
|
x, ok := sel.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if x.Name == "packr" && sel.Sel.Name == "NewBox" {
|
||||||
|
for _, e := range expr.Args {
|
||||||
|
switch at := e.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
switch at.Obj.Kind {
|
||||||
|
case ast.Var:
|
||||||
|
if as, ok := at.Obj.Decl.(*ast.AssignStmt); ok {
|
||||||
|
v.addVariable(as)
|
||||||
|
}
|
||||||
|
case ast.Con:
|
||||||
|
if vs, ok := at.Obj.Decl.(*ast.ValueSpec); ok {
|
||||||
|
v.addConstant(vs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v.evalIdent(at)
|
||||||
|
case *ast.BasicLit:
|
||||||
|
v.addBox(at.Value)
|
||||||
|
case *ast.CallExpr:
|
||||||
|
return v.evalExpr(at)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) evalIdent(i *ast.Ident) error {
|
||||||
|
if i.Obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s, ok := i.Obj.Decl.(*ast.AssignStmt); ok {
|
||||||
|
return v.evalStmt(s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) addBox(b string) {
|
||||||
|
b = strings.Replace(b, "\"", "", -1)
|
||||||
|
v.Boxes = append(v.Boxes, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) addVariable(as *ast.AssignStmt) error {
|
||||||
|
if len(as.Rhs) == 1 {
|
||||||
|
if bs, ok := as.Rhs[0].(*ast.BasicLit); ok {
|
||||||
|
v.addBox(bs.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) addConstant(vs *ast.ValueSpec) error {
|
||||||
|
if len(vs.Values) == 1 {
|
||||||
|
if bs, ok := vs.Values[0].(*ast.BasicLit); ok {
|
||||||
|
v.addBox(bs.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Visitor(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
v := newVisitor("../example/example.go")
|
||||||
|
r.NoError(v.Run())
|
||||||
|
|
||||||
|
r.Equal("example", v.Package)
|
||||||
|
r.Len(v.Errors, 0)
|
||||||
|
r.Len(v.Boxes, 7)
|
||||||
|
r.Equal([]string{"./assets", "./bar", "./constant", "./foo", "./sf", "./templates", "./variable"}, v.Boxes)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var goPath = filepath.Join(os.Getenv("HOME"), "go")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var once sync.Once
|
||||||
|
once.Do(func() {
|
||||||
|
cmd := exec.Command("go", "env", "GOPATH")
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
goPath = strings.TrimSpace(string(b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoPath returns the current GOPATH env var
|
||||||
|
// or if it's missing, the default.
|
||||||
|
func GoPath() string {
|
||||||
|
return goPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoBin returns the current GO_BIN env var
|
||||||
|
// or if it's missing, a default of "go"
|
||||||
|
func GoBin() string {
|
||||||
|
go_bin := os.Getenv("GO_BIN")
|
||||||
|
if go_bin == "" {
|
||||||
|
return "go"
|
||||||
|
}
|
||||||
|
return go_bin
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
background: red;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
alert("hello!");
|
|
@ -0,0 +1,43 @@
|
||||||
|
package example
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gobuffalo/packr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var a = packr.NewBox("./foo")
|
||||||
|
|
||||||
|
const constString = "./constant"
|
||||||
|
|
||||||
|
type S struct{}
|
||||||
|
|
||||||
|
func (S) f(packr.Box) {}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// packr.NewBox("../idontexists")
|
||||||
|
|
||||||
|
b := "./variable"
|
||||||
|
packr.NewBox(b)
|
||||||
|
|
||||||
|
packr.NewBox(constString)
|
||||||
|
|
||||||
|
// Cannot work from a function
|
||||||
|
packr.NewBox(strFromFunc())
|
||||||
|
|
||||||
|
// This variable should not be added
|
||||||
|
fromFunc := strFromFunc()
|
||||||
|
packr.NewBox(fromFunc)
|
||||||
|
|
||||||
|
foo("/templates", packr.NewBox("./templates"))
|
||||||
|
packr.NewBox("./assets")
|
||||||
|
|
||||||
|
packr.NewBox("./bar")
|
||||||
|
|
||||||
|
s := S{}
|
||||||
|
s.f(packr.NewBox("./sf"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func strFromFunc() string {
|
||||||
|
return "./fromFunc"
|
||||||
|
}
|
||||||
|
|
||||||
|
func foo(s string, box packr.Box) {}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package foo
|
||||||
|
|
||||||
|
import "github.com/gobuffalo/packr"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
packr.NewBox("../assets")
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package foo
|
||||||
|
|
||||||
|
import "github.com/gobuffalo/packr"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
packr.NewBox("../templates")
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>Foo</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
body
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>INDEX</title>
|
||||||
|
link
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
body
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,15 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type File interface {
|
||||||
|
io.ReadCloser
|
||||||
|
io.Writer
|
||||||
|
FileInfo() (os.FileInfo, error)
|
||||||
|
Readdir(count int) ([]os.FileInfo, error)
|
||||||
|
Seek(offset int64, whence int) (int64, error)
|
||||||
|
Stat() (os.FileInfo, error)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileInfo struct {
|
||||||
|
Path string
|
||||||
|
Contents []byte
|
||||||
|
size int64
|
||||||
|
modTime time.Time
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileInfo) Name() string {
|
||||||
|
return f.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileInfo) Size() int64 {
|
||||||
|
return f.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileInfo) Mode() os.FileMode {
|
||||||
|
return 0444
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileInfo) ModTime() time.Time {
|
||||||
|
return f.modTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileInfo) IsDir() bool {
|
||||||
|
return f.isDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
goodbye cruel world!
|
|
@ -0,0 +1 @@
|
||||||
|
hello world!
|
|
@ -0,0 +1 @@
|
||||||
|
<h1>Index!</h1>
|
|
@ -0,0 +1,13 @@
|
||||||
|
module github.com/gobuffalo/packr
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/pkg/errors v0.8.0
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/spf13/cobra v0.0.3
|
||||||
|
github.com/spf13/pflag v1.0.2 // indirect
|
||||||
|
github.com/stretchr/testify v1.2.2
|
||||||
|
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
|
||||||
|
)
|
|
@ -0,0 +1,18 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||||
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
|
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
|
||||||
|
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f h1:QM2QVxvDoW9PFSPp/zy9FgxJLfaWTZlS61KEPtBwacM=
|
||||||
|
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
@ -0,0 +1,59 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_HTTPBox(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/", http.FileServer(testBox))
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/hello.txt", nil)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mux.ServeHTTP(res, req)
|
||||||
|
|
||||||
|
r.Equal(200, res.Code)
|
||||||
|
r.Equal("hello world!", strings.TrimSpace(res.Body.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HTTPBox_NotFound(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/", http.FileServer(testBox))
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/notInBox.txt", nil)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mux.ServeHTTP(res, req)
|
||||||
|
|
||||||
|
r.Equal(404, res.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_HTTPBox_Handles_IndexHTML(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/", http.FileServer(testBox))
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/", nil)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mux.ServeHTTP(res, req)
|
||||||
|
|
||||||
|
r.Equal("<h1>Index!</h1>", strings.TrimSpace(res.Body.String()))
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/json"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var gil = &sync.Mutex{}
|
||||||
|
var data = map[string]map[string][]byte{}
|
||||||
|
|
||||||
|
// PackBytes packs bytes for a file into a box.
|
||||||
|
func PackBytes(box string, name string, bb []byte) {
|
||||||
|
gil.Lock()
|
||||||
|
defer gil.Unlock()
|
||||||
|
if _, ok := data[box]; !ok {
|
||||||
|
data[box] = map[string][]byte{}
|
||||||
|
}
|
||||||
|
data[box][name] = bb
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackBytesGzip packets the gzipped compressed bytes into a box.
|
||||||
|
func PackBytesGzip(box string, name string, bb []byte) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
w := gzip.NewWriter(&buf)
|
||||||
|
_, err := w.Write(bb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
PackBytes(box, name, buf.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackJSONBytes packs JSON encoded bytes for a file into a box.
|
||||||
|
func PackJSONBytes(box string, name string, jbb string) error {
|
||||||
|
var bb []byte
|
||||||
|
err := json.Unmarshal([]byte(jbb), &bb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
PackBytes(box, name, bb)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackBytes unpacks bytes for specific box.
|
||||||
|
func UnpackBytes(box string) {
|
||||||
|
gil.Lock()
|
||||||
|
defer gil.Unlock()
|
||||||
|
delete(data, box)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osPaths(paths ...string) []string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
for i, path := range paths {
|
||||||
|
paths[i] = strings.Replace(path, "/", "\\", -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
func osPath(path string) string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return strings.Replace(path, "/", "\\", -1)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/gobuffalo/packr"
|
||||||
|
"github.com/gobuffalo/packr/builder"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// buildCmd represents the build command
|
||||||
|
var buildCmd = &cobra.Command{
|
||||||
|
Use: "build",
|
||||||
|
Short: "Wraps the go build command with packr",
|
||||||
|
DisableFlagParsing: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
defer builder.Clean(input)
|
||||||
|
b := builder.New(context.Background(), input)
|
||||||
|
err := b.Run()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cargs := []string{"build"}
|
||||||
|
cargs = append(cargs, args...)
|
||||||
|
cp := exec.Command(packr.GoBin(), cargs...)
|
||||||
|
cp.Stderr = os.Stderr
|
||||||
|
cp.Stdin = os.Stdin
|
||||||
|
cp.Stdout = os.Stdout
|
||||||
|
|
||||||
|
return cp.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(buildCmd)
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gobuffalo/packr/builder"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cleanCmd = &cobra.Command{
|
||||||
|
Use: "clean",
|
||||||
|
Short: "removes any *-packr.go files",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
builder.Clean(input)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(cleanCmd)
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gobuffalo/packr"
|
||||||
|
"github.com/gobuffalo/packr/builder"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// installCmd represents the install command
|
||||||
|
var installCmd = &cobra.Command{
|
||||||
|
Use: "install",
|
||||||
|
Short: "Wraps the go install command with packr",
|
||||||
|
DisableFlagParsing: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) > 0 {
|
||||||
|
input = args[len(args)-1]
|
||||||
|
if !strings.HasPrefix(input, ".") {
|
||||||
|
input = filepath.Join(packr.GoPath(), "src", input)
|
||||||
|
if _, err := os.Stat(input); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer builder.Clean(input)
|
||||||
|
b := builder.New(context.Background(), input)
|
||||||
|
err := b.Run()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cargs := []string{"install"}
|
||||||
|
cargs = append(cargs, args...)
|
||||||
|
cp := exec.Command(packr.GoBin(), cargs...)
|
||||||
|
cp.Stderr = os.Stderr
|
||||||
|
cp.Stdin = os.Stdin
|
||||||
|
cp.Stdout = os.Stdout
|
||||||
|
|
||||||
|
return cp.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(installCmd)
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gobuffalo/packr/builder"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var input string
|
||||||
|
var compress bool
|
||||||
|
var verbose bool
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "packr",
|
||||||
|
Short: "compiles static files into Go files",
|
||||||
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if !verbose {
|
||||||
|
for _, a := range args {
|
||||||
|
if a == "-v" {
|
||||||
|
verbose = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
builder.DebugLog = func(s string, a ...interface{}) {
|
||||||
|
os.Stdout.WriteString(fmt.Sprintf(s, a...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
b := builder.New(context.Background(), input)
|
||||||
|
b.Compress = compress
|
||||||
|
return b.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
rootCmd.Flags().StringVarP(&input, "input", "i", pwd, "path to scan for packr Boxes")
|
||||||
|
rootCmd.Flags().BoolVarP(&compress, "compress", "z", false, "compress box contents")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "print verbose logging information")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the commands
|
||||||
|
func Execute() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/gobuffalo/packr/packr/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testBox = NewBox("./fixtures")
|
||||||
|
var virtualBox = NewBox("./virtual")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
PackBytes(virtualBox.Path, "a", []byte("a"))
|
||||||
|
PackBytes(virtualBox.Path, "b", []byte("b"))
|
||||||
|
PackBytes(virtualBox.Path, "c", []byte("c"))
|
||||||
|
PackBytes(virtualBox.Path, "d/a", []byte("d/a"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_PackBytes(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
PackBytes(testBox.Path, "foo", []byte("bar"))
|
||||||
|
s := testBox.String("foo")
|
||||||
|
r.Equal("bar", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_PackJSONBytes(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
b, err := json.Marshal([]byte("json bytes"))
|
||||||
|
r.NoError(err)
|
||||||
|
err = PackJSONBytes(testBox.Path, "the bytes", string(b))
|
||||||
|
r.NoError(err)
|
||||||
|
s, err := testBox.MustBytes("the bytes")
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal([]byte("json bytes"), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_PackBytesGzip(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
err := PackBytesGzip(testBox.Path, "gzip", []byte("gzip foobar"))
|
||||||
|
r.NoError(err)
|
||||||
|
s := testBox.String("gzip")
|
||||||
|
r.Equal("gzip foobar", s)
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
var _ File = physicalFile{}
|
||||||
|
|
||||||
|
type physicalFile struct {
|
||||||
|
*os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p physicalFile) FileInfo() (os.FileInfo, error) {
|
||||||
|
return os.Stat(p.Name())
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
# github.com/gobuffalo/packr Stands on the Shoulders of Giants
|
||||||
|
|
||||||
|
github.com/gobuffalo/packr does not try to reinvent the wheel! Instead, it uses the already great wheels developed by the Go community and puts them all together in the best way possible. Without these giants this project would not be possible. Please make sure to check them out and thank them for all of their hard work.
|
||||||
|
|
||||||
|
Thank you to the following **GIANTS**:
|
||||||
|
|
||||||
|
|
||||||
|
* [github.com/pkg/errors](https://godoc.org/github.com/pkg/errors)
|
||||||
|
|
||||||
|
* [github.com/spf13/cobra](https://godoc.org/github.com/spf13/cobra)
|
||||||
|
|
||||||
|
* [github.com/spf13/pflag](https://godoc.org/github.com/spf13/pflag)
|
||||||
|
|
||||||
|
* [github.com/stretchr/testify/assert](https://godoc.org/github.com/stretchr/testify/assert)
|
||||||
|
|
||||||
|
* [github.com/stretchr/testify/require](https://godoc.org/github.com/stretchr/testify/require)
|
||||||
|
|
||||||
|
* [github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew](https://godoc.org/github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew)
|
||||||
|
|
||||||
|
* [github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib](https://godoc.org/github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib)
|
||||||
|
|
||||||
|
* [golang.org/x/net/context](https://godoc.org/golang.org/x/net/context)
|
||||||
|
|
||||||
|
* [golang.org/x/sync/errgroup](https://godoc.org/golang.org/x/sync/errgroup)
|
|
@ -0,0 +1,3 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
const Version = "v1.13.7"
|
|
@ -0,0 +1,57 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var virtualFileModTime = time.Now()
|
||||||
|
var _ File = virtualFile{}
|
||||||
|
|
||||||
|
type virtualFile struct {
|
||||||
|
*bytes.Reader
|
||||||
|
Name string
|
||||||
|
info fileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f virtualFile) FileInfo() (os.FileInfo, error) {
|
||||||
|
return f.info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f virtualFile) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f virtualFile) Write(p []byte) (n int, err error) {
|
||||||
|
return 0, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f virtualFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
return []os.FileInfo{f.info}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f virtualFile) Stat() (os.FileInfo, error) {
|
||||||
|
return f.info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVirtualFile(name string, b []byte) File {
|
||||||
|
return virtualFile{
|
||||||
|
Reader: bytes.NewReader(b),
|
||||||
|
Name: name,
|
||||||
|
info: fileInfo{
|
||||||
|
Path: name,
|
||||||
|
Contents: b,
|
||||||
|
size: int64(len(b)),
|
||||||
|
modTime: virtualFileModTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVirtualDir(name string) File {
|
||||||
|
var b []byte
|
||||||
|
v := newVirtualFile(name, b).(virtualFile)
|
||||||
|
v.info.isDir = true
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WalkFunc func(string, File) error
|
||||||
|
|
||||||
|
// Walk will traverse the box and call the WalkFunc for each file in the box/folder.
|
||||||
|
func (b Box) Walk(wf WalkFunc) error {
|
||||||
|
if data[b.Path] == nil {
|
||||||
|
base, err := filepath.EvalSymlinks(filepath.Join(b.callingDir, b.Path))
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return filepath.Walk(base, func(path string, info os.FileInfo, err error) error {
|
||||||
|
cleanName, err := filepath.Rel(base, path)
|
||||||
|
if err != nil {
|
||||||
|
cleanName = strings.TrimPrefix(path, base)
|
||||||
|
}
|
||||||
|
cleanName = filepath.ToSlash(filepath.Clean(cleanName))
|
||||||
|
cleanName = strings.TrimPrefix(cleanName, "/")
|
||||||
|
cleanName = filepath.FromSlash(cleanName)
|
||||||
|
if info == nil || info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := fileFor(path, cleanName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return wf(cleanName, file)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for n := range data[b.Path] {
|
||||||
|
f, err := b.find(n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = wf(n, f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkPrefix will call box.Walk and call the WalkFunc when it finds paths that have a matching prefix
|
||||||
|
func (b Box) WalkPrefix(prefix string, wf WalkFunc) error {
|
||||||
|
opre := osPath(prefix)
|
||||||
|
return b.Walk(func(path string, f File) error {
|
||||||
|
if strings.HasPrefix(osPath(path), opre) {
|
||||||
|
if err := wf(path, f); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Box_Walk_Physical(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
count := 0
|
||||||
|
err := testBox.Walk(func(path string, f File) error {
|
||||||
|
count++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(3, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Box_Walk_Virtual(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
count := 0
|
||||||
|
err := virtualBox.Walk(func(path string, f File) error {
|
||||||
|
count++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(4, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Box_WalkPrefix_Physical(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
var files []string
|
||||||
|
b := NewBox("../packr/fixtures")
|
||||||
|
err := b.WalkPrefix("foo/", func(path string, f File) error {
|
||||||
|
files = append(files, path)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(2, len(files))
|
||||||
|
mustHave := osPaths("foo/a.txt", "foo/bar/b.txt")
|
||||||
|
r.Equal(mustHave, files)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Box_WalkPrefix_Virtual(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
var files []string
|
||||||
|
err := virtualBox.WalkPrefix("d", func(path string, f File) error {
|
||||||
|
files = append(files, path)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(1, len(files))
|
||||||
|
r.Equal([]string{"d/a"}, files)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,11 @@
|
||||||
|
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 ./...
|
|
@ -0,0 +1,23 @@
|
||||||
|
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.
|
|
@ -0,0 +1,52 @@
|
||||||
|
# errors [](https://travis-ci.org/pkg/errors) [](https://ci.appveyor.com/project/davecheney/errors/branch/master) [](http://godoc.org/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
|
|
@ -0,0 +1,32 @@
|
||||||
|
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
|
|
@ -0,0 +1,59 @@
|
||||||
|
// +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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,269 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,535 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,292 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue