vgo
This commit is contained in:
parent
b0e0b10823
commit
6197f455f6
|
@ -1,99 +0,0 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/GeertJohan/go.rice"
|
|
||||||
packages = [".","embedded"]
|
|
||||||
revision = "c02ca9a983da5807ddf7d796784928f5be4afd09"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/Xe/gopreload"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "a00a8beb369cafd88bb7b32f31fc4ff3219c3565"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/Xe/jsonfeed"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "e21591505612b9064436351ffc9acddfa3b583e8"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/Xe/ln"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "466e05b2ef3e48ce08a367b6aaac09ee29a124e5"
|
|
||||||
version = "v0.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/daaku/go.zipexe"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "a5fe2436ffcb3236e175e5149162b41cd28bd27d"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/google/gops"
|
|
||||||
packages = ["agent","internal","signal"]
|
|
||||||
revision = "57e77c5c37da1f4e1af49f9d1fe760f146c1579e"
|
|
||||||
version = "v0.3.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/gorilla/feeds"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "4b936b5221c53c99fcd4b15ac0b8c38ff490ab89"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/jtolds/qod"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "3abb44dfc7ba8b5cdfdb634786f57e78c7004e1c"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/kardianos/osext"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "ae77be60afb1dcacde03767a8c37337fad28ac14"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/magefile/mage"
|
|
||||||
packages = ["mg","types"]
|
|
||||||
revision = "ab3ca2f6f85577d7ec82e0a6df721147a2e737f9"
|
|
||||||
version = "v2.0.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pkg/errors"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
|
||||||
version = "v0.8.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/russross/blackfriday"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "cadec560ec52d93835bf2f15bd794700d3a2473b"
|
|
||||||
version = "v2.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/shurcooL/sanitized_anchor_name"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/tj/front"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "739be213b0a1c496dccaf9e5df1514150c9548e4"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "v1"
|
|
||||||
name = "gopkg.in/yaml.v1"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "9f9df34309c04878acc86042b16630b0f696e1de"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "0689be1827bc408bc22bad70a56fe65f0d7986f4b7a01376fd4c9420f54aac18"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
71
Gopkg.toml
71
Gopkg.toml
|
@ -1,71 +0,0 @@
|
||||||
|
|
||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/GeertJohan/go.rice"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/Xe/gopreload"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/Xe/jsonfeed"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/Xe/ln"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/google/gops"
|
|
||||||
version = "0.3.2"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/gorilla/feeds"
|
|
||||||
version = "1.0.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/jtolds/qod"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/magefile/mage"
|
|
||||||
version = "2.0.1"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/pkg/errors"
|
|
||||||
version = "0.8.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/russross/blackfriday"
|
|
||||||
version = "2.0.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/tj/front"
|
|
||||||
|
|
||||||
[metadata.heroku]
|
|
||||||
root-package = "github.com/Xe/site"
|
|
||||||
install = [ "." ]
|
|
||||||
ensure = "false"
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
module github.com/Xe/site
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da
|
||||||
|
github.com/Xe/gopreload v0.0.0-20170326043426-a00a8beb369c
|
||||||
|
github.com/Xe/jsonfeed v0.0.0-20170520170432-e21591505612
|
||||||
|
github.com/Xe/ln v0.0.0-20170921000907-466e05b2ef3e
|
||||||
|
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb
|
||||||
|
github.com/google/gops v0.3.2
|
||||||
|
github.com/gorilla/feeds v1.0.0
|
||||||
|
github.com/jtolds/qod v0.0.0-20170925014538-3abb44dfc7ba
|
||||||
|
github.com/kr/pretty v0.1.0
|
||||||
|
github.com/magefile/mage v0.0.0-20171025021237-ab3ca2f6f855
|
||||||
|
github.com/pkg/errors v0.8.0
|
||||||
|
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95
|
||||||
|
github.com/st3fan/jsonfeed v0.0.0-20170519104842-498b2850d26b
|
||||||
|
github.com/stretchr/testify v1.2.2
|
||||||
|
github.com/tj/front v0.0.0-20170212063142-739be213b0a1
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
|
||||||
|
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
|
||||||
|
)
|
|
@ -0,0 +1,18 @@
|
||||||
|
github.com/Xe/gopreload v0.0.0-20170326043426-a00a8beb369c h1:lqTJqaoonxgJMvvfl1ukr/3qCEGWC0nQxzPezbJrhHs=
|
||||||
|
github.com/Xe/jsonfeed v0.0.0-20170520170432-e21591505612 h1:5cPld6YTMozzm3lK9VCnOErgoFbADM2hZc4KDu0YNKs=
|
||||||
|
github.com/Xe/ln v0.0.0-20170921000907-466e05b2ef3e h1:uMC/C/zBov+PItx2c6Vax4lt37X+2V5X7NWRcXsxuzE=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/google/gops v0.3.2 h1:n9jMkrye8dh3WQ0IxG5dzLRIhQeZDZoGaj0D7T7x7hQ=
|
||||||
|
github.com/gorilla/feeds v1.0.0 h1:EbkEvaYf+PXhYNHS20heBG7Rl2X6Zy8l11ZBWAHkWqE=
|
||||||
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52 h1:xZ0R4UuR0EDx7m0OmvsqZaomfqCeYJ/PyLm94WLhnIM=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=
|
||||||
|
github.com/st3fan/jsonfeed v0.0.0-20170519104842-498b2850d26b h1:i9Wk9W2bjrMvUf59rJKX4/lvZXy3L3lcX7g18c8AveU=
|
||||||
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
github.com/tj/front v0.0.0-20170212063142-739be213b0a1 h1:lA+aPRvltlx2fwv/BnxyYSDQo3pIeqzHgMO5GvK0T9E=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
|
|
@ -1,8 +0,0 @@
|
||||||
/example/example
|
|
||||||
/example/example.exe
|
|
||||||
/rice/rice
|
|
||||||
/rice/rice.exe
|
|
||||||
|
|
||||||
*.rice-box.go
|
|
||||||
*.rice-box.syso
|
|
||||||
.wercker
|
|
|
@ -1,19 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- master
|
|
||||||
- 1.x.x
|
|
||||||
- 1.8.x
|
|
||||||
- 1.7.x
|
|
||||||
- 1.6.x
|
|
||||||
- 1.5.x
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go get -t ./...
|
|
||||||
- env
|
|
||||||
- if [ "${TRAVIS_GO_VERSION%.*}" != "1.5" ]; then go get github.com/golang/lint/golint; fi
|
|
||||||
script:
|
|
||||||
- go build -x ./...
|
|
||||||
- go test -cover ./...
|
|
||||||
- go vet ./...
|
|
||||||
- if [ "${TRAVIS_GO_VERSION%.*}" != "1.5" ]; then golint .; fi
|
|
|
@ -1,4 +0,0 @@
|
||||||
Geert-Johan Riemer <geertjohan@geertjohan.net>
|
|
||||||
Paul Maddox <paul.maddox@gmail.com>
|
|
||||||
Vincent Petithory <vincent.petithory@gmail.com>
|
|
||||||
|
|
|
@ -1,151 +0,0 @@
|
||||||
## go.rice
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/GeertJohan/go.rice.png)](https://travis-ci.org/GeertJohan/go.rice)
|
|
||||||
[![Godoc](https://img.shields.io/badge/godoc-go.rice-blue.svg?style=flat-square)](https://godoc.org/github.com/GeertJohan/go.rice)
|
|
||||||
|
|
||||||
go.rice is a [Go](http://golang.org) package that makes working with resources such as html,js,css,images and templates very easy. During development `go.rice` will load required files directly from disk. Upon deployment it is easy to add all resource files to a executable using the `rice` tool, without changing the source code for your package. go.rice provides several methods to add resources to a binary.
|
|
||||||
|
|
||||||
### What does it do?
|
|
||||||
The first thing go.rice does is finding the correct absolute path for your resource files. Say you are executing go binary in your home directory, but your `html-files` are located in `$GOPATH/src/yourApplication/html-files`. `go.rice` will lookup the correct path for that directory (relative to the location of yourApplication). The only thing you have to do is include the resources using `rice.FindBox("html-files")`.
|
|
||||||
|
|
||||||
This only works when the source is available to the machine executing the binary. Which is always the case when the binary was installed with `go get` or `go install`. It might occur that you wish to simply provide a binary, without source. The `rice` tool analyses source code and finds call's to `rice.FindBox(..)` and adds the required directories to the executable binary. There are several methods to add these resources. You can 'embed' by generating go source code, or append the resource to the executable as zip file. In both cases `go.rice` will detect the embedded or appended resources and load those, instead of looking up files from disk.
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
Use `go get` to install the package the `rice` tool.
|
|
||||||
```
|
|
||||||
go get github.com/GeertJohan/go.rice
|
|
||||||
go get github.com/GeertJohan/go.rice/rice
|
|
||||||
```
|
|
||||||
|
|
||||||
### Package usage
|
|
||||||
|
|
||||||
Import the package: `import "github.com/GeertJohan/go.rice"`
|
|
||||||
|
|
||||||
**Serving a static content folder over HTTP with a rice Box**
|
|
||||||
```go
|
|
||||||
http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox()))
|
|
||||||
http.ListenAndServe(":8080", nil)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Service a static content folder over HTTP at a non-root location**
|
|
||||||
```go
|
|
||||||
box := rice.MustFindBox("cssfiles")
|
|
||||||
cssFileServer := http.StripPrefix("/css/", http.FileServer(box.HTTPBox()))
|
|
||||||
http.Handle("/css/", cssFileServer)
|
|
||||||
http.ListenAndServe(":8080", nil)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note the *trailing slash* in `/css/` in both the call to
|
|
||||||
`http.StripPrefix` and `http.Handle`.
|
|
||||||
|
|
||||||
**Loading a template**
|
|
||||||
```go
|
|
||||||
// find a rice.Box
|
|
||||||
templateBox, err := rice.FindBox("example-templates")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// get file contents as string
|
|
||||||
templateString, err := templateBox.String("message.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// parse and execute the template
|
|
||||||
tmplMessage, err := template.New("message").Parse(templateString)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
tmplMessage.Execute(os.Stdout, map[string]string{"Message": "Hello, world!"})
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Never call `FindBox()` or `MustFindBox()` from an `init()` function, as the boxes might have not been loaded at that time.
|
|
||||||
|
|
||||||
### Tool usage
|
|
||||||
The `rice` tool lets you add the resources to a binary executable so the files are not loaded from the filesystem anymore. This creates a 'standalone' executable. There are several ways to add the resources to a binary, each has pro's and con's but all will work without requiring changes to the way you load the resources.
|
|
||||||
|
|
||||||
#### embed-go
|
|
||||||
**Embed resources by generating Go source code**
|
|
||||||
|
|
||||||
This method must be executed before building. It generates a single Go source file called *rice-box.go* for each package, that is compiled by the go compiler into the binary.
|
|
||||||
|
|
||||||
The downside with this option is that the generated go source files can become very large, which will slow down compilation and require lots of memory to compile.
|
|
||||||
|
|
||||||
Execute the following commands:
|
|
||||||
```
|
|
||||||
rice embed-go
|
|
||||||
go build
|
|
||||||
```
|
|
||||||
|
|
||||||
*A Note on Symbolic Links*: `embed-go` uses the `os.Walk` function
|
|
||||||
from the standard library. The `os.Walk` function does **not** follow
|
|
||||||
symbolic links. So, when creating a box, be aware that any symbolic
|
|
||||||
links inside your box's directory will not be followed. **However**,
|
|
||||||
if the box itself is a symbolic link, its actual location will be
|
|
||||||
resolved first and then walked. In summary, if your box location is a
|
|
||||||
symbolic link, it will be followed but none of the symbolic links in
|
|
||||||
the box will be followed.
|
|
||||||
|
|
||||||
#### embed-syso
|
|
||||||
**Embed resources by generating a coff .syso file and some .go source code**
|
|
||||||
|
|
||||||
** This method is experimental and should not be used for production systems just yet **
|
|
||||||
|
|
||||||
This method must be executed before building. It generates a COFF .syso file and Go source file that are compiled by the go compiler into the binary.
|
|
||||||
|
|
||||||
Execute the following commands:
|
|
||||||
```
|
|
||||||
rice embed-syso
|
|
||||||
go build
|
|
||||||
```
|
|
||||||
|
|
||||||
#### append
|
|
||||||
**Append resources to executable as zip file**
|
|
||||||
|
|
||||||
This method changes an already built executable. It appends the resources as zip file to the binary. It makes compilation a lot faster and can be used with large resource files.
|
|
||||||
|
|
||||||
Downsides for appending are that it requires `zip` to be installed and does not provide a working Seek method.
|
|
||||||
|
|
||||||
Run the following commands to create a standalone executable.
|
|
||||||
```
|
|
||||||
go build -o example
|
|
||||||
rice append --exec example
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note: requires zip command to be installed**
|
|
||||||
|
|
||||||
On windows, install zip from http://gnuwin32.sourceforge.net/packages/zip.htm or cygwin/msys toolsets.
|
|
||||||
|
|
||||||
#### Help information
|
|
||||||
Run `rice -h` for information about all options.
|
|
||||||
|
|
||||||
You can run the -h option for each sub-command, e.g. `rice append -h`.
|
|
||||||
|
|
||||||
### Order of precedence
|
|
||||||
When opening a new box, the rice package tries to locate the resources in the following order:
|
|
||||||
|
|
||||||
- embedded in generated go source
|
|
||||||
- appended as zip
|
|
||||||
- 'live' from filesystem
|
|
||||||
|
|
||||||
|
|
||||||
### License
|
|
||||||
This project is licensed under a Simplified BSD license. Please read the [LICENSE file][license].
|
|
||||||
|
|
||||||
### TODO & Development
|
|
||||||
This package is not completed yet. Though it already provides working embedding, some important featuers are still missing.
|
|
||||||
- implement Readdir() correctly on virtualDir
|
|
||||||
- in-code TODO's
|
|
||||||
- find boxes in imported packages
|
|
||||||
|
|
||||||
Less important stuff:
|
|
||||||
- idea, os/arch dependent embeds. rice checks if embedding file has _os_arch or build flags. If box is not requested by file without buildflags, then the buildflags are applied to the embed file.
|
|
||||||
|
|
||||||
### Package documentation
|
|
||||||
|
|
||||||
You will find package documentation at [godoc.org/github.com/GeertJohan/go.rice][godoc].
|
|
||||||
|
|
||||||
|
|
||||||
[license]: https://github.com/GeertJohan/go.rice/blob/master/LICENSE
|
|
||||||
[godoc]: http://godoc.org/github.com/GeertJohan/go.rice
|
|
|
@ -1,138 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/daaku/go.zipexe"
|
|
||||||
"github.com/kardianos/osext"
|
|
||||||
)
|
|
||||||
|
|
||||||
// appendedBox defines an appended box
|
|
||||||
type appendedBox struct {
|
|
||||||
Name string // box name
|
|
||||||
Files map[string]*appendedFile // appended files (*zip.File) by full path
|
|
||||||
}
|
|
||||||
|
|
||||||
type appendedFile struct {
|
|
||||||
zipFile *zip.File
|
|
||||||
dir bool
|
|
||||||
dirInfo *appendedDirInfo
|
|
||||||
children []*appendedFile
|
|
||||||
content []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendedBoxes is a public register of appendes boxes
|
|
||||||
var appendedBoxes = make(map[string]*appendedBox)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// find if exec is appended
|
|
||||||
thisFile, err := osext.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return // not appended or cant find self executable
|
|
||||||
}
|
|
||||||
closer, rd, err := zipexe.OpenCloser(thisFile)
|
|
||||||
if err != nil {
|
|
||||||
return // not appended
|
|
||||||
}
|
|
||||||
defer closer.Close()
|
|
||||||
|
|
||||||
for _, f := range rd.File {
|
|
||||||
// get box and file name from f.Name
|
|
||||||
fileParts := strings.SplitN(strings.TrimLeft(filepath.ToSlash(f.Name), "/"), "/", 2)
|
|
||||||
boxName := fileParts[0]
|
|
||||||
var fileName string
|
|
||||||
if len(fileParts) > 1 {
|
|
||||||
fileName = fileParts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// find box or create new one if doesn't exist
|
|
||||||
box := appendedBoxes[boxName]
|
|
||||||
if box == nil {
|
|
||||||
box = &appendedBox{
|
|
||||||
Name: boxName,
|
|
||||||
Files: make(map[string]*appendedFile),
|
|
||||||
}
|
|
||||||
appendedBoxes[boxName] = box
|
|
||||||
}
|
|
||||||
|
|
||||||
// create and add file to box
|
|
||||||
af := &appendedFile{
|
|
||||||
zipFile: f,
|
|
||||||
}
|
|
||||||
if f.Comment == "dir" {
|
|
||||||
af.dir = true
|
|
||||||
af.dirInfo = &appendedDirInfo{
|
|
||||||
name: filepath.Base(af.zipFile.Name),
|
|
||||||
//++ TODO: use zip modtime when that is set correctly: af.zipFile.ModTime()
|
|
||||||
time: time.Now(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// this is a file, we need it's contents so we can create a bytes.Reader when the file is opened
|
|
||||||
// make a new byteslice
|
|
||||||
af.content = make([]byte, af.zipFile.FileInfo().Size())
|
|
||||||
// ignore reading empty files from zip (empty file still is a valid file to be read though!)
|
|
||||||
if len(af.content) > 0 {
|
|
||||||
// open io.ReadCloser
|
|
||||||
rc, err := af.zipFile.Open()
|
|
||||||
if err != nil {
|
|
||||||
af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
|
|
||||||
// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
|
|
||||||
log.Printf("error opening appended file %s: %v", af.zipFile.Name, err)
|
|
||||||
} else {
|
|
||||||
_, err = rc.Read(af.content)
|
|
||||||
rc.Close()
|
|
||||||
if err != nil {
|
|
||||||
af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
|
|
||||||
// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
|
|
||||||
log.Printf("error reading data for appended file %s: %v", af.zipFile.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add appendedFile to box file list
|
|
||||||
box.Files[fileName] = af
|
|
||||||
|
|
||||||
// add to parent dir (if any)
|
|
||||||
dirName := filepath.Dir(fileName)
|
|
||||||
if dirName == "." {
|
|
||||||
dirName = ""
|
|
||||||
}
|
|
||||||
if fileName != "" { // don't make box root dir a child of itself
|
|
||||||
if dir := box.Files[dirName]; dir != nil {
|
|
||||||
dir.children = append(dir.children, af)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// implements os.FileInfo.
|
|
||||||
// used for Readdir()
|
|
||||||
type appendedDirInfo struct {
|
|
||||||
name string
|
|
||||||
time time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (adi *appendedDirInfo) Name() string {
|
|
||||||
return adi.name
|
|
||||||
}
|
|
||||||
func (adi *appendedDirInfo) Size() int64 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
func (adi *appendedDirInfo) Mode() os.FileMode {
|
|
||||||
return os.ModeDir
|
|
||||||
}
|
|
||||||
func (adi *appendedDirInfo) ModTime() time.Time {
|
|
||||||
return adi.time
|
|
||||||
}
|
|
||||||
func (adi *appendedDirInfo) IsDir() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
func (adi *appendedDirInfo) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,337 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Box abstracts a directory for resources/files.
|
|
||||||
// It can either load files from disk, or from embedded code (when `rice --embed` was ran).
|
|
||||||
type Box struct {
|
|
||||||
name string
|
|
||||||
absolutePath string
|
|
||||||
embed *embedded.EmbeddedBox
|
|
||||||
appendd *appendedBox
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultLocateOrder = []LocateMethod{LocateEmbedded, LocateAppended, LocateFS}
|
|
||||||
|
|
||||||
func findBox(name string, order []LocateMethod) (*Box, error) {
|
|
||||||
b := &Box{name: name}
|
|
||||||
|
|
||||||
// no support for absolute paths since gopath can be different on different machines.
|
|
||||||
// therefore, required box must be located relative to package requiring it.
|
|
||||||
if filepath.IsAbs(name) {
|
|
||||||
return nil, errors.New("given name/path is absolute")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for _, method := range order {
|
|
||||||
switch method {
|
|
||||||
case LocateEmbedded:
|
|
||||||
if embed := embedded.EmbeddedBoxes[name]; embed != nil {
|
|
||||||
b.embed = embed
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case LocateAppended:
|
|
||||||
appendedBoxName := strings.Replace(name, `/`, `-`, -1)
|
|
||||||
if appendd := appendedBoxes[appendedBoxName]; appendd != nil {
|
|
||||||
b.appendd = appendd
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case LocateFS:
|
|
||||||
// resolve absolute directory path
|
|
||||||
err := b.resolveAbsolutePathFromCaller()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// check if absolutePath exists on filesystem
|
|
||||||
info, err := os.Stat(b.absolutePath)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// check if absolutePath is actually a directory
|
|
||||||
if !info.IsDir() {
|
|
||||||
err = errors.New("given name/path is not a directory")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
case LocateWorkingDirectory:
|
|
||||||
// resolve absolute directory path
|
|
||||||
err := b.resolveAbsolutePathFromWorkingDirectory()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// check if absolutePath exists on filesystem
|
|
||||||
info, err := os.Stat(b.absolutePath)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// check if absolutePath is actually a directory
|
|
||||||
if !info.IsDir() {
|
|
||||||
err = errors.New("given name/path is not a directory")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
err = fmt.Errorf("could not locate box %q", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindBox returns a Box instance for given name.
|
|
||||||
// When the given name is a relative path, it's base path will be the calling pkg/cmd's source root.
|
|
||||||
// When the given name is absolute, it's absolute. derp.
|
|
||||||
// Make sure the path doesn't contain any sensitive information as it might be placed into generated go source (embedded).
|
|
||||||
func FindBox(name string) (*Box, error) {
|
|
||||||
return findBox(name, defaultLocateOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFindBox returns a Box instance for given name, like FindBox does.
|
|
||||||
// It does not return an error, instead it panics when an error occurs.
|
|
||||||
func MustFindBox(name string) *Box {
|
|
||||||
box, err := findBox(name, defaultLocateOrder)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return box
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is injected as a mutable function literal so that we can mock it out in
|
|
||||||
// tests and return a fixed test file.
|
|
||||||
var resolveAbsolutePathFromCaller = func(name string, nStackFrames int) (string, error) {
|
|
||||||
_, callingGoFile, _, ok := runtime.Caller(nStackFrames)
|
|
||||||
if !ok {
|
|
||||||
return "", errors.New("couldn't find caller on stack")
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve to proper path
|
|
||||||
pkgDir := filepath.Dir(callingGoFile)
|
|
||||||
// fix for go cover
|
|
||||||
const coverPath = "_test/_obj_test"
|
|
||||||
if !filepath.IsAbs(pkgDir) {
|
|
||||||
if i := strings.Index(pkgDir, coverPath); i >= 0 {
|
|
||||||
pkgDir = pkgDir[:i] + pkgDir[i+len(coverPath):] // remove coverPath
|
|
||||||
pkgDir = filepath.Join(os.Getenv("GOPATH"), "src", pkgDir) // make absolute
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filepath.Join(pkgDir, name), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Box) resolveAbsolutePathFromCaller() error {
|
|
||||||
path, err := resolveAbsolutePathFromCaller(b.name, 4)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.absolutePath = path
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Box) resolveAbsolutePathFromWorkingDirectory() error {
|
|
||||||
path, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.absolutePath = filepath.Join(path, b.name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmbedded indicates wether this box was embedded into the application
|
|
||||||
func (b *Box) IsEmbedded() bool {
|
|
||||||
return b.embed != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAppended indicates wether this box was appended to the application
|
|
||||||
func (b *Box) IsAppended() bool {
|
|
||||||
return b.appendd != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time returns how actual the box is.
|
|
||||||
// When the box is embedded, it's value is saved in the embedding code.
|
|
||||||
// When the box is live, this methods returns time.Now()
|
|
||||||
func (b *Box) Time() time.Time {
|
|
||||||
if b.IsEmbedded() {
|
|
||||||
return b.embed.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
//++ TODO: return time for appended box
|
|
||||||
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open opens a File from the box
|
|
||||||
// If there is an error, it will be of type *os.PathError.
|
|
||||||
func (b *Box) Open(name string) (*File, error) {
|
|
||||||
if Debug {
|
|
||||||
fmt.Printf("Open(%s)\n", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.IsEmbedded() {
|
|
||||||
if Debug {
|
|
||||||
fmt.Println("Box is embedded")
|
|
||||||
}
|
|
||||||
|
|
||||||
// trim prefix (paths are relative to box)
|
|
||||||
name = strings.TrimLeft(name, "/")
|
|
||||||
if Debug {
|
|
||||||
fmt.Printf("Trying %s\n", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// search for file
|
|
||||||
ef := b.embed.Files[name]
|
|
||||||
if ef == nil {
|
|
||||||
if Debug {
|
|
||||||
fmt.Println("Didn't find file in embed")
|
|
||||||
}
|
|
||||||
// file not found, try dir
|
|
||||||
ed := b.embed.Dirs[name]
|
|
||||||
if ed == nil {
|
|
||||||
if Debug {
|
|
||||||
fmt.Println("Didn't find dir in embed")
|
|
||||||
}
|
|
||||||
// dir not found, error out
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "open",
|
|
||||||
Path: name,
|
|
||||||
Err: os.ErrNotExist,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if Debug {
|
|
||||||
fmt.Println("Found dir. Returning virtual dir")
|
|
||||||
}
|
|
||||||
vd := newVirtualDir(ed)
|
|
||||||
return &File{virtualD: vd}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// box is embedded
|
|
||||||
if Debug {
|
|
||||||
fmt.Println("Found file. Returning virtual file")
|
|
||||||
}
|
|
||||||
vf := newVirtualFile(ef)
|
|
||||||
return &File{virtualF: vf}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.IsAppended() {
|
|
||||||
// trim prefix (paths are relative to box)
|
|
||||||
name = strings.TrimLeft(name, "/")
|
|
||||||
|
|
||||||
// search for file
|
|
||||||
appendedFile := b.appendd.Files[name]
|
|
||||||
if appendedFile == nil {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "open",
|
|
||||||
Path: name,
|
|
||||||
Err: os.ErrNotExist,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new file
|
|
||||||
f := &File{
|
|
||||||
appendedF: appendedFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this file is a directory, we want to be able to read and seek
|
|
||||||
if !appendedFile.dir {
|
|
||||||
// looks like malformed data in zip, error now
|
|
||||||
if appendedFile.content == nil {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "open",
|
|
||||||
Path: "name",
|
|
||||||
Err: errors.New("error reading data from zip file"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// create new bytes.Reader
|
|
||||||
f.appendedFileReader = bytes.NewReader(appendedFile.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// all done
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform os open
|
|
||||||
if Debug {
|
|
||||||
fmt.Printf("Using os.Open(%s)", filepath.Join(b.absolutePath, name))
|
|
||||||
}
|
|
||||||
file, err := os.Open(filepath.Join(b.absolutePath, name))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &File{realF: file}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the content of the file with given name as []byte.
|
|
||||||
func (b *Box) Bytes(name string) ([]byte, error) {
|
|
||||||
file, err := b.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
content, err := ioutil.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustBytes returns the content of the file with given name as []byte.
|
|
||||||
// panic's on error.
|
|
||||||
func (b *Box) MustBytes(name string) []byte {
|
|
||||||
bts, err := b.Bytes(name)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return bts
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the content of the file with given name as string.
|
|
||||||
func (b *Box) String(name string) (string, error) {
|
|
||||||
// check if box is embedded, optimized fast path
|
|
||||||
if b.IsEmbedded() {
|
|
||||||
// find file in embed
|
|
||||||
ef := b.embed.Files[name]
|
|
||||||
if ef == nil {
|
|
||||||
return "", os.ErrNotExist
|
|
||||||
}
|
|
||||||
// return as string
|
|
||||||
return ef.Content, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
bts, err := b.Bytes(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(bts), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustString returns the content of the file with given name as string.
|
|
||||||
// panic's on error.
|
|
||||||
func (b *Box) MustString(name string) string {
|
|
||||||
str, err := b.String(name)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the box
|
|
||||||
func (b *Box) Name() string {
|
|
||||||
return b.name
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
// LocateMethod defines how a box is located.
|
|
||||||
type LocateMethod int
|
|
||||||
|
|
||||||
const (
|
|
||||||
LocateFS = LocateMethod(iota) // Locate on the filesystem according to package path.
|
|
||||||
LocateAppended // Locate boxes appended to the executable.
|
|
||||||
LocateEmbedded // Locate embedded boxes.
|
|
||||||
LocateWorkingDirectory // Locate on the binary working directory
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config allows customizing the box lookup behavior.
|
|
||||||
type Config struct {
|
|
||||||
// LocateOrder defines the priority order that boxes are searched for. By
|
|
||||||
// default, the package global FindBox searches for embedded boxes first,
|
|
||||||
// then appended boxes, and then finally boxes on the filesystem. That
|
|
||||||
// search order may be customized by provided the ordered list here. Leaving
|
|
||||||
// out a particular method will omit that from the search space. For
|
|
||||||
// example, []LocateMethod{LocateEmbedded, LocateAppended} will never search
|
|
||||||
// the filesystem for boxes.
|
|
||||||
LocateOrder []LocateMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindBox searches for boxes using the LocateOrder of the config.
|
|
||||||
func (c *Config) FindBox(boxName string) (*Box, error) {
|
|
||||||
return findBox(boxName, c.LocateOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFindBox searches for boxes using the LocateOrder of the config, like
|
|
||||||
// FindBox does. It does not return an error, instead it panics when an error
|
|
||||||
// occurs.
|
|
||||||
func (c *Config) MustFindBox(boxName string) *Box {
|
|
||||||
box, err := findBox(boxName, c.LocateOrder)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return box
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
)
|
|
||||||
|
|
||||||
// For all test code in this package, define a set of test boxes.
|
|
||||||
var eb1 *embedded.EmbeddedBox
|
|
||||||
var ab1, ab2 *appendedBox
|
|
||||||
var fsb1, fsb2, fsb3 string // paths to filesystem boxes
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Box1 exists in all three locations.
|
|
||||||
eb1 = &embedded.EmbeddedBox{Name: "box1"}
|
|
||||||
embedded.RegisterEmbeddedBox(eb1.Name, eb1)
|
|
||||||
ab1 = &appendedBox{Name: "box1"}
|
|
||||||
appendedBoxes["box1"] = ab1
|
|
||||||
fsb1, err = ioutil.TempDir("", "box1")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box2 exists in only appended and FS.
|
|
||||||
ab2 = &appendedBox{Name: "box2"}
|
|
||||||
appendedBoxes["box2"] = ab2
|
|
||||||
fsb2, err = ioutil.TempDir("", "box2")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box3 exists only on disk.
|
|
||||||
fsb3, err = ioutil.TempDir("", "box3")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also, replace the default filesystem lookup path to directly support the
|
|
||||||
// on-disk temp directories.
|
|
||||||
resolveAbsolutePathFromCaller = func(name string, n int) (string, error) {
|
|
||||||
if name == "box1" {
|
|
||||||
return fsb1, nil
|
|
||||||
} else if name == "box2" {
|
|
||||||
return fsb2, nil
|
|
||||||
} else if name == "box3" {
|
|
||||||
return fsb3, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("Unknown box name: %q", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultLookupOrder(t *testing.T) {
|
|
||||||
// Box1 exists in all three, so the default order should find the embedded.
|
|
||||||
b, err := FindBox("box1")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box1, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b.embed != eb1 {
|
|
||||||
t.Fatalf("Expected to find embedded box, but got %#v", b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box2 exists in appended and FS, so find the appended.
|
|
||||||
b2, err := FindBox("box2")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box2, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b2.appendd != ab2 {
|
|
||||||
t.Fatalf("Expected to find appended box, but got %#v", b2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box3 exists only on FS, so find it there.
|
|
||||||
b3, err := FindBox("box3")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box3, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b3.absolutePath != fsb3 {
|
|
||||||
t.Fatalf("Expected to find FS box, but got %#v", b3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigLocateOrder(t *testing.T) {
|
|
||||||
cfg := Config{LocateOrder: []LocateMethod{LocateFS, LocateAppended, LocateEmbedded}}
|
|
||||||
fsb := []string{fsb1, fsb2, fsb3}
|
|
||||||
// All 3 boxes have a FS backend, so we should always find that.
|
|
||||||
for i, boxName := range []string{"box1", "box2", "box3"} {
|
|
||||||
b, err := cfg.FindBox(boxName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find %q, got error: %v", boxName, err)
|
|
||||||
}
|
|
||||||
if b.absolutePath != fsb[i] {
|
|
||||||
t.Fatalf("Expected to find FS box, but got %#v", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.LocateOrder = []LocateMethod{LocateAppended, LocateFS, LocateEmbedded}
|
|
||||||
{
|
|
||||||
b, err := cfg.FindBox("box3")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box3, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b.absolutePath != fsb3 {
|
|
||||||
t.Fatalf("Expected to find FS box, but got %#v", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
b, err := cfg.FindBox("box2")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box2, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b.appendd != ab2 {
|
|
||||||
t.Fatalf("Expected to find appended box, but got %#v", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// What if we don't list all the locate methods?
|
|
||||||
cfg.LocateOrder = []LocateMethod{LocateEmbedded}
|
|
||||||
{
|
|
||||||
b, err := cfg.FindBox("box2")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected not to find box2, but something was found: %#v", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
b, err := cfg.FindBox("box1")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected to find box2, got error: %v", err)
|
|
||||||
}
|
|
||||||
if b.embed != eb1 {
|
|
||||||
t.Fatalf("Expected to find embedded box, but got %#v", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
// Debug can be set to true to enable debugging.
|
|
||||||
var Debug = false
|
|
|
@ -1,90 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
)
|
|
||||||
|
|
||||||
// re-type to make exported methods invisible to user (godoc)
|
|
||||||
// they're not required for the user
|
|
||||||
// embeddedDirInfo implements os.FileInfo
|
|
||||||
type embeddedDirInfo embedded.EmbeddedDir
|
|
||||||
|
|
||||||
// Name returns the base name of the directory
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) Name() string {
|
|
||||||
return ed.Filename
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size always returns 0
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) Size() int64 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns the file mode bits
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) Mode() os.FileMode {
|
|
||||||
return os.FileMode(0555 | os.ModeDir) // dr-xr-xr-x
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModTime returns the modification time
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) ModTime() time.Time {
|
|
||||||
return ed.DirModTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir returns the abbreviation for Mode().IsDir() (always true)
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) IsDir() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sys returns the underlying data source (always nil)
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ed *embeddedDirInfo) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-type to make exported methods invisible to user (godoc)
|
|
||||||
// they're not required for the user
|
|
||||||
// embeddedFileInfo implements os.FileInfo
|
|
||||||
type embeddedFileInfo embedded.EmbeddedFile
|
|
||||||
|
|
||||||
// Name returns the base name of the file
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) Name() string {
|
|
||||||
return ef.Filename
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the length in bytes for regular files; system-dependent for others
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) Size() int64 {
|
|
||||||
return int64(len(ef.Content))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns the file mode bits
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) Mode() os.FileMode {
|
|
||||||
return os.FileMode(0555) // r-xr-xr-x
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModTime returns the modification time
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) ModTime() time.Time {
|
|
||||||
return ef.FileModTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir returns the abbreviation for Mode().IsDir() (always false)
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) IsDir() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sys returns the underlying data source (always nil)
|
|
||||||
// (implementing os.FileInfo)
|
|
||||||
func (ef *embeddedFileInfo) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
// Package embedded defines embedded data types that are shared between the go.rice package and generated code.
|
|
||||||
package embedded
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
EmbedTypeGo = 0
|
|
||||||
EmbedTypeSyso = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// EmbeddedBox defines an embedded box
|
|
||||||
type EmbeddedBox struct {
|
|
||||||
Name string // box name
|
|
||||||
Time time.Time // embed time
|
|
||||||
EmbedType int // kind of embedding
|
|
||||||
Files map[string]*EmbeddedFile // ALL embedded files by full path
|
|
||||||
Dirs map[string]*EmbeddedDir // ALL embedded dirs by full path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link creates the ChildDirs and ChildFiles links in all EmbeddedDir's
|
|
||||||
func (e *EmbeddedBox) Link() {
|
|
||||||
for path, ed := range e.Dirs {
|
|
||||||
fmt.Println(path)
|
|
||||||
ed.ChildDirs = make([]*EmbeddedDir, 0)
|
|
||||||
ed.ChildFiles = make([]*EmbeddedFile, 0)
|
|
||||||
}
|
|
||||||
for path, ed := range e.Dirs {
|
|
||||||
parentDirpath, _ := filepath.Split(path)
|
|
||||||
if strings.HasSuffix(parentDirpath, "/") {
|
|
||||||
parentDirpath = parentDirpath[:len(parentDirpath)-1]
|
|
||||||
}
|
|
||||||
parentDir := e.Dirs[parentDirpath]
|
|
||||||
if parentDir == nil {
|
|
||||||
panic("parentDir `" + parentDirpath + "` is missing in embedded box")
|
|
||||||
}
|
|
||||||
parentDir.ChildDirs = append(parentDir.ChildDirs, ed)
|
|
||||||
}
|
|
||||||
for path, ef := range e.Files {
|
|
||||||
dirpath, _ := filepath.Split(path)
|
|
||||||
if strings.HasSuffix(dirpath, "/") {
|
|
||||||
dirpath = dirpath[:len(dirpath)-1]
|
|
||||||
}
|
|
||||||
dir := e.Dirs[dirpath]
|
|
||||||
if dir == nil {
|
|
||||||
panic("dir `" + dirpath + "` is missing in embedded box")
|
|
||||||
}
|
|
||||||
dir.ChildFiles = append(dir.ChildFiles, ef)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmbeddedDir is instanced in the code generated by the rice tool and contains all necicary information about an embedded file
|
|
||||||
type EmbeddedDir struct {
|
|
||||||
Filename string
|
|
||||||
DirModTime time.Time
|
|
||||||
ChildDirs []*EmbeddedDir // direct childs, as returned by virtualDir.Readdir()
|
|
||||||
ChildFiles []*EmbeddedFile // direct childs, as returned by virtualDir.Readdir()
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmbeddedFile is instanced in the code generated by the rice tool and contains all necicary information about an embedded file
|
|
||||||
type EmbeddedFile struct {
|
|
||||||
Filename string // filename
|
|
||||||
FileModTime time.Time
|
|
||||||
Content string
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmbeddedBoxes is a public register of embedded boxes
|
|
||||||
var EmbeddedBoxes = make(map[string]*EmbeddedBox)
|
|
||||||
|
|
||||||
// RegisterEmbeddedBox registers an EmbeddedBox
|
|
||||||
func RegisterEmbeddedBox(name string, box *EmbeddedBox) {
|
|
||||||
if _, exists := EmbeddedBoxes[name]; exists {
|
|
||||||
panic(fmt.Sprintf("EmbeddedBox with name `%s` exists already", name))
|
|
||||||
}
|
|
||||||
EmbeddedBoxes[name] = box
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
test content
|
|
||||||
break
|
|
Binary file not shown.
Before Width: | Height: | Size: 52 KiB |
|
@ -1 +0,0 @@
|
||||||
I have a message for you: {{.Message}}
|
|
|
@ -1,69 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
conf := rice.Config{
|
|
||||||
LocateOrder: []rice.LocateMethod{rice.LocateEmbedded, rice.LocateAppended, rice.LocateFS},
|
|
||||||
}
|
|
||||||
box, err := conf.FindBox("example-files")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error opening rice.Box: %s\n", err)
|
|
||||||
}
|
|
||||||
// spew.Dump(box)
|
|
||||||
|
|
||||||
contentString, err := box.String("file.txt")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("could not read file contents as string: %s\n", err)
|
|
||||||
}
|
|
||||||
log.Printf("Read some file contents as string:\n%s\n", contentString)
|
|
||||||
|
|
||||||
contentBytes, err := box.Bytes("file.txt")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("could not read file contents as byteSlice: %s\n", err)
|
|
||||||
}
|
|
||||||
log.Printf("Read some file contents as byteSlice:\n%s\n", hex.Dump(contentBytes))
|
|
||||||
|
|
||||||
file, err := box.Open("file.txt")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("could not open file: %s\n", err)
|
|
||||||
}
|
|
||||||
spew.Dump(file)
|
|
||||||
|
|
||||||
// find/create a rice.Box
|
|
||||||
templateBox, err := rice.FindBox("example-templates")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// get file contents as string
|
|
||||||
templateString, err := templateBox.String("message.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// parse and execute the template
|
|
||||||
tmplMessage, err := template.New("message").Parse(templateString)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
tmplMessage.Execute(os.Stdout, map[string]string{"Message": "Hello, world!"})
|
|
||||||
|
|
||||||
http.Handle("/", http.FileServer(box.HTTPBox()))
|
|
||||||
go func() {
|
|
||||||
fmt.Println("Serving files on :8080, press ctrl-C to exit")
|
|
||||||
err := http.ListenAndServe(":8080", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error serving files: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {}
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// File implements the io.Reader, io.Seeker, io.Closer and http.File interfaces
|
|
||||||
type File struct {
|
|
||||||
// File abstracts file methods so the user doesn't see the difference between rice.virtualFile, rice.virtualDir and os.File
|
|
||||||
// TODO: maybe use internal File interface and four implementations: *os.File, appendedFile, virtualFile, virtualDir
|
|
||||||
|
|
||||||
// real file on disk
|
|
||||||
realF *os.File
|
|
||||||
|
|
||||||
// when embedded (go)
|
|
||||||
virtualF *virtualFile
|
|
||||||
virtualD *virtualDir
|
|
||||||
|
|
||||||
// when appended (zip)
|
|
||||||
appendedF *appendedFile
|
|
||||||
appendedFileReader *bytes.Reader
|
|
||||||
// TODO: is appendedFileReader subject of races? Might need a lock here..
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close is like (*os.File).Close()
|
|
||||||
// Visit http://golang.org/pkg/os/#File.Close for more information
|
|
||||||
func (f *File) Close() error {
|
|
||||||
if f.appendedF != nil {
|
|
||||||
if f.appendedFileReader == nil {
|
|
||||||
return errors.New("already closed")
|
|
||||||
}
|
|
||||||
f.appendedFileReader = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if f.virtualF != nil {
|
|
||||||
return f.virtualF.close()
|
|
||||||
}
|
|
||||||
if f.virtualD != nil {
|
|
||||||
return f.virtualD.close()
|
|
||||||
}
|
|
||||||
return f.realF.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat is like (*os.File).Stat()
|
|
||||||
// Visit http://golang.org/pkg/os/#File.Stat for more information
|
|
||||||
func (f *File) Stat() (os.FileInfo, error) {
|
|
||||||
if f.appendedF != nil {
|
|
||||||
if f.appendedF.dir {
|
|
||||||
return f.appendedF.dirInfo, nil
|
|
||||||
}
|
|
||||||
if f.appendedFileReader == nil {
|
|
||||||
return nil, errors.New("file is closed")
|
|
||||||
}
|
|
||||||
return f.appendedF.zipFile.FileInfo(), nil
|
|
||||||
}
|
|
||||||
if f.virtualF != nil {
|
|
||||||
return f.virtualF.stat()
|
|
||||||
}
|
|
||||||
if f.virtualD != nil {
|
|
||||||
return f.virtualD.stat()
|
|
||||||
}
|
|
||||||
return f.realF.Stat()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readdir is like (*os.File).Readdir()
|
|
||||||
// Visit http://golang.org/pkg/os/#File.Readdir for more information
|
|
||||||
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
|
|
||||||
if f.appendedF != nil {
|
|
||||||
if f.appendedF.dir {
|
|
||||||
fi := make([]os.FileInfo, 0, len(f.appendedF.children))
|
|
||||||
for _, childAppendedFile := range f.appendedF.children {
|
|
||||||
if childAppendedFile.dir {
|
|
||||||
fi = append(fi, childAppendedFile.dirInfo)
|
|
||||||
} else {
|
|
||||||
fi = append(fi, childAppendedFile.zipFile.FileInfo())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fi, nil
|
|
||||||
}
|
|
||||||
//++ TODO: is os.ErrInvalid the correct error for Readdir on file?
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
if f.virtualF != nil {
|
|
||||||
return f.virtualF.readdir(count)
|
|
||||||
}
|
|
||||||
if f.virtualD != nil {
|
|
||||||
return f.virtualD.readdir(count)
|
|
||||||
}
|
|
||||||
return f.realF.Readdir(count)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read is like (*os.File).Read()
|
|
||||||
// Visit http://golang.org/pkg/os/#File.Read for more information
|
|
||||||
func (f *File) Read(bts []byte) (int, error) {
|
|
||||||
if f.appendedF != nil {
|
|
||||||
if f.appendedFileReader == nil {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "read",
|
|
||||||
Path: filepath.Base(f.appendedF.zipFile.Name),
|
|
||||||
Err: errors.New("file is closed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if f.appendedF.dir {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "read",
|
|
||||||
Path: filepath.Base(f.appendedF.zipFile.Name),
|
|
||||||
Err: errors.New("is a directory"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f.appendedFileReader.Read(bts)
|
|
||||||
}
|
|
||||||
if f.virtualF != nil {
|
|
||||||
return f.virtualF.read(bts)
|
|
||||||
}
|
|
||||||
if f.virtualD != nil {
|
|
||||||
return f.virtualD.read(bts)
|
|
||||||
}
|
|
||||||
return f.realF.Read(bts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek is like (*os.File).Seek()
|
|
||||||
// Visit http://golang.org/pkg/os/#File.Seek for more information
|
|
||||||
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
|
||||||
if f.appendedF != nil {
|
|
||||||
if f.appendedFileReader == nil {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "seek",
|
|
||||||
Path: filepath.Base(f.appendedF.zipFile.Name),
|
|
||||||
Err: errors.New("file is closed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f.appendedFileReader.Seek(offset, whence)
|
|
||||||
}
|
|
||||||
if f.virtualF != nil {
|
|
||||||
return f.virtualF.seek(offset, whence)
|
|
||||||
}
|
|
||||||
if f.virtualD != nil {
|
|
||||||
return f.virtualD.seek(offset, whence)
|
|
||||||
}
|
|
||||||
return f.realF.Seek(offset, whence)
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTTPBox implements http.FileSystem which allows the use of Box with a http.FileServer.
|
|
||||||
// e.g.: http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox()))
|
|
||||||
type HTTPBox struct {
|
|
||||||
*Box
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPBox creates a new HTTPBox from an existing Box
|
|
||||||
func (b *Box) HTTPBox() *HTTPBox {
|
|
||||||
return &HTTPBox{b}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open returns a File using the http.File interface
|
|
||||||
func (hb *HTTPBox) Open(name string) (http.File, error) {
|
|
||||||
return hb.Box.Open(name)
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
zipexe "github.com/daaku/go.zipexe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func operationAppend(pkgs []*build.Package) {
|
|
||||||
// create tmp zipfile
|
|
||||||
tmpZipfileName := filepath.Join(os.TempDir(), fmt.Sprintf("ricebox-%d-%s.zip", time.Now().Unix(), randomString(10)))
|
|
||||||
verbosef("Will create tmp zipfile: %s\n", tmpZipfileName)
|
|
||||||
tmpZipfile, err := os.Create(tmpZipfileName)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating tmp zipfile: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
tmpZipfile.Close()
|
|
||||||
os.Remove(tmpZipfileName)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// find abs path for binary file
|
|
||||||
binfileName, err := filepath.Abs(flags.Append.Executable)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error finding absolute path for executable to append: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
verbosef("Will append to file: %s\n", binfileName)
|
|
||||||
|
|
||||||
// check that command doesn't already have zip appended
|
|
||||||
if rd, _ := zipexe.Open(binfileName); rd != nil {
|
|
||||||
fmt.Printf("Cannot append to already appended executable. Please remove %s and build a fresh one.\n", binfileName)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// open binfile
|
|
||||||
binfile, err := os.OpenFile(binfileName, os.O_WRONLY, os.ModeAppend)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: unable to open executable file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer binfile.Close()
|
|
||||||
|
|
||||||
binfileInfo, err := binfile.Stat()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error: unable to stat executable file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create zip.Writer
|
|
||||||
zipWriter := zip.NewWriter(tmpZipfile)
|
|
||||||
|
|
||||||
// write the zip offset into the zip data
|
|
||||||
zipWriter.SetOffset(binfileInfo.Size())
|
|
||||||
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
// find boxes for this command
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
|
|
||||||
// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
|
|
||||||
if len(boxMap) == 0 {
|
|
||||||
fmt.Printf("no calls to rice.FindBox() or rice.MustFindBox() found in import path `%s`\n", pkg.ImportPath)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
verbosef("\n")
|
|
||||||
|
|
||||||
for boxname := range boxMap {
|
|
||||||
appendedBoxName := strings.Replace(boxname, `/`, `-`, -1)
|
|
||||||
|
|
||||||
// walk box path's and insert files
|
|
||||||
boxPath := filepath.Clean(filepath.Join(pkg.Dir, boxname))
|
|
||||||
filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if info == nil {
|
|
||||||
fmt.Printf("Error: box \"%s\" not found on disk\n", path)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
// create zipFilename
|
|
||||||
zipFileName := filepath.Join(appendedBoxName, strings.TrimPrefix(path, boxPath))
|
|
||||||
// write directories as empty file with comment "dir"
|
|
||||||
if info.IsDir() {
|
|
||||||
_, err := zipWriter.CreateHeader(&zip.FileHeader{
|
|
||||||
Name: zipFileName,
|
|
||||||
Comment: "dir",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating dir in tmp zip: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// create zipFileWriter
|
|
||||||
zipFileHeader, err := zip.FileInfoHeader(info)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating zip FileHeader: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
zipFileHeader.Name = zipFileName
|
|
||||||
zipFileWriter, err := zipWriter.CreateHeader(zipFileHeader)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating file in tmp zip: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
srcFile, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error opening file to append: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
_, err = io.Copy(zipFileWriter, srcFile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error copying file contents to zip: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
srcFile.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = zipWriter.Close()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error closing tmp zipfile: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tmpZipfile.Sync()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error syncing tmp zipfile: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
_, err = tmpZipfile.Seek(0, 0)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error seeking tmp zipfile: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
_, err = binfile.Seek(0, 2)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error seeking bin file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(binfile, tmpZipfile)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error appending zipfile to executable: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func operationClean(pkg *build.Package) {
|
|
||||||
filepath.Walk(pkg.Dir, func(filename string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error walking pkg dir to clean files: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
verbosef("checking file '%s'\n", filename)
|
|
||||||
if filepath.Base(filename) == "rice-box.go" ||
|
|
||||||
strings.HasSuffix(filename, ".rice-box.go") ||
|
|
||||||
strings.HasSuffix(filename, ".rice-box.syso") {
|
|
||||||
err := os.Remove(filename)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error removing file (%s): %s\n", filename, err)
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
verbosef("removed file '%s'\n", filename)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"go/format"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const boxFilename = "rice-box.go"
|
|
||||||
|
|
||||||
func writeBoxesGo(pkg *build.Package, out io.Writer) error {
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
|
|
||||||
// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
|
|
||||||
if len(boxMap) == 0 {
|
|
||||||
fmt.Println("no calls to rice.FindBox() found")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
verbosef("\n")
|
|
||||||
|
|
||||||
var boxes []*boxDataType
|
|
||||||
|
|
||||||
for boxname := range boxMap {
|
|
||||||
// find path and filename for this box
|
|
||||||
boxPath := filepath.Join(pkg.Dir, boxname)
|
|
||||||
|
|
||||||
// Check to see if the path for the box is a symbolic link. If so, simply
|
|
||||||
// box what the symbolic link points to. Note: the filepath.Walk function
|
|
||||||
// will NOT follow any nested symbolic links. This only handles the case
|
|
||||||
// where the root of the box is a symbolic link.
|
|
||||||
symPath, serr := os.Readlink(boxPath)
|
|
||||||
if serr == nil {
|
|
||||||
boxPath = symPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// verbose info
|
|
||||||
verbosef("embedding box '%s' to '%s'\n", boxname, boxFilename)
|
|
||||||
|
|
||||||
// read box metadata
|
|
||||||
boxInfo, ierr := os.Stat(boxPath)
|
|
||||||
if ierr != nil {
|
|
||||||
return fmt.Errorf("Error: unable to access box at %s\n", boxPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create box datastructure (used by template)
|
|
||||||
box := &boxDataType{
|
|
||||||
BoxName: boxname,
|
|
||||||
UnixNow: boxInfo.ModTime().Unix(),
|
|
||||||
Files: make([]*fileDataType, 0),
|
|
||||||
Dirs: make(map[string]*dirDataType),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !boxInfo.IsDir() {
|
|
||||||
return fmt.Errorf("Error: Box %s must point to a directory but points to %s instead\n",
|
|
||||||
boxname, boxPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill box datastructure with file data
|
|
||||||
err := filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error walking box: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := strings.TrimPrefix(path, boxPath)
|
|
||||||
filename = strings.Replace(filename, "\\", "/", -1)
|
|
||||||
filename = strings.TrimPrefix(filename, "/")
|
|
||||||
if info.IsDir() {
|
|
||||||
dirData := &dirDataType{
|
|
||||||
Identifier: "dir" + nextIdentifier(),
|
|
||||||
FileName: filename,
|
|
||||||
ModTime: info.ModTime().Unix(),
|
|
||||||
ChildFiles: make([]*fileDataType, 0),
|
|
||||||
ChildDirs: make([]*dirDataType, 0),
|
|
||||||
}
|
|
||||||
verbosef("\tincludes dir: '%s'\n", dirData.FileName)
|
|
||||||
box.Dirs[dirData.FileName] = dirData
|
|
||||||
|
|
||||||
// add tree entry (skip for root, it'll create a recursion)
|
|
||||||
if dirData.FileName != "" {
|
|
||||||
pathParts := strings.Split(dirData.FileName, "/")
|
|
||||||
parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
|
|
||||||
parentDir.ChildDirs = append(parentDir.ChildDirs, dirData)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fileData := &fileDataType{
|
|
||||||
Identifier: "file" + nextIdentifier(),
|
|
||||||
FileName: filename,
|
|
||||||
ModTime: info.ModTime().Unix(),
|
|
||||||
}
|
|
||||||
verbosef("\tincludes file: '%s'\n", fileData.FileName)
|
|
||||||
fileData.Content, err = ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error reading file content while walking box: %s\n", err)
|
|
||||||
}
|
|
||||||
box.Files = append(box.Files, fileData)
|
|
||||||
|
|
||||||
// add tree entry
|
|
||||||
pathParts := strings.Split(fileData.FileName, "/")
|
|
||||||
parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
|
|
||||||
if parentDir == nil {
|
|
||||||
return fmt.Errorf("Error: parent of %s is not within the box\n", path)
|
|
||||||
}
|
|
||||||
parentDir.ChildFiles = append(parentDir.ChildFiles, fileData)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
boxes = append(boxes, box)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
embedSourceUnformated := bytes.NewBuffer(make([]byte, 0))
|
|
||||||
|
|
||||||
// execute template to buffer
|
|
||||||
err := tmplEmbeddedBox.Execute(
|
|
||||||
embedSourceUnformated,
|
|
||||||
embedFileDataType{pkg.Name, boxes},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error writing embedded box to file (template execute): %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// format the source code
|
|
||||||
embedSource, err := format.Source(embedSourceUnformated.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error formatting embedSource: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write source to file
|
|
||||||
_, err = io.Copy(out, bytes.NewBuffer(embedSource))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error writing embedSource to file: %s\n", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func operationEmbedGo(pkg *build.Package) {
|
|
||||||
// create go file for box
|
|
||||||
boxFile, err := os.Create(filepath.Join(pkg.Dir, boxFilename))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error creating embedded box file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer boxFile.Close()
|
|
||||||
|
|
||||||
err = writeBoxesGo(pkg, boxFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error creating embedded box file: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,680 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type registeredDir struct {
|
|
||||||
Filename string
|
|
||||||
ModTime int
|
|
||||||
ChildFiles []*registeredFile
|
|
||||||
ChildDirs []*registeredDir
|
|
||||||
}
|
|
||||||
|
|
||||||
type registeredFile struct {
|
|
||||||
Filename string
|
|
||||||
ModTime int
|
|
||||||
Content string
|
|
||||||
}
|
|
||||||
|
|
||||||
type registeredBox struct {
|
|
||||||
Name string
|
|
||||||
Time int
|
|
||||||
// key is path
|
|
||||||
Dirs map[string]*registeredDir
|
|
||||||
// key is path
|
|
||||||
Files map[string]*registeredFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// isSimpleSelector returns true if expr is pkgName.ident
|
|
||||||
func isSimpleSelector(pkgName, ident string, expr ast.Expr) bool {
|
|
||||||
if sel, ok := expr.(*ast.SelectorExpr); ok {
|
|
||||||
if pkgIdent, ok := sel.X.(*ast.Ident); ok && pkgIdent.Name == pkgName && sel.Sel != nil && sel.Sel.Name == ident {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIdent(ident string, expr ast.Expr) bool {
|
|
||||||
if expr, ok := expr.(*ast.Ident); ok && expr.Name == ident {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIdentName(expr ast.Expr) (string, bool) {
|
|
||||||
if expr, ok := expr.(*ast.Ident); ok {
|
|
||||||
return expr.Name, true
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKey(expr *ast.KeyValueExpr) string {
|
|
||||||
if ident, ok := expr.Key.(*ast.Ident); ok {
|
|
||||||
return ident.Name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseModTime parses a time.Unix call, and returns the unix time.
|
|
||||||
func parseModTime(expr ast.Expr) (int, error) {
|
|
||||||
if expr, ok := expr.(*ast.CallExpr); ok {
|
|
||||||
if !isSimpleSelector("time", "Unix", expr.Fun) {
|
|
||||||
return 0, fmt.Errorf("ModTime is not time.Unix: %#v", expr.Fun)
|
|
||||||
}
|
|
||||||
if len(expr.Args) == 0 {
|
|
||||||
return 0, fmt.Errorf("not enough args to time.Unix")
|
|
||||||
}
|
|
||||||
arg0 := expr.Args[0]
|
|
||||||
if lit, ok := arg0.(*ast.BasicLit); ok && lit.Kind == token.INT {
|
|
||||||
return strconv.Atoi(lit.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("not time.Unix: %#v", expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseString(expr ast.Expr) (string, error) {
|
|
||||||
if expr, ok := expr.(*ast.CallExpr); ok && isIdent("string", expr.Fun) && len(expr.Args) == 1 {
|
|
||||||
return parseString(expr.Args[0])
|
|
||||||
}
|
|
||||||
if lit, ok := expr.(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
|
||||||
return strconv.Unquote(lit.Value)
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("not string: %#v", expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDir parses an embedded.EmbeddedDir literal.
|
|
||||||
// It can be either a variable name or a composite literal.
|
|
||||||
// Returns nil if the literal is not embedded.EmbeddedDir.
|
|
||||||
func parseDir(expr ast.Expr, dirs map[string]*registeredDir, files map[string]*registeredFile) (*registeredDir, []error) {
|
|
||||||
|
|
||||||
if varName, ok := getIdentName(expr); ok {
|
|
||||||
dir, ok := dirs[varName]
|
|
||||||
if !ok {
|
|
||||||
return nil, []error{fmt.Errorf("unknown variable %v", varName)}
|
|
||||||
}
|
|
||||||
return dir, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if !ok {
|
|
||||||
return nil, []error{fmt.Errorf("dir is not a composite literal: %#v", expr)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors []error
|
|
||||||
if !isSimpleSelector("embedded", "EmbeddedDir", lit.Type) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
ret := ®isteredDir{}
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
if el, ok := el.(*ast.KeyValueExpr); ok {
|
|
||||||
key := getKey(el)
|
|
||||||
if key == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "DirModTime":
|
|
||||||
var err error
|
|
||||||
ret.ModTime, err = parseModTime(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("DirModTime %s", err))
|
|
||||||
}
|
|
||||||
case "Filename":
|
|
||||||
var err error
|
|
||||||
ret.Filename, err = parseString(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("Filename %s", err))
|
|
||||||
}
|
|
||||||
case "ChildDirs":
|
|
||||||
var errors2 []error
|
|
||||||
ret.ChildDirs, errors2 = parseDirsSlice(el.Value, dirs, files)
|
|
||||||
errors = append(errors, errors2...)
|
|
||||||
case "ChildFiles":
|
|
||||||
var errors2 []error
|
|
||||||
ret.ChildFiles, errors2 = parseFilesSlice(el.Value, files)
|
|
||||||
errors = append(errors, errors2...)
|
|
||||||
default:
|
|
||||||
errors = append(errors, fmt.Errorf("Unknown field: %v: %#v", key, el.Value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseFile parses an embedded.EmbeddedFile literal.
|
|
||||||
// It can be either a variable name or a composite literal.
|
|
||||||
// Returns nil if the literal is not embedded.EmbeddedFile.
|
|
||||||
func parseFile(expr ast.Expr, files map[string]*registeredFile) (*registeredFile, []error) {
|
|
||||||
if varName, ok := getIdentName(expr); ok {
|
|
||||||
file, ok := files[varName]
|
|
||||||
if !ok {
|
|
||||||
return nil, []error{fmt.Errorf("unknown variable %v", varName)}
|
|
||||||
}
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if !ok {
|
|
||||||
return nil, []error{fmt.Errorf("file is not a composite literal: %#v", expr)}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors []error
|
|
||||||
if !isSimpleSelector("embedded", "EmbeddedFile", lit.Type) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
ret := ®isteredFile{}
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
if el, ok := el.(*ast.KeyValueExpr); ok {
|
|
||||||
key := getKey(el)
|
|
||||||
if key == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "FileModTime":
|
|
||||||
var err error
|
|
||||||
ret.ModTime, err = parseModTime(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("DirModTime %s", err))
|
|
||||||
}
|
|
||||||
case "Filename":
|
|
||||||
var err error
|
|
||||||
ret.Filename, err = parseString(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("Filename %s", err))
|
|
||||||
}
|
|
||||||
case "Content":
|
|
||||||
var err error
|
|
||||||
ret.Content, err = parseString(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("Content %s", err))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
errors = append(errors, fmt.Errorf("Unknown field: %v: %#v", key, el.Value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, errors
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRegistration(lit *ast.CompositeLit, dirs map[string]*registeredDir, files map[string]*registeredFile) (*registeredBox, []error) {
|
|
||||||
var errors []error
|
|
||||||
if !isSimpleSelector("embedded", "EmbeddedBox", lit.Type) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
ret := ®isteredBox{
|
|
||||||
Dirs: make(map[string]*registeredDir),
|
|
||||||
Files: make(map[string]*registeredFile),
|
|
||||||
}
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
if el, ok := el.(*ast.KeyValueExpr); ok {
|
|
||||||
key := getKey(el)
|
|
||||||
if key == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "Time":
|
|
||||||
var err error
|
|
||||||
ret.Time, err = parseModTime(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("Time %s", err))
|
|
||||||
}
|
|
||||||
case "Name":
|
|
||||||
var err error
|
|
||||||
ret.Name, err = parseString(el.Value)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("Name %s", err))
|
|
||||||
}
|
|
||||||
case "Dirs":
|
|
||||||
var errors2 []error
|
|
||||||
ret.Dirs, errors2 = parseDirsMap(el.Value, dirs, files)
|
|
||||||
errors = append(errors, errors2...)
|
|
||||||
case "Files":
|
|
||||||
var errors2 []error
|
|
||||||
ret.Files, errors2 = parseFilesMap(el.Value, files)
|
|
||||||
errors = append(errors, errors2...)
|
|
||||||
default:
|
|
||||||
errors = append(errors, fmt.Errorf("Unknown field: %v: %#v", key, el.Value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, errors
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDirsSlice(expr ast.Expr, dirs map[string]*registeredDir, files map[string]*registeredFile) (childDirs []*registeredDir, errors []error) {
|
|
||||||
valid := false
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
if arrType, ok := lit.Type.(*ast.ArrayType); ok {
|
|
||||||
if star, ok := arrType.Elt.(*ast.StarExpr); ok {
|
|
||||||
if isSimpleSelector("embedded", "EmbeddedDir", star.X) {
|
|
||||||
valid = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return nil, []error{fmt.Errorf("not a []*embedded.EmbeddedDir: %#v", expr)}
|
|
||||||
}
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
child, childErrors := parseDir(el, dirs, files)
|
|
||||||
errors = append(errors, childErrors...)
|
|
||||||
childDirs = append(childDirs, child)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFilesSlice(expr ast.Expr, files map[string]*registeredFile) (childFiles []*registeredFile, errors []error) {
|
|
||||||
valid := false
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
if arrType, ok := lit.Type.(*ast.ArrayType); ok {
|
|
||||||
if star, ok := arrType.Elt.(*ast.StarExpr); ok {
|
|
||||||
if isSimpleSelector("embedded", "EmbeddedFile", star.X) {
|
|
||||||
valid = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return nil, []error{fmt.Errorf("not a []*embedded.EmbeddedFile: %#v", expr)}
|
|
||||||
}
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
child, childErrors := parseFile(el, files)
|
|
||||||
errors = append(errors, childErrors...)
|
|
||||||
childFiles = append(childFiles, child)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDirsMap(expr ast.Expr, dirs map[string]*registeredDir, files map[string]*registeredFile) (childDirs map[string]*registeredDir, errors []error) {
|
|
||||||
valid := false
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
if mapType, ok := lit.Type.(*ast.MapType); ok {
|
|
||||||
if star, ok := mapType.Value.(*ast.StarExpr); ok {
|
|
||||||
if isSimpleSelector("embedded", "EmbeddedDir", star.X) && isIdent("string", mapType.Key) {
|
|
||||||
valid = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return nil, []error{fmt.Errorf("not a map[string]*embedded.EmbeddedDir: %#v", expr)}
|
|
||||||
}
|
|
||||||
childDirs = make(map[string]*registeredDir)
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
kv, ok := el.(*ast.KeyValueExpr)
|
|
||||||
if !ok {
|
|
||||||
errors = append(errors, fmt.Errorf("not a KeyValueExpr: %#v", el))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key, err := parseString(kv.Key)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("key %s", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
child, childErrors := parseDir(kv.Value, dirs, files)
|
|
||||||
errors = append(errors, childErrors...)
|
|
||||||
childDirs[key] = child
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFilesMap(expr ast.Expr, files map[string]*registeredFile) (childFiles map[string]*registeredFile, errors []error) {
|
|
||||||
valid := false
|
|
||||||
lit, ok := expr.(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
if mapType, ok := lit.Type.(*ast.MapType); ok {
|
|
||||||
if star, ok := mapType.Value.(*ast.StarExpr); ok {
|
|
||||||
if isSimpleSelector("embedded", "EmbeddedFile", star.X) && isIdent("string", mapType.Key) {
|
|
||||||
valid = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return nil, []error{fmt.Errorf("not a map[string]*embedded.EmbeddedFile: %#v", expr)}
|
|
||||||
}
|
|
||||||
childFiles = make(map[string]*registeredFile)
|
|
||||||
for _, el := range lit.Elts {
|
|
||||||
kv, ok := el.(*ast.KeyValueExpr)
|
|
||||||
if !ok {
|
|
||||||
errors = append(errors, fmt.Errorf("not a KeyValueExpr: %#v", el))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key, err := parseString(kv.Key)
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, fmt.Errorf("key %s", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
child, childErrors := parseFile(kv.Value, files)
|
|
||||||
errors = append(errors, childErrors...)
|
|
||||||
childFiles[key] = child
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpoint returns the expression expr points to
|
|
||||||
// if expr is a & unary expression.
|
|
||||||
func unpoint(expr ast.Expr) ast.Expr {
|
|
||||||
if expr, ok := expr.(*ast.UnaryExpr); ok {
|
|
||||||
if expr.Op == token.AND {
|
|
||||||
return expr.X
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateBox(t *testing.T, box *registeredBox, files []sourceFile) {
|
|
||||||
dirsToBeChecked := make(map[string]struct{})
|
|
||||||
filesToBeChecked := make(map[string]string)
|
|
||||||
for _, file := range files {
|
|
||||||
if !strings.HasPrefix(file.Name, box.Name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pathParts := strings.Split(file.Name, "/")
|
|
||||||
dirs := pathParts[:len(pathParts)-1]
|
|
||||||
dirPath := ""
|
|
||||||
for _, dir := range dirs {
|
|
||||||
if dir != box.Name {
|
|
||||||
dirPath = path.Join(dirPath, dir)
|
|
||||||
}
|
|
||||||
dirsToBeChecked[dirPath] = struct{}{}
|
|
||||||
}
|
|
||||||
filesToBeChecked[path.Join(dirPath, pathParts[len(pathParts)-1])] = string(file.Contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(box.Files) != len(filesToBeChecked) {
|
|
||||||
t.Errorf("box %v has incorrect number of files; expected %v, got %v", box.Name, len(filesToBeChecked), len(box.Files))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(box.Dirs) != len(dirsToBeChecked) {
|
|
||||||
t.Errorf("box %v has incorrect number of dirs; expected %v, got %v", box.Name, len(dirsToBeChecked), len(box.Dirs))
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, content := range filesToBeChecked {
|
|
||||||
f, ok := box.Files[name]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("file %v not present in box %v", name, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if f.Filename != name {
|
|
||||||
t.Errorf("box %v: filename mismatch: key: %v; Filename: %v", box.Name, name, f.Filename)
|
|
||||||
}
|
|
||||||
if f.Content != content {
|
|
||||||
t.Errorf("box %v: file %v content does not match: got %v, expected %v", box.Name, name, f.Content, content)
|
|
||||||
}
|
|
||||||
dirPath, _ := path.Split(name)
|
|
||||||
dirPath = strings.TrimSuffix(dirPath, "/")
|
|
||||||
dir, ok := box.Dirs[dirPath]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("directory %v not present in box %v", dirPath, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
found := false
|
|
||||||
for _, file := range dir.ChildFiles {
|
|
||||||
if file == f {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
t.Errorf("file %v not found in directory %v in box %v", name, dirPath, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for name := range dirsToBeChecked {
|
|
||||||
d, ok := box.Dirs[name]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("directory %v not present in box %v", name, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if d.Filename != name {
|
|
||||||
t.Errorf("box %v: filename mismatch: key: %v; Filename: %v", box.Name, name, d.Filename)
|
|
||||||
}
|
|
||||||
if name != "" {
|
|
||||||
dirPath, _ := path.Split(name)
|
|
||||||
dirPath = strings.TrimSuffix(dirPath, "/")
|
|
||||||
dir, ok := box.Dirs[dirPath]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("directory %v not present in box %v", dirPath, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
found := false
|
|
||||||
for _, dir := range dir.ChildDirs {
|
|
||||||
if dir == d {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
t.Errorf("directory %v not found in directory %v in box %v", name, dirPath, box.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmbedGo(t *testing.T) {
|
|
||||||
sourceFiles := []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rice.MustFindBox("foo")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/test1.txt",
|
|
||||||
[]byte(`This is test 1`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/test2.txt",
|
|
||||||
[]byte(`This is test 2`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/bar/test1.txt",
|
|
||||||
[]byte(`This is test 1 in bar`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/bar/baz/test1.txt",
|
|
||||||
[]byte(`This is test 1 in bar/baz`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/bar/baz/backtick`.txt",
|
|
||||||
[]byte(`Backtick filename`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/bar/baz/\"quote\".txt",
|
|
||||||
[]byte(`double quoted filename`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/bar/baz/'quote'.txt",
|
|
||||||
[]byte(`single quoted filename`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/`/`/`.txt",
|
|
||||||
[]byte(`Backticks everywhere!`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo/new\nline",
|
|
||||||
[]byte("File with newline in name. Yes, this is possible."),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", sourceFiles)
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
|
|
||||||
err = writeBoxesGo(pkg, &buffer)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("Generated file: \n%s", buffer.String())
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, "rice-box.go"), &buffer, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var initFunc *ast.FuncDecl
|
|
||||||
for _, decl := range f.Decls {
|
|
||||||
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Name != nil && decl.Name.Name == "init" {
|
|
||||||
initFunc = decl
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if initFunc == nil {
|
|
||||||
t.Fatal("init function not found in generated file")
|
|
||||||
}
|
|
||||||
if initFunc.Body == nil {
|
|
||||||
t.Fatal("init function has no body in generated file")
|
|
||||||
}
|
|
||||||
var registrations []*ast.CallExpr
|
|
||||||
directories := make(map[string]*registeredDir)
|
|
||||||
files := make(map[string]*registeredFile)
|
|
||||||
_ = directories
|
|
||||||
_ = files
|
|
||||||
for _, stmt := range initFunc.Body.List {
|
|
||||||
if stmt, ok := stmt.(*ast.ExprStmt); ok {
|
|
||||||
if call, ok := stmt.X.(*ast.CallExpr); ok {
|
|
||||||
registrations = append(registrations, call)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if stmt, ok := stmt.(*ast.AssignStmt); ok {
|
|
||||||
for i, rhs := range stmt.Rhs {
|
|
||||||
// Rhs can be EmbeddedDir or EmbeddedFile.
|
|
||||||
var literal *ast.CompositeLit
|
|
||||||
literal, ok := unpoint(rhs).(*ast.CompositeLit)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if lhs, ok := stmt.Lhs[i].(*ast.Ident); ok {
|
|
||||||
// variable
|
|
||||||
edir, direrrs := parseDir(literal, directories, files)
|
|
||||||
efile, fileerrs := parseFile(literal, files)
|
|
||||||
abort := false
|
|
||||||
for _, err := range direrrs {
|
|
||||||
t.Error("error while parsing dir: ", err)
|
|
||||||
abort = true
|
|
||||||
}
|
|
||||||
for _, err := range fileerrs {
|
|
||||||
t.Error("error while parsing file: ", err)
|
|
||||||
abort = true
|
|
||||||
}
|
|
||||||
if abort {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if edir == nil && efile == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if edir != nil {
|
|
||||||
directories[lhs.Name] = edir
|
|
||||||
} else {
|
|
||||||
files[lhs.Name] = efile
|
|
||||||
}
|
|
||||||
} else if lhs, ok := stmt.Lhs[i].(*ast.SelectorExpr); ok {
|
|
||||||
selName, ok := getIdentName(lhs.Sel)
|
|
||||||
if !ok || selName != "ChildDirs" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
varName, ok := getIdentName(lhs.X)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("cannot parse ChildDirs assignment: %#v", lhs)
|
|
||||||
}
|
|
||||||
dir, ok := directories[varName]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("variable %v not found", varName)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors []error
|
|
||||||
dir.ChildDirs, errors = parseDirsSlice(rhs, directories, files)
|
|
||||||
|
|
||||||
abort := false
|
|
||||||
for _, err := range errors {
|
|
||||||
t.Errorf("error parsing child dirs: %s", err)
|
|
||||||
abort = true
|
|
||||||
}
|
|
||||||
if abort {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(registrations) == 0 {
|
|
||||||
t.Fatal("could not find registration of embedded box")
|
|
||||||
}
|
|
||||||
|
|
||||||
boxes := make(map[string]*registeredBox)
|
|
||||||
|
|
||||||
for _, call := range registrations {
|
|
||||||
if isSimpleSelector("embedded", "RegisterEmbeddedBox", call.Fun) {
|
|
||||||
if len(call.Args) != 2 {
|
|
||||||
t.Fatalf("incorrect arguments to embedded.RegisterEmbeddedBox: %#v", call.Args)
|
|
||||||
}
|
|
||||||
boxArg := unpoint(call.Args[1])
|
|
||||||
name, err := parseString(call.Args[0])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("first argument to embedded.RegisterEmbeddedBox incorrect: %s", err)
|
|
||||||
}
|
|
||||||
boxLit, ok := boxArg.(*ast.CompositeLit)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("second argument to embedded.RegisterEmbeddedBox is not a composite literal: %#v", boxArg)
|
|
||||||
}
|
|
||||||
abort := false
|
|
||||||
box, errors := parseRegistration(boxLit, directories, files)
|
|
||||||
for _, err := range errors {
|
|
||||||
t.Error("error while parsing box: ", err)
|
|
||||||
abort = true
|
|
||||||
}
|
|
||||||
if abort {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if box == nil {
|
|
||||||
t.Fatalf("second argument to embedded.RegisterEmbeddedBox is not an embedded.EmbeddedBox: %#v", boxArg)
|
|
||||||
}
|
|
||||||
if box.Name != name {
|
|
||||||
t.Fatalf("first argument to embedded.RegisterEmbeddedBox is not the same as the name in the second argument: %v, %#v", name, boxArg)
|
|
||||||
}
|
|
||||||
boxes[name] = box
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that all boxes are present.
|
|
||||||
if _, ok := boxes["foo"]; !ok {
|
|
||||||
t.Error("box \"foo\" not found")
|
|
||||||
}
|
|
||||||
for _, box := range boxes {
|
|
||||||
validateBox(t, box, sourceFiles)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,204 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
"github.com/akavel/rsrc/coff"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sizedReader struct {
|
|
||||||
*bytes.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sizedReader) Size() int64 {
|
|
||||||
return int64(s.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmplEmbeddedSysoHelper *template.Template
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
tmplEmbeddedSysoHelper, err = template.New("embeddedSysoHelper").Parse(`package {{.Package}}
|
|
||||||
// ############# GENERATED CODE #####################
|
|
||||||
// ## This file was generated by the rice tool.
|
|
||||||
// ## Do not edit unless you know what you're doing.
|
|
||||||
// ##################################################
|
|
||||||
|
|
||||||
// extern char _bricebox_{{.Symname}}[], _ericebox_{{.Symname}};
|
|
||||||
// int get_{{.Symname}}_length() {
|
|
||||||
// return &_ericebox_{{.Symname}} - _bricebox_{{.Symname}};
|
|
||||||
// }
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ptr := unsafe.Pointer(&C._bricebox_{{.Symname}})
|
|
||||||
bts := C.GoBytes(ptr, C.get_{{.Symname}}_length())
|
|
||||||
embeddedBox := &embedded.EmbeddedBox{}
|
|
||||||
err := gob.NewDecoder(bytes.NewReader(bts)).Decode(embeddedBox)
|
|
||||||
if err != nil {
|
|
||||||
panic("error decoding embedded box: "+err.Error())
|
|
||||||
}
|
|
||||||
embeddedBox.Link()
|
|
||||||
embedded.RegisterEmbeddedBox(embeddedBox.Name, embeddedBox)
|
|
||||||
}`)
|
|
||||||
if err != nil {
|
|
||||||
panic("could not parse template embeddedSysoHelper: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type embeddedSysoHelperData struct {
|
|
||||||
Package string
|
|
||||||
Symname string
|
|
||||||
}
|
|
||||||
|
|
||||||
func operationEmbedSyso(pkg *build.Package) {
|
|
||||||
|
|
||||||
regexpSynameReplacer := regexp.MustCompile(`[^a-z0-9_]`)
|
|
||||||
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
|
|
||||||
// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
|
|
||||||
if len(boxMap) == 0 {
|
|
||||||
fmt.Println("no calls to rice.FindBox() found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
verbosef("\n")
|
|
||||||
|
|
||||||
for boxname := range boxMap {
|
|
||||||
// find path and filename for this box
|
|
||||||
boxPath := filepath.Join(pkg.Dir, boxname)
|
|
||||||
boxFilename := strings.Replace(boxname, "/", "-", -1)
|
|
||||||
boxFilename = strings.Replace(boxFilename, "..", "back", -1)
|
|
||||||
boxFilename = strings.Replace(boxFilename, ".", "-", -1)
|
|
||||||
|
|
||||||
// verbose info
|
|
||||||
verbosef("embedding box '%s'\n", boxname)
|
|
||||||
verbosef("\tto file %s\n", boxFilename)
|
|
||||||
|
|
||||||
// read box metadata
|
|
||||||
boxInfo, ierr := os.Stat(boxPath)
|
|
||||||
if ierr != nil {
|
|
||||||
fmt.Printf("Error: unable to access box at %s\n", boxPath)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create box datastructure (used by template)
|
|
||||||
box := &embedded.EmbeddedBox{
|
|
||||||
Name: boxname,
|
|
||||||
Time: boxInfo.ModTime(),
|
|
||||||
EmbedType: embedded.EmbedTypeSyso,
|
|
||||||
Files: make(map[string]*embedded.EmbeddedFile),
|
|
||||||
Dirs: make(map[string]*embedded.EmbeddedDir),
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill box datastructure with file data
|
|
||||||
filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error walking box: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := strings.TrimPrefix(path, boxPath)
|
|
||||||
filename = strings.Replace(filename, "\\", "/", -1)
|
|
||||||
filename = strings.TrimPrefix(filename, "/")
|
|
||||||
if info.IsDir() {
|
|
||||||
embeddedDir := &embedded.EmbeddedDir{
|
|
||||||
Filename: filename,
|
|
||||||
DirModTime: info.ModTime(),
|
|
||||||
}
|
|
||||||
verbosef("\tincludes dir: '%s'\n", embeddedDir.Filename)
|
|
||||||
box.Dirs[embeddedDir.Filename] = embeddedDir
|
|
||||||
|
|
||||||
// add tree entry (skip for root, it'll create a recursion)
|
|
||||||
if embeddedDir.Filename != "" {
|
|
||||||
pathParts := strings.Split(embeddedDir.Filename, "/")
|
|
||||||
parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
|
|
||||||
parentDir.ChildDirs = append(parentDir.ChildDirs, embeddedDir)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
embeddedFile := &embedded.EmbeddedFile{
|
|
||||||
Filename: filename,
|
|
||||||
FileModTime: info.ModTime(),
|
|
||||||
Content: "",
|
|
||||||
}
|
|
||||||
verbosef("\tincludes file: '%s'\n", embeddedFile.Filename)
|
|
||||||
contentBytes, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error reading file content while walking box: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
embeddedFile.Content = string(contentBytes)
|
|
||||||
box.Files[embeddedFile.Filename] = embeddedFile
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// encode embedded box to gob file
|
|
||||||
boxGobBuf := &bytes.Buffer{}
|
|
||||||
err := gob.NewEncoder(boxGobBuf).Encode(box)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error encoding box to gob: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
verbosef("gob-encoded embeddedBox is %d bytes large\n", boxGobBuf.Len())
|
|
||||||
|
|
||||||
// write coff
|
|
||||||
symname := regexpSynameReplacer.ReplaceAllString(boxname, "_")
|
|
||||||
createCoffSyso(boxname, symname, "386", boxGobBuf.Bytes())
|
|
||||||
createCoffSyso(boxname, symname, "amd64", boxGobBuf.Bytes())
|
|
||||||
|
|
||||||
// write go
|
|
||||||
sysoHelperData := embeddedSysoHelperData{
|
|
||||||
Package: pkg.Name,
|
|
||||||
Symname: symname,
|
|
||||||
}
|
|
||||||
fileSysoHelper, err := os.Create(boxFilename + ".rice-box.go")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error creating syso helper: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
err = tmplEmbeddedSysoHelper.Execute(fileSysoHelper, sysoHelperData)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error executing tmplEmbeddedSysoHelper: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createCoffSyso(boxFilename string, symname string, arch string, data []byte) {
|
|
||||||
boxCoff := coff.NewRDATA()
|
|
||||||
switch arch {
|
|
||||||
case "386":
|
|
||||||
case "amd64":
|
|
||||||
boxCoff.FileHeader.Machine = 0x8664
|
|
||||||
default:
|
|
||||||
panic("invalid arch")
|
|
||||||
}
|
|
||||||
boxCoff.AddData("_bricebox_"+symname, sizedReader{bytes.NewReader(data)})
|
|
||||||
boxCoff.AddData("_ericebox_"+symname, io.NewSectionReader(strings.NewReader("\000\000"), 0, 2)) // TODO: why? copied from rsrc, which copied it from as-generated
|
|
||||||
boxCoff.Freeze()
|
|
||||||
err := writeCoff(boxCoff, boxFilename+"_"+arch+".rice-box.syso")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error writing %s coff/.syso: %v\n", arch, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func badArgument(fileset *token.FileSet, p token.Pos) {
|
|
||||||
pos := fileset.Position(p)
|
|
||||||
filename := pos.Filename
|
|
||||||
base, err := os.Getwd()
|
|
||||||
if err == nil {
|
|
||||||
rpath, perr := filepath.Rel(base, pos.Filename)
|
|
||||||
if perr == nil {
|
|
||||||
filename = rpath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg := fmt.Sprintf("%s:%d: Error: found call to rice.FindBox, "+
|
|
||||||
"but argument must be a string literal.\n", filename, pos.Line)
|
|
||||||
fmt.Println(msg)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findBoxes(pkg *build.Package) map[string]bool {
|
|
||||||
// create map of boxes to embed
|
|
||||||
var boxMap = make(map[string]bool)
|
|
||||||
|
|
||||||
// create one list of files for this package
|
|
||||||
filenames := make([]string, 0, len(pkg.GoFiles)+len(pkg.CgoFiles))
|
|
||||||
filenames = append(filenames, pkg.GoFiles...)
|
|
||||||
filenames = append(filenames, pkg.CgoFiles...)
|
|
||||||
|
|
||||||
// loop over files, search for rice.FindBox(..) calls
|
|
||||||
for _, filename := range filenames {
|
|
||||||
// find full filepath
|
|
||||||
fullpath := filepath.Join(pkg.Dir, filename)
|
|
||||||
if strings.HasSuffix(filename, "rice-box.go") {
|
|
||||||
// Ignore *.rice-box.go files
|
|
||||||
verbosef("skipping file %q\n", fullpath)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
verbosef("scanning file %q\n", fullpath)
|
|
||||||
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
f, err := parser.ParseFile(fset, fullpath, nil, 0)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var riceIsImported bool
|
|
||||||
ricePkgName := "rice"
|
|
||||||
for _, imp := range f.Imports {
|
|
||||||
if strings.HasSuffix(imp.Path.Value, "go.rice\"") {
|
|
||||||
if imp.Name != nil {
|
|
||||||
ricePkgName = imp.Name.Name
|
|
||||||
}
|
|
||||||
riceIsImported = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !riceIsImported {
|
|
||||||
// Rice wasn't imported, so we won't find a box.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ricePkgName == "_" {
|
|
||||||
// Rice pkg is unnamed, so we won't find a box.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspect AST, looking for calls to (Must)?FindBox.
|
|
||||||
// First parameter of the func must be a basic literal.
|
|
||||||
// Identifiers won't be resolved.
|
|
||||||
var nextIdentIsBoxFunc bool
|
|
||||||
var nextBasicLitParamIsBoxName bool
|
|
||||||
var boxCall token.Pos
|
|
||||||
var variableToRemember string
|
|
||||||
var validVariablesForBoxes map[string]bool = make(map[string]bool)
|
|
||||||
|
|
||||||
ast.Inspect(f, func(node ast.Node) bool {
|
|
||||||
if node == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch x := node.(type) {
|
|
||||||
// this case fixes the var := func() style assignments, not assignments to vars declared separately from the assignment.
|
|
||||||
case *ast.AssignStmt:
|
|
||||||
var assign = node.(*ast.AssignStmt)
|
|
||||||
name, found := assign.Lhs[0].(*ast.Ident)
|
|
||||||
if found {
|
|
||||||
variableToRemember = name.Name
|
|
||||||
composite, first := assign.Rhs[0].(*ast.CompositeLit)
|
|
||||||
if first {
|
|
||||||
riceSelector, second := composite.Type.(*ast.SelectorExpr)
|
|
||||||
|
|
||||||
if second {
|
|
||||||
callCorrect := riceSelector.Sel.Name == "Config"
|
|
||||||
packageName, third := riceSelector.X.(*ast.Ident)
|
|
||||||
|
|
||||||
if third && callCorrect && packageName.Name == ricePkgName {
|
|
||||||
validVariablesForBoxes[name.Name] = true
|
|
||||||
verbosef("\tfound variable, saving to scan for boxes: %q\n", name.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ast.Ident:
|
|
||||||
if nextIdentIsBoxFunc || ricePkgName == "." {
|
|
||||||
nextIdentIsBoxFunc = false
|
|
||||||
if x.Name == "FindBox" || x.Name == "MustFindBox" {
|
|
||||||
nextBasicLitParamIsBoxName = true
|
|
||||||
boxCall = x.Pos()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if x.Name == ricePkgName || validVariablesForBoxes[x.Name] {
|
|
||||||
nextIdentIsBoxFunc = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ast.BasicLit:
|
|
||||||
if nextBasicLitParamIsBoxName {
|
|
||||||
if x.Kind == token.STRING {
|
|
||||||
nextBasicLitParamIsBoxName = false
|
|
||||||
// trim "" or ``
|
|
||||||
name := x.Value[1 : len(x.Value)-1]
|
|
||||||
boxMap[name] = true
|
|
||||||
verbosef("\tfound box %q\n", name)
|
|
||||||
} else {
|
|
||||||
badArgument(fset, boxCall)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if nextIdentIsBoxFunc {
|
|
||||||
nextIdentIsBoxFunc = false
|
|
||||||
}
|
|
||||||
if nextBasicLitParamIsBoxName {
|
|
||||||
badArgument(fset, boxCall)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return boxMap
|
|
||||||
}
|
|
|
@ -1,302 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sourceFile struct {
|
|
||||||
Name string
|
|
||||||
Contents []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectBoxes(expected []string, actual map[string]bool) error {
|
|
||||||
if len(expected) != len(actual) {
|
|
||||||
return fmt.Errorf("expected %v, got %v", expected, actual)
|
|
||||||
}
|
|
||||||
for _, box := range expected {
|
|
||||||
if _, ok := actual[box]; !ok {
|
|
||||||
return fmt.Errorf("expected %v, got %v", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setUpTestPkg(pkgName string, files []sourceFile) (*build.Package, func(), error) {
|
|
||||||
temp, err := ioutil.TempDir("", "go.rice-test")
|
|
||||||
if err != nil {
|
|
||||||
return nil, func() {}, err
|
|
||||||
}
|
|
||||||
cleanup := func() {
|
|
||||||
os.RemoveAll(temp)
|
|
||||||
}
|
|
||||||
dir := filepath.Join(temp, pkgName)
|
|
||||||
if err := os.Mkdir(dir, 0770); err != nil {
|
|
||||||
return nil, cleanup, err
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
|
||||||
fullPath := filepath.Join(dir, f.Name)
|
|
||||||
if err := os.MkdirAll(filepath.Dir(fullPath), 0770); err != nil {
|
|
||||||
return nil, cleanup, err
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(fullPath, f.Contents, 0660); err != nil {
|
|
||||||
return nil, cleanup, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pkg, err := build.ImportDir(dir, 0)
|
|
||||||
return pkg, cleanup, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindOneBox(t *testing.T) {
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rice.MustFindBox("foo")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedBoxes := []string{"foo"}
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
if err := expectBoxes(expectedBoxes, boxMap); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindOneBoxViaVariable(t *testing.T) {
|
|
||||||
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
conf := rice.Config{
|
|
||||||
LocateOrder: []rice.LocateMethod{rice.LocateEmbedded, rice.LocateAppended, rice.LocateFS},
|
|
||||||
}
|
|
||||||
conf.MustFindBox("foo")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedBoxes := []string{"foo"}
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
if err := expectBoxes(expectedBoxes, boxMap); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindMultipleBoxes(t *testing.T) {
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rice.MustFindBox("foo")
|
|
||||||
rice.MustFindBox("bar")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedBoxes := []string{"foo", "bar"}
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
if err := expectBoxes(expectedBoxes, boxMap); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoBoxFoundIfRiceNotImported(t *testing.T) {
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package main
|
|
||||||
type fakerice struct {}
|
|
||||||
|
|
||||||
func (fr fakerice) FindBox(s string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rice := fakerice{}
|
|
||||||
rice.FindBox("foo")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
if _, ok := boxMap["foo"]; ok {
|
|
||||||
t.Errorf("Unexpected box %q was found", "foo")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnrelatedBoxesAreNotFound(t *testing.T) {
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes.go",
|
|
||||||
[]byte(`package foobar
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakerice struct {}
|
|
||||||
|
|
||||||
func (fr fakerice) FindBox(s string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindBox(s string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadBoxes() {
|
|
||||||
rice := fakerice{}
|
|
||||||
rice.FindBox("foo")
|
|
||||||
|
|
||||||
FindBox("bar")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
for _, box := range []string{"foo", "bar"} {
|
|
||||||
if _, ok := boxMap[box]; ok {
|
|
||||||
t.Errorf("Unexpected box %q was found", box)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMixGoodAndBadBoxes(t *testing.T) {
|
|
||||||
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
|
|
||||||
{
|
|
||||||
"boxes1.go",
|
|
||||||
[]byte(`package foobar
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakerice struct {}
|
|
||||||
|
|
||||||
func (fr fakerice) FindBox(s string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindBox(s string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadBoxes1() {
|
|
||||||
rice := fakerice{}
|
|
||||||
rice.FindBox("foo")
|
|
||||||
|
|
||||||
FindBox("bar")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"boxes2.go",
|
|
||||||
[]byte(`package foobar
|
|
||||||
|
|
||||||
import (
|
|
||||||
noodles "github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoadBoxes2() {
|
|
||||||
FindBox("baz")
|
|
||||||
noodles.FindBox("veggies")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"boxes3.go",
|
|
||||||
[]byte(`package foobar
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoadBoxes3() {
|
|
||||||
rice.FindBox("fish")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"boxes4.go",
|
|
||||||
[]byte(`package foobar
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/GeertJohan/go.rice"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoadBoxes3() {
|
|
||||||
MustFindBox("chicken")
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer cleanup()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxMap := findBoxes(pkg)
|
|
||||||
for _, box := range []string{"foo", "bar", "baz"} {
|
|
||||||
if _, ok := boxMap[box]; ok {
|
|
||||||
t.Errorf("Unexpected box %q was found", box)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, box := range []string{"veggies", "fish", "chicken"} {
|
|
||||||
if _, ok := boxMap[box]; !ok {
|
|
||||||
t.Errorf("Expected box %q not found", box)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
goflags "github.com/jessevdk/go-flags" // rename import to `goflags` (file scope) so we can use `var flags` (package scope)
|
|
||||||
)
|
|
||||||
|
|
||||||
// flags
|
|
||||||
var flags struct {
|
|
||||||
Verbose bool `long:"verbose" short:"v" description:"Show verbose debug information"`
|
|
||||||
ImportPaths []string `long:"import-path" short:"i" description:"Import path(s) to use. Using PWD when left empty. Specify multiple times for more import paths to append"`
|
|
||||||
|
|
||||||
Append struct {
|
|
||||||
Executable string `long:"exec" description:"Executable to append" required:"true"`
|
|
||||||
} `command:"append"`
|
|
||||||
|
|
||||||
EmbedGo struct{} `command:"embed-go" alias:"embed"`
|
|
||||||
EmbedSyso struct{} `command:"embed-syso"`
|
|
||||||
Clean struct{} `command:"clean"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// flags parser
|
|
||||||
var flagsParser *goflags.Parser
|
|
||||||
|
|
||||||
// initFlags parses the given flags.
|
|
||||||
// when the user asks for help (-h or --help): the application exists with status 0
|
|
||||||
// when unexpected flags is given: the application exits with status 1
|
|
||||||
func parseArguments() {
|
|
||||||
// create flags parser in global var, for flagsParser.Active.Name (operation)
|
|
||||||
flagsParser = goflags.NewParser(&flags, goflags.Default)
|
|
||||||
|
|
||||||
// parse flags
|
|
||||||
args, err := flagsParser.Parse()
|
|
||||||
if err != nil {
|
|
||||||
// assert the err to be a flags.Error
|
|
||||||
flagError := err.(*goflags.Error)
|
|
||||||
if flagError.Type == goflags.ErrHelp {
|
|
||||||
// user asked for help on flags.
|
|
||||||
// program can exit successfully
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
if flagError.Type == goflags.ErrUnknownFlag {
|
|
||||||
fmt.Println("Use --help to view available options.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if flagError.Type == goflags.ErrRequired {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("Error parsing flags: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// error on left-over arguments
|
|
||||||
if len(args) > 0 {
|
|
||||||
fmt.Printf("Unexpected arguments: %s\nUse --help to view available options.", args)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// default ImportPath to pwd when not set
|
|
||||||
if len(flags.ImportPaths) == 0 {
|
|
||||||
pwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error getting pwd: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
verbosef("using pwd as import path\n")
|
|
||||||
// find non-absolute path for this pwd
|
|
||||||
pkg, err := build.ImportDir(pwd, build.FindOnly)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error using current directory as import path: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
flags.ImportPaths = append(flags.ImportPaths, pkg.ImportPath)
|
|
||||||
verbosef("using import paths: %s\n", flags.ImportPaths)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.incremental"
|
|
||||||
)
|
|
||||||
|
|
||||||
var identifierCount incremental.Uint64
|
|
||||||
|
|
||||||
func nextIdentifier() string {
|
|
||||||
num := identifierCount.Next()
|
|
||||||
return strconv.FormatUint(num, 36) // 0123456789abcdefghijklmnopqrstuvwxyz
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// parser arguments
|
|
||||||
parseArguments()
|
|
||||||
|
|
||||||
// find package for path
|
|
||||||
var pkgs []*build.Package
|
|
||||||
for _, importPath := range flags.ImportPaths {
|
|
||||||
pkg := pkgForPath(importPath)
|
|
||||||
pkgs = append(pkgs, pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// switch on the operation to perform
|
|
||||||
switch flagsParser.Active.Name {
|
|
||||||
case "embed", "embed-go":
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
operationEmbedGo(pkg)
|
|
||||||
}
|
|
||||||
case "embed-syso":
|
|
||||||
log.Println("WARNING: embedding .syso is experimental..")
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
operationEmbedSyso(pkg)
|
|
||||||
}
|
|
||||||
case "append":
|
|
||||||
operationAppend(pkgs)
|
|
||||||
case "clean":
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
operationClean(pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// all done
|
|
||||||
verbosef("\n")
|
|
||||||
verbosef("rice finished successfully\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function to get *build.Package for given path
|
|
||||||
func pkgForPath(path string) *build.Package {
|
|
||||||
// get pwd for relative imports
|
|
||||||
pwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error getting pwd (required for relative imports): %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// read full package information
|
|
||||||
pkg, err := build.Import(path, pwd, 0)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error reading package: %s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
func verbosef(format string, stuff ...interface{}) {
|
|
||||||
if flags.Verbose {
|
|
||||||
log.Printf(format, stuff...)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
var tmplEmbeddedBox *template.Template
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// parse embedded box template
|
|
||||||
tmplEmbeddedBox, err = template.New("embeddedBox").Parse(`package {{.Package}}
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
{{range .Boxes}}
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
// define files
|
|
||||||
{{range .Files}}{{.Identifier}} := &embedded.EmbeddedFile{
|
|
||||||
Filename: {{.FileName | printf "%q"}},
|
|
||||||
FileModTime: time.Unix({{.ModTime}}, 0),
|
|
||||||
Content: string({{.Content | printf "%q"}}),
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// define dirs
|
|
||||||
{{range .Dirs}}{{.Identifier}} := &embedded.EmbeddedDir{
|
|
||||||
Filename: {{.FileName | printf "%q"}},
|
|
||||||
DirModTime: time.Unix({{.ModTime}}, 0),
|
|
||||||
ChildFiles: []*embedded.EmbeddedFile{
|
|
||||||
{{range .ChildFiles}}{{.Identifier}}, // {{.FileName | printf "%q"}}
|
|
||||||
{{end}}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// link ChildDirs
|
|
||||||
{{range .Dirs}}{{.Identifier}}.ChildDirs = []*embedded.EmbeddedDir{
|
|
||||||
{{range .ChildDirs}}{{.Identifier}}, // {{.FileName | printf "%q"}}
|
|
||||||
{{end}}
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// register embeddedBox
|
|
||||||
embedded.RegisterEmbeddedBox(` + "`" + `{{.BoxName}}` + "`" + `, &embedded.EmbeddedBox{
|
|
||||||
Name: ` + "`" + `{{.BoxName}}` + "`" + `,
|
|
||||||
Time: time.Unix({{.UnixNow}}, 0),
|
|
||||||
Dirs: map[string]*embedded.EmbeddedDir{
|
|
||||||
{{range .Dirs}}{{.FileName | printf "%q"}}: {{.Identifier}},
|
|
||||||
{{end}}
|
|
||||||
},
|
|
||||||
Files: map[string]*embedded.EmbeddedFile{
|
|
||||||
{{range .Files}}{{.FileName | printf "%q"}}: {{.Identifier}},
|
|
||||||
{{end}}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
{{end}}`)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error parsing embedded box template: %s\n", err)
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type embedFileDataType struct {
|
|
||||||
Package string
|
|
||||||
Boxes []*boxDataType
|
|
||||||
}
|
|
||||||
|
|
||||||
type boxDataType struct {
|
|
||||||
BoxName string
|
|
||||||
UnixNow int64
|
|
||||||
Files []*fileDataType
|
|
||||||
Dirs map[string]*dirDataType
|
|
||||||
}
|
|
||||||
|
|
||||||
type fileDataType struct {
|
|
||||||
Identifier string
|
|
||||||
FileName string
|
|
||||||
Content []byte
|
|
||||||
ModTime int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type dirDataType struct {
|
|
||||||
Identifier string
|
|
||||||
FileName string
|
|
||||||
Content []byte
|
|
||||||
ModTime int64
|
|
||||||
ChildDirs []*dirDataType
|
|
||||||
ChildFiles []*fileDataType
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// randomString generates a pseudo-random alpha-numeric string with given length.
|
|
||||||
func randomString(length int) string {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
k := make([]rune, length)
|
|
||||||
for i := 0; i < length; i++ {
|
|
||||||
c := rand.Intn(35)
|
|
||||||
if c < 10 {
|
|
||||||
c += 48 // numbers (0-9) (0+48 == 48 == '0', 9+48 == 57 == '9')
|
|
||||||
} else {
|
|
||||||
c += 87 // lower case alphabets (a-z) (10+87 == 97 == 'a', 35+87 == 122 = 'z')
|
|
||||||
}
|
|
||||||
k[i] = rune(c)
|
|
||||||
}
|
|
||||||
return string(k)
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/akavel/rsrc/binutil"
|
|
||||||
"github.com/akavel/rsrc/coff"
|
|
||||||
)
|
|
||||||
|
|
||||||
// copied from github.com/akavel/rsrc
|
|
||||||
// LICENSE: MIT
|
|
||||||
// Copyright 2013-2014 The rsrc Authors. (https://github.com/akavel/rsrc/blob/master/AUTHORS)
|
|
||||||
func writeCoff(coff *coff.Coff, fnameout string) error {
|
|
||||||
out, err := os.Create(fnameout)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
w := binutil.Writer{W: out}
|
|
||||||
|
|
||||||
// write the resulting file to disk
|
|
||||||
binutil.Walk(coff, func(v reflect.Value, path string) error {
|
|
||||||
if binutil.Plain(v.Kind()) {
|
|
||||||
w.WriteLE(v.Interface())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
vv, ok := v.Interface().(binutil.SizedReader)
|
|
||||||
if ok {
|
|
||||||
w.WriteFromSized(vv)
|
|
||||||
return binutil.WALK_SKIP
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if w.Err != nil {
|
|
||||||
return fmt.Errorf("Error writing output file: %s", w.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
// SortByName allows an array of os.FileInfo objects
|
|
||||||
// to be easily sorted by filename using sort.Sort(SortByName(array))
|
|
||||||
type SortByName []os.FileInfo
|
|
||||||
|
|
||||||
func (f SortByName) Len() int { return len(f) }
|
|
||||||
func (f SortByName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
|
||||||
func (f SortByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
|
||||||
|
|
||||||
// SortByModified allows an array of os.FileInfo objects
|
|
||||||
// to be easily sorted by modified date using sort.Sort(SortByModified(array))
|
|
||||||
type SortByModified []os.FileInfo
|
|
||||||
|
|
||||||
func (f SortByModified) Len() int { return len(f) }
|
|
||||||
func (f SortByModified) Less(i, j int) bool { return f[i].ModTime().Unix() > f[j].ModTime().Unix() }
|
|
||||||
func (f SortByModified) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
|
|
@ -1,252 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/GeertJohan/go.rice/embedded"
|
|
||||||
)
|
|
||||||
|
|
||||||
//++ TODO: IDEA: merge virtualFile and virtualDir, this decreases work done by rice.File
|
|
||||||
|
|
||||||
// Error indicating some function is not implemented yet (but available to satisfy an interface)
|
|
||||||
var ErrNotImplemented = errors.New("not implemented yet")
|
|
||||||
|
|
||||||
// virtualFile is a 'stateful' virtual file.
|
|
||||||
// virtualFile wraps an *EmbeddedFile for a call to Box.Open() and virtualizes 'read cursor' (offset) and 'closing'.
|
|
||||||
// virtualFile is only internally visible and should be exposed through rice.File
|
|
||||||
type virtualFile struct {
|
|
||||||
*embedded.EmbeddedFile // the actual embedded file, embedded to obtain methods
|
|
||||||
offset int64 // read position on the virtual file
|
|
||||||
closed bool // closed when true
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new virtualFile for given EmbeddedFile
|
|
||||||
func newVirtualFile(ef *embedded.EmbeddedFile) *virtualFile {
|
|
||||||
vf := &virtualFile{
|
|
||||||
EmbeddedFile: ef,
|
|
||||||
offset: 0,
|
|
||||||
closed: false,
|
|
||||||
}
|
|
||||||
return vf
|
|
||||||
}
|
|
||||||
|
|
||||||
//++ TODO check for nil pointers in all these methods. When so: return os.PathError with Err: os.ErrInvalid
|
|
||||||
|
|
||||||
func (vf *virtualFile) close() error {
|
|
||||||
if vf.closed {
|
|
||||||
return &os.PathError{
|
|
||||||
Op: "close",
|
|
||||||
Path: vf.EmbeddedFile.Filename,
|
|
||||||
Err: errors.New("already closed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vf.EmbeddedFile = nil
|
|
||||||
vf.closed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vf *virtualFile) stat() (os.FileInfo, error) {
|
|
||||||
if vf.closed {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "stat",
|
|
||||||
Path: vf.EmbeddedFile.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (*embeddedFileInfo)(vf.EmbeddedFile), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vf *virtualFile) readdir(count int) ([]os.FileInfo, error) {
|
|
||||||
if vf.closed {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "readdir",
|
|
||||||
Path: vf.EmbeddedFile.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//TODO: return proper error for a readdir() call on a file
|
|
||||||
return nil, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vf *virtualFile) read(bts []byte) (int, error) {
|
|
||||||
if vf.closed {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "read",
|
|
||||||
Path: vf.EmbeddedFile.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end := vf.offset + int64(len(bts))
|
|
||||||
|
|
||||||
if end >= int64(len(vf.Content)) {
|
|
||||||
// end of file, so return what we have + EOF
|
|
||||||
n := copy(bts, vf.Content[vf.offset:])
|
|
||||||
vf.offset = 0
|
|
||||||
return n, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
n := copy(bts, vf.Content[vf.offset:end])
|
|
||||||
vf.offset += int64(n)
|
|
||||||
return n, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vf *virtualFile) seek(offset int64, whence int) (int64, error) {
|
|
||||||
if vf.closed {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "seek",
|
|
||||||
Path: vf.EmbeddedFile.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var e error
|
|
||||||
|
|
||||||
//++ TODO: check if this is correct implementation for seek
|
|
||||||
switch whence {
|
|
||||||
case os.SEEK_SET:
|
|
||||||
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
|
||||||
vf.offset = offset
|
|
||||||
case os.SEEK_CUR:
|
|
||||||
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
|
||||||
vf.offset += offset
|
|
||||||
case os.SEEK_END:
|
|
||||||
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
|
||||||
vf.offset = int64(len(vf.EmbeddedFile.Content)) - offset
|
|
||||||
}
|
|
||||||
|
|
||||||
if e != nil {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "seek",
|
|
||||||
Path: vf.Filename,
|
|
||||||
Err: e,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return vf.offset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// virtualDir is a 'stateful' virtual directory.
|
|
||||||
// virtualDir wraps an *EmbeddedDir for a call to Box.Open() and virtualizes 'closing'.
|
|
||||||
// virtualDir is only internally visible and should be exposed through rice.File
|
|
||||||
type virtualDir struct {
|
|
||||||
*embedded.EmbeddedDir
|
|
||||||
offset int // readdir position on the directory
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new virtualDir for given EmbeddedDir
|
|
||||||
func newVirtualDir(ed *embedded.EmbeddedDir) *virtualDir {
|
|
||||||
vd := &virtualDir{
|
|
||||||
EmbeddedDir: ed,
|
|
||||||
offset: 0,
|
|
||||||
closed: false,
|
|
||||||
}
|
|
||||||
return vd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vd *virtualDir) close() error {
|
|
||||||
//++ TODO: needs sync mutex?
|
|
||||||
if vd.closed {
|
|
||||||
return &os.PathError{
|
|
||||||
Op: "close",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("already closed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vd.closed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vd *virtualDir) stat() (os.FileInfo, error) {
|
|
||||||
if vd.closed {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "stat",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (*embeddedDirInfo)(vd.EmbeddedDir), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vd *virtualDir) readdir(n int) (fi []os.FileInfo, err error) {
|
|
||||||
|
|
||||||
if vd.closed {
|
|
||||||
return nil, &os.PathError{
|
|
||||||
Op: "readdir",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build up the array of our contents
|
|
||||||
var files []os.FileInfo
|
|
||||||
|
|
||||||
// Add the child directories
|
|
||||||
for _, child := range vd.ChildDirs {
|
|
||||||
child.Filename = filepath.Base(child.Filename)
|
|
||||||
files = append(files, (*embeddedDirInfo)(child))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the child files
|
|
||||||
for _, child := range vd.ChildFiles {
|
|
||||||
child.Filename = filepath.Base(child.Filename)
|
|
||||||
files = append(files, (*embeddedFileInfo)(child))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort it by filename (lexical order)
|
|
||||||
sort.Sort(SortByName(files))
|
|
||||||
|
|
||||||
// Return all contents if that's what is requested
|
|
||||||
if n <= 0 {
|
|
||||||
vd.offset = 0
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If user has requested past the end of our list
|
|
||||||
// return what we can and send an EOF
|
|
||||||
if vd.offset+n >= len(files) {
|
|
||||||
offset := vd.offset
|
|
||||||
vd.offset = 0
|
|
||||||
return files[offset:], io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
offset := vd.offset
|
|
||||||
vd.offset += n
|
|
||||||
return files[offset : offset+n], nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vd *virtualDir) read(bts []byte) (int, error) {
|
|
||||||
if vd.closed {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "read",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "read",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("is a directory"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vd *virtualDir) seek(offset int64, whence int) (int64, error) {
|
|
||||||
if vd.closed {
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "seek",
|
|
||||||
Path: vd.EmbeddedDir.Filename,
|
|
||||||
Err: errors.New("bad file descriptor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, &os.PathError{
|
|
||||||
Op: "seek",
|
|
||||||
Path: vd.Filename,
|
|
||||||
Err: errors.New("is a directory"),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
package rice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Walk is like filepath.Walk()
|
|
||||||
// Visit http://golang.org/pkg/path/filepath/#Walk for more information
|
|
||||||
func (b *Box) Walk(path string, walkFn filepath.WalkFunc) error {
|
|
||||||
|
|
||||||
pathFile, err := b.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer pathFile.Close()
|
|
||||||
|
|
||||||
pathInfo, err := pathFile.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.IsAppended() || b.IsEmbedded() {
|
|
||||||
return b.walk(path, pathInfo, walkFn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't have any embedded or appended box so use live filesystem mode
|
|
||||||
return filepath.Walk(b.absolutePath+string(os.PathSeparator)+path, func(path string, info os.FileInfo, err error) error {
|
|
||||||
|
|
||||||
// Strip out the box name from the returned paths
|
|
||||||
path = strings.TrimPrefix(path, b.absolutePath+string(os.PathSeparator))
|
|
||||||
return walkFn(path, info, err)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// walk recursively descends path.
|
|
||||||
// See walk() in $GOROOT/src/pkg/path/filepath/path.go
|
|
||||||
func (b *Box) walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
|
||||||
|
|
||||||
err := walkFn(path, info, nil)
|
|
||||||
if err != nil {
|
|
||||||
if info.IsDir() && err == filepath.SkipDir {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
names, err := b.readDirNames(path)
|
|
||||||
if err != nil {
|
|
||||||
return walkFn(path, info, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, name := range names {
|
|
||||||
|
|
||||||
filename := filepath.Join(path, name)
|
|
||||||
fileObject, err := b.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fileObject.Close()
|
|
||||||
|
|
||||||
fileInfo, err := fileObject.Stat()
|
|
||||||
if err != nil {
|
|
||||||
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = b.walk(filename, fileInfo, walkFn)
|
|
||||||
if err != nil {
|
|
||||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// readDirNames reads the directory named by path and returns a sorted list of directory entries.
|
|
||||||
// See readDirNames() in $GOROOT/pkg/path/filepath/path.go
|
|
||||||
func (b *Box) readDirNames(path string) ([]string, error) {
|
|
||||||
|
|
||||||
f, err := b.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
stat, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stat.IsDir() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
infos, err := f.Readdir(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var names []string
|
|
||||||
|
|
||||||
for _, info := range infos {
|
|
||||||
names = append(names, info.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(names)
|
|
||||||
return names, nil
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
_ "github.com/Xe/gopreload"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
pkgName = flag.String("pkg", "", "package to underscore import")
|
|
||||||
destPkgName = flag.String("dest", "", "destination package to generate")
|
|
||||||
)
|
|
||||||
|
|
||||||
const codeTemplate = `//+build go1.8
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import _ "$PACKAGE_PATH"`
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *pkgName == "" || *destPkgName == "" {
|
|
||||||
log.Fatal("must set -pkg and -dest")
|
|
||||||
}
|
|
||||||
|
|
||||||
srcDir := filepath.Join(os.Getenv("GOPATH"), "src", *destPkgName)
|
|
||||||
|
|
||||||
err := os.MkdirAll(srcDir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fout, err := os.Create(srcDir + "/main.go")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer fout.Close()
|
|
||||||
|
|
||||||
codeBody := os.Expand(codeTemplate, func(s string) string {
|
|
||||||
if s == "PACKAGE_PATH" {
|
|
||||||
return *pkgName
|
|
||||||
}
|
|
||||||
|
|
||||||
return "no idea man"
|
|
||||||
})
|
|
||||||
|
|
||||||
fmt.Fprintln(fout, codeBody)
|
|
||||||
|
|
||||||
fmt.Println("To build this plugin: ")
|
|
||||||
fmt.Println(" $ go build -buildmode plugin -o /path/to/output.so " + *destPkgName)
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
//+build go1.8
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import _ "github.com/go-sql-driver/mysql"
|
|
|
@ -1,5 +0,0 @@
|
||||||
//+build go1.8
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import _ "github.com/lib/pq"
|
|
|
@ -1,5 +0,0 @@
|
||||||
//+build go1.8
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import _ "github.com/mattn/go-sqlite3"
|
|
|
@ -1,56 +0,0 @@
|
||||||
# manhole
|
|
||||||
|
|
||||||
An opinionated HTTP manhole into Go processes.
|
|
||||||
|
|
||||||
## Assumptions This Package Makes
|
|
||||||
|
|
||||||
- Make each server instance have a unique HTTP port that is randomized by default.
|
|
||||||
This makes it very hard to accidentally route this manhole to the outside world.
|
|
||||||
If more assurance is required I personally suggest using [yubikey totp][yktotp],
|
|
||||||
but do research.
|
|
||||||
- Application code does not touch [`http.DefaultServeMux`][default-servemux]'. This is so that
|
|
||||||
administative control rods can be dynamically flipped in the case they are
|
|
||||||
needed.
|
|
||||||
- [pprof][pprof] endpoints added to `http.DefaultServeMux`. This allows easy
|
|
||||||
access to [pprof runtime tracing][pprof-tracing] to debug issues on long-running
|
|
||||||
applications like HTTP services.
|
|
||||||
- Make the manhole slightly inconvenient to put into place in production. This
|
|
||||||
helps make sure that this tool remains a debugging tool and not a part of a
|
|
||||||
long-term production rollout.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Compile this as a plugin:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ go get -d github.com/Xe/gopreload/manhole
|
|
||||||
$ go build -buildmode plugin -o manhole.so github.com/Xe/gopreload/manhole
|
|
||||||
```
|
|
||||||
|
|
||||||
Then add [`gopreload`][gopreload] to your application:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// gopreload.go
|
|
||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
This file is separate to make it very easy to both add into an application, but
|
|
||||||
also very easy to remove.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import _ "github.com/Xe/gopreload"
|
|
||||||
```
|
|
||||||
|
|
||||||
And at runtime add the `manhole.so` file you created earlier to the target system
|
|
||||||
somehow and add the following environment variable to its run configuration:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
GO_PRELOAD=/path/to/manhole.so
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[pprof]: https://godoc.org/net/http/pprof
|
|
||||||
[default-servemux]: https://godoc.org/net/http#pkg-variables
|
|
||||||
[yktotp]: https://github.com/GeertJohan/yubigo
|
|
||||||
[gopreload]: https://github.com/Xe/gopreload
|
|
|
@ -1,22 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
_ "net/http/pprof"
|
|
||||||
"net/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
l, err := net.Listen("tcp", "127.0.0.2:0")
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("manhole: cannot bind to 127.0.0.2:0: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("manhole: Now listening on http://%s", l.Addr())
|
|
||||||
|
|
||||||
rpc.HandleHTTP()
|
|
||||||
go http.Serve(l, nil)
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
_ "github.com/Xe/gopreload"
|
|
||||||
"github.com/Xe/ln"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
spew()
|
|
||||||
|
|
||||||
ln.Log(ln.F{"action": "gc_spew", "who": r.RemoteAddr})
|
|
||||||
|
|
||||||
fmt.Fprintln(w, "done")
|
|
||||||
})
|
|
||||||
|
|
||||||
http.ListenAndServe(":9184", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeBuffer() []byte {
|
|
||||||
return make([]byte, rand.Intn(5000000)+5000000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func spew() {
|
|
||||||
pool := make([][]byte, 20)
|
|
||||||
|
|
||||||
var m runtime.MemStats
|
|
||||||
makes := 0
|
|
||||||
for _ = range make([]struct{}, 50) {
|
|
||||||
b := makeBuffer()
|
|
||||||
makes += 1
|
|
||||||
i := rand.Intn(len(pool))
|
|
||||||
pool[i] = b
|
|
||||||
|
|
||||||
time.Sleep(time.Millisecond * 250)
|
|
||||||
|
|
||||||
bytes := 0
|
|
||||||
|
|
||||||
for i := 0; i < len(pool); i++ {
|
|
||||||
if pool[i] != nil {
|
|
||||||
bytes += len(pool[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.ReadMemStats(&m)
|
|
||||||
fmt.Printf("%d,%d,%d,%d,%d,%d\n", m.HeapSys, bytes, m.HeapAlloc,
|
|
||||||
m.HeapIdle, m.HeapReleased, makes)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Xe/ln"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ln.Log(ln.F{
|
|
||||||
"action": "started_up",
|
|
||||||
"every": "20_seconds",
|
|
||||||
"what": "gc_metrics",
|
|
||||||
})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(20 * time.Second)
|
|
||||||
gatherMetrics()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func gatherMetrics() {
|
|
||||||
stats := &runtime.MemStats{}
|
|
||||||
runtime.ReadMemStats(stats)
|
|
||||||
|
|
||||||
ln.Log(ln.F{
|
|
||||||
"gc-collections": stats.NumGC,
|
|
||||||
"gc-stw-pause-total": stats.PauseTotalNs,
|
|
||||||
"live-object-count": stats.Mallocs - stats.Frees,
|
|
||||||
"heap-bytes": stats.Alloc,
|
|
||||||
"stack-bytes": stats.StackInuse,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
/*
|
|
||||||
Package ex is a set of extensions and middleware for ln.
|
|
||||||
|
|
||||||
This package will (inevitably) have a lot of third-party dependencies and
|
|
||||||
as such might be broken apart into other packages in the future.
|
|
||||||
*/
|
|
||||||
package ex
|
|
|
@ -1,68 +0,0 @@
|
||||||
package ex
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/Xe/ln"
|
|
||||||
"golang.org/x/net/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
type goEventLogger struct {
|
|
||||||
ev trace.EventLog
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGoEventLogger will log ln information to a given trace.EventLog instance.
|
|
||||||
func NewGoEventLogger(ev trace.EventLog) ln.Filter {
|
|
||||||
return &goEventLogger{ev: ev}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gel *goEventLogger) Apply(ctx context.Context, e ln.Event) bool {
|
|
||||||
data, err := ln.DefaultFormatter.Format(ctx, e)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("wtf: error in log formatting: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if everr := e.Data["err"]; everr != nil {
|
|
||||||
gel.ev.Errorf("%s", string(data))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
gel.ev.Printf("%s", string(data))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gel *goEventLogger) Close() { gel.ev.Finish() }
|
|
||||||
func (gel *goEventLogger) Run() {}
|
|
||||||
|
|
||||||
type sst string
|
|
||||||
|
|
||||||
func (s sst) String() string { return string(s) }
|
|
||||||
|
|
||||||
func goTraceLogger(ctx context.Context, e ln.Event) bool {
|
|
||||||
sp, ok := trace.FromContext(ctx)
|
|
||||||
if !ok {
|
|
||||||
return true // no trace in context
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ln.DefaultFormatter.Format(ctx, e)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("wtf: error in log formatting: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if everr := e.Data["err"]; everr != nil {
|
|
||||||
sp.SetError()
|
|
||||||
}
|
|
||||||
|
|
||||||
sp.LazyLog(sst(string(data)), false)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGoTraceLogger will log ln information to a golang.org/x/net/trace.Trace
|
|
||||||
// if it is present in the context of ln calls.
|
|
||||||
func NewGoTraceLogger() ln.Filter {
|
|
||||||
return ln.FilterFunc(goTraceLogger)
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package ex
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Xe/ln"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HTTPLog(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
host, _, _ := net.SplitHostPort(r.RemoteAddr)
|
|
||||||
f := ln.F{
|
|
||||||
"remote_ip": host,
|
|
||||||
"x_forwarded_for": r.Header.Get("X-Forwarded-For"),
|
|
||||||
"path": r.URL.Path,
|
|
||||||
}
|
|
||||||
ctx := ln.WithF(r.Context(), f)
|
|
||||||
st := time.Now()
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
|
|
||||||
af := time.Now()
|
|
||||||
f["request_duration"] = af.Sub(st)
|
|
||||||
|
|
||||||
ws, ok := w.(interface {
|
|
||||||
Status() int
|
|
||||||
})
|
|
||||||
if ok {
|
|
||||||
f["status"] = ws.Status()
|
|
||||||
}
|
|
||||||
|
|
||||||
ln.Log(r.Context(), f)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package ex
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Xe/ln"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This file deals with formatting of [l2met] style metrics.
|
|
||||||
// [l2met]: https://r.32k.io/l2met-introduction
|
|
||||||
|
|
||||||
// Counter formats a value as a metrics counter.
|
|
||||||
func Counter(name string, value int) ln.Fer {
|
|
||||||
return ln.F{"count#" + name: value}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gauge formats a value as a metrics gauge.
|
|
||||||
func Gauge(name string, value int) ln.Fer {
|
|
||||||
return ln.F{"gauge#" + name: value}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Measure formats a value as a metrics measure.
|
|
||||||
func Measure(name string, ts time.Time) ln.Fer {
|
|
||||||
return ln.F{"measure#" + name: time.Since(ts)}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Xe/ln"
|
|
||||||
"github.com/Xe/ln/ex"
|
|
||||||
"github.com/facebookgo/flagenv"
|
|
||||||
"golang.org/x/net/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
port = flag.String("port", "2145", "http port to listen on")
|
|
||||||
tracingFamily = flag.String("trace-family", "ln example", "tracing family to use for x/net/trace")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flagenv.Parse()
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
ln.DefaultLogger.Filters = append(ln.DefaultLogger.Filters, ex.NewGoTraceLogger())
|
|
||||||
|
|
||||||
http.HandleFunc("/", handleIndex)
|
|
||||||
http.ListenAndServe(":"+*port, middlewareSpan(ex.HTTPLog(http.DefaultServeMux)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func middlewareSpan(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
sp := trace.New(*tracingFamily, "HTTP request")
|
|
||||||
defer sp.Finish()
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
ctx = trace.NewContext(ctx, sp)
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleIndex(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
ln.Log(ctx, ln.Action("index"), ln.F{"there_is": "no_danger"})
|
|
||||||
|
|
||||||
http.Error(w, "There is no danger citizen", http.StatusOK)
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright © 2012-2015 Carlos Castillo
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the “Software”), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
||||||
of the Software, and to permit persons to whom the Software is furnished to do
|
|
||||||
so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,5 +0,0 @@
|
||||||
go.zipexe
|
|
||||||
=========
|
|
||||||
|
|
||||||
This module was taken as-is from https://github.com/cookieo9/resources-go.
|
|
||||||
Documentation: https://godoc.org/github.com/daaku/go.zipexe
|
|
|
@ -1,142 +0,0 @@
|
||||||
// Package zipexe attempts to open an executable binary file as a zip file.
|
|
||||||
package zipexe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"debug/elf"
|
|
||||||
"debug/macho"
|
|
||||||
"debug/pe"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Opens a zip file by path.
|
|
||||||
func Open(path string) (*zip.Reader, error) {
|
|
||||||
_, rd, err := OpenCloser(path)
|
|
||||||
return rd, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenCloser is like Open but returns an additional Closer to avoid leaking open files.
|
|
||||||
func OpenCloser(path string) (io.Closer, *zip.Reader, error) {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
finfo, err := file.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
zr, err := NewReader(file, finfo.Size())
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return file, zr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open a zip file, specially handling various binaries that may have been
|
|
||||||
// augmented with zip data.
|
|
||||||
func NewReader(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
|
||||||
handlers := []func(io.ReaderAt, int64) (*zip.Reader, error){
|
|
||||||
zip.NewReader,
|
|
||||||
zipExeReaderMacho,
|
|
||||||
zipExeReaderElf,
|
|
||||||
zipExeReaderPe,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, handler := range handlers {
|
|
||||||
zfile, err := handler(rda, size)
|
|
||||||
if err == nil {
|
|
||||||
return zfile, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("Couldn't Open As Executable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// zipExeReaderMacho treats the file as a Mach-O binary
|
|
||||||
// (Mac OS X / Darwin executable) and attempts to find a zip archive.
|
|
||||||
func zipExeReaderMacho(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
|
||||||
file, err := macho.NewFile(rda)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var max int64
|
|
||||||
for _, load := range file.Loads {
|
|
||||||
seg, ok := load.(*macho.Segment)
|
|
||||||
if ok {
|
|
||||||
// Check if the segment contains a zip file
|
|
||||||
if zfile, err := zip.NewReader(seg, int64(seg.Filesz)); err == nil {
|
|
||||||
return zfile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise move end of file pointer
|
|
||||||
end := int64(seg.Offset + seg.Filesz)
|
|
||||||
if end > max {
|
|
||||||
max = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No zip file within binary, try appended to end
|
|
||||||
section := io.NewSectionReader(rda, max, size-max)
|
|
||||||
return zip.NewReader(section, section.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
// zipExeReaderPe treats the file as a Portable Exectuable binary
|
|
||||||
// (Windows executable) and attempts to find a zip archive.
|
|
||||||
func zipExeReaderPe(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
|
||||||
file, err := pe.NewFile(rda)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var max int64
|
|
||||||
for _, sec := range file.Sections {
|
|
||||||
// Check if this section has a zip file
|
|
||||||
if zfile, err := zip.NewReader(sec, int64(sec.Size)); err == nil {
|
|
||||||
return zfile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise move end of file pointer
|
|
||||||
end := int64(sec.Offset + sec.Size)
|
|
||||||
if end > max {
|
|
||||||
max = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No zip file within binary, try appended to end
|
|
||||||
section := io.NewSectionReader(rda, max, size-max)
|
|
||||||
return zip.NewReader(section, section.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
// zipExeReaderElf treats the file as a ELF binary
|
|
||||||
// (linux/BSD/etc... executable) and attempts to find a zip archive.
|
|
||||||
func zipExeReaderElf(rda io.ReaderAt, size int64) (*zip.Reader, error) {
|
|
||||||
file, err := elf.NewFile(rda)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var max int64
|
|
||||||
for _, sect := range file.Sections {
|
|
||||||
if sect.Type == elf.SHT_NOBITS {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this section has a zip file
|
|
||||||
if zfile, err := zip.NewReader(sect, int64(sect.Size)); err == nil {
|
|
||||||
return zfile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise move end of file pointer
|
|
||||||
end := int64(sect.Offset + sect.Size)
|
|
||||||
if end > max {
|
|
||||||
max = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No zip file within binary, try appended to end
|
|
||||||
section := io.NewSectionReader(rda, max, size-max)
|
|
||||||
return zip.NewReader(section, section.Size())
|
|
||||||
}
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||||
|
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build !js,!appengine,!safe,!disableunsafe
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = false
|
||||||
|
|
||||||
|
// ptrSize is the size of a pointer on the current arch.
|
||||||
|
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
||||||
|
// internal reflect.Value fields. These values are valid before golang
|
||||||
|
// commit ecccf07e7f9d which changed the format. The are also valid
|
||||||
|
// after commit 82f48826c6c7 which changed the format again to mirror
|
||||||
|
// the original format. Code in the init function updates these offsets
|
||||||
|
// as necessary.
|
||||||
|
offsetPtr = uintptr(ptrSize)
|
||||||
|
offsetScalar = uintptr(0)
|
||||||
|
offsetFlag = uintptr(ptrSize * 2)
|
||||||
|
|
||||||
|
// flagKindWidth and flagKindShift indicate various bits that the
|
||||||
|
// reflect package uses internally to track kind information.
|
||||||
|
//
|
||||||
|
// flagRO indicates whether or not the value field of a reflect.Value is
|
||||||
|
// read-only.
|
||||||
|
//
|
||||||
|
// flagIndir indicates whether the value field of a reflect.Value is
|
||||||
|
// the actual data or a pointer to the data.
|
||||||
|
//
|
||||||
|
// These values are valid before golang commit 90a7c3c86944 which
|
||||||
|
// changed their positions. Code in the init function updates these
|
||||||
|
// flags as necessary.
|
||||||
|
flagKindWidth = uintptr(5)
|
||||||
|
flagKindShift = uintptr(flagKindWidth - 1)
|
||||||
|
flagRO = uintptr(1 << 0)
|
||||||
|
flagIndir = uintptr(1 << 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Older versions of reflect.Value stored small integers directly in the
|
||||||
|
// ptr field (which is named val in the older versions). Versions
|
||||||
|
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
||||||
|
// scalar for this purpose which unfortunately came before the flag
|
||||||
|
// field, so the offset of the flag field is different for those
|
||||||
|
// versions.
|
||||||
|
//
|
||||||
|
// This code constructs a new reflect.Value from a known small integer
|
||||||
|
// and checks if the size of the reflect.Value struct indicates it has
|
||||||
|
// the scalar field. When it does, the offsets are updated accordingly.
|
||||||
|
vv := reflect.ValueOf(0xf00)
|
||||||
|
if unsafe.Sizeof(vv) == (ptrSize * 4) {
|
||||||
|
offsetScalar = ptrSize * 2
|
||||||
|
offsetFlag = ptrSize * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit 90a7c3c86944 changed the flag positions such that the low
|
||||||
|
// order bits are the kind. This code extracts the kind from the flags
|
||||||
|
// field and ensures it's the correct type. When it's not, the flag
|
||||||
|
// order has been changed to the newer format, so the flags are updated
|
||||||
|
// accordingly.
|
||||||
|
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
|
||||||
|
upfv := *(*uintptr)(upf)
|
||||||
|
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
|
||||||
|
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
|
||||||
|
flagKindShift = 0
|
||||||
|
flagRO = 1 << 5
|
||||||
|
flagIndir = 1 << 6
|
||||||
|
|
||||||
|
// Commit adf9b30e5594 modified the flags to separate the
|
||||||
|
// flagRO flag into two bits which specifies whether or not the
|
||||||
|
// field is embedded. This causes flagIndir to move over a bit
|
||||||
|
// and means that flagRO is the combination of either of the
|
||||||
|
// original flagRO bit and the new bit.
|
||||||
|
//
|
||||||
|
// This code detects the change by extracting what used to be
|
||||||
|
// the indirect bit to ensure it's set. When it's not, the flag
|
||||||
|
// order has been changed to the newer format, so the flags are
|
||||||
|
// updated accordingly.
|
||||||
|
if upfv&flagIndir == 0 {
|
||||||
|
flagRO = 3 << 5
|
||||||
|
flagIndir = 1 << 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||||
|
// the typical safety restrictions preventing access to unaddressable and
|
||||||
|
// unexported data. It works by digging the raw pointer to the underlying
|
||||||
|
// value out of the protected value and generating a new unprotected (unsafe)
|
||||||
|
// reflect.Value to it.
|
||||||
|
//
|
||||||
|
// This allows us to check for implementations of the Stringer and error
|
||||||
|
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||||
|
// inaccessible values such as unexported struct fields.
|
||||||
|
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
|
||||||
|
indirects := 1
|
||||||
|
vt := v.Type()
|
||||||
|
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
|
||||||
|
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
|
||||||
|
if rvf&flagIndir != 0 {
|
||||||
|
vt = reflect.PtrTo(v.Type())
|
||||||
|
indirects++
|
||||||
|
} else if offsetScalar != 0 {
|
||||||
|
// The value is in the scalar field when it's not one of the
|
||||||
|
// reference types.
|
||||||
|
switch vt.Kind() {
|
||||||
|
case reflect.Uintptr:
|
||||||
|
case reflect.Chan:
|
||||||
|
case reflect.Func:
|
||||||
|
case reflect.Map:
|
||||||
|
case reflect.Ptr:
|
||||||
|
case reflect.UnsafePointer:
|
||||||
|
default:
|
||||||
|
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
|
||||||
|
offsetScalar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pv := reflect.NewAt(vt, upv)
|
||||||
|
rv = pv
|
||||||
|
for i := 0; i < indirects; i++ {
|
||||||
|
rv = rv.Elem()
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||||
|
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build js appengine safe disableunsafe
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||||
|
// that bypasses the typical safety restrictions preventing access to
|
||||||
|
// unaddressable and unexported data. However, doing this relies on access to
|
||||||
|
// the unsafe package. This is a stub version which simply returns the passed
|
||||||
|
// reflect.Value when the unsafe package is not available.
|
||||||
|
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,341 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||||
|
// the technique used in the fmt package.
|
||||||
|
var (
|
||||||
|
panicBytes = []byte("(PANIC=")
|
||||||
|
plusBytes = []byte("+")
|
||||||
|
iBytes = []byte("i")
|
||||||
|
trueBytes = []byte("true")
|
||||||
|
falseBytes = []byte("false")
|
||||||
|
interfaceBytes = []byte("(interface {})")
|
||||||
|
commaNewlineBytes = []byte(",\n")
|
||||||
|
newlineBytes = []byte("\n")
|
||||||
|
openBraceBytes = []byte("{")
|
||||||
|
openBraceNewlineBytes = []byte("{\n")
|
||||||
|
closeBraceBytes = []byte("}")
|
||||||
|
asteriskBytes = []byte("*")
|
||||||
|
colonBytes = []byte(":")
|
||||||
|
colonSpaceBytes = []byte(": ")
|
||||||
|
openParenBytes = []byte("(")
|
||||||
|
closeParenBytes = []byte(")")
|
||||||
|
spaceBytes = []byte(" ")
|
||||||
|
pointerChainBytes = []byte("->")
|
||||||
|
nilAngleBytes = []byte("<nil>")
|
||||||
|
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||||
|
maxShortBytes = []byte("<max>")
|
||||||
|
circularBytes = []byte("<already shown>")
|
||||||
|
circularShortBytes = []byte("<shown>")
|
||||||
|
invalidAngleBytes = []byte("<invalid>")
|
||||||
|
openBracketBytes = []byte("[")
|
||||||
|
closeBracketBytes = []byte("]")
|
||||||
|
percentBytes = []byte("%")
|
||||||
|
precisionBytes = []byte(".")
|
||||||
|
openAngleBytes = []byte("<")
|
||||||
|
closeAngleBytes = []byte(">")
|
||||||
|
openMapBytes = []byte("map[")
|
||||||
|
closeMapBytes = []byte("]")
|
||||||
|
lenEqualsBytes = []byte("len=")
|
||||||
|
capEqualsBytes = []byte("cap=")
|
||||||
|
)
|
||||||
|
|
||||||
|
// hexDigits is used to map a decimal value to a hex digit.
|
||||||
|
var hexDigits = "0123456789abcdef"
|
||||||
|
|
||||||
|
// catchPanic handles any panics that might occur during the handleMethods
|
||||||
|
// calls.
|
||||||
|
func catchPanic(w io.Writer, v reflect.Value) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
w.Write(panicBytes)
|
||||||
|
fmt.Fprintf(w, "%v", err)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMethods attempts to call the Error and String methods on the underlying
|
||||||
|
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||||
|
//
|
||||||
|
// It handles panics in any called methods by catching and displaying the error
|
||||||
|
// as the formatted value.
|
||||||
|
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||||
|
// We need an interface to check if the type implements the error or
|
||||||
|
// Stringer interface. However, the reflect package won't give us an
|
||||||
|
// interface on certain things like unexported struct fields in order
|
||||||
|
// to enforce visibility rules. We use unsafe, when it's available,
|
||||||
|
// to bypass these restrictions since this package does not mutate the
|
||||||
|
// values.
|
||||||
|
if !v.CanInterface() {
|
||||||
|
if UnsafeDisabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose whether or not to do error and Stringer interface lookups against
|
||||||
|
// the base type or a pointer to the base type depending on settings.
|
||||||
|
// Technically calling one of these methods with a pointer receiver can
|
||||||
|
// mutate the value, however, types which choose to satisify an error or
|
||||||
|
// Stringer interface with a pointer receiver should not be mutating their
|
||||||
|
// state inside these interface methods.
|
||||||
|
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
if v.CanAddr() {
|
||||||
|
v = v.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it an error or Stringer?
|
||||||
|
switch iface := v.Interface().(type) {
|
||||||
|
case error:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
return true
|
||||||
|
|
||||||
|
case fmt.Stringer:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printBool outputs a boolean value as true or false to Writer w.
|
||||||
|
func printBool(w io.Writer, val bool) {
|
||||||
|
if val {
|
||||||
|
w.Write(trueBytes)
|
||||||
|
} else {
|
||||||
|
w.Write(falseBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printInt outputs a signed integer value to Writer w.
|
||||||
|
func printInt(w io.Writer, val int64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printUint outputs an unsigned integer value to Writer w.
|
||||||
|
func printUint(w io.Writer, val uint64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printFloat outputs a floating point value using the specified precision,
|
||||||
|
// which is expected to be 32 or 64bit, to Writer w.
|
||||||
|
func printFloat(w io.Writer, val float64, precision int) {
|
||||||
|
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printComplex outputs a complex value using the specified float precision
|
||||||
|
// for the real and imaginary parts to Writer w.
|
||||||
|
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||||
|
r := real(c)
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||||
|
i := imag(c)
|
||||||
|
if i >= 0 {
|
||||||
|
w.Write(plusBytes)
|
||||||
|
}
|
||||||
|
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||||
|
w.Write(iBytes)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
||||||
|
// prefix to Writer w.
|
||||||
|
func printHexPtr(w io.Writer, p uintptr) {
|
||||||
|
// Null pointer.
|
||||||
|
num := uint64(p)
|
||||||
|
if num == 0 {
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||||
|
buf := make([]byte, 18)
|
||||||
|
|
||||||
|
// It's simpler to construct the hex string right to left.
|
||||||
|
base := uint64(16)
|
||||||
|
i := len(buf) - 1
|
||||||
|
for num >= base {
|
||||||
|
buf[i] = hexDigits[num%base]
|
||||||
|
num /= base
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
buf[i] = hexDigits[num]
|
||||||
|
|
||||||
|
// Add '0x' prefix.
|
||||||
|
i--
|
||||||
|
buf[i] = 'x'
|
||||||
|
i--
|
||||||
|
buf[i] = '0'
|
||||||
|
|
||||||
|
// Strip unused leading bytes.
|
||||||
|
buf = buf[i:]
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||||
|
// elements to be sorted.
|
||||||
|
type valuesSorter struct {
|
||||||
|
values []reflect.Value
|
||||||
|
strings []string // either nil or same len and values
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||||
|
// surrogate keys on which the data should be sorted. It uses flags in
|
||||||
|
// ConfigState to decide if and how to populate those surrogate keys.
|
||||||
|
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||||
|
vs := &valuesSorter{values: values, cs: cs}
|
||||||
|
if canSortSimply(vs.values[0].Kind()) {
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
if !cs.DisableMethods {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if !handleMethods(cs, &b, vs.values[i]) {
|
||||||
|
vs.strings = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
vs.strings[i] = b.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vs.strings == nil && cs.SpewKeys {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||||
|
// directly, or whether it should be considered for sorting by surrogate keys
|
||||||
|
// (if the ConfigState allows it).
|
||||||
|
func canSortSimply(kind reflect.Kind) bool {
|
||||||
|
// This switch parallels valueSortLess, except for the default case.
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return true
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return true
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return true
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return true
|
||||||
|
case reflect.Array:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of values in the slice. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Len() int {
|
||||||
|
return len(s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps the values at the passed indices. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Swap(i, j int) {
|
||||||
|
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||||
|
if s.strings != nil {
|
||||||
|
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueSortLess returns whether the first value should sort before the second
|
||||||
|
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||||
|
// implementation.
|
||||||
|
func valueSortLess(a, b reflect.Value) bool {
|
||||||
|
switch a.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !a.Bool() && b.Bool()
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return a.Int() < b.Int()
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return a.Float() < b.Float()
|
||||||
|
case reflect.String:
|
||||||
|
return a.String() < b.String()
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Array:
|
||||||
|
// Compare the contents of both arrays.
|
||||||
|
l := a.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
av := a.Index(i)
|
||||||
|
bv := b.Index(i)
|
||||||
|
if av.Interface() == bv.Interface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return valueSortLess(av, bv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.String() < b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less returns whether the value at index i should sort before the
|
||||||
|
// value at index j. It is part of the sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Less(i, j int) bool {
|
||||||
|
if s.strings == nil {
|
||||||
|
return valueSortLess(s.values[i], s.values[j])
|
||||||
|
}
|
||||||
|
return s.strings[i] < s.strings[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortValues is a sort function that handles both native types and any type that
|
||||||
|
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||||
|
// their Value.String() value to ensure display stability.
|
||||||
|
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Sort(newValuesSorter(values, cs))
|
||||||
|
}
|
|
@ -0,0 +1,298 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// custom type to test Stinger interface on non-pointer receiver.
|
||||||
|
type stringer string
|
||||||
|
|
||||||
|
// String implements the Stringer interface for testing invocation of custom
|
||||||
|
// stringers on types with non-pointer receivers.
|
||||||
|
func (s stringer) String() string {
|
||||||
|
return "stringer " + string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom type to test Stinger interface on pointer receiver.
|
||||||
|
type pstringer string
|
||||||
|
|
||||||
|
// String implements the Stringer interface for testing invocation of custom
|
||||||
|
// stringers on types with only pointer receivers.
|
||||||
|
func (s *pstringer) String() string {
|
||||||
|
return "stringer " + string(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// xref1 and xref2 are cross referencing structs for testing circular reference
|
||||||
|
// detection.
|
||||||
|
type xref1 struct {
|
||||||
|
ps2 *xref2
|
||||||
|
}
|
||||||
|
type xref2 struct {
|
||||||
|
ps1 *xref1
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
|
||||||
|
// reference for testing detection.
|
||||||
|
type indirCir1 struct {
|
||||||
|
ps2 *indirCir2
|
||||||
|
}
|
||||||
|
type indirCir2 struct {
|
||||||
|
ps3 *indirCir3
|
||||||
|
}
|
||||||
|
type indirCir3 struct {
|
||||||
|
ps1 *indirCir1
|
||||||
|
}
|
||||||
|
|
||||||
|
// embed is used to test embedded structures.
|
||||||
|
type embed struct {
|
||||||
|
a string
|
||||||
|
}
|
||||||
|
|
||||||
|
// embedwrap is used to test embedded structures.
|
||||||
|
type embedwrap struct {
|
||||||
|
*embed
|
||||||
|
e *embed
|
||||||
|
}
|
||||||
|
|
||||||
|
// panicer is used to intentionally cause a panic for testing spew properly
|
||||||
|
// handles them
|
||||||
|
type panicer int
|
||||||
|
|
||||||
|
func (p panicer) String() string {
|
||||||
|
panic("test panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
// customError is used to test custom error interface invocation.
|
||||||
|
type customError int
|
||||||
|
|
||||||
|
func (e customError) Error() string {
|
||||||
|
return fmt.Sprintf("error: %d", int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringizeWants converts a slice of wanted test output into a format suitable
|
||||||
|
// for a test error message.
|
||||||
|
func stringizeWants(wants []string) string {
|
||||||
|
s := ""
|
||||||
|
for i, want := range wants {
|
||||||
|
if i > 0 {
|
||||||
|
s += fmt.Sprintf("want%d: %s", i+1, want)
|
||||||
|
} else {
|
||||||
|
s += "want: " + want
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// testFailed returns whether or not a test failed by checking if the result
|
||||||
|
// of the test is in the slice of wanted strings.
|
||||||
|
func testFailed(result string, wants []string) bool {
|
||||||
|
for _, want := range wants {
|
||||||
|
if result == want {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortableStruct struct {
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss sortableStruct) String() string {
|
||||||
|
return fmt.Sprintf("ss.%d", ss.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsortableStruct struct {
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortTestCase struct {
|
||||||
|
input []reflect.Value
|
||||||
|
expected []reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
|
||||||
|
getInterfaces := func(values []reflect.Value) []interface{} {
|
||||||
|
interfaces := []interface{}{}
|
||||||
|
for _, v := range values {
|
||||||
|
interfaces = append(interfaces, v.Interface())
|
||||||
|
}
|
||||||
|
return interfaces
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
spew.SortValues(test.input, cs)
|
||||||
|
// reflect.DeepEqual cannot really make sense of reflect.Value,
|
||||||
|
// probably because of all the pointer tricks. For instance,
|
||||||
|
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
|
||||||
|
// instead.
|
||||||
|
input := getInterfaces(test.input)
|
||||||
|
expected := getInterfaces(test.expected)
|
||||||
|
if !reflect.DeepEqual(input, expected) {
|
||||||
|
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValues ensures the sort functionality for relect.Value based sorting
|
||||||
|
// works as intended.
|
||||||
|
func TestSortValues(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
embedA := v(embed{"a"})
|
||||||
|
embedB := v(embed{"b"})
|
||||||
|
embedC := v(embed{"c"})
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// No values.
|
||||||
|
{
|
||||||
|
[]reflect.Value{},
|
||||||
|
[]reflect.Value{},
|
||||||
|
},
|
||||||
|
// Bools.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(false), v(true), v(false)},
|
||||||
|
[]reflect.Value{v(false), v(false), v(true)},
|
||||||
|
},
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Uints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
|
||||||
|
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
|
||||||
|
},
|
||||||
|
// Floats.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
|
||||||
|
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// Array
|
||||||
|
{
|
||||||
|
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
|
||||||
|
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
|
||||||
|
},
|
||||||
|
// Uintptrs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
|
||||||
|
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - DisableMethods is set.
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - SpewKeys is false.
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
// Invalid.
|
||||||
|
{
|
||||||
|
[]reflect.Value{embedB, embedA, embedC},
|
||||||
|
[]reflect.Value{embedB, embedA, embedC},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
|
||||||
|
// based sorting works as intended when using string methods.
|
||||||
|
func TestSortValuesWithMethods(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - SpewKeys is false.
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
|
||||||
|
// based sorting works as intended when using spew to stringify keys.
|
||||||
|
func TestSortValuesWithSpew(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
|
@ -0,0 +1,306 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigState houses the configuration options used by spew to format and
|
||||||
|
// display values. There is a global instance, Config, that is used to control
|
||||||
|
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||||
|
// provides methods equivalent to the top-level functions.
|
||||||
|
//
|
||||||
|
// The zero value for ConfigState provides no indentation. You would typically
|
||||||
|
// want to set it to a space or a tab.
|
||||||
|
//
|
||||||
|
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||||
|
// with default settings. See the documentation of NewDefaultConfig for default
|
||||||
|
// values.
|
||||||
|
type ConfigState struct {
|
||||||
|
// Indent specifies the string to use for each indentation level. The
|
||||||
|
// global config instance that all top-level functions use set this to a
|
||||||
|
// single space by default. If you would like more indentation, you might
|
||||||
|
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||||
|
Indent string
|
||||||
|
|
||||||
|
// MaxDepth controls the maximum number of levels to descend into nested
|
||||||
|
// data structures. The default, 0, means there is no limit.
|
||||||
|
//
|
||||||
|
// NOTE: Circular data structures are properly detected, so it is not
|
||||||
|
// necessary to set this value unless you specifically want to limit deeply
|
||||||
|
// nested data structures.
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||||
|
// invoked for types that implement them.
|
||||||
|
DisableMethods bool
|
||||||
|
|
||||||
|
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||||
|
// error and Stringer interfaces on types which only accept a pointer
|
||||||
|
// receiver when the current type is not a pointer.
|
||||||
|
//
|
||||||
|
// NOTE: This might be an unsafe action since calling one of these methods
|
||||||
|
// with a pointer receiver could technically mutate the value, however,
|
||||||
|
// in practice, types which choose to satisify an error or Stringer
|
||||||
|
// interface with a pointer receiver should not be mutating their state
|
||||||
|
// inside these interface methods. As a result, this option relies on
|
||||||
|
// access to the unsafe package, so it will not have any effect when
|
||||||
|
// running in environments without access to the unsafe package such as
|
||||||
|
// Google App Engine or with the "safe" build tag specified.
|
||||||
|
DisablePointerMethods bool
|
||||||
|
|
||||||
|
// DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
// pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
DisablePointerAddresses bool
|
||||||
|
|
||||||
|
// DisableCapacities specifies whether to disable the printing of capacities
|
||||||
|
// for arrays, slices, maps and channels. This is useful when diffing
|
||||||
|
// data structures in tests.
|
||||||
|
DisableCapacities bool
|
||||||
|
|
||||||
|
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||||
|
// a custom error or Stringer interface is invoked. The default, false,
|
||||||
|
// means it will print the results of invoking the custom error or Stringer
|
||||||
|
// interface and return immediately instead of continuing to recurse into
|
||||||
|
// the internals of the data type.
|
||||||
|
//
|
||||||
|
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||||
|
// via the DisableMethods or DisablePointerMethods options.
|
||||||
|
ContinueOnMethod bool
|
||||||
|
|
||||||
|
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||||
|
// this to have a more deterministic, diffable output. Note that only
|
||||||
|
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||||
|
// that support the error or Stringer interfaces (if methods are
|
||||||
|
// enabled) are supported, with other types sorted according to the
|
||||||
|
// reflect.Value.String() output which guarantees display stability.
|
||||||
|
SortKeys bool
|
||||||
|
|
||||||
|
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||||
|
// be spewed to strings and sorted by those strings. This is only
|
||||||
|
// considered if SortKeys is true.
|
||||||
|
SpewKeys bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the active configuration of the top-level functions.
|
||||||
|
// The configuration can be changed by modifying the contents of spew.Config.
|
||||||
|
var Config = ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the formatted string as a value that satisfies error. See NewFormatter
|
||||||
|
// for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
c.Printf, c.Println, or c.Printf.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(c, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(c, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by modifying the public members
|
||||||
|
of c. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) Dump(a ...interface{}) {
|
||||||
|
fdump(c, os.Stdout, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(c, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a spew Formatter interface using
|
||||||
|
// the ConfigState associated with s.
|
||||||
|
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = newFormatter(c, arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||||
|
//
|
||||||
|
// Indent: " "
|
||||||
|
// MaxDepth: 0
|
||||||
|
// DisableMethods: false
|
||||||
|
// DisablePointerMethods: false
|
||||||
|
// ContinueOnMethod: false
|
||||||
|
// SortKeys: false
|
||||||
|
func NewDefaultConfig() *ConfigState {
|
||||||
|
return &ConfigState{Indent: " "}
|
||||||
|
}
|
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
A quick overview of the additional features spew provides over the built-in
|
||||||
|
printing facilities for Go data types are as follows:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output (only when using
|
||||||
|
Dump style)
|
||||||
|
|
||||||
|
There are two different approaches spew allows for dumping Go data structures:
|
||||||
|
|
||||||
|
* Dump style which prints with newlines, customizable indentation,
|
||||||
|
and additional debug information such as types and all pointer addresses
|
||||||
|
used to indirect to the final value
|
||||||
|
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||||
|
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||||
|
similar to the default %v while providing the additional functionality
|
||||||
|
outlined above and passing unsupported format verbs such as %x and %q
|
||||||
|
along to fmt
|
||||||
|
|
||||||
|
Quick Start
|
||||||
|
|
||||||
|
This section demonstrates how to quickly get started with spew. See the
|
||||||
|
sections below for further details on formatting and configuration options.
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||||
|
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||||
|
%#+v (adds types and pointer addresses):
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available
|
||||||
|
via the spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
The following configuration options are available:
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerAddresses
|
||||||
|
DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
|
||||||
|
* DisableCapacities
|
||||||
|
DisableCapacities specifies whether to disable the printing of
|
||||||
|
capacities for arrays, slices, maps and channels. This is useful when
|
||||||
|
diffing data structures in tests.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are
|
||||||
|
supported with other types sorted according to the
|
||||||
|
reflect.Value.String() output which guarantees display
|
||||||
|
stability. Natural map order is used by default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
Specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only
|
||||||
|
considered if SortKeys is true.
|
||||||
|
|
||||||
|
Dump Usage
|
||||||
|
|
||||||
|
Simply call spew.Dump with a list of variables you want to dump:
|
||||||
|
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||||
|
io.Writer. For example, to dump to standard error:
|
||||||
|
|
||||||
|
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||||
|
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Sample Dump Output
|
||||||
|
|
||||||
|
See the Dump example for details on the setup of the types and variables being
|
||||||
|
shown here.
|
||||||
|
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
(string) (len=3) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||||
|
command as shown.
|
||||||
|
([]uint8) (len=32 cap=32) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
|
||||||
|
Custom Formatter
|
||||||
|
|
||||||
|
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||||
|
so that it integrates cleanly with standard fmt package printing functions. The
|
||||||
|
formatter is useful for inline printing of smaller data types similar to the
|
||||||
|
standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Custom Formatter Usage
|
||||||
|
|
||||||
|
The simplest way to make use of the spew custom formatter is to call one of the
|
||||||
|
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||||
|
functions have syntax you are most likely already familiar with:
|
||||||
|
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Println(myVar, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
See the Index for the full list convenience functions.
|
||||||
|
|
||||||
|
Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
|
||||||
|
See the Printf example for details on the setup of variables being shown
|
||||||
|
here.
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||||
|
detects them and handles them internally by printing the panic information
|
||||||
|
inline with the output. Since spew is intended to provide deep pretty printing
|
||||||
|
capabilities on structures, it intentionally does not return any errors.
|
||||||
|
*/
|
||||||
|
package spew
|
|
@ -0,0 +1,509 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||||
|
// convert cgo types to uint8 slices for hexdumping.
|
||||||
|
uint8Type = reflect.TypeOf(uint8(0))
|
||||||
|
|
||||||
|
// cCharRE is a regular expression that matches a cgo char.
|
||||||
|
// It is used to detect character arrays to hexdump them.
|
||||||
|
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
||||||
|
|
||||||
|
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||||
|
// char. It is used to detect unsigned character arrays to hexdump
|
||||||
|
// them.
|
||||||
|
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
||||||
|
|
||||||
|
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||||
|
// It is used to detect uint8_t arrays to hexdump them.
|
||||||
|
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
||||||
|
)
|
||||||
|
|
||||||
|
// dumpState contains information about the state of a dump operation.
|
||||||
|
type dumpState struct {
|
||||||
|
w io.Writer
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
ignoreNextIndent bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// indent performs indentation according to the depth level and cs.Indent
|
||||||
|
// option.
|
||||||
|
func (d *dumpState) indent() {
|
||||||
|
if d.ignoreNextIndent {
|
||||||
|
d.ignoreNextIndent = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range d.pointers {
|
||||||
|
if depth >= d.depth {
|
||||||
|
delete(d.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by dereferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.pointers[addr] = d.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type information.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
d.w.Write([]byte(ve.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
|
||||||
|
// Display pointer information.
|
||||||
|
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
d.w.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(d.w, addr)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
switch {
|
||||||
|
case nilFound == true:
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound == true:
|
||||||
|
d.w.Write(circularBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
d.ignoreNextType = true
|
||||||
|
d.dump(ve)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||||
|
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||||
|
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||||
|
// Determine whether this type should be hex dumped or not. Also,
|
||||||
|
// for types which should be hexdumped, try to use the underlying data
|
||||||
|
// first, then fall back to trying to convert them to a uint8 slice.
|
||||||
|
var buf []uint8
|
||||||
|
doConvert := false
|
||||||
|
doHexDump := false
|
||||||
|
numEntries := v.Len()
|
||||||
|
if numEntries > 0 {
|
||||||
|
vt := v.Index(0).Type()
|
||||||
|
vts := vt.String()
|
||||||
|
switch {
|
||||||
|
// C types that need to be converted.
|
||||||
|
case cCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUnsignedCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUint8tCharRE.MatchString(vts):
|
||||||
|
doConvert = true
|
||||||
|
|
||||||
|
// Try to use existing uint8 slices and fall back to converting
|
||||||
|
// and copying if that fails.
|
||||||
|
case vt.Kind() == reflect.Uint8:
|
||||||
|
// We need an addressable interface to convert the type
|
||||||
|
// to a byte slice. However, the reflect package won't
|
||||||
|
// give us an interface on certain things like
|
||||||
|
// unexported struct fields in order to enforce
|
||||||
|
// visibility rules. We use unsafe, when available, to
|
||||||
|
// bypass these restrictions since this package does not
|
||||||
|
// mutate the values.
|
||||||
|
vs := v
|
||||||
|
if !vs.CanInterface() || !vs.CanAddr() {
|
||||||
|
vs = unsafeReflectValue(vs)
|
||||||
|
}
|
||||||
|
if !UnsafeDisabled {
|
||||||
|
vs = vs.Slice(0, numEntries)
|
||||||
|
|
||||||
|
// Use the existing uint8 slice if it can be
|
||||||
|
// type asserted.
|
||||||
|
iface := vs.Interface()
|
||||||
|
if slice, ok := iface.([]uint8); ok {
|
||||||
|
buf = slice
|
||||||
|
doHexDump = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The underlying data needs to be converted if it can't
|
||||||
|
// be type asserted to a uint8 slice.
|
||||||
|
doConvert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and convert the underlying type if needed.
|
||||||
|
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||||
|
// Convert and copy each element into a uint8 byte
|
||||||
|
// slice.
|
||||||
|
buf = make([]uint8, numEntries)
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
vv := v.Index(i)
|
||||||
|
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||||
|
}
|
||||||
|
doHexDump = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hexdump the entire slice as needed.
|
||||||
|
if doHexDump {
|
||||||
|
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||||
|
str := indent + hex.Dump(buf)
|
||||||
|
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||||
|
str = strings.TrimRight(str, d.cs.Indent)
|
||||||
|
d.w.Write([]byte(str))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively call dump for each item.
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
d.dump(d.unpackValue(v.Index(i)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||||
|
// value to figure out what kind of object we are dealing with and formats it
|
||||||
|
// appropriately. It is a recursive function, however circular data structures
|
||||||
|
// are detected and handled properly.
|
||||||
|
func (d *dumpState) dump(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
d.w.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
d.indent()
|
||||||
|
d.dumpPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !d.ignoreNextType {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write([]byte(v.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.ignoreNextType = false
|
||||||
|
|
||||||
|
// Display length and capacity if the built-in len and cap functions
|
||||||
|
// work with the value's kind and the len/cap itself is non-zero.
|
||||||
|
valueLen, valueCap := 0, 0
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||||
|
valueLen, valueCap = v.Len(), v.Cap()
|
||||||
|
case reflect.Map, reflect.String:
|
||||||
|
valueLen = v.Len()
|
||||||
|
}
|
||||||
|
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(lenEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueLen), 10)
|
||||||
|
}
|
||||||
|
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.w.Write(capEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueCap), 10)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||||
|
// is enabled
|
||||||
|
if !d.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(d.w, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(d.w, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(d.w, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(d.w, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(d.w, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(d.w, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(d.w, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.dumpSlice(v)
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if d.cs.SortKeys {
|
||||||
|
sortValues(keys, d.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
d.dump(d.unpackValue(key))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
numFields := v.NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
d.indent()
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
d.w.Write([]byte(vtf.Name))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.Field(i)))
|
||||||
|
if i < (numFields - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(d.w, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(d.w, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it in case any new
|
||||||
|
// types are added.
|
||||||
|
default:
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fdump is a helper function to consolidate the logic from the various public
|
||||||
|
// methods which take varying writers and config states.
|
||||||
|
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||||
|
for _, arg := range a {
|
||||||
|
if arg == nil {
|
||||||
|
w.Write(interfaceBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
w.Write(newlineBytes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := dumpState{w: w, cs: cs}
|
||||||
|
d.pointers = make(map[uintptr]int)
|
||||||
|
d.dump(reflect.ValueOf(arg))
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(&Config, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(&Config, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by an exported package global,
|
||||||
|
spew.Config. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func Dump(a ...interface{}) {
|
||||||
|
fdump(&Config, os.Stdout, a...)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||||
|
// command line. This means the cgo tests are only added (and hence run) when
|
||||||
|
// specifially requested. This configuration is used because spew itself
|
||||||
|
// does not require cgo to run even though it does handle certain cgo types
|
||||||
|
// specially. Rather than forcing all clients to require cgo and an external
|
||||||
|
// C compiler just to run the tests, this scheme makes them optional.
|
||||||
|
// +build cgo,testcgo
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew/testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addCgoDumpTests() {
|
||||||
|
// C char pointer.
|
||||||
|
v := testdata.GetCgoCharPointer()
|
||||||
|
nv := testdata.GetCgoNullCharPointer()
|
||||||
|
pv := &v
|
||||||
|
vcAddr := fmt.Sprintf("%p", v)
|
||||||
|
vAddr := fmt.Sprintf("%p", pv)
|
||||||
|
pvAddr := fmt.Sprintf("%p", &pv)
|
||||||
|
vt := "*testdata._Ctype_char"
|
||||||
|
vs := "116"
|
||||||
|
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(nv, "("+vt+")(<nil>)\n")
|
||||||
|
|
||||||
|
// C char array.
|
||||||
|
v2, v2l, v2c := testdata.GetCgoCharArray()
|
||||||
|
v2Len := fmt.Sprintf("%d", v2l)
|
||||||
|
v2Cap := fmt.Sprintf("%d", v2c)
|
||||||
|
v2t := "[6]testdata._Ctype_char"
|
||||||
|
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 32 00 " +
|
||||||
|
" |test2.|\n}"
|
||||||
|
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
|
||||||
|
|
||||||
|
// C unsigned char array.
|
||||||
|
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
|
||||||
|
v3Len := fmt.Sprintf("%d", v3l)
|
||||||
|
v3Cap := fmt.Sprintf("%d", v3c)
|
||||||
|
v3t := "[6]testdata._Ctype_unsignedchar"
|
||||||
|
v3t2 := "[6]testdata._Ctype_uchar"
|
||||||
|
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 33 00 " +
|
||||||
|
" |test3.|\n}"
|
||||||
|
addDumpTest(v3, "("+v3t+") "+v3s+"\n", "("+v3t2+") "+v3s+"\n")
|
||||||
|
|
||||||
|
// C signed char array.
|
||||||
|
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
|
||||||
|
v4Len := fmt.Sprintf("%d", v4l)
|
||||||
|
v4Cap := fmt.Sprintf("%d", v4c)
|
||||||
|
v4t := "[6]testdata._Ctype_schar"
|
||||||
|
v4t2 := "testdata._Ctype_schar"
|
||||||
|
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
|
||||||
|
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
|
||||||
|
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
|
||||||
|
") 0\n}"
|
||||||
|
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
|
||||||
|
|
||||||
|
// C uint8_t array.
|
||||||
|
v5, v5l, v5c := testdata.GetCgoUint8tArray()
|
||||||
|
v5Len := fmt.Sprintf("%d", v5l)
|
||||||
|
v5Cap := fmt.Sprintf("%d", v5c)
|
||||||
|
v5t := "[6]testdata._Ctype_uint8_t"
|
||||||
|
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 35 00 " +
|
||||||
|
" |test5.|\n}"
|
||||||
|
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
|
||||||
|
|
||||||
|
// C typedefed unsigned char array.
|
||||||
|
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
|
||||||
|
v6Len := fmt.Sprintf("%d", v6l)
|
||||||
|
v6Cap := fmt.Sprintf("%d", v6c)
|
||||||
|
v6t := "[6]testdata._Ctype_custom_uchar_t"
|
||||||
|
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 36 00 " +
|
||||||
|
" |test6.|\n}"
|
||||||
|
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when either cgo is not supported or "-tags testcgo" is not added to the go
|
||||||
|
// test command line. This file intentionally does not setup any cgo tests in
|
||||||
|
// this scenario.
|
||||||
|
// +build !cgo !testcgo
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
func addCgoDumpTests() {
|
||||||
|
// Don't add any tests for cgo since this file is only compiled when
|
||||||
|
// there should not be any cgo tests.
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOne Flag = iota
|
||||||
|
flagTwo
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagStrings = map[Flag]string{
|
||||||
|
flagOne: "flagOne",
|
||||||
|
flagTwo: "flagTwo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Flag) String() string {
|
||||||
|
if s, ok := flagStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
unexportedField Bar
|
||||||
|
ExportedField map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use Dump to dump variables to stdout.
|
||||||
|
func ExampleDump() {
|
||||||
|
// The following package level declarations are assumed for this example:
|
||||||
|
/*
|
||||||
|
type Flag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOne Flag = iota
|
||||||
|
flagTwo
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagStrings = map[Flag]string{
|
||||||
|
flagOne: "flagOne",
|
||||||
|
flagTwo: "flagTwo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Flag) String() string {
|
||||||
|
if s, ok := flagStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
unexportedField Bar
|
||||||
|
ExportedField map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Setup some sample data structures for the example.
|
||||||
|
bar := Bar{uintptr(0)}
|
||||||
|
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||||
|
f := Flag(5)
|
||||||
|
b := []byte{
|
||||||
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||||
|
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||||
|
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||||
|
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
|
||||||
|
0x31, 0x32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump!
|
||||||
|
spew.Dump(s1, f, b)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (spew_test.Flag) Unknown flag (5)
|
||||||
|
// ([]uint8) (len=34 cap=34) {
|
||||||
|
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
// 00000020 31 32 |12|
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use Printf to display a variable with a
|
||||||
|
// format string and inline formatting.
|
||||||
|
func ExamplePrintf() {
|
||||||
|
// Create a double pointer to a uint 8.
|
||||||
|
ui8 := uint8(5)
|
||||||
|
pui8 := &ui8
|
||||||
|
ppui8 := &pui8
|
||||||
|
|
||||||
|
// Create a circular data type.
|
||||||
|
type circular struct {
|
||||||
|
ui8 uint8
|
||||||
|
c *circular
|
||||||
|
}
|
||||||
|
c := circular{ui8: 1}
|
||||||
|
c.c = &c
|
||||||
|
|
||||||
|
// Print!
|
||||||
|
spew.Printf("ppui8: %v\n", ppui8)
|
||||||
|
spew.Printf("circular: %v\n", c)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ppui8: <**>5
|
||||||
|
// circular: {1 <*>{1 <*><shown>}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use a ConfigState.
|
||||||
|
func ExampleConfigState() {
|
||||||
|
// Modify the indent level of the ConfigState only. The global
|
||||||
|
// configuration is not modified.
|
||||||
|
scs := spew.ConfigState{Indent: "\t"}
|
||||||
|
|
||||||
|
// Output using the ConfigState instance.
|
||||||
|
v := map[string]int{"one": 1}
|
||||||
|
scs.Printf("v: %v\n", v)
|
||||||
|
scs.Dump(v)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// v: map[one:1]
|
||||||
|
// (map[string]int) (len=1) {
|
||||||
|
// (string) (len=3) "one": (int) 1
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use ConfigState.Dump to dump variables to
|
||||||
|
// stdout
|
||||||
|
func ExampleConfigState_Dump() {
|
||||||
|
// See the top-level Dump example for details on the types used in this
|
||||||
|
// example.
|
||||||
|
|
||||||
|
// Create two ConfigState instances with different indentation.
|
||||||
|
scs := spew.ConfigState{Indent: "\t"}
|
||||||
|
scs2 := spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Setup some sample data structures for the example.
|
||||||
|
bar := Bar{uintptr(0)}
|
||||||
|
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||||
|
|
||||||
|
// Dump using the ConfigState instances.
|
||||||
|
scs.Dump(s1)
|
||||||
|
scs2.Dump(s1)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use ConfigState.Printf to display a variable
|
||||||
|
// with a format string and inline formatting.
|
||||||
|
func ExampleConfigState_Printf() {
|
||||||
|
// See the top-level Dump example for details on the types used in this
|
||||||
|
// example.
|
||||||
|
|
||||||
|
// Create two ConfigState instances and modify the method handling of the
|
||||||
|
// first ConfigState only.
|
||||||
|
scs := spew.NewDefaultConfig()
|
||||||
|
scs2 := spew.NewDefaultConfig()
|
||||||
|
scs.DisableMethods = true
|
||||||
|
|
||||||
|
// Alternatively
|
||||||
|
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||||
|
// scs2 := spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// This is of type Flag which implements a Stringer and has raw value 1.
|
||||||
|
f := flagTwo
|
||||||
|
|
||||||
|
// Dump using the ConfigState instances.
|
||||||
|
scs.Printf("f: %v\n", f)
|
||||||
|
scs2.Printf("f: %v\n", f)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// f: 1
|
||||||
|
// f: flagTwo
|
||||||
|
}
|
|
@ -0,0 +1,419 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||||
|
const supportedFlags = "0-+# "
|
||||||
|
|
||||||
|
// formatState implements the fmt.Formatter interface and contains information
|
||||||
|
// about the state of a formatting operation. The NewFormatter function can
|
||||||
|
// be used to get a new Formatter which can be used directly as arguments
|
||||||
|
// in standard fmt package printing calls.
|
||||||
|
type formatState struct {
|
||||||
|
value interface{}
|
||||||
|
fs fmt.State
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDefaultFormat recreates the original format string without precision
|
||||||
|
// and width information to pass in to fmt.Sprintf in the case of an
|
||||||
|
// unrecognized type. Unless new types are added to the language, this
|
||||||
|
// function won't ever be called.
|
||||||
|
func (f *formatState) buildDefaultFormat() (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune('v')
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructOrigFormat recreates the original format string including precision
|
||||||
|
// and width information to pass along to the standard fmt package. This allows
|
||||||
|
// automatic deferral of all format strings this package doesn't support.
|
||||||
|
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if width, ok := f.fs.Width(); ok {
|
||||||
|
buf.WriteString(strconv.Itoa(width))
|
||||||
|
}
|
||||||
|
|
||||||
|
if precision, ok := f.fs.Precision(); ok {
|
||||||
|
buf.Write(precisionBytes)
|
||||||
|
buf.WriteString(strconv.Itoa(precision))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune(verb)
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||||
|
// ensures that types for values which have been unpacked from an interface
|
||||||
|
// are displayed when the show types flag is also set.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface {
|
||||||
|
f.ignoreNextType = false
|
||||||
|
if !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (f *formatState) formatPtr(v reflect.Value) {
|
||||||
|
// Display nil if top level pointer is nil.
|
||||||
|
showTypes := f.fs.Flag('#')
|
||||||
|
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range f.pointers {
|
||||||
|
if depth >= f.depth {
|
||||||
|
delete(f.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to possibly show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by derferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f.pointers[addr] = f.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type or indirection level depending on flags.
|
||||||
|
if showTypes && !f.ignoreNextType {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
f.fs.Write([]byte(ve.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
} else {
|
||||||
|
if nilFound || cycleFound {
|
||||||
|
indirects += strings.Count(ve.Type().String(), "*")
|
||||||
|
}
|
||||||
|
f.fs.Write(openAngleBytes)
|
||||||
|
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||||
|
f.fs.Write(closeAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display pointer information depending on flags.
|
||||||
|
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(f.fs, addr)
|
||||||
|
}
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
switch {
|
||||||
|
case nilFound == true:
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound == true:
|
||||||
|
f.fs.Write(circularShortBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(ve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// format is the main workhorse for providing the Formatter interface. It
|
||||||
|
// uses the passed reflect value to figure out what kind of object we are
|
||||||
|
// dealing with and formats it appropriately. It is a recursive function,
|
||||||
|
// however circular data structures are detected and handled properly.
|
||||||
|
func (f *formatState) format(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
f.fs.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
f.formatPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write([]byte(v.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = false
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods
|
||||||
|
// flag is enabled.
|
||||||
|
if !f.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(f.fs, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(f.fs, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(f.fs, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(f.fs, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(f.fs, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(f.fs, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(f.fs, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
f.fs.Write(openBracketBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.Index(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBracketBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
f.fs.Write([]byte(v.String()))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fs.Write(openMapBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if f.cs.SortKeys {
|
||||||
|
sortValues(keys, f.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(key))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.MapIndex(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeMapBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
numFields := v.NumField()
|
||||||
|
f.fs.Write(openBraceBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||||
|
f.fs.Write([]byte(vtf.Name))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
}
|
||||||
|
f.format(f.unpackValue(v.Field(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(f.fs, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it if any get added.
|
||||||
|
default:
|
||||||
|
format := f.buildDefaultFormat()
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(f.fs, format, v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(f.fs, format, v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||||
|
// details.
|
||||||
|
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||||
|
f.fs = fs
|
||||||
|
|
||||||
|
// Use standard formatting for verbs that are not v.
|
||||||
|
if verb != 'v' {
|
||||||
|
format := f.constructOrigFormat(verb)
|
||||||
|
fmt.Fprintf(fs, format, f.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.value == nil {
|
||||||
|
if fs.Flag('#') {
|
||||||
|
fs.Write(interfaceBytes)
|
||||||
|
}
|
||||||
|
fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.format(reflect.ValueOf(f.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFormatter is a helper function to consolidate the logic from the various
|
||||||
|
// public methods which take varying config states.
|
||||||
|
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||||
|
fs := &formatState{value: v, cs: cs}
|
||||||
|
fs.pointers = make(map[uintptr]int)
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
Printf, Println, or Fprintf.
|
||||||
|
*/
|
||||||
|
func NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(&Config, v)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the spew package rather than than the spew_test
|
||||||
|
package because it needs access to internals to properly test certain cases
|
||||||
|
which are not possible via the public interface since they should never happen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dummyFmtState implements a fake fmt.State to use for testing invalid
|
||||||
|
// reflect.Value handling. This is necessary because the fmt package catches
|
||||||
|
// invalid values before invoking the formatter on them.
|
||||||
|
type dummyFmtState struct {
|
||||||
|
bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Flag(f int) bool {
|
||||||
|
if f == int('+') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Precision() (int, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Width() (int, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvalidReflectValue ensures the dump and formatter code handles an
|
||||||
|
// invalid reflect value properly. This needs access to internal state since it
|
||||||
|
// should never happen in real code and therefore can't be tested via the public
|
||||||
|
// API.
|
||||||
|
func TestInvalidReflectValue(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
|
||||||
|
// Dump invalid reflect value.
|
||||||
|
v := new(reflect.Value)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
d := dumpState{w: buf, cs: &Config}
|
||||||
|
d.dump(*v)
|
||||||
|
s := buf.String()
|
||||||
|
want := "<invalid>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter invalid reflect value.
|
||||||
|
buf2 := new(dummyFmtState)
|
||||||
|
f := formatState{value: *v, cs: &Config, fs: buf2}
|
||||||
|
f.format(*v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "<invalid>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortValues makes the internal sortValues function available to the test
|
||||||
|
// package.
|
||||||
|
func SortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
sortValues(values, cs)
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||||
|
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build !js,!appengine,!safe,!disableunsafe
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the spew package rather than than the spew_test
|
||||||
|
package because it needs access to internals to properly test certain cases
|
||||||
|
which are not possible via the public interface since they should never happen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
|
||||||
|
// the maximum kind value which does not exist. This is needed to test the
|
||||||
|
// fallback code which punts to the standard fmt library for new types that
|
||||||
|
// might get added to the language.
|
||||||
|
func changeKind(v *reflect.Value, readOnly bool) {
|
||||||
|
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
|
||||||
|
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
|
||||||
|
if readOnly {
|
||||||
|
*rvf |= flagRO
|
||||||
|
} else {
|
||||||
|
*rvf &= ^uintptr(flagRO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAddedReflectValue tests functionaly of the dump and formatter code which
|
||||||
|
// falls back to the standard fmt library for new types that might get added to
|
||||||
|
// the language.
|
||||||
|
func TestAddedReflectValue(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
|
||||||
|
// Dump using a reflect.Value that is exported.
|
||||||
|
v := reflect.ValueOf(int8(5))
|
||||||
|
changeKind(&v, false)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
d := dumpState{w: buf, cs: &Config}
|
||||||
|
d.dump(v)
|
||||||
|
s := buf.String()
|
||||||
|
want := "(int8) 5"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Dump using a reflect.Value that is not exported.
|
||||||
|
changeKind(&v, true)
|
||||||
|
buf.Reset()
|
||||||
|
d.dump(v)
|
||||||
|
s = buf.String()
|
||||||
|
want = "(int8) <int8 Value>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter using a reflect.Value that is exported.
|
||||||
|
changeKind(&v, false)
|
||||||
|
buf2 := new(dummyFmtState)
|
||||||
|
f := formatState{value: v, cs: &Config, fs: buf2}
|
||||||
|
f.format(v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "5"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter using a reflect.Value that is not exported.
|
||||||
|
changeKind(&v, true)
|
||||||
|
buf2.Reset()
|
||||||
|
f = formatState{value: v, cs: &Config, fs: buf2}
|
||||||
|
f.format(v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "<int8 Value>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the formatted string as a value that satisfies error. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a default spew Formatter interface.
|
||||||
|
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = NewFormatter(arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// spewFunc is used to identify which public function of the spew package or
|
||||||
|
// ConfigState a test applies to.
|
||||||
|
type spewFunc int
|
||||||
|
|
||||||
|
const (
|
||||||
|
fCSFdump spewFunc = iota
|
||||||
|
fCSFprint
|
||||||
|
fCSFprintf
|
||||||
|
fCSFprintln
|
||||||
|
fCSPrint
|
||||||
|
fCSPrintln
|
||||||
|
fCSSdump
|
||||||
|
fCSSprint
|
||||||
|
fCSSprintf
|
||||||
|
fCSSprintln
|
||||||
|
fCSErrorf
|
||||||
|
fCSNewFormatter
|
||||||
|
fErrorf
|
||||||
|
fFprint
|
||||||
|
fFprintln
|
||||||
|
fPrint
|
||||||
|
fPrintln
|
||||||
|
fSdump
|
||||||
|
fSprint
|
||||||
|
fSprintf
|
||||||
|
fSprintln
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of spewFunc values to names for pretty printing.
|
||||||
|
var spewFuncStrings = map[spewFunc]string{
|
||||||
|
fCSFdump: "ConfigState.Fdump",
|
||||||
|
fCSFprint: "ConfigState.Fprint",
|
||||||
|
fCSFprintf: "ConfigState.Fprintf",
|
||||||
|
fCSFprintln: "ConfigState.Fprintln",
|
||||||
|
fCSSdump: "ConfigState.Sdump",
|
||||||
|
fCSPrint: "ConfigState.Print",
|
||||||
|
fCSPrintln: "ConfigState.Println",
|
||||||
|
fCSSprint: "ConfigState.Sprint",
|
||||||
|
fCSSprintf: "ConfigState.Sprintf",
|
||||||
|
fCSSprintln: "ConfigState.Sprintln",
|
||||||
|
fCSErrorf: "ConfigState.Errorf",
|
||||||
|
fCSNewFormatter: "ConfigState.NewFormatter",
|
||||||
|
fErrorf: "spew.Errorf",
|
||||||
|
fFprint: "spew.Fprint",
|
||||||
|
fFprintln: "spew.Fprintln",
|
||||||
|
fPrint: "spew.Print",
|
||||||
|
fPrintln: "spew.Println",
|
||||||
|
fSdump: "spew.Sdump",
|
||||||
|
fSprint: "spew.Sprint",
|
||||||
|
fSprintf: "spew.Sprintf",
|
||||||
|
fSprintln: "spew.Sprintln",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f spewFunc) String() string {
|
||||||
|
if s, ok := spewFuncStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// spewTest is used to describe a test to be performed against the public
|
||||||
|
// functions of the spew package or ConfigState.
|
||||||
|
type spewTest struct {
|
||||||
|
cs *spew.ConfigState
|
||||||
|
f spewFunc
|
||||||
|
format string
|
||||||
|
in interface{}
|
||||||
|
want string
|
||||||
|
}
|
||||||
|
|
||||||
|
// spewTests houses the tests to be performed against the public functions of
|
||||||
|
// the spew package and ConfigState.
|
||||||
|
//
|
||||||
|
// These tests are only intended to ensure the public functions are exercised
|
||||||
|
// and are intentionally not exhaustive of types. The exhaustive type
|
||||||
|
// tests are handled in the dump and format tests.
|
||||||
|
var spewTests []spewTest
|
||||||
|
|
||||||
|
// redirStdout is a helper function to return the standard output from f as a
|
||||||
|
// byte slice.
|
||||||
|
func redirStdout(f func()) ([]byte, error) {
|
||||||
|
tempFile, err := ioutil.TempFile("", "ss-test")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fileName := tempFile.Name()
|
||||||
|
defer os.Remove(fileName) // Ignore error
|
||||||
|
|
||||||
|
origStdout := os.Stdout
|
||||||
|
os.Stdout = tempFile
|
||||||
|
f()
|
||||||
|
os.Stdout = origStdout
|
||||||
|
tempFile.Close()
|
||||||
|
|
||||||
|
return ioutil.ReadFile(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSpewTests() {
|
||||||
|
// Config states with various settings.
|
||||||
|
scsDefault := spew.NewDefaultConfig()
|
||||||
|
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||||
|
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
|
||||||
|
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
|
||||||
|
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
|
||||||
|
scsNoPtrAddr := &spew.ConfigState{DisablePointerAddresses: true}
|
||||||
|
scsNoCap := &spew.ConfigState{DisableCapacities: true}
|
||||||
|
|
||||||
|
// Variables for tests on types which implement Stringer interface with and
|
||||||
|
// without a pointer receiver.
|
||||||
|
ts := stringer("test")
|
||||||
|
tps := pstringer("test")
|
||||||
|
|
||||||
|
type ptrTester struct {
|
||||||
|
s *struct{}
|
||||||
|
}
|
||||||
|
tptr := &ptrTester{s: &struct{}{}}
|
||||||
|
|
||||||
|
// depthTester is used to test max depth handling for structs, array, slices
|
||||||
|
// and maps.
|
||||||
|
type depthTester struct {
|
||||||
|
ic indirCir1
|
||||||
|
arr [1]string
|
||||||
|
slice []string
|
||||||
|
m map[string]int
|
||||||
|
}
|
||||||
|
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
|
||||||
|
map[string]int{"one": 1}}
|
||||||
|
|
||||||
|
// Variable for tests on types which implement error interface.
|
||||||
|
te := customError(10)
|
||||||
|
|
||||||
|
spewTests = []spewTest{
|
||||||
|
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
|
||||||
|
{scsDefault, fCSFprint, "", int16(32767), "32767"},
|
||||||
|
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
|
||||||
|
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
|
||||||
|
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
|
||||||
|
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
|
||||||
|
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
|
||||||
|
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
|
||||||
|
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
|
||||||
|
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
|
||||||
|
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
|
||||||
|
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
|
||||||
|
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
|
||||||
|
{scsDefault, fFprint, "", float32(3.14), "3.14"},
|
||||||
|
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
|
||||||
|
{scsDefault, fPrint, "", true, "true"},
|
||||||
|
{scsDefault, fPrintln, "", false, "false\n"},
|
||||||
|
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
|
||||||
|
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
|
||||||
|
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
|
||||||
|
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
|
||||||
|
{scsNoMethods, fCSFprint, "", ts, "test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", tps, "test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", tps, "test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
|
||||||
|
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
|
||||||
|
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
|
||||||
|
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
|
||||||
|
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||||
|
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||||
|
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
|
||||||
|
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
|
||||||
|
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
|
||||||
|
"(len=4) (stringer test) \"test\"\n"},
|
||||||
|
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
|
||||||
|
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
|
||||||
|
"(error: 10) 10\n"},
|
||||||
|
{scsNoPtrAddr, fCSFprint, "", tptr, "<*>{<*>{}}"},
|
||||||
|
{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
|
||||||
|
{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
|
||||||
|
{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpew executes all of the tests described by spewTests.
|
||||||
|
func TestSpew(t *testing.T) {
|
||||||
|
initSpewTests()
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(spewTests))
|
||||||
|
for i, test := range spewTests {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
switch test.f {
|
||||||
|
case fCSFdump:
|
||||||
|
test.cs.Fdump(buf, test.in)
|
||||||
|
|
||||||
|
case fCSFprint:
|
||||||
|
test.cs.Fprint(buf, test.in)
|
||||||
|
|
||||||
|
case fCSFprintf:
|
||||||
|
test.cs.Fprintf(buf, test.format, test.in)
|
||||||
|
|
||||||
|
case fCSFprintln:
|
||||||
|
test.cs.Fprintln(buf, test.in)
|
||||||
|
|
||||||
|
case fCSPrint:
|
||||||
|
b, err := redirStdout(func() { test.cs.Print(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fCSPrintln:
|
||||||
|
b, err := redirStdout(func() { test.cs.Println(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fCSSdump:
|
||||||
|
str := test.cs.Sdump(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprint:
|
||||||
|
str := test.cs.Sprint(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprintf:
|
||||||
|
str := test.cs.Sprintf(test.format, test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprintln:
|
||||||
|
str := test.cs.Sprintln(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSErrorf:
|
||||||
|
err := test.cs.Errorf(test.format, test.in)
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
|
||||||
|
case fCSNewFormatter:
|
||||||
|
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
|
||||||
|
|
||||||
|
case fErrorf:
|
||||||
|
err := spew.Errorf(test.format, test.in)
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
|
||||||
|
case fFprint:
|
||||||
|
spew.Fprint(buf, test.in)
|
||||||
|
|
||||||
|
case fFprintln:
|
||||||
|
spew.Fprintln(buf, test.in)
|
||||||
|
|
||||||
|
case fPrint:
|
||||||
|
b, err := redirStdout(func() { spew.Print(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fPrintln:
|
||||||
|
b, err := redirStdout(func() { spew.Println(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fSdump:
|
||||||
|
str := spew.Sdump(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprint:
|
||||||
|
str := spew.Sprint(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprintf:
|
||||||
|
str := spew.Sprintf(test.format, test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprintln:
|
||||||
|
str := spew.Sprintln(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Errorf("%v #%d unrecognized function", test.f, i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := buf.String()
|
||||||
|
if test.want != s {
|
||||||
|
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||||
|
// command line. This code should really only be in the dumpcgo_test.go file,
|
||||||
|
// but unfortunately Go will not allow cgo in test files, so this is a
|
||||||
|
// workaround to allow cgo types to be tested. This configuration is used
|
||||||
|
// because spew itself does not require cgo to run even though it does handle
|
||||||
|
// certain cgo types specially. Rather than forcing all clients to require cgo
|
||||||
|
// and an external C compiler just to run the tests, this scheme makes them
|
||||||
|
// optional.
|
||||||
|
// +build cgo,testcgo
|
||||||
|
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef unsigned char custom_uchar_t;
|
||||||
|
|
||||||
|
char *ncp = 0;
|
||||||
|
char *cp = "test";
|
||||||
|
char ca[6] = {'t', 'e', 's', 't', '2', '\0'};
|
||||||
|
unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'};
|
||||||
|
signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'};
|
||||||
|
uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'};
|
||||||
|
custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'};
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// GetCgoNullCharPointer returns a null char pointer via cgo. This is only
|
||||||
|
// used for tests.
|
||||||
|
func GetCgoNullCharPointer() interface{} {
|
||||||
|
return C.ncp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoCharPointer returns a char pointer via cgo. This is only used for
|
||||||
|
// tests.
|
||||||
|
func GetCgoCharPointer() interface{} {
|
||||||
|
return C.cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoCharArray returns a char array via cgo and the array's len and cap.
|
||||||
|
// This is only used for tests.
|
||||||
|
func GetCgoCharArray() (interface{}, int, int) {
|
||||||
|
return C.ca, len(C.ca), cap(C.ca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
|
||||||
|
// array's len and cap. This is only used for tests.
|
||||||
|
func GetCgoUnsignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.uca, len(C.uca), cap(C.uca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
|
||||||
|
// and cap. This is only used for tests.
|
||||||
|
func GetCgoSignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.sca, len(C.sca), cap(C.sca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
|
||||||
|
// cap. This is only used for tests.
|
||||||
|
func GetCgoUint8tArray() (interface{}, int, int) {
|
||||||
|
return C.ui8ta, len(C.ui8ta), cap(C.ui8ta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
|
||||||
|
// cgo and the array's len and cap. This is only used for tests.
|
||||||
|
func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.tuca, len(C.tuca), cap(C.tuca)
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
# Golang CircleCI 2.0 configuration file
|
|
||||||
#
|
|
||||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
|
||||||
version: 2
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:1.9
|
|
||||||
|
|
||||||
working_directory: /go/src/github.com/google/gops
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
|
|
||||||
# specify any bash command here prefixed with `run: `
|
|
||||||
- run: go test -v ./...
|
|
|
@ -1,33 +0,0 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/kardianos/osext"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "ae77be60afb1dcacde03767a8c37337fad28ac14"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/keybase/go-ps"
|
|
||||||
packages = [".","darwincgo"]
|
|
||||||
revision = "668c8856d9992f97248b3177d45743d2cc1068db"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/arch"
|
|
||||||
packages = ["arm/armasm","ppc64/ppc64asm","x86/x86asm"]
|
|
||||||
revision = "f185940480d2c897e1ca8cf2f1be122e1258341b"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "rsc.io/goversion"
|
|
||||||
packages = ["version"]
|
|
||||||
revision = "84cd9d26d7efce780a7aaafd0944961371da741f"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "4b494828d559e89fc1fe70b9bfdd0f5c8ad62ee16258a3b2bcf1028c140faa32"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
|
@ -1,34 +0,0 @@
|
||||||
|
|
||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/kardianos/osext"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/keybase/go-ps"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/arch"
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2016 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,150 +0,0 @@
|
||||||
# gops
|
|
||||||
|
|
||||||
[![Build status](https://circleci.com/gh/google/gops/tree/master.svg?style=shield&circle-token=2637dc1e57d5407ae250480a86a2e553a7d20482)](https://circleci.com/gh/google/gops)
|
|
||||||
[![GoDoc](https://godoc.org/github.com/google/gops/agent?status.svg)](https://godoc.org/github.com/google/gops/agent)
|
|
||||||
|
|
||||||
gops is a command to list and diagnose Go processes currently running on your system.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ gops
|
|
||||||
983 uplink-soecks (/usr/local/bin/uplink-soecks)
|
|
||||||
52697 gops (/Users/jbd/bin/gops)
|
|
||||||
4132* foops (/Users/jbd/bin/foops)
|
|
||||||
51130 gocode (/Users/jbd/bin/gocode)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```
|
|
||||||
$ go get -u github.com/google/gops
|
|
||||||
```
|
|
||||||
|
|
||||||
## Diagnostics
|
|
||||||
|
|
||||||
For processes that starts the diagnostics agent, gops can report
|
|
||||||
additional information such as the current stack trace, Go version, memory
|
|
||||||
stats, etc.
|
|
||||||
|
|
||||||
In order to start the diagnostics agent, see the [hello example](https://github.com/google/gops/blob/master/examples/hello/main.go).
|
|
||||||
|
|
||||||
``` go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/gops/agent"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := agent.Listen(nil); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Hour)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual
|
|
||||||
|
|
||||||
It is possible to use gops tool both in local and remote mode.
|
|
||||||
|
|
||||||
Local mode requires that you start the target binary as the same user that runs gops binary.
|
|
||||||
To use gops in a remote mode you need to know target's agent address.
|
|
||||||
|
|
||||||
In Local mode use process's PID as a target; in Remote mode target is a `host:port` combination.
|
|
||||||
|
|
||||||
#### 0. Listing all processes running locally
|
|
||||||
|
|
||||||
To print all go processes, run `gops` without arguments:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ gops
|
|
||||||
983 uplink-soecks (/usr/local/bin/uplink-soecks)
|
|
||||||
52697 gops (/Users/jbd/bin/gops)
|
|
||||||
4132* foops (/Users/jbd/bin/foops)
|
|
||||||
51130 gocode (/Users/jbd/bin/gocode)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that processes running the agent are marked with `*` next to the PID (e.g. `4132*`).
|
|
||||||
|
|
||||||
#### $ gops stack (\<pid\>|\<addr\>)
|
|
||||||
|
|
||||||
In order to print the current stack trace from a target program, run the following command:
|
|
||||||
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ gops stack (<pid>|<addr>)
|
|
||||||
gops stack 85709
|
|
||||||
goroutine 8 [running]:
|
|
||||||
runtime/pprof.writeGoroutineStacks(0x13c7bc0, 0xc42000e008, 0xc420ec8520, 0xc420ec8520)
|
|
||||||
/Users/jbd/go/src/runtime/pprof/pprof.go:603 +0x79
|
|
||||||
runtime/pprof.writeGoroutine(0x13c7bc0, 0xc42000e008, 0x2, 0xc428f1c048, 0xc420ec8608)
|
|
||||||
/Users/jbd/go/src/runtime/pprof/pprof.go:592 +0x44
|
|
||||||
runtime/pprof.(*Profile).WriteTo(0x13eeda0, 0x13c7bc0, 0xc42000e008, 0x2, 0xc42000e008, 0x0)
|
|
||||||
/Users/jbd/go/src/runtime/pprof/pprof.go:302 +0x3b5
|
|
||||||
github.com/google/gops/agent.handle(0x13cd560, 0xc42000e008, 0xc420186000, 0x1, 0x1, 0x0, 0x0)
|
|
||||||
/Users/jbd/src/github.com/google/gops/agent/agent.go:150 +0x1b3
|
|
||||||
github.com/google/gops/agent.listen()
|
|
||||||
/Users/jbd/src/github.com/google/gops/agent/agent.go:113 +0x2b2
|
|
||||||
created by github.com/google/gops/agent.Listen
|
|
||||||
/Users/jbd/src/github.com/google/gops/agent/agent.go:94 +0x480
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
#### $ gops memstats (\<pid\>|\<addr\>)
|
|
||||||
|
|
||||||
To print the current memory stats, run the following command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ gops memstats (<pid>|<addr>)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### $ gops gc (\<pid\>|\<addr\>)
|
|
||||||
|
|
||||||
If you want to force run garbage collection on the target program, run `gc`.
|
|
||||||
It will block until the GC is completed.
|
|
||||||
|
|
||||||
|
|
||||||
#### $ gops version (\<pid\>|\<addr\>)
|
|
||||||
|
|
||||||
gops reports the Go version the target program is built with, if you run the following:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ gops version (<pid>|<addr>)
|
|
||||||
devel +6a3c6c0 Sat Jan 14 05:57:07 2017 +0000
|
|
||||||
```
|
|
||||||
|
|
||||||
#### $ gops stats (\<pid\>|\<addr\>)
|
|
||||||
|
|
||||||
To print the runtime statistics such as number of goroutines and `GOMAXPROCS`.
|
|
||||||
|
|
||||||
#### Profiling
|
|
||||||
|
|
||||||
|
|
||||||
##### Pprof
|
|
||||||
|
|
||||||
gops supports CPU and heap pprof profiles. After reading either heap or CPU profile,
|
|
||||||
it shells out to the `go tool pprof` and let you interatively examine the profiles.
|
|
||||||
|
|
||||||
To enter the CPU profile, run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ gops pprof-cpu (<pid>|<addr>)
|
|
||||||
```
|
|
||||||
|
|
||||||
To enter the heap profile, run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ gops pprof-heap (<pid>|<addr>)
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Execution trace
|
|
||||||
|
|
||||||
gops allows you to start the runtime tracer for 5 seconds and examine the results.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ gops trace (<pid>|<addr>)
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,185 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/google/gops/internal"
|
|
||||||
"github.com/google/gops/signal"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cmds = map[string](func(addr net.TCPAddr) error){
|
|
||||||
"stack": stackTrace,
|
|
||||||
"gc": gc,
|
|
||||||
"memstats": memStats,
|
|
||||||
"version": version,
|
|
||||||
"pprof-heap": pprofHeap,
|
|
||||||
"pprof-cpu": pprofCPU,
|
|
||||||
"stats": stats,
|
|
||||||
"trace": trace,
|
|
||||||
}
|
|
||||||
|
|
||||||
func stackTrace(addr net.TCPAddr) error {
|
|
||||||
return cmdWithPrint(addr, signal.StackTrace)
|
|
||||||
}
|
|
||||||
|
|
||||||
func gc(addr net.TCPAddr) error {
|
|
||||||
_, err := cmd(addr, signal.GC)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func memStats(addr net.TCPAddr) error {
|
|
||||||
return cmdWithPrint(addr, signal.MemStats)
|
|
||||||
}
|
|
||||||
|
|
||||||
func version(addr net.TCPAddr) error {
|
|
||||||
return cmdWithPrint(addr, signal.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pprofHeap(addr net.TCPAddr) error {
|
|
||||||
return pprof(addr, signal.HeapProfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pprofCPU(addr net.TCPAddr) error {
|
|
||||||
fmt.Println("Profiling CPU now, will take 30 secs...")
|
|
||||||
return pprof(addr, signal.CPUProfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func trace(addr net.TCPAddr) error {
|
|
||||||
fmt.Println("Tracing now, will take 5 secs...")
|
|
||||||
out, err := cmd(addr, signal.Trace)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(out) == 0 {
|
|
||||||
return errors.New("nothing has traced")
|
|
||||||
}
|
|
||||||
tmpfile, err := ioutil.TempFile("", "trace")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.Remove(tmpfile.Name())
|
|
||||||
if err := ioutil.WriteFile(tmpfile.Name(), out, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("Trace dump saved to: %s\n", tmpfile.Name())
|
|
||||||
cmd := exec.Command("go", "tool", "trace", tmpfile.Name())
|
|
||||||
cmd.Env = os.Environ()
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func pprof(addr net.TCPAddr, p byte) error {
|
|
||||||
|
|
||||||
tmpDumpFile, err := ioutil.TempFile("", "profile")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
{
|
|
||||||
out, err := cmd(addr, p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(out) == 0 {
|
|
||||||
return errors.New("failed to read the profile")
|
|
||||||
}
|
|
||||||
defer os.Remove(tmpDumpFile.Name())
|
|
||||||
if err := ioutil.WriteFile(tmpDumpFile.Name(), out, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Download running binary
|
|
||||||
tmpBinFile, err := ioutil.TempFile("", "binary")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
{
|
|
||||||
out, err := cmd(addr, signal.BinaryDump)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read the binary: %v", err)
|
|
||||||
}
|
|
||||||
if len(out) == 0 {
|
|
||||||
return errors.New("failed to read the binary")
|
|
||||||
}
|
|
||||||
defer os.Remove(tmpBinFile.Name())
|
|
||||||
if err := ioutil.WriteFile(tmpBinFile.Name(), out, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf("Profiling dump saved to: %s\n", tmpDumpFile.Name())
|
|
||||||
fmt.Printf("Binary file saved to: %s\n", tmpBinFile.Name())
|
|
||||||
cmd := exec.Command("go", "tool", "pprof", tmpBinFile.Name(), tmpDumpFile.Name())
|
|
||||||
cmd.Env = os.Environ()
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func stats(addr net.TCPAddr) error {
|
|
||||||
return cmdWithPrint(addr, signal.Stats)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cmdWithPrint(addr net.TCPAddr, c byte) error {
|
|
||||||
out, err := cmd(addr, c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("%s", out)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// targetToAddr tries to parse the target string, be it remote host:port
|
|
||||||
// or local process's PID.
|
|
||||||
func targetToAddr(target string) (*net.TCPAddr, error) {
|
|
||||||
if strings.Index(target, ":") != -1 {
|
|
||||||
// addr host:port passed
|
|
||||||
var err error
|
|
||||||
addr, err := net.ResolveTCPAddr("tcp", target)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't parse dst address: %v", err)
|
|
||||||
}
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
// try to find port by pid then, connect to local
|
|
||||||
pid, err := strconv.Atoi(target)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't parse PID: %v", err)
|
|
||||||
}
|
|
||||||
port, err := internal.GetPort(pid)
|
|
||||||
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:"+port)
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cmd(addr net.TCPAddr, c byte) ([]byte, error) {
|
|
||||||
conn, err := cmdLazy(addr, c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("couldn't get port by PID: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
all, err := ioutil.ReadAll(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return all, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cmdLazy(addr net.TCPAddr, c byte) (io.Reader, error) {
|
|
||||||
conn, err := net.DialTCP("tcp", nil, &addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := conn.Write([]byte{c}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/gops/agent"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := agent.Listen(nil); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Hour)
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package goprocess reports the Go processes running on a host.
|
|
||||||
package goprocess
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
goversion "rsc.io/goversion/version"
|
|
||||||
|
|
||||||
"github.com/google/gops/internal"
|
|
||||||
ps "github.com/keybase/go-ps"
|
|
||||||
)
|
|
||||||
|
|
||||||
// P represents a Go process.
|
|
||||||
type P struct {
|
|
||||||
PID int
|
|
||||||
PPID int
|
|
||||||
Exec string
|
|
||||||
Path string
|
|
||||||
BuildVersion string
|
|
||||||
Agent bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindAll returns all the Go processes currently running on this host.
|
|
||||||
func FindAll() []P {
|
|
||||||
var results []P
|
|
||||||
|
|
||||||
pss, err := ps.Processes()
|
|
||||||
if err != nil {
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(len(pss))
|
|
||||||
|
|
||||||
for _, pr := range pss {
|
|
||||||
pr := pr
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
path, version, agent, ok, err := isGo(pr)
|
|
||||||
if err != nil {
|
|
||||||
// TODO(jbd): Return a list of errors.
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
results = append(results, P{
|
|
||||||
PID: pr.Pid(),
|
|
||||||
PPID: pr.PPid(),
|
|
||||||
Exec: pr.Executable(),
|
|
||||||
Path: path,
|
|
||||||
BuildVersion: version,
|
|
||||||
Agent: agent,
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find finds info about the process identified with the given PID.
|
|
||||||
func Find(pid int) (p P, ok bool, err error) {
|
|
||||||
pr, err := ps.FindProcess(pid)
|
|
||||||
if err != nil {
|
|
||||||
return P{}, false, err
|
|
||||||
}
|
|
||||||
path, version, agent, ok, err := isGo(pr)
|
|
||||||
if !ok {
|
|
||||||
return P{}, false, nil
|
|
||||||
}
|
|
||||||
return P{
|
|
||||||
PID: pr.Pid(),
|
|
||||||
PPID: pr.PPid(),
|
|
||||||
Exec: pr.Executable(),
|
|
||||||
Path: path,
|
|
||||||
BuildVersion: version,
|
|
||||||
Agent: agent,
|
|
||||||
}, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isGo looks up the runtime.buildVersion symbol
|
|
||||||
// in the process' binary and determines if the process
|
|
||||||
// if a Go process or not. If the process is a Go process,
|
|
||||||
// it reports PID, binary name and full path of the binary.
|
|
||||||
func isGo(pr ps.Process) (path, version string, agent, ok bool, err error) {
|
|
||||||
if pr.Pid() == 0 {
|
|
||||||
// ignore system process
|
|
||||||
return
|
|
||||||
}
|
|
||||||
path, err = pr.Path()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var versionInfo goversion.Version
|
|
||||||
versionInfo, err = goversion.ReadExe(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ok = true
|
|
||||||
version = versionInfo.Release
|
|
||||||
pidfile, err := internal.PIDFile(pr.Pid())
|
|
||||||
if err == nil {
|
|
||||||
_, err := os.Stat(pidfile)
|
|
||||||
agent = err == nil
|
|
||||||
}
|
|
||||||
return path, version, agent, ok, nil
|
|
||||||
}
|
|
|
@ -1,600 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package dwarf generates DWARF debugging information.
|
|
||||||
// DWARF generation is split between the compiler and the linker,
|
|
||||||
// this package contains the shared code.
|
|
||||||
package dwarf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InfoPrefix is the prefix for all the symbols containing DWARF info entries.
|
|
||||||
const InfoPrefix = "go.info."
|
|
||||||
|
|
||||||
// Sym represents a symbol.
|
|
||||||
type Sym interface {
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Var represents a local variable or a function parameter.
|
|
||||||
type Var struct {
|
|
||||||
Name string
|
|
||||||
Abbrev int // Either DW_ABRV_AUTO or DW_ABRV_PARAM
|
|
||||||
Offset int32
|
|
||||||
Type Sym
|
|
||||||
Link *Var
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Context specifies how to add data to a Sym.
|
|
||||||
type Context interface {
|
|
||||||
PtrSize() int
|
|
||||||
AddInt(s Sym, size int, i int64)
|
|
||||||
AddBytes(s Sym, b []byte)
|
|
||||||
AddAddress(s Sym, t interface{}, ofs int64)
|
|
||||||
AddSectionOffset(s Sym, size int, t interface{}, ofs int64)
|
|
||||||
AddString(s Sym, v string)
|
|
||||||
SymValue(s Sym) int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendUleb128 appends v to b using DWARF's unsigned LEB128 encoding.
|
|
||||||
func AppendUleb128(b []byte, v uint64) []byte {
|
|
||||||
for {
|
|
||||||
c := uint8(v & 0x7f)
|
|
||||||
v >>= 7
|
|
||||||
if v != 0 {
|
|
||||||
c |= 0x80
|
|
||||||
}
|
|
||||||
b = append(b, c)
|
|
||||||
if c&0x80 == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendSleb128 appends v to b using DWARF's signed LEB128 encoding.
|
|
||||||
func AppendSleb128(b []byte, v int64) []byte {
|
|
||||||
for {
|
|
||||||
c := uint8(v & 0x7f)
|
|
||||||
s := uint8(v & 0x40)
|
|
||||||
v >>= 7
|
|
||||||
if (v != -1 || s == 0) && (v != 0 || s != 0) {
|
|
||||||
c |= 0x80
|
|
||||||
}
|
|
||||||
b = append(b, c)
|
|
||||||
if c&0x80 == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
var encbuf [20]byte
|
|
||||||
|
|
||||||
// AppendUleb128 appends v to s using DWARF's unsigned LEB128 encoding.
|
|
||||||
func Uleb128put(ctxt Context, s Sym, v int64) {
|
|
||||||
b := AppendUleb128(encbuf[:0], uint64(v))
|
|
||||||
ctxt.AddBytes(s, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendUleb128 appends v to s using DWARF's signed LEB128 encoding.
|
|
||||||
func Sleb128put(ctxt Context, s Sym, v int64) {
|
|
||||||
b := AppendSleb128(encbuf[:0], v)
|
|
||||||
ctxt.AddBytes(s, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Defining Abbrevs. This is hardcoded, and there will be
|
|
||||||
* only a handful of them. The DWARF spec places no restriction on
|
|
||||||
* the ordering of attributes in the Abbrevs and DIEs, and we will
|
|
||||||
* always write them out in the order of declaration in the abbrev.
|
|
||||||
*/
|
|
||||||
type dwAttrForm struct {
|
|
||||||
attr uint16
|
|
||||||
form uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go-specific type attributes.
|
|
||||||
const (
|
|
||||||
DW_AT_go_kind = 0x2900
|
|
||||||
DW_AT_go_key = 0x2901
|
|
||||||
DW_AT_go_elem = 0x2902
|
|
||||||
|
|
||||||
DW_AT_internal_location = 253 // params and locals; not emitted
|
|
||||||
)
|
|
||||||
|
|
||||||
// Index into the abbrevs table below.
|
|
||||||
// Keep in sync with ispubname() and ispubtype() below.
|
|
||||||
// ispubtype considers >= NULLTYPE public
|
|
||||||
const (
|
|
||||||
DW_ABRV_NULL = iota
|
|
||||||
DW_ABRV_COMPUNIT
|
|
||||||
DW_ABRV_FUNCTION
|
|
||||||
DW_ABRV_VARIABLE
|
|
||||||
DW_ABRV_AUTO
|
|
||||||
DW_ABRV_PARAM
|
|
||||||
DW_ABRV_STRUCTFIELD
|
|
||||||
DW_ABRV_FUNCTYPEPARAM
|
|
||||||
DW_ABRV_DOTDOTDOT
|
|
||||||
DW_ABRV_ARRAYRANGE
|
|
||||||
DW_ABRV_NULLTYPE
|
|
||||||
DW_ABRV_BASETYPE
|
|
||||||
DW_ABRV_ARRAYTYPE
|
|
||||||
DW_ABRV_CHANTYPE
|
|
||||||
DW_ABRV_FUNCTYPE
|
|
||||||
DW_ABRV_IFACETYPE
|
|
||||||
DW_ABRV_MAPTYPE
|
|
||||||
DW_ABRV_PTRTYPE
|
|
||||||
DW_ABRV_BARE_PTRTYPE // only for void*, no DW_AT_type attr to please gdb 6.
|
|
||||||
DW_ABRV_SLICETYPE
|
|
||||||
DW_ABRV_STRINGTYPE
|
|
||||||
DW_ABRV_STRUCTTYPE
|
|
||||||
DW_ABRV_TYPEDECL
|
|
||||||
DW_NABRV
|
|
||||||
)
|
|
||||||
|
|
||||||
type dwAbbrev struct {
|
|
||||||
tag uint8
|
|
||||||
children uint8
|
|
||||||
attr []dwAttrForm
|
|
||||||
}
|
|
||||||
|
|
||||||
var abbrevs = [DW_NABRV]dwAbbrev{
|
|
||||||
/* The mandatory DW_ABRV_NULL entry. */
|
|
||||||
{0, 0, []dwAttrForm{}},
|
|
||||||
|
|
||||||
/* COMPUNIT */
|
|
||||||
{
|
|
||||||
DW_TAG_compile_unit,
|
|
||||||
DW_CHILDREN_yes,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_language, DW_FORM_data1},
|
|
||||||
{DW_AT_low_pc, DW_FORM_addr},
|
|
||||||
{DW_AT_high_pc, DW_FORM_addr},
|
|
||||||
{DW_AT_stmt_list, DW_FORM_data4},
|
|
||||||
{DW_AT_comp_dir, DW_FORM_string},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* FUNCTION */
|
|
||||||
{
|
|
||||||
DW_TAG_subprogram,
|
|
||||||
DW_CHILDREN_yes,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_low_pc, DW_FORM_addr},
|
|
||||||
{DW_AT_high_pc, DW_FORM_addr},
|
|
||||||
{DW_AT_external, DW_FORM_flag},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* VARIABLE */
|
|
||||||
{
|
|
||||||
DW_TAG_variable,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_location, DW_FORM_block1},
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
{DW_AT_external, DW_FORM_flag},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* AUTO */
|
|
||||||
{
|
|
||||||
DW_TAG_variable,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_location, DW_FORM_block1},
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* PARAM */
|
|
||||||
{
|
|
||||||
DW_TAG_formal_parameter,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_location, DW_FORM_block1},
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* STRUCTFIELD */
|
|
||||||
{
|
|
||||||
DW_TAG_member,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_data_member_location, DW_FORM_block1},
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* FUNCTYPEPARAM */
|
|
||||||
{
|
|
||||||
DW_TAG_formal_parameter,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
|
|
||||||
// No name!
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* DOTDOTDOT */
|
|
||||||
{
|
|
||||||
DW_TAG_unspecified_parameters,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* ARRAYRANGE */
|
|
||||||
{
|
|
||||||
DW_TAG_subrange_type,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
|
|
||||||
// No name!
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
{DW_AT_count, DW_FORM_udata},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Below here are the types considered public by ispubtype
|
|
||||||
/* NULLTYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_unspecified_type,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* BASETYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_base_type,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_encoding, DW_FORM_data1},
|
|
||||||
{DW_AT_byte_size, DW_FORM_data1},
|
|
||||||
{DW_AT_go_kind, DW_FORM_data1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* ARRAYTYPE */
|
|
||||||
// child is subrange with upper bound
|
|
||||||
{
|
|
||||||
DW_TAG_array_type,
|
|
||||||
DW_CHILDREN_yes,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
{DW_AT_byte_size, DW_FORM_udata},
|
|
||||||
{DW_AT_go_kind, DW_FORM_data1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* CHANTYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_typedef,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
{DW_AT_go_kind, DW_FORM_data1},
|
|
||||||
{DW_AT_go_elem, DW_FORM_ref_addr},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* FUNCTYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_subroutine_type,
|
|
||||||
DW_CHILDREN_yes,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
// {DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
{DW_AT_go_kind, DW_FORM_data1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* IFACETYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_typedef,
|
|
||||||
DW_CHILDREN_yes,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
{DW_AT_go_kind, DW_FORM_data1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* MAPTYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_typedef,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
{DW_AT_go_kind, DW_FORM_data1},
|
|
||||||
{DW_AT_go_key, DW_FORM_ref_addr},
|
|
||||||
{DW_AT_go_elem, DW_FORM_ref_addr},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* PTRTYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_pointer_type,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
{DW_AT_go_kind, DW_FORM_data1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* BARE_PTRTYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_pointer_type,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* SLICETYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_structure_type,
|
|
||||||
DW_CHILDREN_yes,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_byte_size, DW_FORM_udata},
|
|
||||||
{DW_AT_go_kind, DW_FORM_data1},
|
|
||||||
{DW_AT_go_elem, DW_FORM_ref_addr},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* STRINGTYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_structure_type,
|
|
||||||
DW_CHILDREN_yes,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_byte_size, DW_FORM_udata},
|
|
||||||
{DW_AT_go_kind, DW_FORM_data1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* STRUCTTYPE */
|
|
||||||
{
|
|
||||||
DW_TAG_structure_type,
|
|
||||||
DW_CHILDREN_yes,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_byte_size, DW_FORM_udata},
|
|
||||||
{DW_AT_go_kind, DW_FORM_data1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/* TYPEDECL */
|
|
||||||
{
|
|
||||||
DW_TAG_typedef,
|
|
||||||
DW_CHILDREN_no,
|
|
||||||
[]dwAttrForm{
|
|
||||||
{DW_AT_name, DW_FORM_string},
|
|
||||||
{DW_AT_type, DW_FORM_ref_addr},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAbbrev returns the contents of the .debug_abbrev section.
|
|
||||||
func GetAbbrev() []byte {
|
|
||||||
var buf []byte
|
|
||||||
for i := 1; i < DW_NABRV; i++ {
|
|
||||||
// See section 7.5.3
|
|
||||||
buf = AppendUleb128(buf, uint64(i))
|
|
||||||
|
|
||||||
buf = AppendUleb128(buf, uint64(abbrevs[i].tag))
|
|
||||||
buf = append(buf, byte(abbrevs[i].children))
|
|
||||||
for _, f := range abbrevs[i].attr {
|
|
||||||
buf = AppendUleb128(buf, uint64(f.attr))
|
|
||||||
buf = AppendUleb128(buf, uint64(f.form))
|
|
||||||
}
|
|
||||||
buf = append(buf, 0, 0)
|
|
||||||
}
|
|
||||||
return append(buf, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Debugging Information Entries and their attributes.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// DWAttr represents an attribute of a DWDie.
|
|
||||||
//
|
|
||||||
// For DW_CLS_string and _block, value should contain the length, and
|
|
||||||
// data the data, for _reference, value is 0 and data is a DWDie* to
|
|
||||||
// the referenced instance, for all others, value is the whole thing
|
|
||||||
// and data is null.
|
|
||||||
type DWAttr struct {
|
|
||||||
Link *DWAttr
|
|
||||||
Atr uint16 // DW_AT_
|
|
||||||
Cls uint8 // DW_CLS_
|
|
||||||
Value int64
|
|
||||||
Data interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DWDie represents a DWARF debug info entry.
|
|
||||||
type DWDie struct {
|
|
||||||
Abbrev int
|
|
||||||
Link *DWDie
|
|
||||||
Child *DWDie
|
|
||||||
Attr *DWAttr
|
|
||||||
Sym Sym
|
|
||||||
}
|
|
||||||
|
|
||||||
func putattr(ctxt Context, s Sym, abbrev int, form int, cls int, value int64, data interface{}) error {
|
|
||||||
switch form {
|
|
||||||
case DW_FORM_addr: // address
|
|
||||||
ctxt.AddAddress(s, data, value)
|
|
||||||
|
|
||||||
case DW_FORM_block1: // block
|
|
||||||
if cls == DW_CLS_ADDRESS {
|
|
||||||
ctxt.AddInt(s, 1, int64(1+ctxt.PtrSize()))
|
|
||||||
ctxt.AddInt(s, 1, DW_OP_addr)
|
|
||||||
ctxt.AddAddress(s, data, 0)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
value &= 0xff
|
|
||||||
ctxt.AddInt(s, 1, value)
|
|
||||||
p := data.([]byte)[:value]
|
|
||||||
ctxt.AddBytes(s, p)
|
|
||||||
|
|
||||||
case DW_FORM_block2: // block
|
|
||||||
value &= 0xffff
|
|
||||||
|
|
||||||
ctxt.AddInt(s, 2, value)
|
|
||||||
p := data.([]byte)[:value]
|
|
||||||
ctxt.AddBytes(s, p)
|
|
||||||
|
|
||||||
case DW_FORM_block4: // block
|
|
||||||
value &= 0xffffffff
|
|
||||||
|
|
||||||
ctxt.AddInt(s, 4, value)
|
|
||||||
p := data.([]byte)[:value]
|
|
||||||
ctxt.AddBytes(s, p)
|
|
||||||
|
|
||||||
case DW_FORM_block: // block
|
|
||||||
Uleb128put(ctxt, s, value)
|
|
||||||
|
|
||||||
p := data.([]byte)[:value]
|
|
||||||
ctxt.AddBytes(s, p)
|
|
||||||
|
|
||||||
case DW_FORM_data1: // constant
|
|
||||||
ctxt.AddInt(s, 1, value)
|
|
||||||
|
|
||||||
case DW_FORM_data2: // constant
|
|
||||||
ctxt.AddInt(s, 2, value)
|
|
||||||
|
|
||||||
case DW_FORM_data4: // constant, {line,loclist,mac,rangelist}ptr
|
|
||||||
if cls == DW_CLS_PTR { // DW_AT_stmt_list
|
|
||||||
ctxt.AddSectionOffset(s, 4, data, 0)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ctxt.AddInt(s, 4, value)
|
|
||||||
|
|
||||||
case DW_FORM_data8: // constant, {line,loclist,mac,rangelist}ptr
|
|
||||||
ctxt.AddInt(s, 8, value)
|
|
||||||
|
|
||||||
case DW_FORM_sdata: // constant
|
|
||||||
Sleb128put(ctxt, s, value)
|
|
||||||
|
|
||||||
case DW_FORM_udata: // constant
|
|
||||||
Uleb128put(ctxt, s, value)
|
|
||||||
|
|
||||||
case DW_FORM_string: // string
|
|
||||||
str := data.(string)
|
|
||||||
ctxt.AddString(s, str)
|
|
||||||
// TODO(ribrdb): verify padded strings are never used and remove this
|
|
||||||
for i := int64(len(str)); i < value; i++ {
|
|
||||||
ctxt.AddInt(s, 1, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
case DW_FORM_flag: // flag
|
|
||||||
if value != 0 {
|
|
||||||
ctxt.AddInt(s, 1, 1)
|
|
||||||
} else {
|
|
||||||
ctxt.AddInt(s, 1, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// In DWARF 2 (which is what we claim to generate),
|
|
||||||
// the ref_addr is the same size as a normal address.
|
|
||||||
// In DWARF 3 it is always 32 bits, unless emitting a large
|
|
||||||
// (> 4 GB of debug info aka "64-bit") unit, which we don't implement.
|
|
||||||
case DW_FORM_ref_addr: // reference to a DIE in the .info section
|
|
||||||
if data == nil {
|
|
||||||
return fmt.Errorf("dwarf: null reference in %d", abbrev)
|
|
||||||
} else {
|
|
||||||
ctxt.AddSectionOffset(s, ctxt.PtrSize(), data, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
case DW_FORM_ref1, // reference within the compilation unit
|
|
||||||
DW_FORM_ref2, // reference
|
|
||||||
DW_FORM_ref4, // reference
|
|
||||||
DW_FORM_ref8, // reference
|
|
||||||
DW_FORM_ref_udata, // reference
|
|
||||||
|
|
||||||
DW_FORM_strp, // string
|
|
||||||
DW_FORM_indirect: // (see Section 7.5.3)
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("dwarf: unsupported attribute form %d / class %d", form, cls)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutAttrs writes the attributes for a DIE to symbol 's'.
|
|
||||||
//
|
|
||||||
// Note that we can (and do) add arbitrary attributes to a DIE, but
|
|
||||||
// only the ones actually listed in the Abbrev will be written out.
|
|
||||||
func PutAttrs(ctxt Context, s Sym, abbrev int, attr *DWAttr) {
|
|
||||||
Outer:
|
|
||||||
for _, f := range abbrevs[abbrev].attr {
|
|
||||||
for ap := attr; ap != nil; ap = ap.Link {
|
|
||||||
if ap.Atr == f.attr {
|
|
||||||
putattr(ctxt, s, abbrev, int(f.form), int(ap.Cls), ap.Value, ap.Data)
|
|
||||||
continue Outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
putattr(ctxt, s, abbrev, int(f.form), 0, 0, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasChildren returns true if 'die' uses an abbrev that supports children.
|
|
||||||
func HasChildren(die *DWDie) bool {
|
|
||||||
return abbrevs[die.Abbrev].children != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutFunc writes a DIE for a function to s.
|
|
||||||
// It also writes child DIEs for each variable in vars.
|
|
||||||
func PutFunc(ctxt Context, s Sym, name string, external bool, startPC Sym, size int64, vars *Var) {
|
|
||||||
Uleb128put(ctxt, s, DW_ABRV_FUNCTION)
|
|
||||||
putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_string, DW_CLS_STRING, int64(len(name)), name)
|
|
||||||
putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, 0, startPC)
|
|
||||||
putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, size+ctxt.SymValue(startPC), startPC)
|
|
||||||
var ev int64
|
|
||||||
if external {
|
|
||||||
ev = 1
|
|
||||||
}
|
|
||||||
putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_flag, DW_CLS_FLAG, ev, 0)
|
|
||||||
names := make(map[string]bool)
|
|
||||||
for v := vars; v != nil; v = v.Link {
|
|
||||||
var n string
|
|
||||||
if names[v.Name] {
|
|
||||||
n = fmt.Sprintf("%s#%d", v.Name, len(names))
|
|
||||||
} else {
|
|
||||||
n = v.Name
|
|
||||||
}
|
|
||||||
names[n] = true
|
|
||||||
|
|
||||||
Uleb128put(ctxt, s, int64(v.Abbrev))
|
|
||||||
putattr(ctxt, s, v.Abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n)
|
|
||||||
loc := append(encbuf[:0], DW_OP_call_frame_cfa)
|
|
||||||
if v.Offset != 0 {
|
|
||||||
loc = append(loc, DW_OP_consts)
|
|
||||||
loc = AppendSleb128(loc, int64(v.Offset))
|
|
||||||
loc = append(loc, DW_OP_plus)
|
|
||||||
}
|
|
||||||
putattr(ctxt, s, v.Abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc)
|
|
||||||
putattr(ctxt, s, v.Abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type)
|
|
||||||
|
|
||||||
}
|
|
||||||
Uleb128put(ctxt, s, 0)
|
|
||||||
}
|
|
|
@ -1,483 +0,0 @@
|
||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package dwarf
|
|
||||||
|
|
||||||
// Cut, pasted, tr-and-awk'ed from tables in
|
|
||||||
// http://dwarfstd.org/doc/Dwarf3.pdf
|
|
||||||
|
|
||||||
// Table 18
|
|
||||||
const (
|
|
||||||
DW_TAG_array_type = 0x01
|
|
||||||
DW_TAG_class_type = 0x02
|
|
||||||
DW_TAG_entry_point = 0x03
|
|
||||||
DW_TAG_enumeration_type = 0x04
|
|
||||||
DW_TAG_formal_parameter = 0x05
|
|
||||||
DW_TAG_imported_declaration = 0x08
|
|
||||||
DW_TAG_label = 0x0a
|
|
||||||
DW_TAG_lexical_block = 0x0b
|
|
||||||
DW_TAG_member = 0x0d
|
|
||||||
DW_TAG_pointer_type = 0x0f
|
|
||||||
DW_TAG_reference_type = 0x10
|
|
||||||
DW_TAG_compile_unit = 0x11
|
|
||||||
DW_TAG_string_type = 0x12
|
|
||||||
DW_TAG_structure_type = 0x13
|
|
||||||
DW_TAG_subroutine_type = 0x15
|
|
||||||
DW_TAG_typedef = 0x16
|
|
||||||
DW_TAG_union_type = 0x17
|
|
||||||
DW_TAG_unspecified_parameters = 0x18
|
|
||||||
DW_TAG_variant = 0x19
|
|
||||||
DW_TAG_common_block = 0x1a
|
|
||||||
DW_TAG_common_inclusion = 0x1b
|
|
||||||
DW_TAG_inheritance = 0x1c
|
|
||||||
DW_TAG_inlined_subroutine = 0x1d
|
|
||||||
DW_TAG_module = 0x1e
|
|
||||||
DW_TAG_ptr_to_member_type = 0x1f
|
|
||||||
DW_TAG_set_type = 0x20
|
|
||||||
DW_TAG_subrange_type = 0x21
|
|
||||||
DW_TAG_with_stmt = 0x22
|
|
||||||
DW_TAG_access_declaration = 0x23
|
|
||||||
DW_TAG_base_type = 0x24
|
|
||||||
DW_TAG_catch_block = 0x25
|
|
||||||
DW_TAG_const_type = 0x26
|
|
||||||
DW_TAG_constant = 0x27
|
|
||||||
DW_TAG_enumerator = 0x28
|
|
||||||
DW_TAG_file_type = 0x29
|
|
||||||
DW_TAG_friend = 0x2a
|
|
||||||
DW_TAG_namelist = 0x2b
|
|
||||||
DW_TAG_namelist_item = 0x2c
|
|
||||||
DW_TAG_packed_type = 0x2d
|
|
||||||
DW_TAG_subprogram = 0x2e
|
|
||||||
DW_TAG_template_type_parameter = 0x2f
|
|
||||||
DW_TAG_template_value_parameter = 0x30
|
|
||||||
DW_TAG_thrown_type = 0x31
|
|
||||||
DW_TAG_try_block = 0x32
|
|
||||||
DW_TAG_variant_part = 0x33
|
|
||||||
DW_TAG_variable = 0x34
|
|
||||||
DW_TAG_volatile_type = 0x35
|
|
||||||
// Dwarf3
|
|
||||||
DW_TAG_dwarf_procedure = 0x36
|
|
||||||
DW_TAG_restrict_type = 0x37
|
|
||||||
DW_TAG_interface_type = 0x38
|
|
||||||
DW_TAG_namespace = 0x39
|
|
||||||
DW_TAG_imported_module = 0x3a
|
|
||||||
DW_TAG_unspecified_type = 0x3b
|
|
||||||
DW_TAG_partial_unit = 0x3c
|
|
||||||
DW_TAG_imported_unit = 0x3d
|
|
||||||
DW_TAG_condition = 0x3f
|
|
||||||
DW_TAG_shared_type = 0x40
|
|
||||||
// Dwarf4
|
|
||||||
DW_TAG_type_unit = 0x41
|
|
||||||
DW_TAG_rvalue_reference_type = 0x42
|
|
||||||
DW_TAG_template_alias = 0x43
|
|
||||||
|
|
||||||
// User defined
|
|
||||||
DW_TAG_lo_user = 0x4080
|
|
||||||
DW_TAG_hi_user = 0xffff
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 19
|
|
||||||
const (
|
|
||||||
DW_CHILDREN_no = 0x00
|
|
||||||
DW_CHILDREN_yes = 0x01
|
|
||||||
)
|
|
||||||
|
|
||||||
// Not from the spec, but logically belongs here
|
|
||||||
const (
|
|
||||||
DW_CLS_ADDRESS = 0x01 + iota
|
|
||||||
DW_CLS_BLOCK
|
|
||||||
DW_CLS_CONSTANT
|
|
||||||
DW_CLS_FLAG
|
|
||||||
DW_CLS_PTR // lineptr, loclistptr, macptr, rangelistptr
|
|
||||||
DW_CLS_REFERENCE
|
|
||||||
DW_CLS_ADDRLOC
|
|
||||||
DW_CLS_STRING
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 20
|
|
||||||
const (
|
|
||||||
DW_AT_sibling = 0x01 // reference
|
|
||||||
DW_AT_location = 0x02 // block, loclistptr
|
|
||||||
DW_AT_name = 0x03 // string
|
|
||||||
DW_AT_ordering = 0x09 // constant
|
|
||||||
DW_AT_byte_size = 0x0b // block, constant, reference
|
|
||||||
DW_AT_bit_offset = 0x0c // block, constant, reference
|
|
||||||
DW_AT_bit_size = 0x0d // block, constant, reference
|
|
||||||
DW_AT_stmt_list = 0x10 // lineptr
|
|
||||||
DW_AT_low_pc = 0x11 // address
|
|
||||||
DW_AT_high_pc = 0x12 // address
|
|
||||||
DW_AT_language = 0x13 // constant
|
|
||||||
DW_AT_discr = 0x15 // reference
|
|
||||||
DW_AT_discr_value = 0x16 // constant
|
|
||||||
DW_AT_visibility = 0x17 // constant
|
|
||||||
DW_AT_import = 0x18 // reference
|
|
||||||
DW_AT_string_length = 0x19 // block, loclistptr
|
|
||||||
DW_AT_common_reference = 0x1a // reference
|
|
||||||
DW_AT_comp_dir = 0x1b // string
|
|
||||||
DW_AT_const_value = 0x1c // block, constant, string
|
|
||||||
DW_AT_containing_type = 0x1d // reference
|
|
||||||
DW_AT_default_value = 0x1e // reference
|
|
||||||
DW_AT_inline = 0x20 // constant
|
|
||||||
DW_AT_is_optional = 0x21 // flag
|
|
||||||
DW_AT_lower_bound = 0x22 // block, constant, reference
|
|
||||||
DW_AT_producer = 0x25 // string
|
|
||||||
DW_AT_prototyped = 0x27 // flag
|
|
||||||
DW_AT_return_addr = 0x2a // block, loclistptr
|
|
||||||
DW_AT_start_scope = 0x2c // constant
|
|
||||||
DW_AT_bit_stride = 0x2e // constant
|
|
||||||
DW_AT_upper_bound = 0x2f // block, constant, reference
|
|
||||||
DW_AT_abstract_origin = 0x31 // reference
|
|
||||||
DW_AT_accessibility = 0x32 // constant
|
|
||||||
DW_AT_address_class = 0x33 // constant
|
|
||||||
DW_AT_artificial = 0x34 // flag
|
|
||||||
DW_AT_base_types = 0x35 // reference
|
|
||||||
DW_AT_calling_convention = 0x36 // constant
|
|
||||||
DW_AT_count = 0x37 // block, constant, reference
|
|
||||||
DW_AT_data_member_location = 0x38 // block, constant, loclistptr
|
|
||||||
DW_AT_decl_column = 0x39 // constant
|
|
||||||
DW_AT_decl_file = 0x3a // constant
|
|
||||||
DW_AT_decl_line = 0x3b // constant
|
|
||||||
DW_AT_declaration = 0x3c // flag
|
|
||||||
DW_AT_discr_list = 0x3d // block
|
|
||||||
DW_AT_encoding = 0x3e // constant
|
|
||||||
DW_AT_external = 0x3f // flag
|
|
||||||
DW_AT_frame_base = 0x40 // block, loclistptr
|
|
||||||
DW_AT_friend = 0x41 // reference
|
|
||||||
DW_AT_identifier_case = 0x42 // constant
|
|
||||||
DW_AT_macro_info = 0x43 // macptr
|
|
||||||
DW_AT_namelist_item = 0x44 // block
|
|
||||||
DW_AT_priority = 0x45 // reference
|
|
||||||
DW_AT_segment = 0x46 // block, loclistptr
|
|
||||||
DW_AT_specification = 0x47 // reference
|
|
||||||
DW_AT_static_link = 0x48 // block, loclistptr
|
|
||||||
DW_AT_type = 0x49 // reference
|
|
||||||
DW_AT_use_location = 0x4a // block, loclistptr
|
|
||||||
DW_AT_variable_parameter = 0x4b // flag
|
|
||||||
DW_AT_virtuality = 0x4c // constant
|
|
||||||
DW_AT_vtable_elem_location = 0x4d // block, loclistptr
|
|
||||||
// Dwarf3
|
|
||||||
DW_AT_allocated = 0x4e // block, constant, reference
|
|
||||||
DW_AT_associated = 0x4f // block, constant, reference
|
|
||||||
DW_AT_data_location = 0x50 // block
|
|
||||||
DW_AT_byte_stride = 0x51 // block, constant, reference
|
|
||||||
DW_AT_entry_pc = 0x52 // address
|
|
||||||
DW_AT_use_UTF8 = 0x53 // flag
|
|
||||||
DW_AT_extension = 0x54 // reference
|
|
||||||
DW_AT_ranges = 0x55 // rangelistptr
|
|
||||||
DW_AT_trampoline = 0x56 // address, flag, reference, string
|
|
||||||
DW_AT_call_column = 0x57 // constant
|
|
||||||
DW_AT_call_file = 0x58 // constant
|
|
||||||
DW_AT_call_line = 0x59 // constant
|
|
||||||
DW_AT_description = 0x5a // string
|
|
||||||
DW_AT_binary_scale = 0x5b // constant
|
|
||||||
DW_AT_decimal_scale = 0x5c // constant
|
|
||||||
DW_AT_small = 0x5d // reference
|
|
||||||
DW_AT_decimal_sign = 0x5e // constant
|
|
||||||
DW_AT_digit_count = 0x5f // constant
|
|
||||||
DW_AT_picture_string = 0x60 // string
|
|
||||||
DW_AT_mutable = 0x61 // flag
|
|
||||||
DW_AT_threads_scaled = 0x62 // flag
|
|
||||||
DW_AT_explicit = 0x63 // flag
|
|
||||||
DW_AT_object_pointer = 0x64 // reference
|
|
||||||
DW_AT_endianity = 0x65 // constant
|
|
||||||
DW_AT_elemental = 0x66 // flag
|
|
||||||
DW_AT_pure = 0x67 // flag
|
|
||||||
DW_AT_recursive = 0x68 // flag
|
|
||||||
|
|
||||||
DW_AT_lo_user = 0x2000 // ---
|
|
||||||
DW_AT_hi_user = 0x3fff // ---
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 21
|
|
||||||
const (
|
|
||||||
DW_FORM_addr = 0x01 // address
|
|
||||||
DW_FORM_block2 = 0x03 // block
|
|
||||||
DW_FORM_block4 = 0x04 // block
|
|
||||||
DW_FORM_data2 = 0x05 // constant
|
|
||||||
DW_FORM_data4 = 0x06 // constant, lineptr, loclistptr, macptr, rangelistptr
|
|
||||||
DW_FORM_data8 = 0x07 // constant, lineptr, loclistptr, macptr, rangelistptr
|
|
||||||
DW_FORM_string = 0x08 // string
|
|
||||||
DW_FORM_block = 0x09 // block
|
|
||||||
DW_FORM_block1 = 0x0a // block
|
|
||||||
DW_FORM_data1 = 0x0b // constant
|
|
||||||
DW_FORM_flag = 0x0c // flag
|
|
||||||
DW_FORM_sdata = 0x0d // constant
|
|
||||||
DW_FORM_strp = 0x0e // string
|
|
||||||
DW_FORM_udata = 0x0f // constant
|
|
||||||
DW_FORM_ref_addr = 0x10 // reference
|
|
||||||
DW_FORM_ref1 = 0x11 // reference
|
|
||||||
DW_FORM_ref2 = 0x12 // reference
|
|
||||||
DW_FORM_ref4 = 0x13 // reference
|
|
||||||
DW_FORM_ref8 = 0x14 // reference
|
|
||||||
DW_FORM_ref_udata = 0x15 // reference
|
|
||||||
DW_FORM_indirect = 0x16 // (see Section 7.5.3)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 24 (#operands, notes)
|
|
||||||
const (
|
|
||||||
DW_OP_addr = 0x03 // 1 constant address (size target specific)
|
|
||||||
DW_OP_deref = 0x06 // 0
|
|
||||||
DW_OP_const1u = 0x08 // 1 1-byte constant
|
|
||||||
DW_OP_const1s = 0x09 // 1 1-byte constant
|
|
||||||
DW_OP_const2u = 0x0a // 1 2-byte constant
|
|
||||||
DW_OP_const2s = 0x0b // 1 2-byte constant
|
|
||||||
DW_OP_const4u = 0x0c // 1 4-byte constant
|
|
||||||
DW_OP_const4s = 0x0d // 1 4-byte constant
|
|
||||||
DW_OP_const8u = 0x0e // 1 8-byte constant
|
|
||||||
DW_OP_const8s = 0x0f // 1 8-byte constant
|
|
||||||
DW_OP_constu = 0x10 // 1 ULEB128 constant
|
|
||||||
DW_OP_consts = 0x11 // 1 SLEB128 constant
|
|
||||||
DW_OP_dup = 0x12 // 0
|
|
||||||
DW_OP_drop = 0x13 // 0
|
|
||||||
DW_OP_over = 0x14 // 0
|
|
||||||
DW_OP_pick = 0x15 // 1 1-byte stack index
|
|
||||||
DW_OP_swap = 0x16 // 0
|
|
||||||
DW_OP_rot = 0x17 // 0
|
|
||||||
DW_OP_xderef = 0x18 // 0
|
|
||||||
DW_OP_abs = 0x19 // 0
|
|
||||||
DW_OP_and = 0x1a // 0
|
|
||||||
DW_OP_div = 0x1b // 0
|
|
||||||
DW_OP_minus = 0x1c // 0
|
|
||||||
DW_OP_mod = 0x1d // 0
|
|
||||||
DW_OP_mul = 0x1e // 0
|
|
||||||
DW_OP_neg = 0x1f // 0
|
|
||||||
DW_OP_not = 0x20 // 0
|
|
||||||
DW_OP_or = 0x21 // 0
|
|
||||||
DW_OP_plus = 0x22 // 0
|
|
||||||
DW_OP_plus_uconst = 0x23 // 1 ULEB128 addend
|
|
||||||
DW_OP_shl = 0x24 // 0
|
|
||||||
DW_OP_shr = 0x25 // 0
|
|
||||||
DW_OP_shra = 0x26 // 0
|
|
||||||
DW_OP_xor = 0x27 // 0
|
|
||||||
DW_OP_skip = 0x2f // 1 signed 2-byte constant
|
|
||||||
DW_OP_bra = 0x28 // 1 signed 2-byte constant
|
|
||||||
DW_OP_eq = 0x29 // 0
|
|
||||||
DW_OP_ge = 0x2a // 0
|
|
||||||
DW_OP_gt = 0x2b // 0
|
|
||||||
DW_OP_le = 0x2c // 0
|
|
||||||
DW_OP_lt = 0x2d // 0
|
|
||||||
DW_OP_ne = 0x2e // 0
|
|
||||||
DW_OP_lit0 = 0x30 // 0 ...
|
|
||||||
DW_OP_lit31 = 0x4f // 0 literals 0..31 = (DW_OP_lit0 + literal)
|
|
||||||
DW_OP_reg0 = 0x50 // 0 ..
|
|
||||||
DW_OP_reg31 = 0x6f // 0 reg 0..31 = (DW_OP_reg0 + regnum)
|
|
||||||
DW_OP_breg0 = 0x70 // 1 ...
|
|
||||||
DW_OP_breg31 = 0x8f // 1 SLEB128 offset base register 0..31 = (DW_OP_breg0 + regnum)
|
|
||||||
DW_OP_regx = 0x90 // 1 ULEB128 register
|
|
||||||
DW_OP_fbreg = 0x91 // 1 SLEB128 offset
|
|
||||||
DW_OP_bregx = 0x92 // 2 ULEB128 register followed by SLEB128 offset
|
|
||||||
DW_OP_piece = 0x93 // 1 ULEB128 size of piece addressed
|
|
||||||
DW_OP_deref_size = 0x94 // 1 1-byte size of data retrieved
|
|
||||||
DW_OP_xderef_size = 0x95 // 1 1-byte size of data retrieved
|
|
||||||
DW_OP_nop = 0x96 // 0
|
|
||||||
DW_OP_push_object_address = 0x97 // 0
|
|
||||||
DW_OP_call2 = 0x98 // 1 2-byte offset of DIE
|
|
||||||
DW_OP_call4 = 0x99 // 1 4-byte offset of DIE
|
|
||||||
DW_OP_call_ref = 0x9a // 1 4- or 8-byte offset of DIE
|
|
||||||
DW_OP_form_tls_address = 0x9b // 0
|
|
||||||
DW_OP_call_frame_cfa = 0x9c // 0
|
|
||||||
DW_OP_bit_piece = 0x9d // 2
|
|
||||||
DW_OP_lo_user = 0xe0
|
|
||||||
DW_OP_hi_user = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 25
|
|
||||||
const (
|
|
||||||
DW_ATE_address = 0x01
|
|
||||||
DW_ATE_boolean = 0x02
|
|
||||||
DW_ATE_complex_float = 0x03
|
|
||||||
DW_ATE_float = 0x04
|
|
||||||
DW_ATE_signed = 0x05
|
|
||||||
DW_ATE_signed_char = 0x06
|
|
||||||
DW_ATE_unsigned = 0x07
|
|
||||||
DW_ATE_unsigned_char = 0x08
|
|
||||||
DW_ATE_imaginary_float = 0x09
|
|
||||||
DW_ATE_packed_decimal = 0x0a
|
|
||||||
DW_ATE_numeric_string = 0x0b
|
|
||||||
DW_ATE_edited = 0x0c
|
|
||||||
DW_ATE_signed_fixed = 0x0d
|
|
||||||
DW_ATE_unsigned_fixed = 0x0e
|
|
||||||
DW_ATE_decimal_float = 0x0f
|
|
||||||
DW_ATE_lo_user = 0x80
|
|
||||||
DW_ATE_hi_user = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 26
|
|
||||||
const (
|
|
||||||
DW_DS_unsigned = 0x01
|
|
||||||
DW_DS_leading_overpunch = 0x02
|
|
||||||
DW_DS_trailing_overpunch = 0x03
|
|
||||||
DW_DS_leading_separate = 0x04
|
|
||||||
DW_DS_trailing_separate = 0x05
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 27
|
|
||||||
const (
|
|
||||||
DW_END_default = 0x00
|
|
||||||
DW_END_big = 0x01
|
|
||||||
DW_END_little = 0x02
|
|
||||||
DW_END_lo_user = 0x40
|
|
||||||
DW_END_hi_user = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 28
|
|
||||||
const (
|
|
||||||
DW_ACCESS_public = 0x01
|
|
||||||
DW_ACCESS_protected = 0x02
|
|
||||||
DW_ACCESS_private = 0x03
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 29
|
|
||||||
const (
|
|
||||||
DW_VIS_local = 0x01
|
|
||||||
DW_VIS_exported = 0x02
|
|
||||||
DW_VIS_qualified = 0x03
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 30
|
|
||||||
const (
|
|
||||||
DW_VIRTUALITY_none = 0x00
|
|
||||||
DW_VIRTUALITY_virtual = 0x01
|
|
||||||
DW_VIRTUALITY_pure_virtual = 0x02
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 31
|
|
||||||
const (
|
|
||||||
DW_LANG_C89 = 0x0001
|
|
||||||
DW_LANG_C = 0x0002
|
|
||||||
DW_LANG_Ada83 = 0x0003
|
|
||||||
DW_LANG_C_plus_plus = 0x0004
|
|
||||||
DW_LANG_Cobol74 = 0x0005
|
|
||||||
DW_LANG_Cobol85 = 0x0006
|
|
||||||
DW_LANG_Fortran77 = 0x0007
|
|
||||||
DW_LANG_Fortran90 = 0x0008
|
|
||||||
DW_LANG_Pascal83 = 0x0009
|
|
||||||
DW_LANG_Modula2 = 0x000a
|
|
||||||
// Dwarf3
|
|
||||||
DW_LANG_Java = 0x000b
|
|
||||||
DW_LANG_C99 = 0x000c
|
|
||||||
DW_LANG_Ada95 = 0x000d
|
|
||||||
DW_LANG_Fortran95 = 0x000e
|
|
||||||
DW_LANG_PLI = 0x000f
|
|
||||||
DW_LANG_ObjC = 0x0010
|
|
||||||
DW_LANG_ObjC_plus_plus = 0x0011
|
|
||||||
DW_LANG_UPC = 0x0012
|
|
||||||
DW_LANG_D = 0x0013
|
|
||||||
// Dwarf4
|
|
||||||
DW_LANG_Python = 0x0014
|
|
||||||
// Dwarf5
|
|
||||||
DW_LANG_Go = 0x0016
|
|
||||||
|
|
||||||
DW_LANG_lo_user = 0x8000
|
|
||||||
DW_LANG_hi_user = 0xffff
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 32
|
|
||||||
const (
|
|
||||||
DW_ID_case_sensitive = 0x00
|
|
||||||
DW_ID_up_case = 0x01
|
|
||||||
DW_ID_down_case = 0x02
|
|
||||||
DW_ID_case_insensitive = 0x03
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 33
|
|
||||||
const (
|
|
||||||
DW_CC_normal = 0x01
|
|
||||||
DW_CC_program = 0x02
|
|
||||||
DW_CC_nocall = 0x03
|
|
||||||
DW_CC_lo_user = 0x40
|
|
||||||
DW_CC_hi_user = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 34
|
|
||||||
const (
|
|
||||||
DW_INL_not_inlined = 0x00
|
|
||||||
DW_INL_inlined = 0x01
|
|
||||||
DW_INL_declared_not_inlined = 0x02
|
|
||||||
DW_INL_declared_inlined = 0x03
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 35
|
|
||||||
const (
|
|
||||||
DW_ORD_row_major = 0x00
|
|
||||||
DW_ORD_col_major = 0x01
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 36
|
|
||||||
const (
|
|
||||||
DW_DSC_label = 0x00
|
|
||||||
DW_DSC_range = 0x01
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 37
|
|
||||||
const (
|
|
||||||
DW_LNS_copy = 0x01
|
|
||||||
DW_LNS_advance_pc = 0x02
|
|
||||||
DW_LNS_advance_line = 0x03
|
|
||||||
DW_LNS_set_file = 0x04
|
|
||||||
DW_LNS_set_column = 0x05
|
|
||||||
DW_LNS_negate_stmt = 0x06
|
|
||||||
DW_LNS_set_basic_block = 0x07
|
|
||||||
DW_LNS_const_add_pc = 0x08
|
|
||||||
DW_LNS_fixed_advance_pc = 0x09
|
|
||||||
// Dwarf3
|
|
||||||
DW_LNS_set_prologue_end = 0x0a
|
|
||||||
DW_LNS_set_epilogue_begin = 0x0b
|
|
||||||
DW_LNS_set_isa = 0x0c
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 38
|
|
||||||
const (
|
|
||||||
DW_LNE_end_sequence = 0x01
|
|
||||||
DW_LNE_set_address = 0x02
|
|
||||||
DW_LNE_define_file = 0x03
|
|
||||||
DW_LNE_lo_user = 0x80
|
|
||||||
DW_LNE_hi_user = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 39
|
|
||||||
const (
|
|
||||||
DW_MACINFO_define = 0x01
|
|
||||||
DW_MACINFO_undef = 0x02
|
|
||||||
DW_MACINFO_start_file = 0x03
|
|
||||||
DW_MACINFO_end_file = 0x04
|
|
||||||
DW_MACINFO_vendor_ext = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table 40.
|
|
||||||
const (
|
|
||||||
// operand,...
|
|
||||||
DW_CFA_nop = 0x00
|
|
||||||
DW_CFA_set_loc = 0x01 // address
|
|
||||||
DW_CFA_advance_loc1 = 0x02 // 1-byte delta
|
|
||||||
DW_CFA_advance_loc2 = 0x03 // 2-byte delta
|
|
||||||
DW_CFA_advance_loc4 = 0x04 // 4-byte delta
|
|
||||||
DW_CFA_offset_extended = 0x05 // ULEB128 register, ULEB128 offset
|
|
||||||
DW_CFA_restore_extended = 0x06 // ULEB128 register
|
|
||||||
DW_CFA_undefined = 0x07 // ULEB128 register
|
|
||||||
DW_CFA_same_value = 0x08 // ULEB128 register
|
|
||||||
DW_CFA_register = 0x09 // ULEB128 register, ULEB128 register
|
|
||||||
DW_CFA_remember_state = 0x0a
|
|
||||||
DW_CFA_restore_state = 0x0b
|
|
||||||
|
|
||||||
DW_CFA_def_cfa = 0x0c // ULEB128 register, ULEB128 offset
|
|
||||||
DW_CFA_def_cfa_register = 0x0d // ULEB128 register
|
|
||||||
DW_CFA_def_cfa_offset = 0x0e // ULEB128 offset
|
|
||||||
DW_CFA_def_cfa_expression = 0x0f // BLOCK
|
|
||||||
DW_CFA_expression = 0x10 // ULEB128 register, BLOCK
|
|
||||||
DW_CFA_offset_extended_sf = 0x11 // ULEB128 register, SLEB128 offset
|
|
||||||
DW_CFA_def_cfa_sf = 0x12 // ULEB128 register, SLEB128 offset
|
|
||||||
DW_CFA_def_cfa_offset_sf = 0x13 // SLEB128 offset
|
|
||||||
DW_CFA_val_offset = 0x14 // ULEB128, ULEB128
|
|
||||||
DW_CFA_val_offset_sf = 0x15 // ULEB128, SLEB128
|
|
||||||
DW_CFA_val_expression = 0x16 // ULEB128, BLOCK
|
|
||||||
|
|
||||||
DW_CFA_lo_user = 0x1c
|
|
||||||
DW_CFA_hi_user = 0x3f
|
|
||||||
|
|
||||||
// Opcodes that take an addend operand.
|
|
||||||
DW_CFA_advance_loc = 0x1 << 6 // +delta
|
|
||||||
DW_CFA_offset = 0x2 << 6 // +register (ULEB128 offset)
|
|
||||||
DW_CFA_restore = 0x3 << 6 // +register
|
|
||||||
)
|
|
|
@ -1,714 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package goobj implements reading of Go object files and archives.
|
|
||||||
//
|
|
||||||
// TODO(rsc): Decide where this package should live. (golang.org/issue/6932)
|
|
||||||
// TODO(rsc): Decide the appropriate integer types for various fields.
|
|
||||||
// TODO(rsc): Write tests. (File format still up in the air a little.)
|
|
||||||
package goobj
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/google/gops/internal/obj"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A SymKind describes the kind of memory represented by a symbol.
|
|
||||||
type SymKind int
|
|
||||||
|
|
||||||
// This list is taken from include/link.h.
|
|
||||||
|
|
||||||
// Defined SymKind values.
|
|
||||||
// TODO(rsc): Give idiomatic Go names.
|
|
||||||
// TODO(rsc): Reduce the number of symbol types in the object files.
|
|
||||||
const (
|
|
||||||
// readonly, executable
|
|
||||||
STEXT = SymKind(obj.STEXT)
|
|
||||||
SELFRXSECT = SymKind(obj.SELFRXSECT)
|
|
||||||
|
|
||||||
// readonly, non-executable
|
|
||||||
STYPE = SymKind(obj.STYPE)
|
|
||||||
SSTRING = SymKind(obj.SSTRING)
|
|
||||||
SGOSTRING = SymKind(obj.SGOSTRING)
|
|
||||||
SGOFUNC = SymKind(obj.SGOFUNC)
|
|
||||||
SRODATA = SymKind(obj.SRODATA)
|
|
||||||
SFUNCTAB = SymKind(obj.SFUNCTAB)
|
|
||||||
STYPELINK = SymKind(obj.STYPELINK)
|
|
||||||
SITABLINK = SymKind(obj.SITABLINK)
|
|
||||||
SSYMTAB = SymKind(obj.SSYMTAB) // TODO: move to unmapped section
|
|
||||||
SPCLNTAB = SymKind(obj.SPCLNTAB)
|
|
||||||
SELFROSECT = SymKind(obj.SELFROSECT)
|
|
||||||
|
|
||||||
// writable, non-executable
|
|
||||||
SMACHOPLT = SymKind(obj.SMACHOPLT)
|
|
||||||
SELFSECT = SymKind(obj.SELFSECT)
|
|
||||||
SMACHO = SymKind(obj.SMACHO) // Mach-O __nl_symbol_ptr
|
|
||||||
SMACHOGOT = SymKind(obj.SMACHOGOT)
|
|
||||||
SWINDOWS = SymKind(obj.SWINDOWS)
|
|
||||||
SELFGOT = SymKind(obj.SELFGOT)
|
|
||||||
SNOPTRDATA = SymKind(obj.SNOPTRDATA)
|
|
||||||
SINITARR = SymKind(obj.SINITARR)
|
|
||||||
SDATA = SymKind(obj.SDATA)
|
|
||||||
SBSS = SymKind(obj.SBSS)
|
|
||||||
SNOPTRBSS = SymKind(obj.SNOPTRBSS)
|
|
||||||
STLSBSS = SymKind(obj.STLSBSS)
|
|
||||||
|
|
||||||
// not mapped
|
|
||||||
SXREF = SymKind(obj.SXREF)
|
|
||||||
SMACHOSYMSTR = SymKind(obj.SMACHOSYMSTR)
|
|
||||||
SMACHOSYMTAB = SymKind(obj.SMACHOSYMTAB)
|
|
||||||
SMACHOINDIRECTPLT = SymKind(obj.SMACHOINDIRECTPLT)
|
|
||||||
SMACHOINDIRECTGOT = SymKind(obj.SMACHOINDIRECTGOT)
|
|
||||||
SFILE = SymKind(obj.SFILE)
|
|
||||||
SFILEPATH = SymKind(obj.SFILEPATH)
|
|
||||||
SCONST = SymKind(obj.SCONST)
|
|
||||||
SDYNIMPORT = SymKind(obj.SDYNIMPORT)
|
|
||||||
SHOSTOBJ = SymKind(obj.SHOSTOBJ)
|
|
||||||
)
|
|
||||||
|
|
||||||
var symKindStrings = []string{
|
|
||||||
SBSS: "SBSS",
|
|
||||||
SCONST: "SCONST",
|
|
||||||
SDATA: "SDATA",
|
|
||||||
SDYNIMPORT: "SDYNIMPORT",
|
|
||||||
SELFROSECT: "SELFROSECT",
|
|
||||||
SELFRXSECT: "SELFRXSECT",
|
|
||||||
SELFSECT: "SELFSECT",
|
|
||||||
SFILE: "SFILE",
|
|
||||||
SFILEPATH: "SFILEPATH",
|
|
||||||
SFUNCTAB: "SFUNCTAB",
|
|
||||||
SGOFUNC: "SGOFUNC",
|
|
||||||
SGOSTRING: "SGOSTRING",
|
|
||||||
SHOSTOBJ: "SHOSTOBJ",
|
|
||||||
SINITARR: "SINITARR",
|
|
||||||
SMACHO: "SMACHO",
|
|
||||||
SMACHOGOT: "SMACHOGOT",
|
|
||||||
SMACHOINDIRECTGOT: "SMACHOINDIRECTGOT",
|
|
||||||
SMACHOINDIRECTPLT: "SMACHOINDIRECTPLT",
|
|
||||||
SMACHOPLT: "SMACHOPLT",
|
|
||||||
SMACHOSYMSTR: "SMACHOSYMSTR",
|
|
||||||
SMACHOSYMTAB: "SMACHOSYMTAB",
|
|
||||||
SNOPTRBSS: "SNOPTRBSS",
|
|
||||||
SNOPTRDATA: "SNOPTRDATA",
|
|
||||||
SPCLNTAB: "SPCLNTAB",
|
|
||||||
SRODATA: "SRODATA",
|
|
||||||
SSTRING: "SSTRING",
|
|
||||||
SSYMTAB: "SSYMTAB",
|
|
||||||
STEXT: "STEXT",
|
|
||||||
STLSBSS: "STLSBSS",
|
|
||||||
STYPE: "STYPE",
|
|
||||||
STYPELINK: "STYPELINK",
|
|
||||||
SITABLINK: "SITABLINK",
|
|
||||||
SWINDOWS: "SWINDOWS",
|
|
||||||
SXREF: "SXREF",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k SymKind) String() string {
|
|
||||||
if k < 0 || int(k) >= len(symKindStrings) {
|
|
||||||
return fmt.Sprintf("SymKind(%d)", k)
|
|
||||||
}
|
|
||||||
return symKindStrings[k]
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Sym is a named symbol in an object file.
|
|
||||||
type Sym struct {
|
|
||||||
SymID // symbol identifier (name and version)
|
|
||||||
Kind SymKind // kind of symbol
|
|
||||||
DupOK bool // are duplicate definitions okay?
|
|
||||||
Size int // size of corresponding data
|
|
||||||
Type SymID // symbol for Go type information
|
|
||||||
Data Data // memory image of symbol
|
|
||||||
Reloc []Reloc // relocations to apply to Data
|
|
||||||
Func *Func // additional data for functions
|
|
||||||
}
|
|
||||||
|
|
||||||
// A SymID - the combination of Name and Version - uniquely identifies
|
|
||||||
// a symbol within a package.
|
|
||||||
type SymID struct {
|
|
||||||
// Name is the name of a symbol.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Version is zero for symbols with global visibility.
|
|
||||||
// Symbols with only file visibility (such as file-level static
|
|
||||||
// declarations in C) have a non-zero version distinguishing
|
|
||||||
// a symbol in one file from a symbol of the same name
|
|
||||||
// in another file
|
|
||||||
Version int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SymID) String() string {
|
|
||||||
if s.Version == 0 {
|
|
||||||
return s.Name
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s<%d>", s.Name, s.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Data is a reference to data stored in an object file.
|
|
||||||
// It records the offset and size of the data, so that a client can
|
|
||||||
// read the data only if necessary.
|
|
||||||
type Data struct {
|
|
||||||
Offset int64
|
|
||||||
Size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Reloc describes a relocation applied to a memory image to refer
|
|
||||||
// to an address within a particular symbol.
|
|
||||||
type Reloc struct {
|
|
||||||
// The bytes at [Offset, Offset+Size) within the containing Sym
|
|
||||||
// should be updated to refer to the address Add bytes after the start
|
|
||||||
// of the symbol Sym.
|
|
||||||
Offset int
|
|
||||||
Size int
|
|
||||||
Sym SymID
|
|
||||||
Add int
|
|
||||||
|
|
||||||
// The Type records the form of address expected in the bytes
|
|
||||||
// described by the previous fields: absolute, PC-relative, and so on.
|
|
||||||
// TODO(rsc): The interpretation of Type is not exposed by this package.
|
|
||||||
Type obj.RelocType
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Var describes a variable in a function stack frame: a declared
|
|
||||||
// local variable, an input argument, or an output result.
|
|
||||||
type Var struct {
|
|
||||||
// The combination of Name, Kind, and Offset uniquely
|
|
||||||
// identifies a variable in a function stack frame.
|
|
||||||
// Using fewer of these - in particular, using only Name - does not.
|
|
||||||
Name string // Name of variable.
|
|
||||||
Kind int // TODO(rsc): Define meaning.
|
|
||||||
Offset int // Frame offset. TODO(rsc): Define meaning.
|
|
||||||
|
|
||||||
Type SymID // Go type for variable.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Func contains additional per-symbol information specific to functions.
|
|
||||||
type Func struct {
|
|
||||||
Args int // size in bytes of argument frame: inputs and outputs
|
|
||||||
Frame int // size in bytes of local variable frame
|
|
||||||
Leaf bool // function omits save of link register (ARM)
|
|
||||||
NoSplit bool // function omits stack split prologue
|
|
||||||
Var []Var // detail about local variables
|
|
||||||
PCSP Data // PC → SP offset map
|
|
||||||
PCFile Data // PC → file number map (index into File)
|
|
||||||
PCLine Data // PC → line number map
|
|
||||||
PCData []Data // PC → runtime support data map
|
|
||||||
FuncData []FuncData // non-PC-specific runtime support data
|
|
||||||
File []string // paths indexed by PCFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add PCData []byte and PCDataIter (similar to liblink).
|
|
||||||
|
|
||||||
// A FuncData is a single function-specific data value.
|
|
||||||
type FuncData struct {
|
|
||||||
Sym SymID // symbol holding data
|
|
||||||
Offset int64 // offset into symbol for funcdata pointer
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Package is a parsed Go object file or archive defining a Go package.
|
|
||||||
type Package struct {
|
|
||||||
ImportPath string // import path denoting this package
|
|
||||||
Imports []string // packages imported by this package
|
|
||||||
SymRefs []SymID // list of symbol names and versions referred to by this pack
|
|
||||||
Syms []*Sym // symbols defined by this package
|
|
||||||
MaxVersion int // maximum Version in any SymID in Syms
|
|
||||||
Arch string // architecture
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
archiveHeader = []byte("!<arch>\n")
|
|
||||||
archiveMagic = []byte("`\n")
|
|
||||||
goobjHeader = []byte("go objec") // truncated to size of archiveHeader
|
|
||||||
|
|
||||||
errCorruptArchive = errors.New("corrupt archive")
|
|
||||||
errTruncatedArchive = errors.New("truncated archive")
|
|
||||||
errCorruptObject = errors.New("corrupt object file")
|
|
||||||
errNotObject = errors.New("unrecognized object file format")
|
|
||||||
)
|
|
||||||
|
|
||||||
// An objReader is an object file reader.
|
|
||||||
type objReader struct {
|
|
||||||
p *Package
|
|
||||||
b *bufio.Reader
|
|
||||||
f io.ReadSeeker
|
|
||||||
err error
|
|
||||||
offset int64
|
|
||||||
dataOffset int64
|
|
||||||
limit int64
|
|
||||||
tmp [256]byte
|
|
||||||
pkgprefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
// importPathToPrefix returns the prefix that will be used in the
|
|
||||||
// final symbol table for the given import path.
|
|
||||||
// We escape '%', '"', all control characters and non-ASCII bytes,
|
|
||||||
// and any '.' after the final slash.
|
|
||||||
//
|
|
||||||
// See ../../../cmd/ld/lib.c:/^pathtoprefix and
|
|
||||||
// ../../../cmd/gc/subr.c:/^pathtoprefix.
|
|
||||||
func importPathToPrefix(s string) string {
|
|
||||||
// find index of last slash, if any, or else -1.
|
|
||||||
// used for determining whether an index is after the last slash.
|
|
||||||
slash := strings.LastIndex(s, "/")
|
|
||||||
|
|
||||||
// check for chars that need escaping
|
|
||||||
n := 0
|
|
||||||
for r := 0; r < len(s); r++ {
|
|
||||||
if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// quick exit
|
|
||||||
if n == 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// escape
|
|
||||||
const hex = "0123456789abcdef"
|
|
||||||
p := make([]byte, 0, len(s)+2*n)
|
|
||||||
for r := 0; r < len(s); r++ {
|
|
||||||
if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F {
|
|
||||||
p = append(p, '%', hex[c>>4], hex[c&0xF])
|
|
||||||
} else {
|
|
||||||
p = append(p, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// init initializes r to read package p from f.
|
|
||||||
func (r *objReader) init(f io.ReadSeeker, p *Package) {
|
|
||||||
r.f = f
|
|
||||||
r.p = p
|
|
||||||
r.offset, _ = f.Seek(0, io.SeekCurrent)
|
|
||||||
r.limit, _ = f.Seek(0, io.SeekEnd)
|
|
||||||
f.Seek(r.offset, io.SeekStart)
|
|
||||||
r.b = bufio.NewReader(f)
|
|
||||||
r.pkgprefix = importPathToPrefix(p.ImportPath) + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
// error records that an error occurred.
|
|
||||||
// It returns only the first error, so that an error
|
|
||||||
// caused by an earlier error does not discard information
|
|
||||||
// about the earlier error.
|
|
||||||
func (r *objReader) error(err error) error {
|
|
||||||
if r.err == nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
r.err = err
|
|
||||||
}
|
|
||||||
// panic("corrupt") // useful for debugging
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// readByte reads and returns a byte from the input file.
|
|
||||||
// On I/O error or EOF, it records the error but returns byte 0.
|
|
||||||
// A sequence of 0 bytes will eventually terminate any
|
|
||||||
// parsing state in the object file. In particular, it ends the
|
|
||||||
// reading of a varint.
|
|
||||||
func (r *objReader) readByte() byte {
|
|
||||||
if r.err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if r.offset >= r.limit {
|
|
||||||
r.error(io.ErrUnexpectedEOF)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
b, err := r.b.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
r.error(err)
|
|
||||||
b = 0
|
|
||||||
} else {
|
|
||||||
r.offset++
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// read reads exactly len(b) bytes from the input file.
|
|
||||||
// If an error occurs, read returns the error but also
|
|
||||||
// records it, so it is safe for callers to ignore the result
|
|
||||||
// as long as delaying the report is not a problem.
|
|
||||||
func (r *objReader) readFull(b []byte) error {
|
|
||||||
if r.err != nil {
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
if r.offset+int64(len(b)) > r.limit {
|
|
||||||
return r.error(io.ErrUnexpectedEOF)
|
|
||||||
}
|
|
||||||
n, err := io.ReadFull(r.b, b)
|
|
||||||
r.offset += int64(n)
|
|
||||||
if err != nil {
|
|
||||||
return r.error(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readInt reads a zigzag varint from the input file.
|
|
||||||
func (r *objReader) readInt() int {
|
|
||||||
var u uint64
|
|
||||||
|
|
||||||
for shift := uint(0); ; shift += 7 {
|
|
||||||
if shift >= 64 {
|
|
||||||
r.error(errCorruptObject)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
c := r.readByte()
|
|
||||||
u |= uint64(c&0x7F) << shift
|
|
||||||
if c&0x80 == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
v := int64(u>>1) ^ (int64(u) << 63 >> 63)
|
|
||||||
if int64(int(v)) != v {
|
|
||||||
r.error(errCorruptObject) // TODO
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readString reads a length-delimited string from the input file.
|
|
||||||
func (r *objReader) readString() string {
|
|
||||||
n := r.readInt()
|
|
||||||
buf := make([]byte, n)
|
|
||||||
r.readFull(buf)
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readSymID reads a SymID from the input file.
|
|
||||||
func (r *objReader) readSymID() SymID {
|
|
||||||
i := r.readInt()
|
|
||||||
return r.p.SymRefs[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *objReader) readRef() {
|
|
||||||
name, vers := r.readString(), r.readInt()
|
|
||||||
|
|
||||||
// In a symbol name in an object file, "". denotes the
|
|
||||||
// prefix for the package in which the object file has been found.
|
|
||||||
// Expand it.
|
|
||||||
name = strings.Replace(name, `"".`, r.pkgprefix, -1)
|
|
||||||
|
|
||||||
// An individual object file only records version 0 (extern) or 1 (static).
|
|
||||||
// To make static symbols unique across all files being read, we
|
|
||||||
// replace version 1 with the version corresponding to the current
|
|
||||||
// file number. The number is incremented on each call to parseObject.
|
|
||||||
if vers != 0 {
|
|
||||||
vers = r.p.MaxVersion
|
|
||||||
}
|
|
||||||
r.p.SymRefs = append(r.p.SymRefs, SymID{name, vers})
|
|
||||||
}
|
|
||||||
|
|
||||||
// readData reads a data reference from the input file.
|
|
||||||
func (r *objReader) readData() Data {
|
|
||||||
n := r.readInt()
|
|
||||||
d := Data{Offset: r.dataOffset, Size: int64(n)}
|
|
||||||
r.dataOffset += int64(n)
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip skips n bytes in the input.
|
|
||||||
func (r *objReader) skip(n int64) {
|
|
||||||
if n < 0 {
|
|
||||||
r.error(fmt.Errorf("debug/goobj: internal error: misuse of skip"))
|
|
||||||
}
|
|
||||||
if n < int64(len(r.tmp)) {
|
|
||||||
// Since the data is so small, a just reading from the buffered
|
|
||||||
// reader is better than flushing the buffer and seeking.
|
|
||||||
r.readFull(r.tmp[:n])
|
|
||||||
} else if n <= int64(r.b.Buffered()) {
|
|
||||||
// Even though the data is not small, it has already been read.
|
|
||||||
// Advance the buffer instead of seeking.
|
|
||||||
for n > int64(len(r.tmp)) {
|
|
||||||
r.readFull(r.tmp[:])
|
|
||||||
n -= int64(len(r.tmp))
|
|
||||||
}
|
|
||||||
r.readFull(r.tmp[:n])
|
|
||||||
} else {
|
|
||||||
// Seek, giving up buffered data.
|
|
||||||
_, err := r.f.Seek(r.offset+n, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
r.error(err)
|
|
||||||
}
|
|
||||||
r.offset += n
|
|
||||||
r.b.Reset(r.f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses an object file or archive from r,
|
|
||||||
// assuming that its import path is pkgpath.
|
|
||||||
func Parse(r io.ReadSeeker, pkgpath string) (*Package, error) {
|
|
||||||
if pkgpath == "" {
|
|
||||||
pkgpath = `""`
|
|
||||||
}
|
|
||||||
p := new(Package)
|
|
||||||
p.ImportPath = pkgpath
|
|
||||||
|
|
||||||
var rd objReader
|
|
||||||
rd.init(r, p)
|
|
||||||
err := rd.readFull(rd.tmp[:8])
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
default:
|
|
||||||
return nil, errNotObject
|
|
||||||
|
|
||||||
case bytes.Equal(rd.tmp[:8], archiveHeader):
|
|
||||||
if err := rd.parseArchive(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case bytes.Equal(rd.tmp[:8], goobjHeader):
|
|
||||||
if err := rd.parseObject(goobjHeader); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// trimSpace removes trailing spaces from b and returns the corresponding string.
|
|
||||||
// This effectively parses the form used in archive headers.
|
|
||||||
func trimSpace(b []byte) string {
|
|
||||||
return string(bytes.TrimRight(b, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseArchive parses a Unix archive of Go object files.
|
|
||||||
// TODO(rsc): Need to skip non-Go object files.
|
|
||||||
// TODO(rsc): Maybe record table of contents in r.p so that
|
|
||||||
// linker can avoid having code to parse archives too.
|
|
||||||
func (r *objReader) parseArchive() error {
|
|
||||||
for r.offset < r.limit {
|
|
||||||
if err := r.readFull(r.tmp[:60]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := r.tmp[:60]
|
|
||||||
|
|
||||||
// Each file is preceded by this text header (slice indices in first column):
|
|
||||||
// 0:16 name
|
|
||||||
// 16:28 date
|
|
||||||
// 28:34 uid
|
|
||||||
// 34:40 gid
|
|
||||||
// 40:48 mode
|
|
||||||
// 48:58 size
|
|
||||||
// 58:60 magic - `\n
|
|
||||||
// We only care about name, size, and magic.
|
|
||||||
// The fields are space-padded on the right.
|
|
||||||
// The size is in decimal.
|
|
||||||
// The file data - size bytes - follows the header.
|
|
||||||
// Headers are 2-byte aligned, so if size is odd, an extra padding
|
|
||||||
// byte sits between the file data and the next header.
|
|
||||||
// The file data that follows is padded to an even number of bytes:
|
|
||||||
// if size is odd, an extra padding byte is inserted betw the next header.
|
|
||||||
if len(data) < 60 {
|
|
||||||
return errTruncatedArchive
|
|
||||||
}
|
|
||||||
if !bytes.Equal(data[58:60], archiveMagic) {
|
|
||||||
return errCorruptArchive
|
|
||||||
}
|
|
||||||
name := trimSpace(data[0:16])
|
|
||||||
size, err := strconv.ParseInt(trimSpace(data[48:58]), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return errCorruptArchive
|
|
||||||
}
|
|
||||||
data = data[60:]
|
|
||||||
fsize := size + size&1
|
|
||||||
if fsize < 0 || fsize < size {
|
|
||||||
return errCorruptArchive
|
|
||||||
}
|
|
||||||
switch name {
|
|
||||||
case "__.PKGDEF":
|
|
||||||
r.skip(size)
|
|
||||||
default:
|
|
||||||
oldLimit := r.limit
|
|
||||||
r.limit = r.offset + size
|
|
||||||
if err := r.parseObject(nil); err != nil {
|
|
||||||
return fmt.Errorf("parsing archive member %q: %v", name, err)
|
|
||||||
}
|
|
||||||
r.skip(r.limit - r.offset)
|
|
||||||
r.limit = oldLimit
|
|
||||||
}
|
|
||||||
if size&1 != 0 {
|
|
||||||
r.skip(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseObject parses a single Go object file.
|
|
||||||
// The prefix is the bytes already read from the file,
|
|
||||||
// typically in order to detect that this is an object file.
|
|
||||||
// The object file consists of a textual header ending in "\n!\n"
|
|
||||||
// and then the part we want to parse begins.
|
|
||||||
// The format of that part is defined in a comment at the top
|
|
||||||
// of src/liblink/objfile.c.
|
|
||||||
func (r *objReader) parseObject(prefix []byte) error {
|
|
||||||
r.p.MaxVersion++
|
|
||||||
h := make([]byte, 0, 256)
|
|
||||||
h = append(h, prefix...)
|
|
||||||
var c1, c2, c3 byte
|
|
||||||
for {
|
|
||||||
c1, c2, c3 = c2, c3, r.readByte()
|
|
||||||
h = append(h, c3)
|
|
||||||
// The new export format can contain 0 bytes.
|
|
||||||
// Don't consider them errors, only look for r.err != nil.
|
|
||||||
if r.err != nil {
|
|
||||||
return errCorruptObject
|
|
||||||
}
|
|
||||||
if c1 == '\n' && c2 == '!' && c3 == '\n' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hs := strings.Fields(string(h))
|
|
||||||
if len(hs) >= 4 {
|
|
||||||
r.p.Arch = hs[3]
|
|
||||||
}
|
|
||||||
// TODO: extract OS + build ID if/when we need it
|
|
||||||
|
|
||||||
r.readFull(r.tmp[:8])
|
|
||||||
if !bytes.Equal(r.tmp[:8], []byte("\x00\x00go17ld")) {
|
|
||||||
return r.error(errCorruptObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
b := r.readByte()
|
|
||||||
if b != 1 {
|
|
||||||
return r.error(errCorruptObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Direct package dependencies.
|
|
||||||
for {
|
|
||||||
s := r.readString()
|
|
||||||
if s == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.p.Imports = append(r.p.Imports, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.p.SymRefs = []SymID{{"", 0}}
|
|
||||||
for {
|
|
||||||
if b := r.readByte(); b != 0xfe {
|
|
||||||
if b != 0xff {
|
|
||||||
return r.error(errCorruptObject)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
r.readRef()
|
|
||||||
}
|
|
||||||
|
|
||||||
dataLength := r.readInt()
|
|
||||||
r.readInt() // n relocations - ignore
|
|
||||||
r.readInt() // n pcdata - ignore
|
|
||||||
r.readInt() // n autom - ignore
|
|
||||||
r.readInt() // n funcdata - ignore
|
|
||||||
r.readInt() // n files - ignore
|
|
||||||
|
|
||||||
r.dataOffset = r.offset
|
|
||||||
r.skip(int64(dataLength))
|
|
||||||
|
|
||||||
// Symbols.
|
|
||||||
for {
|
|
||||||
if b := r.readByte(); b != 0xfe {
|
|
||||||
if b != 0xff {
|
|
||||||
return r.error(errCorruptObject)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
typ := r.readInt()
|
|
||||||
s := &Sym{SymID: r.readSymID()}
|
|
||||||
r.p.Syms = append(r.p.Syms, s)
|
|
||||||
s.Kind = SymKind(typ)
|
|
||||||
flags := r.readInt()
|
|
||||||
s.DupOK = flags&1 != 0
|
|
||||||
s.Size = r.readInt()
|
|
||||||
s.Type = r.readSymID()
|
|
||||||
s.Data = r.readData()
|
|
||||||
s.Reloc = make([]Reloc, r.readInt())
|
|
||||||
for i := range s.Reloc {
|
|
||||||
rel := &s.Reloc[i]
|
|
||||||
rel.Offset = r.readInt()
|
|
||||||
rel.Size = r.readInt()
|
|
||||||
rel.Type = obj.RelocType(r.readInt())
|
|
||||||
rel.Add = r.readInt()
|
|
||||||
rel.Sym = r.readSymID()
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Kind == STEXT {
|
|
||||||
f := new(Func)
|
|
||||||
s.Func = f
|
|
||||||
f.Args = r.readInt()
|
|
||||||
f.Frame = r.readInt()
|
|
||||||
flags := r.readInt()
|
|
||||||
f.Leaf = flags&1 != 0
|
|
||||||
f.NoSplit = r.readInt() != 0
|
|
||||||
f.Var = make([]Var, r.readInt())
|
|
||||||
for i := range f.Var {
|
|
||||||
v := &f.Var[i]
|
|
||||||
v.Name = r.readSymID().Name
|
|
||||||
v.Offset = r.readInt()
|
|
||||||
v.Kind = r.readInt()
|
|
||||||
v.Type = r.readSymID()
|
|
||||||
}
|
|
||||||
|
|
||||||
f.PCSP = r.readData()
|
|
||||||
f.PCFile = r.readData()
|
|
||||||
f.PCLine = r.readData()
|
|
||||||
f.PCData = make([]Data, r.readInt())
|
|
||||||
for i := range f.PCData {
|
|
||||||
f.PCData[i] = r.readData()
|
|
||||||
}
|
|
||||||
f.FuncData = make([]FuncData, r.readInt())
|
|
||||||
for i := range f.FuncData {
|
|
||||||
f.FuncData[i].Sym = r.readSymID()
|
|
||||||
}
|
|
||||||
for i := range f.FuncData {
|
|
||||||
f.FuncData[i].Offset = int64(r.readInt()) // TODO
|
|
||||||
}
|
|
||||||
f.File = make([]string, r.readInt())
|
|
||||||
for i := range f.File {
|
|
||||||
f.File[i] = r.readSymID().Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.readFull(r.tmp[:7])
|
|
||||||
if !bytes.Equal(r.tmp[:7], []byte("\xffgo17ld")) {
|
|
||||||
return r.error(errCorruptObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reloc) String(insnOffset uint64) string {
|
|
||||||
delta := r.Offset - int(insnOffset)
|
|
||||||
s := fmt.Sprintf("[%d:%d]%s", delta, delta+r.Size, r.Type)
|
|
||||||
if r.Sym.Name != "" {
|
|
||||||
if r.Add != 0 {
|
|
||||||
return fmt.Sprintf("%s:%s+%d", s, r.Sym.Name, r.Add)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s:%s", s, r.Sym.Name)
|
|
||||||
}
|
|
||||||
if r.Add != 0 {
|
|
||||||
return fmt.Sprintf("%s:%d", s, r.Add)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package goobj
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
var importPathToPrefixTests = []struct {
|
|
||||||
in string
|
|
||||||
out string
|
|
||||||
}{
|
|
||||||
{"runtime", "runtime"},
|
|
||||||
{"sync/atomic", "sync/atomic"},
|
|
||||||
{"golang.org/x/tools/godoc", "golang.org/x/tools/godoc"},
|
|
||||||
{"foo.bar/baz.quux", "foo.bar/baz%2equux"},
|
|
||||||
{"", ""},
|
|
||||||
{"%foo%bar", "%25foo%25bar"},
|
|
||||||
{"\x01\x00\x7F☺", "%01%00%7f%e2%98%ba"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImportPathToPrefix(t *testing.T) {
|
|
||||||
for _, tt := range importPathToPrefixTests {
|
|
||||||
if out := importPathToPrefix(tt.in); out != tt.out {
|
|
||||||
t.Errorf("importPathToPrefix(%q) = %q, want %q", tt.in, out, tt.out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
// Code generated by "stringer -type AddrType cmd/internal/obj"; DO NOT EDIT
|
|
||||||
|
|
||||||
package obj
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
const (
|
|
||||||
_AddrType_name_0 = "TYPE_NONE"
|
|
||||||
_AddrType_name_1 = "TYPE_BRANCHTYPE_TEXTSIZETYPE_MEMTYPE_CONSTTYPE_FCONSTTYPE_SCONSTTYPE_REGTYPE_ADDRTYPE_SHIFTTYPE_REGREGTYPE_REGREG2TYPE_INDIRTYPE_REGLIST"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_AddrType_index_0 = [...]uint8{0, 9}
|
|
||||||
_AddrType_index_1 = [...]uint8{0, 11, 24, 32, 42, 53, 64, 72, 81, 91, 102, 114, 124, 136}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (i AddrType) String() string {
|
|
||||||
switch {
|
|
||||||
case i == 0:
|
|
||||||
return _AddrType_name_0
|
|
||||||
case 6 <= i && i <= 18:
|
|
||||||
i -= 6
|
|
||||||
return _AddrType_name_1[_AddrType_index_1[i]:_AddrType_index_1[i+1]]
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("AddrType(%d)", i)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,338 +0,0 @@
|
||||||
// Inferno utils/5c/5.out.h
|
|
||||||
// https://bitbucket.org/inferno-os/inferno-os/src/default/utils/5c/5.out.h
|
|
||||||
//
|
|
||||||
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
|
|
||||||
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
|
|
||||||
// Portions Copyright © 1997-1999 Vita Nuova Limited
|
|
||||||
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
|
|
||||||
// Portions Copyright © 2004,2006 Bruce Ellis
|
|
||||||
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
|
|
||||||
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
|
|
||||||
// Portions Copyright © 2009 The Go Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
package arm
|
|
||||||
|
|
||||||
import "github.com/google/gops/internal/obj"
|
|
||||||
|
|
||||||
//go:generate go run ../stringer.go -i $GOFILE -o anames.go -p arm
|
|
||||||
|
|
||||||
const (
|
|
||||||
NSNAME = 8
|
|
||||||
NSYM = 50
|
|
||||||
NREG = 16
|
|
||||||
)
|
|
||||||
|
|
||||||
/* -1 disables use of REGARG */
|
|
||||||
const (
|
|
||||||
REGARG = -1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
REG_R0 = obj.RBaseARM + iota // must be 16-aligned
|
|
||||||
REG_R1
|
|
||||||
REG_R2
|
|
||||||
REG_R3
|
|
||||||
REG_R4
|
|
||||||
REG_R5
|
|
||||||
REG_R6
|
|
||||||
REG_R7
|
|
||||||
REG_R8
|
|
||||||
REG_R9
|
|
||||||
REG_R10
|
|
||||||
REG_R11
|
|
||||||
REG_R12
|
|
||||||
REG_R13
|
|
||||||
REG_R14
|
|
||||||
REG_R15
|
|
||||||
|
|
||||||
REG_F0 // must be 16-aligned
|
|
||||||
REG_F1
|
|
||||||
REG_F2
|
|
||||||
REG_F3
|
|
||||||
REG_F4
|
|
||||||
REG_F5
|
|
||||||
REG_F6
|
|
||||||
REG_F7
|
|
||||||
REG_F8
|
|
||||||
REG_F9
|
|
||||||
REG_F10
|
|
||||||
REG_F11
|
|
||||||
REG_F12
|
|
||||||
REG_F13
|
|
||||||
REG_F14
|
|
||||||
REG_F15
|
|
||||||
|
|
||||||
REG_FPSR // must be 2-aligned
|
|
||||||
REG_FPCR
|
|
||||||
|
|
||||||
REG_CPSR // must be 2-aligned
|
|
||||||
REG_SPSR
|
|
||||||
|
|
||||||
MAXREG
|
|
||||||
REGRET = REG_R0
|
|
||||||
/* compiler allocates R1 up as temps */
|
|
||||||
/* compiler allocates register variables R3 up */
|
|
||||||
/* compiler allocates external registers R10 down */
|
|
||||||
REGEXT = REG_R10
|
|
||||||
/* these two registers are declared in runtime.h */
|
|
||||||
REGG = REGEXT - 0
|
|
||||||
REGM = REGEXT - 1
|
|
||||||
|
|
||||||
REGCTXT = REG_R7
|
|
||||||
REGTMP = REG_R11
|
|
||||||
REGSP = REG_R13
|
|
||||||
REGLINK = REG_R14
|
|
||||||
REGPC = REG_R15
|
|
||||||
|
|
||||||
NFREG = 16
|
|
||||||
/* compiler allocates register variables F0 up */
|
|
||||||
/* compiler allocates external registers F7 down */
|
|
||||||
FREGRET = REG_F0
|
|
||||||
FREGEXT = REG_F7
|
|
||||||
FREGTMP = REG_F15
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
C_NONE = iota
|
|
||||||
C_REG
|
|
||||||
C_REGREG
|
|
||||||
C_REGREG2
|
|
||||||
C_REGLIST
|
|
||||||
C_SHIFT
|
|
||||||
C_FREG
|
|
||||||
C_PSR
|
|
||||||
C_FCR
|
|
||||||
|
|
||||||
C_RCON /* 0xff rotated */
|
|
||||||
C_NCON /* ~RCON */
|
|
||||||
C_SCON /* 0xffff */
|
|
||||||
C_LCON
|
|
||||||
C_LCONADDR
|
|
||||||
C_ZFCON
|
|
||||||
C_SFCON
|
|
||||||
C_LFCON
|
|
||||||
|
|
||||||
C_RACON
|
|
||||||
C_LACON
|
|
||||||
|
|
||||||
C_SBRA
|
|
||||||
C_LBRA
|
|
||||||
|
|
||||||
C_HAUTO /* halfword insn offset (-0xff to 0xff) */
|
|
||||||
C_FAUTO /* float insn offset (0 to 0x3fc, word aligned) */
|
|
||||||
C_HFAUTO /* both H and F */
|
|
||||||
C_SAUTO /* -0xfff to 0xfff */
|
|
||||||
C_LAUTO
|
|
||||||
|
|
||||||
C_HOREG
|
|
||||||
C_FOREG
|
|
||||||
C_HFOREG
|
|
||||||
C_SOREG
|
|
||||||
C_ROREG
|
|
||||||
C_SROREG /* both nil and R */
|
|
||||||
C_LOREG
|
|
||||||
|
|
||||||
C_PC
|
|
||||||
C_SP
|
|
||||||
C_HREG
|
|
||||||
|
|
||||||
C_ADDR /* reference to relocatable address */
|
|
||||||
|
|
||||||
// TLS "var" in local exec mode: will become a constant offset from
|
|
||||||
// thread local base that is ultimately chosen by the program linker.
|
|
||||||
C_TLS_LE
|
|
||||||
|
|
||||||
// TLS "var" in initial exec mode: will become a memory address (chosen
|
|
||||||
// by the program linker) that the dynamic linker will fill with the
|
|
||||||
// offset from the thread local base.
|
|
||||||
C_TLS_IE
|
|
||||||
|
|
||||||
C_TEXTSIZE
|
|
||||||
|
|
||||||
C_GOK
|
|
||||||
|
|
||||||
C_NCLASS /* must be the last */
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
AAND = obj.ABaseARM + obj.A_ARCHSPECIFIC + iota
|
|
||||||
AEOR
|
|
||||||
ASUB
|
|
||||||
ARSB
|
|
||||||
AADD
|
|
||||||
AADC
|
|
||||||
ASBC
|
|
||||||
ARSC
|
|
||||||
ATST
|
|
||||||
ATEQ
|
|
||||||
ACMP
|
|
||||||
ACMN
|
|
||||||
AORR
|
|
||||||
ABIC
|
|
||||||
|
|
||||||
AMVN
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Do not reorder or fragment the conditional branch
|
|
||||||
* opcodes, or the predication code will break
|
|
||||||
*/
|
|
||||||
ABEQ
|
|
||||||
ABNE
|
|
||||||
ABCS
|
|
||||||
ABHS
|
|
||||||
ABCC
|
|
||||||
ABLO
|
|
||||||
ABMI
|
|
||||||
ABPL
|
|
||||||
ABVS
|
|
||||||
ABVC
|
|
||||||
ABHI
|
|
||||||
ABLS
|
|
||||||
ABGE
|
|
||||||
ABLT
|
|
||||||
ABGT
|
|
||||||
ABLE
|
|
||||||
|
|
||||||
AMOVWD
|
|
||||||
AMOVWF
|
|
||||||
AMOVDW
|
|
||||||
AMOVFW
|
|
||||||
AMOVFD
|
|
||||||
AMOVDF
|
|
||||||
AMOVF
|
|
||||||
AMOVD
|
|
||||||
|
|
||||||
ACMPF
|
|
||||||
ACMPD
|
|
||||||
AADDF
|
|
||||||
AADDD
|
|
||||||
ASUBF
|
|
||||||
ASUBD
|
|
||||||
AMULF
|
|
||||||
AMULD
|
|
||||||
ADIVF
|
|
||||||
ADIVD
|
|
||||||
ASQRTF
|
|
||||||
ASQRTD
|
|
||||||
AABSF
|
|
||||||
AABSD
|
|
||||||
ANEGF
|
|
||||||
ANEGD
|
|
||||||
|
|
||||||
ASRL
|
|
||||||
ASRA
|
|
||||||
ASLL
|
|
||||||
AMULU
|
|
||||||
ADIVU
|
|
||||||
AMUL
|
|
||||||
ADIV
|
|
||||||
AMOD
|
|
||||||
AMODU
|
|
||||||
|
|
||||||
AMOVB
|
|
||||||
AMOVBS
|
|
||||||
AMOVBU
|
|
||||||
AMOVH
|
|
||||||
AMOVHS
|
|
||||||
AMOVHU
|
|
||||||
AMOVW
|
|
||||||
AMOVM
|
|
||||||
ASWPBU
|
|
||||||
ASWPW
|
|
||||||
|
|
||||||
ARFE
|
|
||||||
ASWI
|
|
||||||
AMULA
|
|
||||||
|
|
||||||
AWORD
|
|
||||||
|
|
||||||
AMULL
|
|
||||||
AMULAL
|
|
||||||
AMULLU
|
|
||||||
AMULALU
|
|
||||||
|
|
||||||
ABX
|
|
||||||
ABXRET
|
|
||||||
ADWORD
|
|
||||||
|
|
||||||
ALDREX
|
|
||||||
ASTREX
|
|
||||||
ALDREXD
|
|
||||||
ASTREXD
|
|
||||||
|
|
||||||
APLD
|
|
||||||
|
|
||||||
ACLZ
|
|
||||||
|
|
||||||
AMULWT
|
|
||||||
AMULWB
|
|
||||||
AMULAWT
|
|
||||||
AMULAWB
|
|
||||||
|
|
||||||
ADATABUNDLE
|
|
||||||
ADATABUNDLEEND
|
|
||||||
|
|
||||||
AMRC // MRC/MCR
|
|
||||||
|
|
||||||
ALAST
|
|
||||||
|
|
||||||
// aliases
|
|
||||||
AB = obj.AJMP
|
|
||||||
ABL = obj.ACALL
|
|
||||||
)
|
|
||||||
|
|
||||||
/* scond byte */
|
|
||||||
const (
|
|
||||||
C_SCOND = (1 << 4) - 1
|
|
||||||
C_SBIT = 1 << 4
|
|
||||||
C_PBIT = 1 << 5
|
|
||||||
C_WBIT = 1 << 6
|
|
||||||
C_FBIT = 1 << 7 /* psr flags-only */
|
|
||||||
C_UBIT = 1 << 7 /* up bit, unsigned bit */
|
|
||||||
|
|
||||||
// These constants are the ARM condition codes encodings,
|
|
||||||
// XORed with 14 so that C_SCOND_NONE has value 0,
|
|
||||||
// so that a zeroed Prog.scond means "always execute".
|
|
||||||
C_SCOND_XOR = 14
|
|
||||||
|
|
||||||
C_SCOND_EQ = 0 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_NE = 1 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_HS = 2 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_LO = 3 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_MI = 4 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_PL = 5 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_VS = 6 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_VC = 7 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_HI = 8 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_LS = 9 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_GE = 10 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_LT = 11 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_GT = 12 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_LE = 13 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_NONE = 14 ^ C_SCOND_XOR
|
|
||||||
C_SCOND_NV = 15 ^ C_SCOND_XOR
|
|
||||||
|
|
||||||
/* D_SHIFT type */
|
|
||||||
SHIFT_LL = 0 << 5
|
|
||||||
SHIFT_LR = 1 << 5
|
|
||||||
SHIFT_AR = 2 << 5
|
|
||||||
SHIFT_RR = 3 << 5
|
|
||||||
)
|
|
|
@ -1,108 +0,0 @@
|
||||||
// Generated by stringer -i a.out.go -o anames.go -p arm
|
|
||||||
// Do not edit.
|
|
||||||
|
|
||||||
package arm
|
|
||||||
|
|
||||||
import "github.com/google/gops/internal/obj"
|
|
||||||
|
|
||||||
var Anames = []string{
|
|
||||||
obj.A_ARCHSPECIFIC: "AND",
|
|
||||||
"EOR",
|
|
||||||
"SUB",
|
|
||||||
"RSB",
|
|
||||||
"ADD",
|
|
||||||
"ADC",
|
|
||||||
"SBC",
|
|
||||||
"RSC",
|
|
||||||
"TST",
|
|
||||||
"TEQ",
|
|
||||||
"CMP",
|
|
||||||
"CMN",
|
|
||||||
"ORR",
|
|
||||||
"BIC",
|
|
||||||
"MVN",
|
|
||||||
"BEQ",
|
|
||||||
"BNE",
|
|
||||||
"BCS",
|
|
||||||
"BHS",
|
|
||||||
"BCC",
|
|
||||||
"BLO",
|
|
||||||
"BMI",
|
|
||||||
"BPL",
|
|
||||||
"BVS",
|
|
||||||
"BVC",
|
|
||||||
"BHI",
|
|
||||||
"BLS",
|
|
||||||
"BGE",
|
|
||||||
"BLT",
|
|
||||||
"BGT",
|
|
||||||
"BLE",
|
|
||||||
"MOVWD",
|
|
||||||
"MOVWF",
|
|
||||||
"MOVDW",
|
|
||||||
"MOVFW",
|
|
||||||
"MOVFD",
|
|
||||||
"MOVDF",
|
|
||||||
"MOVF",
|
|
||||||
"MOVD",
|
|
||||||
"CMPF",
|
|
||||||
"CMPD",
|
|
||||||
"ADDF",
|
|
||||||
"ADDD",
|
|
||||||
"SUBF",
|
|
||||||
"SUBD",
|
|
||||||
"MULF",
|
|
||||||
"MULD",
|
|
||||||
"DIVF",
|
|
||||||
"DIVD",
|
|
||||||
"SQRTF",
|
|
||||||
"SQRTD",
|
|
||||||
"ABSF",
|
|
||||||
"ABSD",
|
|
||||||
"NEGF",
|
|
||||||
"NEGD",
|
|
||||||
"SRL",
|
|
||||||
"SRA",
|
|
||||||
"SLL",
|
|
||||||
"MULU",
|
|
||||||
"DIVU",
|
|
||||||
"MUL",
|
|
||||||
"DIV",
|
|
||||||
"MOD",
|
|
||||||
"MODU",
|
|
||||||
"MOVB",
|
|
||||||
"MOVBS",
|
|
||||||
"MOVBU",
|
|
||||||
"MOVH",
|
|
||||||
"MOVHS",
|
|
||||||
"MOVHU",
|
|
||||||
"MOVW",
|
|
||||||
"MOVM",
|
|
||||||
"SWPBU",
|
|
||||||
"SWPW",
|
|
||||||
"RFE",
|
|
||||||
"SWI",
|
|
||||||
"MULA",
|
|
||||||
"WORD",
|
|
||||||
"MULL",
|
|
||||||
"MULAL",
|
|
||||||
"MULLU",
|
|
||||||
"MULALU",
|
|
||||||
"BX",
|
|
||||||
"BXRET",
|
|
||||||
"DWORD",
|
|
||||||
"LDREX",
|
|
||||||
"STREX",
|
|
||||||
"LDREXD",
|
|
||||||
"STREXD",
|
|
||||||
"PLD",
|
|
||||||
"CLZ",
|
|
||||||
"MULWT",
|
|
||||||
"MULWB",
|
|
||||||
"MULAWT",
|
|
||||||
"MULAWB",
|
|
||||||
"DATABUNDLE",
|
|
||||||
"DATABUNDLEEND",
|
|
||||||
"MRC",
|
|
||||||
"LAST",
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package arm
|
|
||||||
|
|
||||||
var cnames5 = []string{
|
|
||||||
"NONE",
|
|
||||||
"REG",
|
|
||||||
"REGREG",
|
|
||||||
"REGREG2",
|
|
||||||
"REGLIST",
|
|
||||||
"SHIFT",
|
|
||||||
"FREG",
|
|
||||||
"PSR",
|
|
||||||
"FCR",
|
|
||||||
"RCON",
|
|
||||||
"NCON",
|
|
||||||
"SCON",
|
|
||||||
"LCON",
|
|
||||||
"LCONADDR",
|
|
||||||
"ZFCON",
|
|
||||||
"SFCON",
|
|
||||||
"LFCON",
|
|
||||||
"RACON",
|
|
||||||
"LACON",
|
|
||||||
"SBRA",
|
|
||||||
"LBRA",
|
|
||||||
"HAUTO",
|
|
||||||
"FAUTO",
|
|
||||||
"HFAUTO",
|
|
||||||
"SAUTO",
|
|
||||||
"LAUTO",
|
|
||||||
"HOREG",
|
|
||||||
"FOREG",
|
|
||||||
"HFOREG",
|
|
||||||
"SOREG",
|
|
||||||
"ROREG",
|
|
||||||
"SROREG",
|
|
||||||
"LOREG",
|
|
||||||
"PC",
|
|
||||||
"SP",
|
|
||||||
"HREG",
|
|
||||||
"ADDR",
|
|
||||||
"C_TLS_LE",
|
|
||||||
"C_TLS_IE",
|
|
||||||
"TEXTSIZE",
|
|
||||||
"GOK",
|
|
||||||
"NCLASS",
|
|
||||||
"SCOND = (1<<4)-1",
|
|
||||||
"SBIT = 1<<4",
|
|
||||||
"PBIT = 1<<5",
|
|
||||||
"WBIT = 1<<6",
|
|
||||||
"FBIT = 1<<7",
|
|
||||||
"UBIT = 1<<7",
|
|
||||||
"SCOND_XOR = 14",
|
|
||||||
"SCOND_EQ = 0 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_NE = 1 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_HS = 2 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_LO = 3 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_MI = 4 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_PL = 5 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_VS = 6 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_VC = 7 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_HI = 8 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_LS = 9 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_GE = 10 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_LT = 11 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_GT = 12 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_LE = 13 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_NONE = 14 ^ C_SCOND_XOR",
|
|
||||||
"SCOND_NV = 15 ^ C_SCOND_XOR",
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,84 +0,0 @@
|
||||||
// Inferno utils/5c/list.c
|
|
||||||
// https://bitbucket.org/inferno-os/inferno-os/src/default/utils/5c/list.c
|
|
||||||
//
|
|
||||||
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
|
|
||||||
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
|
|
||||||
// Portions Copyright © 1997-1999 Vita Nuova Limited
|
|
||||||
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
|
|
||||||
// Portions Copyright © 2004,2006 Bruce Ellis
|
|
||||||
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
|
|
||||||
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
|
|
||||||
// Portions Copyright © 2009 The Go Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
package arm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/gops/internal/obj"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
obj.RegisterRegister(obj.RBaseARM, MAXREG, Rconv)
|
|
||||||
obj.RegisterOpcode(obj.ABaseARM, Anames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Rconv(r int) string {
|
|
||||||
if r == 0 {
|
|
||||||
return "NONE"
|
|
||||||
}
|
|
||||||
if r == REGG {
|
|
||||||
// Special case.
|
|
||||||
return "g"
|
|
||||||
}
|
|
||||||
if REG_R0 <= r && r <= REG_R15 {
|
|
||||||
return fmt.Sprintf("R%d", r-REG_R0)
|
|
||||||
}
|
|
||||||
if REG_F0 <= r && r <= REG_F15 {
|
|
||||||
return fmt.Sprintf("F%d", r-REG_F0)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch r {
|
|
||||||
case REG_FPSR:
|
|
||||||
return "FPSR"
|
|
||||||
|
|
||||||
case REG_FPCR:
|
|
||||||
return "FPCR"
|
|
||||||
|
|
||||||
case REG_CPSR:
|
|
||||||
return "CPSR"
|
|
||||||
|
|
||||||
case REG_SPSR:
|
|
||||||
return "SPSR"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("Rgok(%d)", r-obj.RBaseARM)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DRconv(a int) string {
|
|
||||||
s := "C_??"
|
|
||||||
if a >= C_NONE && a <= C_NCLASS {
|
|
||||||
s = cnames5[a]
|
|
||||||
}
|
|
||||||
var fp string
|
|
||||||
fp += s
|
|
||||||
return fp
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,719 +0,0 @@
|
||||||
// cmd/7c/7.out.h from Vita Nuova.
|
|
||||||
// https://code.google.com/p/ken-cc/source/browse/src/cmd/7c/7.out.h
|
|
||||||
//
|
|
||||||
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
|
|
||||||
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
|
|
||||||
// Portions Copyright © 1997-1999 Vita Nuova Limited
|
|
||||||
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
|
|
||||||
// Portions Copyright © 2004,2006 Bruce Ellis
|
|
||||||
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
|
|
||||||
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
|
|
||||||
// Portions Copyright © 2009 The Go Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
package arm64
|
|
||||||
|
|
||||||
import "github.com/google/gops/internal/obj"
|
|
||||||
|
|
||||||
const (
|
|
||||||
NSNAME = 8
|
|
||||||
NSYM = 50
|
|
||||||
NREG = 32 /* number of general registers */
|
|
||||||
NFREG = 32 /* number of floating point registers */
|
|
||||||
)
|
|
||||||
|
|
||||||
// General purpose registers, kept in the low bits of Prog.Reg.
|
|
||||||
const (
|
|
||||||
// integer
|
|
||||||
REG_R0 = obj.RBaseARM64 + iota
|
|
||||||
REG_R1
|
|
||||||
REG_R2
|
|
||||||
REG_R3
|
|
||||||
REG_R4
|
|
||||||
REG_R5
|
|
||||||
REG_R6
|
|
||||||
REG_R7
|
|
||||||
REG_R8
|
|
||||||
REG_R9
|
|
||||||
REG_R10
|
|
||||||
REG_R11
|
|
||||||
REG_R12
|
|
||||||
REG_R13
|
|
||||||
REG_R14
|
|
||||||
REG_R15
|
|
||||||
REG_R16
|
|
||||||
REG_R17
|
|
||||||
REG_R18
|
|
||||||
REG_R19
|
|
||||||
REG_R20
|
|
||||||
REG_R21
|
|
||||||
REG_R22
|
|
||||||
REG_R23
|
|
||||||
REG_R24
|
|
||||||
REG_R25
|
|
||||||
REG_R26
|
|
||||||
REG_R27
|
|
||||||
REG_R28
|
|
||||||
REG_R29
|
|
||||||
REG_R30
|
|
||||||
REG_R31
|
|
||||||
|
|
||||||
// scalar floating point
|
|
||||||
REG_F0
|
|
||||||
REG_F1
|
|
||||||
REG_F2
|
|
||||||
REG_F3
|
|
||||||
REG_F4
|
|
||||||
REG_F5
|
|
||||||
REG_F6
|
|
||||||
REG_F7
|
|
||||||
REG_F8
|
|
||||||
REG_F9
|
|
||||||
REG_F10
|
|
||||||
REG_F11
|
|
||||||
REG_F12
|
|
||||||
REG_F13
|
|
||||||
REG_F14
|
|
||||||
REG_F15
|
|
||||||
REG_F16
|
|
||||||
REG_F17
|
|
||||||
REG_F18
|
|
||||||
REG_F19
|
|
||||||
REG_F20
|
|
||||||
REG_F21
|
|
||||||
REG_F22
|
|
||||||
REG_F23
|
|
||||||
REG_F24
|
|
||||||
REG_F25
|
|
||||||
REG_F26
|
|
||||||
REG_F27
|
|
||||||
REG_F28
|
|
||||||
REG_F29
|
|
||||||
REG_F30
|
|
||||||
REG_F31
|
|
||||||
|
|
||||||
// SIMD
|
|
||||||
REG_V0
|
|
||||||
REG_V1
|
|
||||||
REG_V2
|
|
||||||
REG_V3
|
|
||||||
REG_V4
|
|
||||||
REG_V5
|
|
||||||
REG_V6
|
|
||||||
REG_V7
|
|
||||||
REG_V8
|
|
||||||
REG_V9
|
|
||||||
REG_V10
|
|
||||||
REG_V11
|
|
||||||
REG_V12
|
|
||||||
REG_V13
|
|
||||||
REG_V14
|
|
||||||
REG_V15
|
|
||||||
REG_V16
|
|
||||||
REG_V17
|
|
||||||
REG_V18
|
|
||||||
REG_V19
|
|
||||||
REG_V20
|
|
||||||
REG_V21
|
|
||||||
REG_V22
|
|
||||||
REG_V23
|
|
||||||
REG_V24
|
|
||||||
REG_V25
|
|
||||||
REG_V26
|
|
||||||
REG_V27
|
|
||||||
REG_V28
|
|
||||||
REG_V29
|
|
||||||
REG_V30
|
|
||||||
REG_V31
|
|
||||||
|
|
||||||
// The EQ in
|
|
||||||
// CSET EQ, R0
|
|
||||||
// is encoded as TYPE_REG, even though it's not really a register.
|
|
||||||
COND_EQ
|
|
||||||
COND_NE
|
|
||||||
COND_HS
|
|
||||||
COND_LO
|
|
||||||
COND_MI
|
|
||||||
COND_PL
|
|
||||||
COND_VS
|
|
||||||
COND_VC
|
|
||||||
COND_HI
|
|
||||||
COND_LS
|
|
||||||
COND_GE
|
|
||||||
COND_LT
|
|
||||||
COND_GT
|
|
||||||
COND_LE
|
|
||||||
COND_AL
|
|
||||||
COND_NV
|
|
||||||
|
|
||||||
REG_RSP = REG_V31 + 32 // to differentiate ZR/SP, REG_RSP&0x1f = 31
|
|
||||||
)
|
|
||||||
|
|
||||||
// Not registers, but flags that can be combined with regular register
|
|
||||||
// constants to indicate extended register conversion. When checking,
|
|
||||||
// you should subtract obj.RBaseARM64 first. From this difference, bit 11
|
|
||||||
// indicates extended register, bits 8-10 select the conversion mode.
|
|
||||||
const REG_EXT = obj.RBaseARM64 + 1<<11
|
|
||||||
|
|
||||||
const (
|
|
||||||
REG_UXTB = REG_EXT + iota<<8
|
|
||||||
REG_UXTH
|
|
||||||
REG_UXTW
|
|
||||||
REG_UXTX
|
|
||||||
REG_SXTB
|
|
||||||
REG_SXTH
|
|
||||||
REG_SXTW
|
|
||||||
REG_SXTX
|
|
||||||
)
|
|
||||||
|
|
||||||
// Special registers, after subtracting obj.RBaseARM64, bit 12 indicates
|
|
||||||
// a special register and the low bits select the register.
|
|
||||||
const (
|
|
||||||
REG_SPECIAL = obj.RBaseARM64 + 1<<12 + iota
|
|
||||||
REG_DAIF
|
|
||||||
REG_NZCV
|
|
||||||
REG_FPSR
|
|
||||||
REG_FPCR
|
|
||||||
REG_SPSR_EL1
|
|
||||||
REG_ELR_EL1
|
|
||||||
REG_SPSR_EL2
|
|
||||||
REG_ELR_EL2
|
|
||||||
REG_CurrentEL
|
|
||||||
REG_SP_EL0
|
|
||||||
REG_SPSel
|
|
||||||
REG_DAIFSet
|
|
||||||
REG_DAIFClr
|
|
||||||
)
|
|
||||||
|
|
||||||
// Register assignments:
|
|
||||||
//
|
|
||||||
// compiler allocates R0 up as temps
|
|
||||||
// compiler allocates register variables R7-R25
|
|
||||||
// compiler allocates external registers R26 down
|
|
||||||
//
|
|
||||||
// compiler allocates register variables F7-F26
|
|
||||||
// compiler allocates external registers F26 down
|
|
||||||
const (
|
|
||||||
REGMIN = REG_R7 // register variables allocated from here to REGMAX
|
|
||||||
REGRT1 = REG_R16 // ARM64 IP0, for external linker, runtime, duffzero and duffcopy
|
|
||||||
REGRT2 = REG_R17 // ARM64 IP1, for external linker, runtime, duffcopy
|
|
||||||
REGPR = REG_R18 // ARM64 platform register, unused in the Go toolchain
|
|
||||||
REGMAX = REG_R25
|
|
||||||
|
|
||||||
REGCTXT = REG_R26 // environment for closures
|
|
||||||
REGTMP = REG_R27 // reserved for liblink
|
|
||||||
REGG = REG_R28 // G
|
|
||||||
REGFP = REG_R29 // frame pointer, unused in the Go toolchain
|
|
||||||
REGLINK = REG_R30
|
|
||||||
|
|
||||||
// ARM64 uses R31 as both stack pointer and zero register,
|
|
||||||
// depending on the instruction. To differentiate RSP from ZR,
|
|
||||||
// we use a different numeric value for REGZERO and REGSP.
|
|
||||||
REGZERO = REG_R31
|
|
||||||
REGSP = REG_RSP
|
|
||||||
|
|
||||||
FREGRET = REG_F0
|
|
||||||
FREGMIN = REG_F7 // first register variable
|
|
||||||
FREGMAX = REG_F26 // last register variable for 7g only
|
|
||||||
FREGEXT = REG_F26 // first external register
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
BIG = 2048 - 8
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
/* mark flags */
|
|
||||||
LABEL = 1 << iota
|
|
||||||
LEAF
|
|
||||||
FLOAT
|
|
||||||
BRANCH
|
|
||||||
LOAD
|
|
||||||
FCMP
|
|
||||||
SYNC
|
|
||||||
LIST
|
|
||||||
FOLL
|
|
||||||
NOSCHED
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
C_NONE = iota
|
|
||||||
C_REG // R0..R30
|
|
||||||
C_RSP // R0..R30, RSP
|
|
||||||
C_FREG // F0..F31
|
|
||||||
C_VREG // V0..V31
|
|
||||||
C_PAIR // (Rn, Rm)
|
|
||||||
C_SHIFT // Rn<<2
|
|
||||||
C_EXTREG // Rn.UXTB<<3
|
|
||||||
C_SPR // REG_NZCV
|
|
||||||
C_COND // EQ, NE, etc
|
|
||||||
|
|
||||||
C_ZCON // $0 or ZR
|
|
||||||
C_ADDCON0 // 12-bit unsigned, unshifted
|
|
||||||
C_ADDCON // 12-bit unsigned, shifted left by 0 or 12
|
|
||||||
C_MOVCON // generated by a 16-bit constant, optionally inverted and/or shifted by multiple of 16
|
|
||||||
C_BITCON // bitfield and logical immediate masks
|
|
||||||
C_ABCON0 // could be C_ADDCON0 or C_BITCON
|
|
||||||
C_ABCON // could be C_ADDCON or C_BITCON
|
|
||||||
C_MBCON // could be C_MOVCON or C_BITCON
|
|
||||||
C_LCON // 32-bit constant
|
|
||||||
C_VCON // 64-bit constant
|
|
||||||
C_FCON // floating-point constant
|
|
||||||
C_VCONADDR // 64-bit memory address
|
|
||||||
|
|
||||||
C_AACON // ADDCON offset in auto constant $a(FP)
|
|
||||||
C_LACON // 32-bit offset in auto constant $a(FP)
|
|
||||||
C_AECON // ADDCON offset in extern constant $e(SB)
|
|
||||||
|
|
||||||
// TODO(aram): only one branch class should be enough
|
|
||||||
C_SBRA // for TYPE_BRANCH
|
|
||||||
C_LBRA
|
|
||||||
|
|
||||||
C_NPAUTO // -512 <= x < 0, 0 mod 8
|
|
||||||
C_NSAUTO // -256 <= x < 0
|
|
||||||
C_PSAUTO // 0 to 255
|
|
||||||
C_PPAUTO // 0 to 504, 0 mod 8
|
|
||||||
C_UAUTO4K // 0 to 4095
|
|
||||||
C_UAUTO8K // 0 to 8190, 0 mod 2
|
|
||||||
C_UAUTO16K // 0 to 16380, 0 mod 4
|
|
||||||
C_UAUTO32K // 0 to 32760, 0 mod 8
|
|
||||||
C_UAUTO64K // 0 to 65520, 0 mod 16
|
|
||||||
C_LAUTO // any other 32-bit constant
|
|
||||||
|
|
||||||
C_SEXT1 // 0 to 4095, direct
|
|
||||||
C_SEXT2 // 0 to 8190
|
|
||||||
C_SEXT4 // 0 to 16380
|
|
||||||
C_SEXT8 // 0 to 32760
|
|
||||||
C_SEXT16 // 0 to 65520
|
|
||||||
C_LEXT
|
|
||||||
|
|
||||||
// TODO(aram): s/AUTO/INDIR/
|
|
||||||
C_ZOREG // 0(R)
|
|
||||||
C_NPOREG // mirror NPAUTO, etc
|
|
||||||
C_NSOREG
|
|
||||||
C_PSOREG
|
|
||||||
C_PPOREG
|
|
||||||
C_UOREG4K
|
|
||||||
C_UOREG8K
|
|
||||||
C_UOREG16K
|
|
||||||
C_UOREG32K
|
|
||||||
C_UOREG64K
|
|
||||||
C_LOREG
|
|
||||||
|
|
||||||
C_ADDR // TODO(aram): explain difference from C_VCONADDR
|
|
||||||
|
|
||||||
// The GOT slot for a symbol in -dynlink mode.
|
|
||||||
C_GOTADDR
|
|
||||||
|
|
||||||
// TLS "var" in local exec mode: will become a constant offset from
|
|
||||||
// thread local base that is ultimately chosen by the program linker.
|
|
||||||
C_TLS_LE
|
|
||||||
|
|
||||||
// TLS "var" in initial exec mode: will become a memory address (chosen
|
|
||||||
// by the program linker) that the dynamic linker will fill with the
|
|
||||||
// offset from the thread local base.
|
|
||||||
C_TLS_IE
|
|
||||||
|
|
||||||
C_ROFF // register offset (including register extended)
|
|
||||||
|
|
||||||
C_GOK
|
|
||||||
C_TEXTSIZE
|
|
||||||
C_NCLASS // must be last
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
C_XPRE = 1 << 6 // match arm.C_WBIT, so Prog.String know how to print it
|
|
||||||
C_XPOST = 1 << 5 // match arm.C_PBIT, so Prog.String know how to print it
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate go run ../stringer.go -i $GOFILE -o anames.go -p arm64
|
|
||||||
|
|
||||||
const (
|
|
||||||
AADC = obj.ABaseARM64 + obj.A_ARCHSPECIFIC + iota
|
|
||||||
AADCS
|
|
||||||
AADCSW
|
|
||||||
AADCW
|
|
||||||
AADD
|
|
||||||
AADDS
|
|
||||||
AADDSW
|
|
||||||
AADDW
|
|
||||||
AADR
|
|
||||||
AADRP
|
|
||||||
AAND
|
|
||||||
AANDS
|
|
||||||
AANDSW
|
|
||||||
AANDW
|
|
||||||
AASR
|
|
||||||
AASRW
|
|
||||||
AAT
|
|
||||||
ABFI
|
|
||||||
ABFIW
|
|
||||||
ABFM
|
|
||||||
ABFMW
|
|
||||||
ABFXIL
|
|
||||||
ABFXILW
|
|
||||||
ABIC
|
|
||||||
ABICS
|
|
||||||
ABICSW
|
|
||||||
ABICW
|
|
||||||
ABRK
|
|
||||||
ACBNZ
|
|
||||||
ACBNZW
|
|
||||||
ACBZ
|
|
||||||
ACBZW
|
|
||||||
ACCMN
|
|
||||||
ACCMNW
|
|
||||||
ACCMP
|
|
||||||
ACCMPW
|
|
||||||
ACINC
|
|
||||||
ACINCW
|
|
||||||
ACINV
|
|
||||||
ACINVW
|
|
||||||
ACLREX
|
|
||||||
ACLS
|
|
||||||
ACLSW
|
|
||||||
ACLZ
|
|
||||||
ACLZW
|
|
||||||
ACMN
|
|
||||||
ACMNW
|
|
||||||
ACMP
|
|
||||||
ACMPW
|
|
||||||
ACNEG
|
|
||||||
ACNEGW
|
|
||||||
ACRC32B
|
|
||||||
ACRC32CB
|
|
||||||
ACRC32CH
|
|
||||||
ACRC32CW
|
|
||||||
ACRC32CX
|
|
||||||
ACRC32H
|
|
||||||
ACRC32W
|
|
||||||
ACRC32X
|
|
||||||
ACSEL
|
|
||||||
ACSELW
|
|
||||||
ACSET
|
|
||||||
ACSETM
|
|
||||||
ACSETMW
|
|
||||||
ACSETW
|
|
||||||
ACSINC
|
|
||||||
ACSINCW
|
|
||||||
ACSINV
|
|
||||||
ACSINVW
|
|
||||||
ACSNEG
|
|
||||||
ACSNEGW
|
|
||||||
ADC
|
|
||||||
ADCPS1
|
|
||||||
ADCPS2
|
|
||||||
ADCPS3
|
|
||||||
ADMB
|
|
||||||
ADRPS
|
|
||||||
ADSB
|
|
||||||
AEON
|
|
||||||
AEONW
|
|
||||||
AEOR
|
|
||||||
AEORW
|
|
||||||
AERET
|
|
||||||
AEXTR
|
|
||||||
AEXTRW
|
|
||||||
AHINT
|
|
||||||
AHLT
|
|
||||||
AHVC
|
|
||||||
AIC
|
|
||||||
AISB
|
|
||||||
ALDAR
|
|
||||||
ALDARB
|
|
||||||
ALDARH
|
|
||||||
ALDARW
|
|
||||||
ALDAXP
|
|
||||||
ALDAXPW
|
|
||||||
ALDAXR
|
|
||||||
ALDAXRB
|
|
||||||
ALDAXRH
|
|
||||||
ALDAXRW
|
|
||||||
ALDP
|
|
||||||
ALDXR
|
|
||||||
ALDXRB
|
|
||||||
ALDXRH
|
|
||||||
ALDXRW
|
|
||||||
ALDXP
|
|
||||||
ALDXPW
|
|
||||||
ALSL
|
|
||||||
ALSLW
|
|
||||||
ALSR
|
|
||||||
ALSRW
|
|
||||||
AMADD
|
|
||||||
AMADDW
|
|
||||||
AMNEG
|
|
||||||
AMNEGW
|
|
||||||
AMOVK
|
|
||||||
AMOVKW
|
|
||||||
AMOVN
|
|
||||||
AMOVNW
|
|
||||||
AMOVZ
|
|
||||||
AMOVZW
|
|
||||||
AMRS
|
|
||||||
AMSR
|
|
||||||
AMSUB
|
|
||||||
AMSUBW
|
|
||||||
AMUL
|
|
||||||
AMULW
|
|
||||||
AMVN
|
|
||||||
AMVNW
|
|
||||||
ANEG
|
|
||||||
ANEGS
|
|
||||||
ANEGSW
|
|
||||||
ANEGW
|
|
||||||
ANGC
|
|
||||||
ANGCS
|
|
||||||
ANGCSW
|
|
||||||
ANGCW
|
|
||||||
AORN
|
|
||||||
AORNW
|
|
||||||
AORR
|
|
||||||
AORRW
|
|
||||||
APRFM
|
|
||||||
APRFUM
|
|
||||||
ARBIT
|
|
||||||
ARBITW
|
|
||||||
AREM
|
|
||||||
AREMW
|
|
||||||
AREV
|
|
||||||
AREV16
|
|
||||||
AREV16W
|
|
||||||
AREV32
|
|
||||||
AREVW
|
|
||||||
AROR
|
|
||||||
ARORW
|
|
||||||
ASBC
|
|
||||||
ASBCS
|
|
||||||
ASBCSW
|
|
||||||
ASBCW
|
|
||||||
ASBFIZ
|
|
||||||
ASBFIZW
|
|
||||||
ASBFM
|
|
||||||
ASBFMW
|
|
||||||
ASBFX
|
|
||||||
ASBFXW
|
|
||||||
ASDIV
|
|
||||||
ASDIVW
|
|
||||||
ASEV
|
|
||||||
ASEVL
|
|
||||||
ASMADDL
|
|
||||||
ASMC
|
|
||||||
ASMNEGL
|
|
||||||
ASMSUBL
|
|
||||||
ASMULH
|
|
||||||
ASMULL
|
|
||||||
ASTXR
|
|
||||||
ASTXRB
|
|
||||||
ASTXRH
|
|
||||||
ASTXP
|
|
||||||
ASTXPW
|
|
||||||
ASTXRW
|
|
||||||
ASTLP
|
|
||||||
ASTLPW
|
|
||||||
ASTLR
|
|
||||||
ASTLRB
|
|
||||||
ASTLRH
|
|
||||||
ASTLRW
|
|
||||||
ASTLXP
|
|
||||||
ASTLXPW
|
|
||||||
ASTLXR
|
|
||||||
ASTLXRB
|
|
||||||
ASTLXRH
|
|
||||||
ASTLXRW
|
|
||||||
ASTP
|
|
||||||
ASUB
|
|
||||||
ASUBS
|
|
||||||
ASUBSW
|
|
||||||
ASUBW
|
|
||||||
ASVC
|
|
||||||
ASXTB
|
|
||||||
ASXTBW
|
|
||||||
ASXTH
|
|
||||||
ASXTHW
|
|
||||||
ASXTW
|
|
||||||
ASYS
|
|
||||||
ASYSL
|
|
||||||
ATBNZ
|
|
||||||
ATBZ
|
|
||||||
ATLBI
|
|
||||||
ATST
|
|
||||||
ATSTW
|
|
||||||
AUBFIZ
|
|
||||||
AUBFIZW
|
|
||||||
AUBFM
|
|
||||||
AUBFMW
|
|
||||||
AUBFX
|
|
||||||
AUBFXW
|
|
||||||
AUDIV
|
|
||||||
AUDIVW
|
|
||||||
AUMADDL
|
|
||||||
AUMNEGL
|
|
||||||
AUMSUBL
|
|
||||||
AUMULH
|
|
||||||
AUMULL
|
|
||||||
AUREM
|
|
||||||
AUREMW
|
|
||||||
AUXTB
|
|
||||||
AUXTH
|
|
||||||
AUXTW
|
|
||||||
AUXTBW
|
|
||||||
AUXTHW
|
|
||||||
AWFE
|
|
||||||
AWFI
|
|
||||||
AYIELD
|
|
||||||
AMOVB
|
|
||||||
AMOVBU
|
|
||||||
AMOVH
|
|
||||||
AMOVHU
|
|
||||||
AMOVW
|
|
||||||
AMOVWU
|
|
||||||
AMOVD
|
|
||||||
AMOVNP
|
|
||||||
AMOVNPW
|
|
||||||
AMOVP
|
|
||||||
AMOVPD
|
|
||||||
AMOVPQ
|
|
||||||
AMOVPS
|
|
||||||
AMOVPSW
|
|
||||||
AMOVPW
|
|
||||||
ABEQ
|
|
||||||
ABNE
|
|
||||||
ABCS
|
|
||||||
ABHS
|
|
||||||
ABCC
|
|
||||||
ABLO
|
|
||||||
ABMI
|
|
||||||
ABPL
|
|
||||||
ABVS
|
|
||||||
ABVC
|
|
||||||
ABHI
|
|
||||||
ABLS
|
|
||||||
ABGE
|
|
||||||
ABLT
|
|
||||||
ABGT
|
|
||||||
ABLE
|
|
||||||
AFABSD
|
|
||||||
AFABSS
|
|
||||||
AFADDD
|
|
||||||
AFADDS
|
|
||||||
AFCCMPD
|
|
||||||
AFCCMPED
|
|
||||||
AFCCMPS
|
|
||||||
AFCCMPES
|
|
||||||
AFCMPD
|
|
||||||
AFCMPED
|
|
||||||
AFCMPES
|
|
||||||
AFCMPS
|
|
||||||
AFCVTSD
|
|
||||||
AFCVTDS
|
|
||||||
AFCVTZSD
|
|
||||||
AFCVTZSDW
|
|
||||||
AFCVTZSS
|
|
||||||
AFCVTZSSW
|
|
||||||
AFCVTZUD
|
|
||||||
AFCVTZUDW
|
|
||||||
AFCVTZUS
|
|
||||||
AFCVTZUSW
|
|
||||||
AFDIVD
|
|
||||||
AFDIVS
|
|
||||||
AFMOVD
|
|
||||||
AFMOVS
|
|
||||||
AFMULD
|
|
||||||
AFMULS
|
|
||||||
AFNEGD
|
|
||||||
AFNEGS
|
|
||||||
AFSQRTD
|
|
||||||
AFSQRTS
|
|
||||||
AFSUBD
|
|
||||||
AFSUBS
|
|
||||||
ASCVTFD
|
|
||||||
ASCVTFS
|
|
||||||
ASCVTFWD
|
|
||||||
ASCVTFWS
|
|
||||||
AUCVTFD
|
|
||||||
AUCVTFS
|
|
||||||
AUCVTFWD
|
|
||||||
AUCVTFWS
|
|
||||||
AWORD
|
|
||||||
ADWORD
|
|
||||||
AFCSELS
|
|
||||||
AFCSELD
|
|
||||||
AFMAXS
|
|
||||||
AFMINS
|
|
||||||
AFMAXD
|
|
||||||
AFMIND
|
|
||||||
AFMAXNMS
|
|
||||||
AFMAXNMD
|
|
||||||
AFNMULS
|
|
||||||
AFNMULD
|
|
||||||
AFRINTNS
|
|
||||||
AFRINTND
|
|
||||||
AFRINTPS
|
|
||||||
AFRINTPD
|
|
||||||
AFRINTMS
|
|
||||||
AFRINTMD
|
|
||||||
AFRINTZS
|
|
||||||
AFRINTZD
|
|
||||||
AFRINTAS
|
|
||||||
AFRINTAD
|
|
||||||
AFRINTXS
|
|
||||||
AFRINTXD
|
|
||||||
AFRINTIS
|
|
||||||
AFRINTID
|
|
||||||
AFMADDS
|
|
||||||
AFMADDD
|
|
||||||
AFMSUBS
|
|
||||||
AFMSUBD
|
|
||||||
AFNMADDS
|
|
||||||
AFNMADDD
|
|
||||||
AFNMSUBS
|
|
||||||
AFNMSUBD
|
|
||||||
AFMINNMS
|
|
||||||
AFMINNMD
|
|
||||||
AFCVTDH
|
|
||||||
AFCVTHS
|
|
||||||
AFCVTHD
|
|
||||||
AFCVTSH
|
|
||||||
AAESD
|
|
||||||
AAESE
|
|
||||||
AAESIMC
|
|
||||||
AAESMC
|
|
||||||
ASHA1C
|
|
||||||
ASHA1H
|
|
||||||
ASHA1M
|
|
||||||
ASHA1P
|
|
||||||
ASHA1SU0
|
|
||||||
ASHA1SU1
|
|
||||||
ASHA256H
|
|
||||||
ASHA256H2
|
|
||||||
ASHA256SU0
|
|
||||||
ASHA256SU1
|
|
||||||
ALAST
|
|
||||||
AB = obj.AJMP
|
|
||||||
ABL = obj.ACALL
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// shift types
|
|
||||||
SHIFT_LL = 0 << 22
|
|
||||||
SHIFT_LR = 1 << 22
|
|
||||||
SHIFT_AR = 2 << 22
|
|
||||||
)
|
|
|
@ -1,370 +0,0 @@
|
||||||
// Generated by stringer -i a.out.go -o anames.go -p arm64
|
|
||||||
// Do not edit.
|
|
||||||
|
|
||||||
package arm64
|
|
||||||
|
|
||||||
import "github.com/google/gops/internal/obj"
|
|
||||||
|
|
||||||
var Anames = []string{
|
|
||||||
obj.A_ARCHSPECIFIC: "ADC",
|
|
||||||
"ADCS",
|
|
||||||
"ADCSW",
|
|
||||||
"ADCW",
|
|
||||||
"ADD",
|
|
||||||
"ADDS",
|
|
||||||
"ADDSW",
|
|
||||||
"ADDW",
|
|
||||||
"ADR",
|
|
||||||
"ADRP",
|
|
||||||
"AND",
|
|
||||||
"ANDS",
|
|
||||||
"ANDSW",
|
|
||||||
"ANDW",
|
|
||||||
"ASR",
|
|
||||||
"ASRW",
|
|
||||||
"AT",
|
|
||||||
"BFI",
|
|
||||||
"BFIW",
|
|
||||||
"BFM",
|
|
||||||
"BFMW",
|
|
||||||
"BFXIL",
|
|
||||||
"BFXILW",
|
|
||||||
"BIC",
|
|
||||||
"BICS",
|
|
||||||
"BICSW",
|
|
||||||
"BICW",
|
|
||||||
"BRK",
|
|
||||||
"CBNZ",
|
|
||||||
"CBNZW",
|
|
||||||
"CBZ",
|
|
||||||
"CBZW",
|
|
||||||
"CCMN",
|
|
||||||
"CCMNW",
|
|
||||||
"CCMP",
|
|
||||||
"CCMPW",
|
|
||||||
"CINC",
|
|
||||||
"CINCW",
|
|
||||||
"CINV",
|
|
||||||
"CINVW",
|
|
||||||
"CLREX",
|
|
||||||
"CLS",
|
|
||||||
"CLSW",
|
|
||||||
"CLZ",
|
|
||||||
"CLZW",
|
|
||||||
"CMN",
|
|
||||||
"CMNW",
|
|
||||||
"CMP",
|
|
||||||
"CMPW",
|
|
||||||
"CNEG",
|
|
||||||
"CNEGW",
|
|
||||||
"CRC32B",
|
|
||||||
"CRC32CB",
|
|
||||||
"CRC32CH",
|
|
||||||
"CRC32CW",
|
|
||||||
"CRC32CX",
|
|
||||||
"CRC32H",
|
|
||||||
"CRC32W",
|
|
||||||
"CRC32X",
|
|
||||||
"CSEL",
|
|
||||||
"CSELW",
|
|
||||||
"CSET",
|
|
||||||
"CSETM",
|
|
||||||
"CSETMW",
|
|
||||||
"CSETW",
|
|
||||||
"CSINC",
|
|
||||||
"CSINCW",
|
|
||||||
"CSINV",
|
|
||||||
"CSINVW",
|
|
||||||
"CSNEG",
|
|
||||||
"CSNEGW",
|
|
||||||
"DC",
|
|
||||||
"DCPS1",
|
|
||||||
"DCPS2",
|
|
||||||
"DCPS3",
|
|
||||||
"DMB",
|
|
||||||
"DRPS",
|
|
||||||
"DSB",
|
|
||||||
"EON",
|
|
||||||
"EONW",
|
|
||||||
"EOR",
|
|
||||||
"EORW",
|
|
||||||
"ERET",
|
|
||||||
"EXTR",
|
|
||||||
"EXTRW",
|
|
||||||
"HINT",
|
|
||||||
"HLT",
|
|
||||||
"HVC",
|
|
||||||
"IC",
|
|
||||||
"ISB",
|
|
||||||
"LDAR",
|
|
||||||
"LDARB",
|
|
||||||
"LDARH",
|
|
||||||
"LDARW",
|
|
||||||
"LDAXP",
|
|
||||||
"LDAXPW",
|
|
||||||
"LDAXR",
|
|
||||||
"LDAXRB",
|
|
||||||
"LDAXRH",
|
|
||||||
"LDAXRW",
|
|
||||||
"LDP",
|
|
||||||
"LDXR",
|
|
||||||
"LDXRB",
|
|
||||||
"LDXRH",
|
|
||||||
"LDXRW",
|
|
||||||
"LDXP",
|
|
||||||
"LDXPW",
|
|
||||||
"LSL",
|
|
||||||
"LSLW",
|
|
||||||
"LSR",
|
|
||||||
"LSRW",
|
|
||||||
"MADD",
|
|
||||||
"MADDW",
|
|
||||||
"MNEG",
|
|
||||||
"MNEGW",
|
|
||||||
"MOVK",
|
|
||||||
"MOVKW",
|
|
||||||
"MOVN",
|
|
||||||
"MOVNW",
|
|
||||||
"MOVZ",
|
|
||||||
"MOVZW",
|
|
||||||
"MRS",
|
|
||||||
"MSR",
|
|
||||||
"MSUB",
|
|
||||||
"MSUBW",
|
|
||||||
"MUL",
|
|
||||||
"MULW",
|
|
||||||
"MVN",
|
|
||||||
"MVNW",
|
|
||||||
"NEG",
|
|
||||||
"NEGS",
|
|
||||||
"NEGSW",
|
|
||||||
"NEGW",
|
|
||||||
"NGC",
|
|
||||||
"NGCS",
|
|
||||||
"NGCSW",
|
|
||||||
"NGCW",
|
|
||||||
"ORN",
|
|
||||||
"ORNW",
|
|
||||||
"ORR",
|
|
||||||
"ORRW",
|
|
||||||
"PRFM",
|
|
||||||
"PRFUM",
|
|
||||||
"RBIT",
|
|
||||||
"RBITW",
|
|
||||||
"REM",
|
|
||||||
"REMW",
|
|
||||||
"REV",
|
|
||||||
"REV16",
|
|
||||||
"REV16W",
|
|
||||||
"REV32",
|
|
||||||
"REVW",
|
|
||||||
"ROR",
|
|
||||||
"RORW",
|
|
||||||
"SBC",
|
|
||||||
"SBCS",
|
|
||||||
"SBCSW",
|
|
||||||
"SBCW",
|
|
||||||
"SBFIZ",
|
|
||||||
"SBFIZW",
|
|
||||||
"SBFM",
|
|
||||||
"SBFMW",
|
|
||||||
"SBFX",
|
|
||||||
"SBFXW",
|
|
||||||
"SDIV",
|
|
||||||
"SDIVW",
|
|
||||||
"SEV",
|
|
||||||
"SEVL",
|
|
||||||
"SMADDL",
|
|
||||||
"SMC",
|
|
||||||
"SMNEGL",
|
|
||||||
"SMSUBL",
|
|
||||||
"SMULH",
|
|
||||||
"SMULL",
|
|
||||||
"STXR",
|
|
||||||
"STXRB",
|
|
||||||
"STXRH",
|
|
||||||
"STXP",
|
|
||||||
"STXPW",
|
|
||||||
"STXRW",
|
|
||||||
"STLP",
|
|
||||||
"STLPW",
|
|
||||||
"STLR",
|
|
||||||
"STLRB",
|
|
||||||
"STLRH",
|
|
||||||
"STLRW",
|
|
||||||
"STLXP",
|
|
||||||
"STLXPW",
|
|
||||||
"STLXR",
|
|
||||||
"STLXRB",
|
|
||||||
"STLXRH",
|
|
||||||
"STLXRW",
|
|
||||||
"STP",
|
|
||||||
"SUB",
|
|
||||||
"SUBS",
|
|
||||||
"SUBSW",
|
|
||||||
"SUBW",
|
|
||||||
"SVC",
|
|
||||||
"SXTB",
|
|
||||||
"SXTBW",
|
|
||||||
"SXTH",
|
|
||||||
"SXTHW",
|
|
||||||
"SXTW",
|
|
||||||
"SYS",
|
|
||||||
"SYSL",
|
|
||||||
"TBNZ",
|
|
||||||
"TBZ",
|
|
||||||
"TLBI",
|
|
||||||
"TST",
|
|
||||||
"TSTW",
|
|
||||||
"UBFIZ",
|
|
||||||
"UBFIZW",
|
|
||||||
"UBFM",
|
|
||||||
"UBFMW",
|
|
||||||
"UBFX",
|
|
||||||
"UBFXW",
|
|
||||||
"UDIV",
|
|
||||||
"UDIVW",
|
|
||||||
"UMADDL",
|
|
||||||
"UMNEGL",
|
|
||||||
"UMSUBL",
|
|
||||||
"UMULH",
|
|
||||||
"UMULL",
|
|
||||||
"UREM",
|
|
||||||
"UREMW",
|
|
||||||
"UXTB",
|
|
||||||
"UXTH",
|
|
||||||
"UXTW",
|
|
||||||
"UXTBW",
|
|
||||||
"UXTHW",
|
|
||||||
"WFE",
|
|
||||||
"WFI",
|
|
||||||
"YIELD",
|
|
||||||
"MOVB",
|
|
||||||
"MOVBU",
|
|
||||||
"MOVH",
|
|
||||||
"MOVHU",
|
|
||||||
"MOVW",
|
|
||||||
"MOVWU",
|
|
||||||
"MOVD",
|
|
||||||
"MOVNP",
|
|
||||||
"MOVNPW",
|
|
||||||
"MOVP",
|
|
||||||
"MOVPD",
|
|
||||||
"MOVPQ",
|
|
||||||
"MOVPS",
|
|
||||||
"MOVPSW",
|
|
||||||
"MOVPW",
|
|
||||||
"BEQ",
|
|
||||||
"BNE",
|
|
||||||
"BCS",
|
|
||||||
"BHS",
|
|
||||||
"BCC",
|
|
||||||
"BLO",
|
|
||||||
"BMI",
|
|
||||||
"BPL",
|
|
||||||
"BVS",
|
|
||||||
"BVC",
|
|
||||||
"BHI",
|
|
||||||
"BLS",
|
|
||||||
"BGE",
|
|
||||||
"BLT",
|
|
||||||
"BGT",
|
|
||||||
"BLE",
|
|
||||||
"FABSD",
|
|
||||||
"FABSS",
|
|
||||||
"FADDD",
|
|
||||||
"FADDS",
|
|
||||||
"FCCMPD",
|
|
||||||
"FCCMPED",
|
|
||||||
"FCCMPS",
|
|
||||||
"FCCMPES",
|
|
||||||
"FCMPD",
|
|
||||||
"FCMPED",
|
|
||||||
"FCMPES",
|
|
||||||
"FCMPS",
|
|
||||||
"FCVTSD",
|
|
||||||
"FCVTDS",
|
|
||||||
"FCVTZSD",
|
|
||||||
"FCVTZSDW",
|
|
||||||
"FCVTZSS",
|
|
||||||
"FCVTZSSW",
|
|
||||||
"FCVTZUD",
|
|
||||||
"FCVTZUDW",
|
|
||||||
"FCVTZUS",
|
|
||||||
"FCVTZUSW",
|
|
||||||
"FDIVD",
|
|
||||||
"FDIVS",
|
|
||||||
"FMOVD",
|
|
||||||
"FMOVS",
|
|
||||||
"FMULD",
|
|
||||||
"FMULS",
|
|
||||||
"FNEGD",
|
|
||||||
"FNEGS",
|
|
||||||
"FSQRTD",
|
|
||||||
"FSQRTS",
|
|
||||||
"FSUBD",
|
|
||||||
"FSUBS",
|
|
||||||
"SCVTFD",
|
|
||||||
"SCVTFS",
|
|
||||||
"SCVTFWD",
|
|
||||||
"SCVTFWS",
|
|
||||||
"UCVTFD",
|
|
||||||
"UCVTFS",
|
|
||||||
"UCVTFWD",
|
|
||||||
"UCVTFWS",
|
|
||||||
"WORD",
|
|
||||||
"DWORD",
|
|
||||||
"FCSELS",
|
|
||||||
"FCSELD",
|
|
||||||
"FMAXS",
|
|
||||||
"FMINS",
|
|
||||||
"FMAXD",
|
|
||||||
"FMIND",
|
|
||||||
"FMAXNMS",
|
|
||||||
"FMAXNMD",
|
|
||||||
"FNMULS",
|
|
||||||
"FNMULD",
|
|
||||||
"FRINTNS",
|
|
||||||
"FRINTND",
|
|
||||||
"FRINTPS",
|
|
||||||
"FRINTPD",
|
|
||||||
"FRINTMS",
|
|
||||||
"FRINTMD",
|
|
||||||
"FRINTZS",
|
|
||||||
"FRINTZD",
|
|
||||||
"FRINTAS",
|
|
||||||
"FRINTAD",
|
|
||||||
"FRINTXS",
|
|
||||||
"FRINTXD",
|
|
||||||
"FRINTIS",
|
|
||||||
"FRINTID",
|
|
||||||
"FMADDS",
|
|
||||||
"FMADDD",
|
|
||||||
"FMSUBS",
|
|
||||||
"FMSUBD",
|
|
||||||
"FNMADDS",
|
|
||||||
"FNMADDD",
|
|
||||||
"FNMSUBS",
|
|
||||||
"FNMSUBD",
|
|
||||||
"FMINNMS",
|
|
||||||
"FMINNMD",
|
|
||||||
"FCVTDH",
|
|
||||||
"FCVTHS",
|
|
||||||
"FCVTHD",
|
|
||||||
"FCVTSH",
|
|
||||||
"AESD",
|
|
||||||
"AESE",
|
|
||||||
"AESIMC",
|
|
||||||
"AESMC",
|
|
||||||
"SHA1C",
|
|
||||||
"SHA1H",
|
|
||||||
"SHA1M",
|
|
||||||
"SHA1P",
|
|
||||||
"SHA1SU0",
|
|
||||||
"SHA1SU1",
|
|
||||||
"SHA256H",
|
|
||||||
"SHA256H2",
|
|
||||||
"SHA256SU0",
|
|
||||||
"SHA256SU1",
|
|
||||||
"LAST",
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package arm64
|
|
||||||
|
|
||||||
var cnames7 = []string{
|
|
||||||
"NONE",
|
|
||||||
"REG",
|
|
||||||
"RSP",
|
|
||||||
"FREG",
|
|
||||||
"VREG",
|
|
||||||
"PAIR",
|
|
||||||
"SHIFT",
|
|
||||||
"EXTREG",
|
|
||||||
"SPR",
|
|
||||||
"COND",
|
|
||||||
"ZCON",
|
|
||||||
"ADDCON0",
|
|
||||||
"ADDCON",
|
|
||||||
"MOVCON",
|
|
||||||
"BITCON",
|
|
||||||
"ABCON0",
|
|
||||||
"ABCON",
|
|
||||||
"MBCON",
|
|
||||||
"LCON",
|
|
||||||
"VCON",
|
|
||||||
"FCON",
|
|
||||||
"VCONADDR",
|
|
||||||
"AACON",
|
|
||||||
"LACON",
|
|
||||||
"AECON",
|
|
||||||
"SBRA",
|
|
||||||
"LBRA",
|
|
||||||
"NPAUTO",
|
|
||||||
"NSAUTO",
|
|
||||||
"PSAUTO",
|
|
||||||
"PPAUTO",
|
|
||||||
"UAUTO4K",
|
|
||||||
"UAUTO8K",
|
|
||||||
"UAUTO16K",
|
|
||||||
"UAUTO32K",
|
|
||||||
"UAUTO64K",
|
|
||||||
"LAUTO",
|
|
||||||
"SEXT1",
|
|
||||||
"SEXT2",
|
|
||||||
"SEXT4",
|
|
||||||
"SEXT8",
|
|
||||||
"SEXT16",
|
|
||||||
"LEXT",
|
|
||||||
"ZOREG",
|
|
||||||
"NPOREG",
|
|
||||||
"NSOREG",
|
|
||||||
"PSOREG",
|
|
||||||
"PPOREG",
|
|
||||||
"UOREG4K",
|
|
||||||
"UOREG8K",
|
|
||||||
"UOREG16K",
|
|
||||||
"UOREG32K",
|
|
||||||
"UOREG64K",
|
|
||||||
"LOREG",
|
|
||||||
"ADDR",
|
|
||||||
"GOTADDR",
|
|
||||||
"TLS_LE",
|
|
||||||
"TLS_IE",
|
|
||||||
"ROFF",
|
|
||||||
"GOK",
|
|
||||||
"TEXTSIZE",
|
|
||||||
"NCLASS",
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,115 +0,0 @@
|
||||||
// cmd/7l/list.c and cmd/7l/sub.c from Vita Nuova.
|
|
||||||
// https://code.google.com/p/ken-cc/source/browse/
|
|
||||||
//
|
|
||||||
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
|
|
||||||
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
|
|
||||||
// Portions Copyright © 1997-1999 Vita Nuova Limited
|
|
||||||
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
|
|
||||||
// Portions Copyright © 2004,2006 Bruce Ellis
|
|
||||||
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
|
|
||||||
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
|
|
||||||
// Portions Copyright © 2009 The Go Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
package arm64
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/gops/internal/obj"
|
|
||||||
)
|
|
||||||
|
|
||||||
var strcond = [16]string{
|
|
||||||
"EQ",
|
|
||||||
"NE",
|
|
||||||
"HS",
|
|
||||||
"LO",
|
|
||||||
"MI",
|
|
||||||
"PL",
|
|
||||||
"VS",
|
|
||||||
"VC",
|
|
||||||
"HI",
|
|
||||||
"LS",
|
|
||||||
"GE",
|
|
||||||
"LT",
|
|
||||||
"GT",
|
|
||||||
"LE",
|
|
||||||
"AL",
|
|
||||||
"NV",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
obj.RegisterRegister(obj.RBaseARM64, REG_SPECIAL+1024, Rconv)
|
|
||||||
obj.RegisterOpcode(obj.ABaseARM64, Anames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Rconv(r int) string {
|
|
||||||
if r == REGG {
|
|
||||||
return "g"
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case REG_R0 <= r && r <= REG_R30:
|
|
||||||
return fmt.Sprintf("R%d", r-REG_R0)
|
|
||||||
case r == REG_R31:
|
|
||||||
return "ZR"
|
|
||||||
case REG_F0 <= r && r <= REG_F31:
|
|
||||||
return fmt.Sprintf("F%d", r-REG_F0)
|
|
||||||
case REG_V0 <= r && r <= REG_V31:
|
|
||||||
return fmt.Sprintf("V%d", r-REG_V0)
|
|
||||||
case COND_EQ <= r && r <= COND_NV:
|
|
||||||
return strcond[r-COND_EQ]
|
|
||||||
case r == REGSP:
|
|
||||||
return "RSP"
|
|
||||||
case r == REG_DAIF:
|
|
||||||
return "DAIF"
|
|
||||||
case r == REG_NZCV:
|
|
||||||
return "NZCV"
|
|
||||||
case r == REG_FPSR:
|
|
||||||
return "FPSR"
|
|
||||||
case r == REG_FPCR:
|
|
||||||
return "FPCR"
|
|
||||||
case r == REG_SPSR_EL1:
|
|
||||||
return "SPSR_EL1"
|
|
||||||
case r == REG_ELR_EL1:
|
|
||||||
return "ELR_EL1"
|
|
||||||
case r == REG_SPSR_EL2:
|
|
||||||
return "SPSR_EL2"
|
|
||||||
case r == REG_ELR_EL2:
|
|
||||||
return "ELR_EL2"
|
|
||||||
case r == REG_CurrentEL:
|
|
||||||
return "CurrentEL"
|
|
||||||
case r == REG_SP_EL0:
|
|
||||||
return "SP_EL0"
|
|
||||||
case r == REG_SPSel:
|
|
||||||
return "SPSel"
|
|
||||||
case r == REG_DAIFSet:
|
|
||||||
return "DAIFSet"
|
|
||||||
case r == REG_DAIFClr:
|
|
||||||
return "DAIFClr"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("badreg(%d)", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DRconv(a int) string {
|
|
||||||
if a >= C_NONE && a <= C_NCLASS {
|
|
||||||
return cnames7[a]
|
|
||||||
}
|
|
||||||
return "C_??"
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,190 +0,0 @@
|
||||||
// Derived from Inferno utils/6l/obj.c and utils/6l/span.c
|
|
||||||
// https://bitbucket.org/inferno-os/inferno-os/src/default/utils/6l/obj.c
|
|
||||||
// https://bitbucket.org/inferno-os/inferno-os/src/default/utils/6l/span.c
|
|
||||||
//
|
|
||||||
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
|
|
||||||
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
|
|
||||||
// Portions Copyright © 1997-1999 Vita Nuova Limited
|
|
||||||
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
|
|
||||||
// Portions Copyright © 2004,2006 Bruce Ellis
|
|
||||||
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
|
|
||||||
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
|
|
||||||
// Portions Copyright © 2009 The Go Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
package obj
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Grow increases the length of s.P to lsiz.
|
|
||||||
func (s *LSym) Grow(lsiz int64) {
|
|
||||||
siz := int(lsiz)
|
|
||||||
if int64(siz) != lsiz {
|
|
||||||
log.Fatalf("LSym.Grow size %d too long", lsiz)
|
|
||||||
}
|
|
||||||
if len(s.P) >= siz {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO(dfc) append cap-len at once, rather than
|
|
||||||
// one byte at a time.
|
|
||||||
for cap(s.P) < siz {
|
|
||||||
s.P = append(s.P[:cap(s.P)], 0)
|
|
||||||
}
|
|
||||||
s.P = s.P[:siz]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GrowCap increases the capacity of s.P to c.
|
|
||||||
func (s *LSym) GrowCap(c int64) {
|
|
||||||
if int64(cap(s.P)) >= c {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.P == nil {
|
|
||||||
s.P = make([]byte, 0, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b := make([]byte, len(s.P), c)
|
|
||||||
copy(b, s.P)
|
|
||||||
s.P = b
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepwrite prepares to write data of size siz into s at offset off.
|
|
||||||
func (s *LSym) prepwrite(ctxt *Link, off int64, siz int) {
|
|
||||||
if off < 0 || siz < 0 || off >= 1<<30 {
|
|
||||||
log.Fatalf("prepwrite: bad off=%d siz=%d", off, siz)
|
|
||||||
}
|
|
||||||
if s.Type == SBSS || s.Type == STLSBSS {
|
|
||||||
ctxt.Diag("cannot supply data for BSS var")
|
|
||||||
}
|
|
||||||
l := off + int64(siz)
|
|
||||||
s.Grow(l)
|
|
||||||
if l > s.Size {
|
|
||||||
s.Size = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteFloat32 writes f into s at offset off.
|
|
||||||
func (s *LSym) WriteFloat32(ctxt *Link, off int64, f float32) {
|
|
||||||
s.prepwrite(ctxt, off, 4)
|
|
||||||
ctxt.Arch.ByteOrder.PutUint32(s.P[off:], math.Float32bits(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteFloat64 writes f into s at offset off.
|
|
||||||
func (s *LSym) WriteFloat64(ctxt *Link, off int64, f float64) {
|
|
||||||
s.prepwrite(ctxt, off, 8)
|
|
||||||
ctxt.Arch.ByteOrder.PutUint64(s.P[off:], math.Float64bits(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteInt writes an integer i of size siz into s at offset off.
|
|
||||||
func (s *LSym) WriteInt(ctxt *Link, off int64, siz int, i int64) {
|
|
||||||
s.prepwrite(ctxt, off, siz)
|
|
||||||
switch siz {
|
|
||||||
default:
|
|
||||||
ctxt.Diag("WriteInt: bad integer size: %d", siz)
|
|
||||||
case 1:
|
|
||||||
s.P[off] = byte(i)
|
|
||||||
case 2:
|
|
||||||
ctxt.Arch.ByteOrder.PutUint16(s.P[off:], uint16(i))
|
|
||||||
case 4:
|
|
||||||
ctxt.Arch.ByteOrder.PutUint32(s.P[off:], uint32(i))
|
|
||||||
case 8:
|
|
||||||
ctxt.Arch.ByteOrder.PutUint64(s.P[off:], uint64(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAddr writes an address of size siz into s at offset off.
|
|
||||||
// rsym and roff specify the relocation for the address.
|
|
||||||
func (s *LSym) WriteAddr(ctxt *Link, off int64, siz int, rsym *LSym, roff int64) {
|
|
||||||
if siz != ctxt.Arch.PtrSize {
|
|
||||||
ctxt.Diag("WriteAddr: bad address size %d in %s", siz, s.Name)
|
|
||||||
}
|
|
||||||
s.prepwrite(ctxt, off, siz)
|
|
||||||
r := Addrel(s)
|
|
||||||
r.Off = int32(off)
|
|
||||||
if int64(r.Off) != off {
|
|
||||||
ctxt.Diag("WriteAddr: off overflow %d in %s", off, s.Name)
|
|
||||||
}
|
|
||||||
r.Siz = uint8(siz)
|
|
||||||
r.Sym = rsym
|
|
||||||
r.Type = R_ADDR
|
|
||||||
r.Add = roff
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteOff writes a 4 byte offset to rsym+roff into s at offset off.
|
|
||||||
// After linking the 4 bytes stored at s+off will be
|
|
||||||
// rsym+roff-(start of section that s is in).
|
|
||||||
func (s *LSym) WriteOff(ctxt *Link, off int64, rsym *LSym, roff int64) {
|
|
||||||
s.prepwrite(ctxt, off, 4)
|
|
||||||
r := Addrel(s)
|
|
||||||
r.Off = int32(off)
|
|
||||||
if int64(r.Off) != off {
|
|
||||||
ctxt.Diag("WriteOff: off overflow %d in %s", off, s.Name)
|
|
||||||
}
|
|
||||||
r.Siz = 4
|
|
||||||
r.Sym = rsym
|
|
||||||
r.Type = R_ADDROFF
|
|
||||||
r.Add = roff
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteString writes a string of size siz into s at offset off.
|
|
||||||
func (s *LSym) WriteString(ctxt *Link, off int64, siz int, str string) {
|
|
||||||
if siz < len(str) {
|
|
||||||
ctxt.Diag("WriteString: bad string size: %d < %d", siz, len(str))
|
|
||||||
}
|
|
||||||
s.prepwrite(ctxt, off, siz)
|
|
||||||
copy(s.P[off:off+int64(siz)], str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteBytes writes a slice of bytes into s at offset off.
|
|
||||||
func (s *LSym) WriteBytes(ctxt *Link, off int64, b []byte) int64 {
|
|
||||||
s.prepwrite(ctxt, off, len(b))
|
|
||||||
copy(s.P[off:], b)
|
|
||||||
return off + int64(len(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Addrel(s *LSym) *Reloc {
|
|
||||||
s.R = append(s.R, Reloc{})
|
|
||||||
return &s.R[len(s.R)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func Setuintxx(ctxt *Link, s *LSym, off int64, v uint64, wid int64) int64 {
|
|
||||||
if s.Type == 0 {
|
|
||||||
s.Type = SDATA
|
|
||||||
}
|
|
||||||
if s.Size < off+wid {
|
|
||||||
s.Size = off + wid
|
|
||||||
s.Grow(s.Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch wid {
|
|
||||||
case 1:
|
|
||||||
s.P[off] = uint8(v)
|
|
||||||
case 2:
|
|
||||||
ctxt.Arch.ByteOrder.PutUint16(s.P[off:], uint16(v))
|
|
||||||
case 4:
|
|
||||||
ctxt.Arch.ByteOrder.PutUint32(s.P[off:], uint32(v))
|
|
||||||
case 8:
|
|
||||||
ctxt.Arch.ByteOrder.PutUint64(s.P[off:], v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return off + wid
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package obj
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Flagfn2(string, string, func(string, string)) { panic("flag") }
|
|
||||||
|
|
||||||
func Flagcount(name, usage string, val *int) {
|
|
||||||
flag.Var((*count)(val), name, usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flagint32(name, usage string, val *int32) {
|
|
||||||
flag.Var((*int32Value)(val), name, usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flagint64(name, usage string, val *int64) {
|
|
||||||
flag.Int64Var(val, name, *val, usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flagstr(name, usage string, val *string) {
|
|
||||||
flag.StringVar(val, name, *val, usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flagfn0(name, usage string, f func()) {
|
|
||||||
flag.Var(fn0(f), name, usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flagfn1(name, usage string, f func(string)) {
|
|
||||||
flag.Var(fn1(f), name, usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flagprint(fd int) {
|
|
||||||
if fd == 1 {
|
|
||||||
flag.CommandLine.SetOutput(os.Stdout)
|
|
||||||
}
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flagparse(usage func()) {
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
// count is a flag.Value that is like a flag.Bool and a flag.Int.
|
|
||||||
// If used as -name, it increments the count, but -name=x sets the count.
|
|
||||||
// Used for verbose flag -v.
|
|
||||||
type count int
|
|
||||||
|
|
||||||
func (c *count) String() string {
|
|
||||||
return fmt.Sprint(int(*c))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *count) Set(s string) error {
|
|
||||||
switch s {
|
|
||||||
case "true":
|
|
||||||
*c++
|
|
||||||
case "false":
|
|
||||||
*c = 0
|
|
||||||
default:
|
|
||||||
n, err := strconv.Atoi(s)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid count %q", s)
|
|
||||||
}
|
|
||||||
*c = count(n)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *count) IsBoolFlag() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type int32Value int32
|
|
||||||
|
|
||||||
func (i *int32Value) Set(s string) error {
|
|
||||||
v, err := strconv.ParseInt(s, 0, 64)
|
|
||||||
*i = int32Value(v)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *int32Value) Get() interface{} { return int32(*i) }
|
|
||||||
|
|
||||||
func (i *int32Value) String() string { return fmt.Sprint(*i) }
|
|
||||||
|
|
||||||
type fn0 func()
|
|
||||||
|
|
||||||
func (f fn0) Set(s string) error {
|
|
||||||
f()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f fn0) Get() interface{} { return nil }
|
|
||||||
|
|
||||||
func (f fn0) String() string { return "" }
|
|
||||||
|
|
||||||
func (f fn0) IsBoolFlag() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type fn1 func(string)
|
|
||||||
|
|
||||||
func (f fn1) Set(s string) error {
|
|
||||||
f(s)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f fn1) String() string { return "" }
|
|
|
@ -1,48 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package obj
|
|
||||||
|
|
||||||
// This file defines the IDs for PCDATA and FUNCDATA instructions
|
|
||||||
// in Go binaries. It is included by assembly sources, so it must
|
|
||||||
// be written using #defines.
|
|
||||||
//
|
|
||||||
// The Go compiler also #includes this file, for now.
|
|
||||||
//
|
|
||||||
// symtab.go also contains a copy of these constants.
|
|
||||||
|
|
||||||
// Pseudo-assembly statements.
|
|
||||||
|
|
||||||
// GO_ARGS, GO_RESULTS_INITIALIZED, and NO_LOCAL_POINTERS are macros
|
|
||||||
// that communicate to the runtime information about the location and liveness
|
|
||||||
// of pointers in an assembly function's arguments, results, and stack frame.
|
|
||||||
// This communication is only required in assembly functions that make calls
|
|
||||||
// to other functions that might be preempted or grow the stack.
|
|
||||||
// NOSPLIT functions that make no calls do not need to use these macros.
|
|
||||||
|
|
||||||
// GO_ARGS indicates that the Go prototype for this assembly function
|
|
||||||
// defines the pointer map for the function's arguments.
|
|
||||||
// GO_ARGS should be the first instruction in a function that uses it.
|
|
||||||
// It can be omitted if there are no arguments at all.
|
|
||||||
// GO_ARGS is inserted implicitly by the linker for any function
|
|
||||||
// that also has a Go prototype and therefore is usually not necessary
|
|
||||||
// to write explicitly.
|
|
||||||
|
|
||||||
// GO_RESULTS_INITIALIZED indicates that the assembly function
|
|
||||||
// has initialized the stack space for its results and that those results
|
|
||||||
// should be considered live for the remainder of the function.
|
|
||||||
|
|
||||||
// NO_LOCAL_POINTERS indicates that the assembly function stores
|
|
||||||
// no pointers to heap objects in its local stack variables.
|
|
||||||
|
|
||||||
// ArgsSizeUnknown is set in Func.argsize to mark all functions
|
|
||||||
// whose argument size is unknown (C vararg functions, and
|
|
||||||
// assembly code without an explicit specification).
|
|
||||||
// This value is generated by the compiler, assembler, or linker.
|
|
||||||
const (
|
|
||||||
PCDATA_StackMapIndex = 0
|
|
||||||
FUNCDATA_ArgsPointerMaps = 0
|
|
||||||
FUNCDATA_LocalsPointerMaps = 1
|
|
||||||
ArgsSizeUnknown = -0x80000000
|
|
||||||
)
|
|
|
@ -1,86 +0,0 @@
|
||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package obj
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// go-specific code shared across loaders (5l, 6l, 8l).
|
|
||||||
|
|
||||||
var (
|
|
||||||
framepointer_enabled int
|
|
||||||
Fieldtrack_enabled int
|
|
||||||
)
|
|
||||||
|
|
||||||
// Toolchain experiments.
|
|
||||||
// These are controlled by the GOEXPERIMENT environment
|
|
||||||
// variable recorded when the toolchain is built.
|
|
||||||
// This list is also known to cmd/gc.
|
|
||||||
var exper = []struct {
|
|
||||||
name string
|
|
||||||
val *int
|
|
||||||
}{
|
|
||||||
{"fieldtrack", &Fieldtrack_enabled},
|
|
||||||
{"framepointer", &framepointer_enabled},
|
|
||||||
}
|
|
||||||
|
|
||||||
func addexp(s string) {
|
|
||||||
// Could do general integer parsing here, but the runtime copy doesn't yet.
|
|
||||||
v := 1
|
|
||||||
name := s
|
|
||||||
if len(name) > 2 && name[:2] == "no" {
|
|
||||||
v = 0
|
|
||||||
name = name[2:]
|
|
||||||
}
|
|
||||||
for i := 0; i < len(exper); i++ {
|
|
||||||
if exper[i].name == name {
|
|
||||||
if exper[i].val != nil {
|
|
||||||
*exper[i].val = v
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("unknown experiment %s\n", s)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
framepointer_enabled = 1 // default
|
|
||||||
for _, f := range strings.Split(goexperiment, ",") {
|
|
||||||
if f != "" {
|
|
||||||
addexp(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Framepointer_enabled(goos, goarch string) bool {
|
|
||||||
return framepointer_enabled != 0 && goarch == "amd64" && goos != "nacl"
|
|
||||||
}
|
|
||||||
|
|
||||||
func Nopout(p *Prog) {
|
|
||||||
p.As = ANOP
|
|
||||||
p.Scond = 0
|
|
||||||
p.From = Addr{}
|
|
||||||
p.From3 = nil
|
|
||||||
p.Reg = 0
|
|
||||||
p.To = Addr{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Expstring() string {
|
|
||||||
buf := "X"
|
|
||||||
for i := range exper {
|
|
||||||
if *exper[i].val != 0 {
|
|
||||||
buf += "," + exper[i].name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if buf == "X" {
|
|
||||||
buf += ",none"
|
|
||||||
}
|
|
||||||
return "X:" + buf[2:]
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue