From 52bc6ad2cb63056f2c0349fff0420d360516775e Mon Sep 17 00:00:00 2001 From: Torin Sandall Date: Wed, 4 May 2016 13:24:44 -0700 Subject: [PATCH 1/2] Add github.com/pkg/errors and update deps --- glide.lock | 19 +- glide.yaml | 1 + .../github.com/golang/lint/golint/golint.go | 12 +- vendor/github.com/golang/lint/lint.go | 11 + .../github.com/golang/lint/testdata/names.go | 4 + .../peterh/liner/.github/CONTRIBUTING.md | 6 + vendor/github.com/peterh/liner/bsdinput.go | 2 + vendor/github.com/peterh/liner/input.go | 3 +- .../github.com/peterh/liner/input_darwin.go | 4 + vendor/github.com/peterh/liner/input_linux.go | 2 + .../github.com/peterh/liner/input_windows.go | 3 + vendor/github.com/peterh/liner/output.go | 10 +- .../github.com/peterh/liner/output_windows.go | 4 + vendor/github.com/pkg/errors/.gitignore | 24 ++ vendor/github.com/pkg/errors/.travis.yml | 10 + vendor/github.com/pkg/errors/LICENSE | 24 ++ vendor/github.com/pkg/errors/README.md | 52 +++ vendor/github.com/pkg/errors/errors.go | 248 ++++++++++++ vendor/github.com/pkg/errors/errors_test.go | 189 +++++++++ vendor/github.com/pkg/errors/example_test.go | 71 ++++ vendor/github.com/spf13/cobra/README.md | 17 +- .../spf13/cobra/cobra/cmd/helpers.go | 9 + .../github.com/spf13/cobra/cobra/cmd/init.go | 59 ++- .../spf13/cobra/cobra/cmd/licenses.go | 379 +++++++++++++++--- .../github.com/spf13/cobra/cobra/cmd/root.go | 14 +- vendor/github.com/spf13/cobra/command.go | 32 +- vendor/github.com/spf13/pflag/README.md | 2 +- vendor/github.com/spf13/pflag/flag.go | 3 + .../golang.org/x/tools/cmd/godoc/handlers.go | 1 + .../golang.org/x/tools/cmd/guru/definition.go | 99 +++++ vendor/golang.org/x/tools/cmd/guru/go-guru.el | 320 ++++++++------- .../golang.org/x/tools/cmd/guru/guru_test.go | 39 +- .../golang.org/x/tools/cmd/guru/implements.go | 8 +- .../guru/testdata/src/definition-json/main.go | 44 ++ .../testdata/src/definition-json/main.golden | 67 ++++ .../cmd/guru/testdata/src/implements/main.go | 5 + .../guru/testdata/src/implements/main.golden | 6 + .../testdata/src/referrers-json/main.golden | 33 ++ .../guru/testdata/src/referrers/main.golden | 35 +- .../x/tools/cmd/html2article/conv.go | 74 ++-- vendor/golang.org/x/tools/cmd/tip/Dockerfile | 4 +- vendor/golang.org/x/tools/cmd/tip/talks.go | 2 +- vendor/golang.org/x/tools/cmd/tip/tip.go | 22 +- .../x/tools/go/gcimporter15/bexport.go | 93 ++++- .../x/tools/go/gcimporter15/bimport.go | 32 +- .../golang.org/x/tools/go/ssa/builder_test.go | 5 +- .../x/tools/go/ssa/interp/external.go | 6 + .../x/tools/go/ssa/interp/interp.go | 17 +- vendor/golang.org/x/tools/godoc/godoc.go | 2 +- vendor/golang.org/x/tools/imports/imports.go | 2 + vendor/golang.org/x/tools/imports/mkstdlib.go | 7 +- vendor/golang.org/x/tools/imports/zstdlib.go | 144 ++++++- .../golang.org/x/tools/playground/common.go | 2 +- 53 files changed, 1882 insertions(+), 401 deletions(-) create mode 100644 vendor/github.com/pkg/errors/.gitignore create mode 100644 vendor/github.com/pkg/errors/.travis.yml create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/README.md create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/errors_test.go create mode 100644 vendor/github.com/pkg/errors/example_test.go create mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main.go create mode 100644 vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main.golden diff --git a/glide.lock b/glide.lock index 8c67ba936e..e59efaf747 100644 --- a/glide.lock +++ b/glide.lock @@ -1,22 +1,27 @@ -hash: b87d4690bc63145c6d84283e7db811522ea81720e06458026768ccf55ae1acf5 -updated: 2016-04-22T11:25:26.611039123-07:00 +hash: b83f7fe2bc145c3d1fb1b4d2dfcd824133609907df0a90ca9372bd4d2edf34b6 +updated: 2016-05-04T13:23:16.671370976-07:00 imports: - name: github.com/apcera/termtables version: 683d3fb424194f29991b0442b37521de9bc9d66b + subpackages: + - locale + - term - name: github.com/golang/lint - version: 8f348af5e29faa4262efdc14302797f23774e477 + version: c7bacac2b21ca01afa1dee0acf64df3ce047c28f - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/peterh/liner - version: 49ca65981c3cd7db64145977af1d186e9d317afa + version: 0e4af131b90a9786839c8b1b01717be263e8555a +- name: github.com/pkg/errors + version: 0d62637d0446292fceb852ab68e3ce6e07bddc00 - name: github.com/PuerkitoBio/pigeon version: a5221784523de14130c00a8c389148a1b2ad260c - name: github.com/spf13/cobra - version: 4c05eb1145f16d0e6bb4a3e1b6d769f4713cb41f + version: 336d629de064dbb12166b46c06039dbcfb935de1 - name: github.com/spf13/pflag - version: 8f6a28b0916586e7f22fe931ae2fcfc380b1c0e6 + version: cb88ea77998c3f024757528e3305022ab50b43be - name: golang.org/x/tools - version: 477d3b98e5c650e877b858e6c26b9de2ef46341a + version: 0b2f4dcf4db5371000beb66b65cde17aa91a28a2 subpackages: - cmd/goimports devImports: [] diff --git a/glide.yaml b/glide.yaml index 3dd2df0a9f..b092e75acd 100644 --- a/glide.yaml +++ b/glide.yaml @@ -8,3 +8,4 @@ import: - package: github.com/golang/lint - package: github.com/peterh/liner - package: github.com/apcera/termtables +- package: github.com/pkg/errors diff --git a/vendor/github.com/golang/lint/golint/golint.go b/vendor/github.com/golang/lint/golint/golint.go index eb39199efa..83b0e2657c 100644 --- a/vendor/github.com/golang/lint/golint/golint.go +++ b/vendor/github.com/golang/lint/golint/golint.go @@ -19,7 +19,11 @@ import ( "github.com/golang/lint" ) -var minConfidence = flag.Float64("min_confidence", 0.8, "minimum confidence of a problem to print it") +var ( + minConfidence = flag.Float64("min_confidence", 0.8, "minimum confidence of a problem to print it") + setExitStatus = flag.Bool("set_exit_status", false, "set exit status to 1 if any issues are found") + suggestions int +) func usage() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) @@ -56,6 +60,11 @@ func main() { default: lintFiles(flag.Args()...) } + + if *setExitStatus && suggestions > 0 { + fmt.Fprintf(os.Stderr, "Found %d lint suggestions; failing.\n", suggestions) + os.Exit(1) + } } func isDir(filename string) bool { @@ -88,6 +97,7 @@ func lintFiles(filenames ...string) { for _, p := range ps { if p.Confidence >= *minConfidence { fmt.Printf("%v: %s\n", p.Position, p.Text) + suggestions++ } } } diff --git a/vendor/github.com/golang/lint/lint.go b/vendor/github.com/golang/lint/lint.go index e84f46daf1..6315eba685 100644 --- a/vendor/github.com/golang/lint/lint.go +++ b/vendor/github.com/golang/lint/lint.go @@ -522,6 +522,14 @@ func (f *file) lintExported() { var allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`) +// knownNameExceptions is a set of names that are known to be exempt from naming checks. +// This is usually because they are constrained by having to match names in the +// standard library. +var knownNameExceptions = map[string]bool{ + "LastInsertId": true, // must match database/sql + "kWh": true, +} + // lintNames examines all names in the file. // It complains if any use underscores or incorrect known initialisms. func (f *file) lintNames() { @@ -534,6 +542,9 @@ func (f *file) lintNames() { if id.Name == "_" { return } + if knownNameExceptions[id.Name] { + return + } // Handle two common styles from other languages that don't belong in Go. if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") { diff --git a/vendor/github.com/golang/lint/testdata/names.go b/vendor/github.com/golang/lint/testdata/names.go index 4e84597b58..f884fabcec 100644 --- a/vendor/github.com/golang/lint/testdata/names.go +++ b/vendor/github.com/golang/lint/testdata/names.go @@ -85,3 +85,7 @@ func case3_1(case3_2 int) (case3_3 string) { return "" } + +type t struct{} + +func (t) LastInsertId() (int64, error) { return 0, nil } // okay because it matches a known style violation diff --git a/vendor/github.com/peterh/liner/.github/CONTRIBUTING.md b/vendor/github.com/peterh/liner/.github/CONTRIBUTING.md index 5c899148a1..e299cf06c8 100644 --- a/vendor/github.com/peterh/liner/.github/CONTRIBUTING.md +++ b/vendor/github.com/peterh/liner/.github/CONTRIBUTING.md @@ -7,3 +7,9 @@ If you are opening a feature request, you are implicitly volunteering to implement that feature. Obvious feature requests should be made via a pull request. Complex feature requests will be interpreted as a request-for-comments, and will be closed once comments are given. + +#### Liner must remain backwards compatible + +The API of Liner must not change in an incompatible way. When making +changes to liner, please use the [Go 1 Compatibility Promise](https://golang.org/doc/go1compat) +as a guideline. diff --git a/vendor/github.com/peterh/liner/bsdinput.go b/vendor/github.com/peterh/liner/bsdinput.go index 4b552d44d9..35933982f1 100644 --- a/vendor/github.com/peterh/liner/bsdinput.go +++ b/vendor/github.com/peterh/liner/bsdinput.go @@ -37,3 +37,5 @@ type termios struct { Ispeed int32 Ospeed int32 } + +const cursorColumn = false diff --git a/vendor/github.com/peterh/liner/input.go b/vendor/github.com/peterh/liner/input.go index c80c85f69d..0ee6be7afa 100644 --- a/vendor/github.com/peterh/liner/input.go +++ b/vendor/github.com/peterh/liner/input.go @@ -67,8 +67,7 @@ func NewLiner() *State { } if !s.outputRedirected { - s.getColumns() - s.outputRedirected = s.columns <= 0 + s.outputRedirected = !s.getColumns() } return &s diff --git a/vendor/github.com/peterh/liner/input_darwin.go b/vendor/github.com/peterh/liner/input_darwin.go index 23c9c5da0f..e98ab4a49f 100644 --- a/vendor/github.com/peterh/liner/input_darwin.go +++ b/vendor/github.com/peterh/liner/input_darwin.go @@ -37,3 +37,7 @@ type termios struct { Ispeed uintptr Ospeed uintptr } + +// Terminal.app needs a column for the cursor when the input line is at the +// bottom of the window. +const cursorColumn = true diff --git a/vendor/github.com/peterh/liner/input_linux.go b/vendor/github.com/peterh/liner/input_linux.go index 6ca87124ea..56ed185fad 100644 --- a/vendor/github.com/peterh/liner/input_linux.go +++ b/vendor/github.com/peterh/liner/input_linux.go @@ -24,3 +24,5 @@ const ( type termios struct { syscall.Termios } + +const cursorColumn = false diff --git a/vendor/github.com/peterh/liner/input_windows.go b/vendor/github.com/peterh/liner/input_windows.go index 9dcc3115c7..199b9428e4 100644 --- a/vendor/github.com/peterh/liner/input_windows.go +++ b/vendor/github.com/peterh/liner/input_windows.go @@ -168,6 +168,9 @@ func (s *State) readNext() (interface{}, error) { if input.eventType == window_buffer_size_event { xy := (*coord)(unsafe.Pointer(&input.blob[0])) s.columns = int(xy.x) + if s.columns > 1 { + s.columns-- + } return winch, nil } if input.eventType != key_event { diff --git a/vendor/github.com/peterh/liner/output.go b/vendor/github.com/peterh/liner/output.go index 049273b539..6d83d4ebfd 100644 --- a/vendor/github.com/peterh/liner/output.go +++ b/vendor/github.com/peterh/liner/output.go @@ -48,14 +48,18 @@ type winSize struct { xpixel, ypixel uint16 } -func (s *State) getColumns() { +func (s *State) getColumns() bool { var ws winSize ok, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws))) - if ok < 0 { - s.columns = 80 + if int(ok) < 0 { + return false } s.columns = int(ws.col) + if cursorColumn && s.columns > 1 { + s.columns-- + } + return true } func (s *State) checkOutput() { diff --git a/vendor/github.com/peterh/liner/output_windows.go b/vendor/github.com/peterh/liner/output_windows.go index 45cd978c96..63c9c5d757 100644 --- a/vendor/github.com/peterh/liner/output_windows.go +++ b/vendor/github.com/peterh/liner/output_windows.go @@ -69,4 +69,8 @@ func (s *State) getColumns() { var sbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) s.columns = int(sbi.dwSize.x) + if s.columns > 1 { + // Windows 10 needs a spare column for the cursor + s.columns-- + } } diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore new file mode 100644 index 0000000000..daf913b1b3 --- /dev/null +++ b/vendor/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml new file mode 100644 index 0000000000..13f087a7d9 --- /dev/null +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -0,0 +1,10 @@ +language: go +go_import_path: github.com/pkg/errors +go: + - 1.4.3 + - 1.5.4 + - 1.6.1 + - tip + +script: + - go test -v ./... diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 0000000000..fafcaafdc7 --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md new file mode 100644 index 0000000000..a9f7d88ef5 --- /dev/null +++ b/vendor/github.com/pkg/errors/README.md @@ -0,0 +1,52 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) + +Package errors implements functions for manipulating errors. + +The traditional error handling idiom in Go is roughly akin to +``` +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +``` +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +In addition, `errors.Wrap` records the file and line where it was called, allowing the programmer to retrieve the path to the original error. + +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to recurse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +``` +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +``` +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +Would you like to know more? Read the [blog post](http://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully). + +## Contributing + +We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. + +Before proposing a change, please discuss your change by raising an issue. + +## Licence + +MIT diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 0000000000..b0da7c5619 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,248 @@ +// Package errors implements functions for manipulating errors. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// In addition, errors.Wrap records the file and line where it was called, +// allowing the programmer to retrieve the path to the original error. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +package errors + +import ( + "errors" + "fmt" + "io" + "os" + "runtime" + "strings" +) + +// location represents a program counter that +// implements the Location() method. +type location uintptr + +func (l location) Location() (string, int) { + pc := uintptr(l) - 1 + fn := runtime.FuncForPC(pc) + if fn == nil { + return "unknown", 0 + } + + file, line := fn.FileLine(pc) + + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(fn.Name(), sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + + return file, line +} + +// New returns an error that formats as the given text. +func New(text string) error { + pc, _, _, _ := runtime.Caller(1) + return struct { + error + location + }{ + errors.New(text), + location(pc), + } +} + +type cause struct { + cause error + message string +} + +func (c cause) Error() string { return c.Message() + ": " + c.Cause().Error() } +func (c cause) Cause() error { return c.cause } +func (c cause) Message() string { return c.message } + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +func Errorf(format string, args ...interface{}) error { + pc, _, _, _ := runtime.Caller(1) + return struct { + error + location + }{ + fmt.Errorf(format, args...), + location(pc), + } +} + +// Wrap returns an error annotating the cause with message. +// If cause is nil, Wrap returns nil. +func Wrap(cause error, message string) error { + if cause == nil { + return nil + } + pc, _, _, _ := runtime.Caller(1) + return wrap(cause, message, pc) +} + +// Wrapf returns an error annotating the cause with the format specifier. +// If cause is nil, Wrapf returns nil. +func Wrapf(cause error, format string, args ...interface{}) error { + if cause == nil { + return nil + } + pc, _, _, _ := runtime.Caller(1) + return wrap(cause, fmt.Sprintf(format, args...), pc) +} + +func wrap(err error, msg string, pc uintptr) error { + return struct { + cause + location + }{ + cause{ + cause: err, + message: msg, + }, + location(pc), + } +} + +type causer interface { + Cause() error +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type Causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} + +// Print prints the error to Stderr. +// If the error implements the Causer interface described in Cause +// Print will recurse into the error's cause. +// If the error implements the inteface: +// +// type Location interface { +// Location() (file string, line int) +// } +// +// Print will also print the file and line of the error. +func Print(err error) { + Fprint(os.Stderr, err) +} + +// Fprint prints the error to the supplied writer. +// The format of the output is the same as Print. +// If err is nil, nothing is printed. +func Fprint(w io.Writer, err error) { + type location interface { + Location() (string, int) + } + type message interface { + Message() string + } + + for err != nil { + if err, ok := err.(location); ok { + file, line := err.Location() + fmt.Fprintf(w, "%s:%d: ", file, line) + } + switch err := err.(type) { + case message: + fmt.Fprintln(w, err.Message()) + default: + fmt.Fprintln(w, err.Error()) + } + + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } +} diff --git a/vendor/github.com/pkg/errors/errors_test.go b/vendor/github.com/pkg/errors/errors_test.go new file mode 100644 index 0000000000..4d0e669f94 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors_test.go @@ -0,0 +1,189 @@ +package errors + +import ( + "bytes" + "errors" + "fmt" + "io" + "reflect" + "testing" +) + +func TestNew(t *testing.T) { + tests := []struct { + err string + want error + }{ + {"", fmt.Errorf("")}, + {"foo", fmt.Errorf("foo")}, + {"foo", New("foo")}, + {"string with format specifiers: %v", errors.New("string with format specifiers: %v")}, + } + + for _, tt := range tests { + got := New(tt.err) + if got.Error() != tt.want.Error() { + t.Errorf("New.Error(): got: %q, want %q", got, tt.want) + } + } +} + +func TestWrapNil(t *testing.T) { + got := Wrap(nil, "no error") + if got != nil { + t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWrap(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"}, + } + + for _, tt := range tests { + got := Wrap(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) + } + } +} + +type nilError struct{} + +func (nilError) Error() string { return "nil error" } + +type causeError struct { + cause error +} + +func (e *causeError) Error() string { return "cause error" } +func (e *causeError) Cause() error { return e.cause } + +func TestCause(t *testing.T) { + x := New("error") + tests := []struct { + err error + want error + }{{ + // nil error is nil + err: nil, + want: nil, + }, { + // explicit nil error is nil + err: (error)(nil), + want: nil, + }, { + // typed nil is nil + err: (*nilError)(nil), + want: (*nilError)(nil), + }, { + // uncaused error is unaffected + err: io.EOF, + want: io.EOF, + }, { + // caused error returns cause + err: &causeError{cause: io.EOF}, + want: io.EOF, + }, { + err: x, // return from errors.New + want: x, + }} + + for i, tt := range tests { + got := Cause(tt.err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want) + } + } +} + +func TestFprint(t *testing.T) { + x := New("error") + tests := []struct { + err error + want string + }{{ + // nil error is nil + err: nil, + }, { + // explicit nil error is nil + err: (error)(nil), + }, { + // uncaused error is unaffected + err: io.EOF, + want: "EOF\n", + }, { + // caused error returns cause + err: &causeError{cause: io.EOF}, + want: "cause error\nEOF\n", + }, { + err: x, // return from errors.New + want: "github.com/pkg/errors/errors_test.go:106: error\n", + }, { + err: Wrap(x, "message"), + want: "github.com/pkg/errors/errors_test.go:128: message\ngithub.com/pkg/errors/errors_test.go:106: error\n", + }, { + err: Wrap(Wrap(x, "message"), "another message"), + want: "github.com/pkg/errors/errors_test.go:131: another message\ngithub.com/pkg/errors/errors_test.go:131: message\ngithub.com/pkg/errors/errors_test.go:106: error\n", + }, { + err: Wrapf(x, "message"), + want: "github.com/pkg/errors/errors_test.go:134: message\ngithub.com/pkg/errors/errors_test.go:106: error\n", + }} + + for i, tt := range tests { + var w bytes.Buffer + Fprint(&w, tt.err) + got := w.String() + if got != tt.want { + t.Errorf("test %d: Fprint(w, %q): got %q, want %q", i+1, tt.err, got, tt.want) + } + } +} + +func TestWrapfNil(t *testing.T) { + got := Wrapf(nil, "no error") + if got != nil { + t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWrapf(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"}, + {Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, + } + + for _, tt := range tests { + got := Wrapf(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) + } + } +} + +func TestErrorf(t *testing.T) { + tests := []struct { + err error + want string + }{ + {Errorf("read error without format specifiers"), "read error without format specifiers"}, + {Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"}, + } + + for _, tt := range tests { + got := tt.err.Error() + if got != tt.want { + t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want) + } + } +} diff --git a/vendor/github.com/pkg/errors/example_test.go b/vendor/github.com/pkg/errors/example_test.go new file mode 100644 index 0000000000..c5f20230a0 --- /dev/null +++ b/vendor/github.com/pkg/errors/example_test.go @@ -0,0 +1,71 @@ +package errors_test + +import ( + "fmt" + "os" + + "github.com/pkg/errors" +) + +func ExampleNew() { + err := errors.New("whoops") + fmt.Println(err) + + // Output: whoops +} + +func ExampleNew_fprint() { + err := errors.New("whoops") + errors.Fprint(os.Stdout, err) + + // Output: github.com/pkg/errors/example_test.go:18: whoops +} + +func ExampleWrap() { + cause := errors.New("whoops") + err := errors.Wrap(cause, "oh noes") + fmt.Println(err) + + // Output: oh noes: whoops +} + +func fn() error { + e1 := errors.New("error") + e2 := errors.Wrap(e1, "inner") + e3 := errors.Wrap(e2, "middle") + return errors.Wrap(e3, "outer") +} + +func ExampleCause() { + err := fn() + fmt.Println(err) + fmt.Println(errors.Cause(err)) + + // Output: outer: middle: inner: error + // error +} + +func ExampleFprint() { + err := fn() + errors.Fprint(os.Stdout, err) + + // Output: github.com/pkg/errors/example_test.go:36: outer + // github.com/pkg/errors/example_test.go:35: middle + // github.com/pkg/errors/example_test.go:34: inner + // github.com/pkg/errors/example_test.go:33: error +} + +func ExampleWrapf() { + cause := errors.New("whoops") + err := errors.Wrapf(cause, "oh noes #%d", 2) + fmt.Println(err) + + // Output: oh noes #2: whoops +} + +func ExampleErrorf() { + err := errors.Errorf("whoops: %s", "foo") + errors.Fprint(os.Stdout, err) + + // Output: github.com/pkg/errors/example_test.go:67: whoops: foo +} diff --git a/vendor/github.com/spf13/cobra/README.md b/vendor/github.com/spf13/cobra/README.md index f326c7fa84..34c79eb8eb 100644 --- a/vendor/github.com/spf13/cobra/README.md +++ b/vendor/github.com/spf13/cobra/README.md @@ -22,6 +22,7 @@ Many of the most widely used Go projects are built using Cobra including: [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) [![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra) +[![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra) ![cobra](https://cloud.githubusercontent.com/assets/173412/10911369/84832a8e-8212-11e5-9f82-cc96660a4794.gif) @@ -226,13 +227,27 @@ The cobra generator will be easier to use if you provide a simple configuration file which will help you eliminate providing a bunch of repeated information in flags over and over. -an example ~/.cobra.yaml file: +An example ~/.cobra.yaml file: ```yaml author: Steve Francia license: MIT ``` +You can specify no license by setting `license` to `none` or you can specify +a custom license: + +```yaml +license: + header: This file is part of {{ .appName }}. + text: | + {{ .copyright }} + + This is my license. There are many like it, but this one is mine. + My license is my best friend. It is my life. I must master it as I must + master my life. +``` + ## Manually implementing Cobra To manually implement cobra you need to create a bare main.go file and a RootCmd file. diff --git a/vendor/github.com/spf13/cobra/cobra/cmd/helpers.go b/vendor/github.com/spf13/cobra/cobra/cmd/helpers.go index f8b79b1fc0..7cd3be18be 100644 --- a/vendor/github.com/spf13/cobra/cobra/cmd/helpers.go +++ b/vendor/github.com/spf13/cobra/cobra/cmd/helpers.go @@ -319,6 +319,15 @@ func whichLicense() string { // default to viper's setting + if viper.IsSet("license.header") || viper.IsSet("license.text") { + if custom, ok := Licenses["custom"]; ok { + custom.Header = viper.GetString("license.header") + custom.Text = viper.GetString("license.text") + Licenses["custom"] = custom + return "custom" + } + } + return matchLicense(viper.GetString("license")) } diff --git a/vendor/github.com/spf13/cobra/cobra/cmd/init.go b/vendor/github.com/spf13/cobra/cobra/cmd/init.go index 342fb9ccdf..13792f12fb 100644 --- a/vendor/github.com/spf13/cobra/cobra/cmd/init.go +++ b/vendor/github.com/spf13/cobra/cobra/cmd/init.go @@ -14,6 +14,7 @@ package cmd import ( + "bytes" "fmt" "os" "strings" @@ -89,27 +90,34 @@ func initializePath(path string) { func createLicenseFile() { lic := getLicense() - template := lic.Text + // Don't bother writing a LICENSE file if there is no text. + if lic.Text != "" { + data := make(map[string]interface{}) - var data map[string]interface{} - data = make(map[string]interface{}) + // Try to remove the email address, if any + data["copyright"] = strings.Split(copyrightLine(), " <")[0] - // Try to remove the email address, if any - data["copyright"] = strings.Split(copyrightLine(), " <")[0] + data["appName"] = projectName() - err := writeTemplateToFile(ProjectPath(), "LICENSE", template, data) - _ = err - // if err != nil { - // er(err) - // } + // Generate license template from text and data. + r, _ := templateToReader(lic.Text, data) + buf := new(bytes.Buffer) + buf.ReadFrom(r) + + err := writeTemplateToFile(ProjectPath(), "LICENSE", buf.String(), data) + _ = err + // if err != nil { + // er(err) + // } + } } func createMainFile() { lic := getLicense() template := `{{ comment .copyright }} -{{ comment .license }} - +{{if .license}}{{ comment .license }} +{{end}} package main import "{{ .importpath }}" @@ -118,11 +126,17 @@ func main() { cmd.Execute() } ` - var data map[string]interface{} - data = make(map[string]interface{}) + data := make(map[string]interface{}) data["copyright"] = copyrightLine() - data["license"] = lic.Header + data["appName"] = projectName() + + // Generate license template from header and data. + r, _ := templateToReader(lic.Header, data) + buf := new(bytes.Buffer) + buf.ReadFrom(r) + data["license"] = buf.String() + data["importpath"] = guessImportPath() + "/" + guessCmdDir() err := writeTemplateToFile(ProjectPath(), "main.go", template, data) @@ -136,8 +150,8 @@ func createRootCmdFile() { lic := getLicense() template := `{{ comment .copyright }} -{{ comment .license }} - +{{if .license}}{{ comment .license }} +{{end}} package cmd import ( @@ -206,12 +220,17 @@ func initConfig() { } {{ end }}` - var data map[string]interface{} - data = make(map[string]interface{}) + data := make(map[string]interface{}) data["copyright"] = copyrightLine() - data["license"] = lic.Header data["appName"] = projectName() + + // Generate license template from header and data. + r, _ := templateToReader(lic.Header, data) + buf := new(bytes.Buffer) + buf.ReadFrom(r) + data["license"] = buf.String() + data["viper"] = viper.GetBool("useViper") err := writeTemplateToFile(ProjectPath()+string(os.PathSeparator)+guessCmdDir(), "root.go", template, data) diff --git a/vendor/github.com/spf13/cobra/cobra/cmd/licenses.go b/vendor/github.com/spf13/cobra/cobra/cmd/licenses.go index 5ad9c96ef1..b4c742c375 100644 --- a/vendor/github.com/spf13/cobra/cobra/cmd/licenses.go +++ b/vendor/github.com/spf13/cobra/cobra/cmd/licenses.go @@ -46,6 +46,12 @@ func matchLicense(in string) string { func init() { Licenses = make(map[string]License) + // Allows a user to not use a license. + Licenses["none"] = License{"None", []string{"none", "false"}, "", ""} + + // Allows a user to use config for a custom license. + Licenses["custom"] = License{"Custom", []string{}, "", ""} + Licenses["apache"] = License{ Name: "Apache 2.0", PossibleMatches: []string{"apache", "apache20", "apache 2.0", "apache2.0", "apache-2.0"}, @@ -425,25 +431,323 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. `, } - Licenses["gpl3"] = License{ - Name: "GNU General Public License 3.0", - PossibleMatches: []string{"gpl3", "gpl", "gnu gpl3", "gnu gpl"}, + Licenses["gpl2"] = License{ + Name: "GNU General Public License 2.0", + PossibleMatches: []string{"gpl2", "gnu gpl2"}, Header: `{{ .copyright }} - This file is part of {{ .appName }}. - - {{ .appName }} is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + {{ .appName }} is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. {{ .appName }} is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. + GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License - along with {{ .appName }}. If not, see . + along with {{ .appName }}. If not, see .`, + Text: ` GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS +`, + } + Licenses["gpl3"] = License{ + Name: "GNU General Public License 3.0", + PossibleMatches: []string{"gpl3", "gpl", "gnu gpl3", "gnu gpl"}, + Header: ` +This file is part of {{ .appName }}. + +{{ .appName }} is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +{{ .appName }} is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with {{ .appName }}. If not, see . `, Text: ` GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 @@ -1066,59 +1370,6 @@ Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type 'show c' for details. - -The hypothetical commands 'show w' and 'show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. `, } diff --git a/vendor/github.com/spf13/cobra/cobra/cmd/root.go b/vendor/github.com/spf13/cobra/cobra/cmd/root.go index dbe607beb5..065c8bf40c 100644 --- a/vendor/github.com/spf13/cobra/cobra/cmd/root.go +++ b/vendor/github.com/spf13/cobra/cobra/cmd/root.go @@ -46,25 +46,13 @@ func init() { RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory, e.g. github.com/spf13/") RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") - RootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)") + RootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `license` in config)") RootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration") viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author")) viper.BindPFlag("projectbase", RootCmd.PersistentFlags().Lookup("projectbase")) viper.BindPFlag("useViper", RootCmd.PersistentFlags().Lookup("viper")) viper.SetDefault("author", "NAME HERE ") viper.SetDefault("license", "apache") - viper.SetDefault("licenseText", ` -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -`) } // Read in config file and ENV variables if set. diff --git a/vendor/github.com/spf13/cobra/command.go b/vendor/github.com/spf13/cobra/command.go index 0efeeb1957..aa3f33aa8a 100644 --- a/vendor/github.com/spf13/cobra/command.go +++ b/vendor/github.com/spf13/cobra/command.go @@ -261,7 +261,7 @@ func (c *Command) UsageTemplate() string { return c.parent.UsageTemplate() } return `Usage:{{if .Runnable}} - {{if .HasFlags}}{{appendIfNotPresent .UseLine "[flags]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasSubCommands}} + {{if .HasAvailableFlags}}{{appendIfNotPresent .UseLine "[flags]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasAvailableSubCommands}} {{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}} Aliases: @@ -272,16 +272,16 @@ Examples: {{ .Example }}{{end}}{{ if .HasAvailableSubCommands}} Available Commands:{{range .Commands}}{{if .IsAvailableCommand}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasLocalFlags}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasAvailableLocalFlags}} Flags: -{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasInheritedFlags}} +{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableInheritedFlags}} Global Flags: {{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}} Additional help topics:{{range .Commands}}{{if .IsHelpCommand}} - {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasAvailableSubCommands }} Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} ` @@ -1114,10 +1114,34 @@ func (c *Command) HasLocalFlags() bool { return c.LocalFlags().HasFlags() } +// Does the command have flags inherited from its parent command func (c *Command) HasInheritedFlags() bool { return c.InheritedFlags().HasFlags() } +// Does the command contain any flags (local plus persistent from the entire +// structure) which are not hidden or deprecated +func (c *Command) HasAvailableFlags() bool { + return c.Flags().HasAvailableFlags() +} + +// Does the command contain persistent flags which are not hidden or deprecated +func (c *Command) HasAvailablePersistentFlags() bool { + return c.PersistentFlags().HasAvailableFlags() +} + +// Does the command has flags specifically declared locally which are not hidden +// or deprecated +func (c *Command) HasAvailableLocalFlags() bool { + return c.LocalFlags().HasAvailableFlags() +} + +// Does the command have flags inherited from its parent command which are +// not hidden or deprecated +func (c *Command) HasAvailableInheritedFlags() bool { + return c.InheritedFlags().HasAvailableFlags() +} + // Flag climbs up the command tree looking for matching flag func (c *Command) Flag(name string) (flag *flag.Flag) { flag = c.Flags().Lookup(name) diff --git a/vendor/github.com/spf13/pflag/README.md b/vendor/github.com/spf13/pflag/README.md index e74dd50b41..0bafd385fc 100644 --- a/vendor/github.com/spf13/pflag/README.md +++ b/vendor/github.com/spf13/pflag/README.md @@ -85,7 +85,7 @@ fmt.Println("flagvar has value ", flagvar) ``` There are helpers function to get values later if you have the FlagSet but -it was difficult to keep up with all of the the flag pointers in your code. +it was difficult to keep up with all of the flag pointers in your code. If you have a pflag.FlagSet with a flag called 'flagname' of type int you can use GetInt() to get the int value. But notice that 'flagname' must exist and it must be an int. GetString("flagname") will fail. diff --git a/vendor/github.com/spf13/pflag/flag.go b/vendor/github.com/spf13/pflag/flag.go index bb03cfcaad..965df13797 100644 --- a/vendor/github.com/spf13/pflag/flag.go +++ b/vendor/github.com/spf13/pflag/flag.go @@ -778,6 +778,9 @@ func (f *FlagSet) parseLongArg(s string, args []string) (a []string, err error) } func (f *FlagSet) parseSingleShortArg(shorthands string, args []string) (outShorts string, outArgs []string, err error) { + if strings.HasPrefix(shorthands, "test.") { + return + } outArgs = args outShorts = shorthands[1:] c := shorthands[0] diff --git a/vendor/golang.org/x/tools/cmd/godoc/handlers.go b/vendor/golang.org/x/tools/cmd/godoc/handlers.go index dda1bb87b2..600be6854c 100644 --- a/vendor/golang.org/x/tools/cmd/godoc/handlers.go +++ b/vendor/golang.org/x/tools/cmd/godoc/handlers.go @@ -54,6 +54,7 @@ func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, r.URL.String(), http.StatusFound) return } + w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload") h.h.ServeHTTP(w, r) } diff --git a/vendor/golang.org/x/tools/cmd/guru/definition.go b/vendor/golang.org/x/tools/cmd/guru/definition.go index 828316da27..575ed92a00 100644 --- a/vendor/golang.org/x/tools/cmd/guru/definition.go +++ b/vendor/golang.org/x/tools/cmd/guru/definition.go @@ -7,9 +7,15 @@ package main import ( "fmt" "go/ast" + "go/build" + "go/parser" "go/token" + pathpkg "path" + "path/filepath" + "strconv" "golang.org/x/tools/cmd/guru/serial" + "golang.org/x/tools/go/buildutil" "golang.org/x/tools/go/loader" ) @@ -30,6 +36,7 @@ func definition(q *Query) error { return fmt.Errorf("no identifier here") } + // Did the parser resolve it to a local object? if obj := id.Obj; obj != nil && obj.Pos().IsValid() { q.Output(qpos.fset, &definitionResult{ pos: obj.Pos(), @@ -37,6 +44,22 @@ func definition(q *Query) error { }) return nil // success } + + // Qualified identifier? + if pkg := packageForQualIdent(qpos.path, id); pkg != "" { + srcdir := filepath.Dir(qpos.fset.File(qpos.start).Name()) + tok, pos, err := findPackageMember(q.Build, qpos.fset, srcdir, pkg, id.Name) + if err != nil { + return err + } + q.Output(qpos.fset, &definitionResult{ + pos: pos, + descr: fmt.Sprintf("%s %s.%s", tok, pkg, id.Name), + }) + return nil // success + } + + // Fall back on the type checker. } // Run the type checker. @@ -82,6 +105,82 @@ func definition(q *Query) error { return nil } +// packageForQualIdent returns the package p if id is X in a qualified +// identifier p.X; it returns "" otherwise. +// +// Precondition: id is path[0], and the parser did not resolve id to a +// local object. For speed, packageForQualIdent assumes that p is a +// package iff it is the basename of an import path (and not, say, a +// package-level decl in another file or a predeclared identifier). +func packageForQualIdent(path []ast.Node, id *ast.Ident) string { + if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == id && ast.IsExported(id.Name) { + if pkgid, ok := sel.X.(*ast.Ident); ok && pkgid.Obj == nil { + f := path[len(path)-1].(*ast.File) + for _, imp := range f.Imports { + path, _ := strconv.Unquote(imp.Path.Value) + if imp.Name != nil { + if imp.Name.Name == pkgid.Name { + return path // renaming import + } + } else if pathpkg.Base(path) == pkgid.Name { + return path // ordinary import + } + } + } + } + return "" +} + +// findPackageMember returns the type and position of the declaration of +// pkg.member by loading and parsing the files of that package. +// srcdir is the directory in which the import appears. +func findPackageMember(ctxt *build.Context, fset *token.FileSet, srcdir, pkg, member string) (token.Token, token.Pos, error) { + bp, err := ctxt.Import(pkg, srcdir, 0) + if err != nil { + return 0, token.NoPos, err // no files for package + } + + // TODO(adonovan): opt: parallelize. + for _, fname := range bp.GoFiles { + filename := filepath.Join(bp.Dir, fname) + + // Parse the file, opening it the file via the build.Context + // so that we observe the effects of the -modified flag. + f, _ := buildutil.ParseFile(fset, ctxt, nil, ".", filename, parser.Mode(0)) + if f == nil { + continue + } + + // Find a package-level decl called 'member'. + for _, decl := range f.Decls { + switch decl := decl.(type) { + case *ast.GenDecl: + for _, spec := range decl.Specs { + switch spec := spec.(type) { + case *ast.ValueSpec: + // const or var + for _, id := range spec.Names { + if id.Name == member { + return decl.Tok, id.Pos(), nil + } + } + case *ast.TypeSpec: + if spec.Name.Name == member { + return token.TYPE, spec.Name.Pos(), nil + } + } + } + case *ast.FuncDecl: + if decl.Recv == nil && decl.Name.Name == member { + return token.FUNC, decl.Name.Pos(), nil + } + } + } + } + + return 0, token.NoPos, fmt.Errorf("couldn't find declaration of %s in %q", member, pkg) +} + type definitionResult struct { pos token.Pos // (nonzero) location of definition descr string // description of object it denotes diff --git a/vendor/golang.org/x/tools/cmd/guru/go-guru.el b/vendor/golang.org/x/tools/cmd/guru/go-guru.el index c4be257117..fc289cc08c 100644 --- a/vendor/golang.org/x/tools/cmd/guru/go-guru.el +++ b/vendor/golang.org/x/tools/cmd/guru/go-guru.el @@ -109,147 +109,152 @@ A pattern preceded by '-' is negative, so the scope matches all encoding packages except encoding/xml." (interactive) (let ((scope (read-from-minibuffer "Go guru scope: " - go-guru-scope - nil - nil - 'go-guru--scope-history))) + go-guru-scope + nil + nil + 'go-guru--scope-history))) (if (string-equal "" scope) - (error "You must specify a non-empty scope for the Go guru")) + (error "You must specify a non-empty scope for the Go guru")) (setq go-guru-scope scope))) -(defun go-guru--run (mode &optional need-scope) - "Run the Go guru in the specified MODE, passing it the selected -region of the current buffer. If NEED-SCOPE, prompt for a scope -if not already set. Mark up the output using `compilation-mode`, -replacing each file name with a small hyperlink, and display the -result." - (let ((output (go-guru--exec mode need-scope)) - (display (get-buffer-create "*go-guru*")) - (dir default-directory)) - (with-current-buffer display - (setq buffer-read-only nil) - (setq default-directory dir) - (erase-buffer) - (insert-buffer-substring output) - (go-guru--compilation-markup)))) +(defun go-guru--set-scope-if-empty () + (if (string-equal "" go-guru-scope) + (go-guru-set-scope))) -(defun go-guru--exec (mode &optional need-scope flags allow-unnamed) +(defun go-guru--json (mode) "Execute the Go guru in the specified MODE, passing it the -selected region of the current buffer. If NEED-SCOPE, prompt for -a scope if not already set. If ALLOW-UNNAMED is non-nil, a -synthetic file for the unnamed buffer will be created. This -should only be used with queries that work on single files -only (e.g. 'what'). If ALLOW-UNNAMED is nil and the buffer has no -associated name, an error will be signaled. - -Return the output buffer." - (or - buffer-file-name - allow-unnamed - (error "Cannot use guru on a buffer without a file name")) - (and need-scope - (string-equal "" go-guru-scope) - (go-guru-set-scope)) - (let* ((is-unnamed (not buffer-file-name)) - (filename (file-truename (or buffer-file-name "synthetic.go"))) - (posn (if (use-region-p) +selected region of the current buffer, requesting JSON output. +Parse and return the resulting JSON object." + ;; A "what" query works even in a buffer without a file name. + (let* ((filename (file-truename (or buffer-file-name "synthetic.go"))) + (cmd (go-guru--command mode filename '("-json"))) + (buf (current-buffer)) + ;; Use temporary buffers to avoid conflict with go-guru--start. + (json-buffer (generate-new-buffer "*go-guru-json-output*")) + (input-buffer (generate-new-buffer "*go-guru-json-input*"))) + (unwind-protect + ;; Run guru, feeding it the input buffer (modified files). + (with-current-buffer input-buffer + (go-guru--insert-modified-files) + (unless (buffer-file-name buf) + (go-guru--insert-modified-file filename buf)) + (let ((exitcode (apply #'call-process-region + (append (list (point-min) + (point-max) + (car cmd) ; guru + nil ; delete + json-buffer ; output + nil) ; display + (cdr cmd))))) ; args + (with-current-buffer json-buffer + (unless (zerop exitcode) + ;; Failed: use buffer contents (sans final \n) as an error. + (error "%s" (buffer-substring (point-min) (1- (point-max))))) + ;; Success: parse JSON. + (goto-char (point-min)) + (json-read)))) + ;; Clean up temporary buffers. + (kill-buffer json-buffer) + (kill-buffer input-buffer)))) + +(define-compilation-mode go-guru-output-mode "Go guru" + "Go guru output mode is a variant of `compilation-mode' for the +output of the Go guru tool." + (set (make-local-variable 'compilation-error-screen-columns) nil) + (set (make-local-variable 'compilation-filter-hook) #'go-guru--compilation-filter-hook) + (set (make-local-variable 'compilation-start-hook) #'go-guru--compilation-start-hook)) + +(defun go-guru--compilation-filter-hook () + "Post-process a blob of input to the go-guru-output buffer." + ;; For readability, truncate each "file:line:col:" prefix to a fixed width. + ;; If the prefix is longer than 20, show "…/last/19chars.go". + ;; This usually includes the last segment of the package name. + ;; Hide the line and column numbers. + (let ((start compilation-filter-start) + (end (point))) + (goto-char start) + (unless (bolp) + ;; TODO(adonovan): not quite right: the filter may be called + ;; with chunks of output containing incomplete lines. Moving to + ;; beginning-of-line may cause duplicate post-processing. + (beginning-of-line)) + (setq start (point)) + (while (< start end) + (let ((p (search-forward ": " end t))) + (if (null p) + (setq start end) ; break out of loop + (setq p (1- p)) ; exclude final space + (let* ((posn (buffer-substring-no-properties start p)) + (flen (search ":" posn)) ; length of filename + (filename (if (< flen 19) + (substring posn 0 flen) + (concat "…" (substring posn (- flen 19) flen))))) + (put-text-property start p 'display filename) + (forward-line 1) + (setq start (point)))))))) + +(defun go-guru--compilation-start-hook (proc) + "Erase default output header inserted by `compilation-mode'." + (with-current-buffer (process-buffer proc) + (let ((inhibit-read-only t)) + (beginning-of-buffer) + (delete-region (point) (point-max))))) + +(defun go-guru--start (mode) + "Start an asynchronous Go guru process for the specified query +MODE, passing it the selected region of the current buffer, and +feeding its standard input with the contents of all modified Go +buffers. Its output is handled by `go-guru-output-mode', a +variant of `compilation-mode'." + (or buffer-file-name + (error "Cannot use guru on a buffer without a file name")) + (let* ((filename (file-truename buffer-file-name)) + (cmd (combine-and-quote-strings (go-guru--command mode filename))) + (process-connection-type nil) ; use pipe (not pty) so EOF closes stdin + (procbuf (compilation-start cmd 'go-guru-output-mode))) + (with-current-buffer procbuf + (setq truncate-lines t)) ; the output is neater without line wrapping + (with-current-buffer (get-buffer-create "*go-guru-input*") + (erase-buffer) + (go-guru--insert-modified-files) + (process-send-region procbuf (point-min) (point-max)) + (process-send-eof procbuf)) + procbuf)) + +(defun go-guru--command (mode filename &optional flags) + "Return a command and argument list for a Go guru query of MODE, passing it +the selected region of the current buffer. FILENAME is the +effective name of the current buffer." + (let* ((posn (if (use-region-p) (format "%s:#%d,#%d" filename (1- (go--position-bytes (region-beginning))) (1- (go--position-bytes (region-end)))) (format "%s:#%d" filename - (1- (position-bytes (point)))))) - (env-vars (go-root-and-paths)) - (goroot-env (concat "GOROOT=" (car env-vars))) - (gopath-env (concat "GOPATH=" (mapconcat #'identity (cdr env-vars) ":"))) - (output-buffer (get-buffer-create "*go-guru-output*")) - (buf (current-buffer))) - (with-current-buffer output-buffer - (setq buffer-read-only nil) - (erase-buffer)) - (with-current-buffer (get-buffer-create "*go-guru-input*") - (setq buffer-read-only nil) - (erase-buffer) - (if is-unnamed - (go-guru--insert-modified-file filename buf) - (go-guru--insert-modified-files)) - (let* ((args (append (list "-modified" - "-scope" go-guru-scope - "-tags" go-guru-build-tags) - flags - (list mode posn)))) - ;; Log the command to *Messages*, for debugging. - (when go-guru-debug - (message "Command: %s:" args) - (message nil) ; clears/shrinks minibuffer - (message "Running guru %s..." mode)) - ;; Use dynamic binding to modify/restore the environment - (let* ((process-environment (list* goroot-env gopath-env process-environment)) - (c-p-args (append (list (point-min) - (point-max) - go-guru-command - nil ; delete - output-buffer - t) - args)) - (exitcode (apply #'call-process-region c-p-args))) - ;; If the command fails, don't show the output buffer, - ;; but use its contents (sans final \n) as an error. - (unless (zerop exitcode) - (with-current-buffer output-buffer - (bury-buffer) - (error "%s" (buffer-substring (point-min) (1- (point-max))))))))) - output-buffer)) - -(defun go-guru--compilation-markup () - "Present guru output in the current buffer using `compilation-mode'." - (goto-char (point-max)) - (insert "\n") - (compilation-mode) - (setq compilation-error-screen-columns nil) - - ;; Hide the file/line info to save space. - ;; Replace each with a little widget. - ;; compilation-mode + this loop = slooow. - ;; TODO(adonovan): have guru give us JSON - ;; and we'll do the markup directly. - (let ((buffer-read-only nil) - (p 1)) - (while (not (null p)) - (let ((np (compilation-next-single-property-change p 'compilation-message))) - (if np - (when (equal (line-number-at-pos p) (line-number-at-pos np)) - ;; Using a fixed width greatly improves readability, so - ;; if the filename is longer than 20, show ".../last/17chars.go". - ;; This usually includes the last segment of the package name. - ;; Don't show the line or column number. - (let* ((loc (buffer-substring p np)) ; "/home/foo/go/pkg/file.go:1:2-3:4" - (i (search ":" loc))) - (setq loc (cond - ((null i) "...") - ((>= i 17) (concat "..." (substring loc (- i 17) i))) - (t (substring loc 0 i)))) - ;; np is (typically) the space following ":"; consume it too. - (put-text-property p np 'display (concat loc ":"))) - (goto-char np) - (insert " ") - (incf np))) ; so we don't get stuck (e.g. on a panic stack dump) - (setq p np))) - (message nil)) - - (let ((w (display-buffer (current-buffer)))) - (set-window-point w (point-min)))) + (1- (go--position-bytes (point)))))) + (cmd (append (list go-guru-command + "-modified" + "-scope" go-guru-scope + (format "-tags=%s" (mapconcat 'identity go-guru-build-tags ","))) + flags + (list mode + posn)))) + ;; Log the command to *Messages*, for debugging. + (when go-guru-debug + (message "go-guru--command: %s" cmd) + (message nil)) ; clear/shrink minibuffer + cmd)) (defun go-guru--insert-modified-files () "Insert the contents of each modified Go buffer into the current buffer in the format specified by guru's -modified flag." (mapc #'(lambda (b) - (and (buffer-modified-p b) - (buffer-file-name b) - (string= (file-name-extension (buffer-file-name b)) "go") - (go-guru--insert-modified-file (buffer-file-name b) b))) - (buffer-list))) + (and (buffer-modified-p b) + (buffer-file-name b) + (string= (file-name-extension (buffer-file-name b)) "go") + (go-guru--insert-modified-file (buffer-file-name b) b))) + (buffer-list))) (defun go-guru--insert-modified-file (name buffer) (insert (format "%s\n%d\n" name (go-guru--buffer-size-bytes buffer))) @@ -260,7 +265,7 @@ current buffer in the format specified by guru's -modified flag." If BUFFER, return the number of characters in that buffer instead." (with-current-buffer (or buffer (current-buffer)) (string-bytes (buffer-substring (point-min) - (point-max))))) + (point-max))))) (defun go-guru--goto-byte (offset) "Go to the OFFSETth byte in the buffer." @@ -291,28 +296,31 @@ component will be ignored." (defun go-guru-callees () "Show possible callees of the function call at the current point." (interactive) - (go-guru--run "callees" t)) + (go-guru--set-scope-if-empty) + (go-guru--start "callees")) ;;;###autoload (defun go-guru-callers () "Show the set of callers of the function containing the current point." (interactive) - (go-guru--run "callers" t)) + (go-guru--set-scope-if-empty) + (go-guru--start "callers")) ;;;###autoload (defun go-guru-callstack () "Show an arbitrary path from a root of the call graph to the function containing the current point." (interactive) - (go-guru--run "callstack" t)) + (go-guru--set-scope-if-empty) + (go-guru--start "callstack")) ;;;###autoload (defun go-guru-definition () "Jump to the definition of the selected identifier." (interactive) - (let* ((res (with-current-buffer (go-guru--exec "definition" nil '("-json")) - (goto-char (point-min)) - (json-read))) + (or buffer-file-name + (error "Cannot use guru on a buffer without a file name")) + (let* ((res (go-guru--json "definition")) (desc (cdr (assoc 'desc res)))) (push-mark) (ring-insert find-tag-marker-ring (point-marker)) @@ -323,54 +331,55 @@ function containing the current point." (defun go-guru-describe () "Describe the selected syntax, its kind, type and methods." (interactive) - (go-guru--run "describe")) + (go-guru--start "describe")) ;;;###autoload (defun go-guru-pointsto () "Show what the selected expression points to." (interactive) - (go-guru--run "pointsto" t)) + (go-guru--set-scope-if-empty) + (go-guru--start "pointsto")) ;;;###autoload (defun go-guru-implements () "Describe the 'implements' relation for types in the package containing the current point." (interactive) - (go-guru--run "implements")) + (go-guru--start "implements")) ;;;###autoload (defun go-guru-freevars () "Enumerate the free variables of the current selection." (interactive) - (go-guru--run "freevars")) + (go-guru--start "freevars")) ;;;###autoload (defun go-guru-peers () "Enumerate the set of possible corresponding sends/receives for this channel receive/send operation." (interactive) - (go-guru--run "peers" t)) + (go-guru--set-scope-if-empty) + (go-guru--start "peers")) ;;;###autoload (defun go-guru-referrers () "Enumerate all references to the object denoted by the selected identifier." (interactive) - (go-guru--run "referrers")) + (go-guru--start "referrers")) ;;;###autoload (defun go-guru-whicherrs () "Show globals, constants and types to which the selected expression (of type 'error') may refer." (interactive) - (go-guru--run "whicherrs" t)) + (go-guru--set-scope-if-empty) + (go-guru--start "whicherrs")) (defun go-guru-what () "Run a 'what' query and return the parsed JSON response as an association list." - (with-current-buffer (go-guru--exec "what" nil '("-json") t) - (goto-char (point-min)) - (json-read))) + (go-guru--json "what")) (defun go-guru--hl-symbols (posn face id) "Highlight the symbols at the positions POSN by creating @@ -411,8 +420,8 @@ identifier at point, if necessary." ;; every time the timer runs, e.g. because of a malformed ;; buffer. (condition-case nil - (go-guru-hl-identifier) - (error nil))) + (go-guru-hl-identifier) + (error nil))) (unless (eq go-guru--current-hl-identifier-idle-time go-guru-hl-identifier-idle-time) (go-guru--hl-set-timer)))) @@ -466,20 +475,20 @@ Two regions are considered equal if they have the same start and end point." (let ((enclosing (go-guru--enclosing))) (cl-remove-duplicates enclosing - :from-end t - :test (lambda (a b) - (and (= (cdr (assoc 'start a)) - (cdr (assoc 'start b))) - (= (cdr (assoc 'end a)) - (cdr (assoc 'end b)))))))) + :from-end t + :test (lambda (a b) + (and (= (cdr (assoc 'start a)) + (cdr (assoc 'start b))) + (= (cdr (assoc 'end a)) + (cdr (assoc 'end b)))))))) (defun go-guru-expand-region () "Expand region to the next enclosing syntactic unit." (interactive) (let* ((enclosing (if (eq last-command #'go-guru-expand-region) - go-guru--last-enclosing - (go-guru--enclosing-unique))) - (block (if (> (length enclosing) 0) (elt enclosing 0)))) + go-guru--last-enclosing + (go-guru--enclosing-unique))) + (block (if (> (length enclosing) 0) (elt enclosing 0)))) (when block (go-guru--goto-byte (1+ (cdr (assoc 'start block)))) (set-mark (byte-to-position (1+ (cdr (assoc 'end block))))) @@ -490,4 +499,9 @@ end point." (provide 'go-guru) +;; Local variables: +;; indent-tabs-mode: t +;; tab-width: 8 +;; End + ;;; go-guru.el ends here diff --git a/vendor/golang.org/x/tools/cmd/guru/guru_test.go b/vendor/golang.org/x/tools/cmd/guru/guru_test.go index 9637464018..b4073d945d 100644 --- a/vendor/golang.org/x/tools/cmd/guru/guru_test.go +++ b/vendor/golang.org/x/tools/cmd/guru/guru_test.go @@ -160,18 +160,22 @@ func doQuery(out io.Writer, q *query, json bool) { buildContext.GOPATH = "testdata" pkg := filepath.Dir(strings.TrimPrefix(q.filename, "testdata/src/")) - var outputMu sync.Mutex // guards out, jsons - var jsons []string - output := func(fset *token.FileSet, qr guru.QueryResult) { + gopathAbs, _ := filepath.Abs(buildContext.GOPATH) + + var outputMu sync.Mutex // guards outputs + var outputs []string // JSON objects or lines of text + outputFn := func(fset *token.FileSet, qr guru.QueryResult) { outputMu.Lock() defer outputMu.Unlock() if json { - jsons = append(jsons, string(qr.JSON(fset))) + jsonstr := string(qr.JSON(fset)) + // Sanitize any absolute filenames that creep in. + jsonstr = strings.Replace(jsonstr, gopathAbs, "$GOPATH", -1) + outputs = append(outputs, jsonstr) } else { // suppress position information qr.PrintPlain(func(_ interface{}, format string, args ...interface{}) { - fmt.Fprintf(out, format, args...) - io.WriteString(out, "\n") + outputs = append(outputs, fmt.Sprintf(format, args...)) }) } } @@ -181,7 +185,7 @@ func doQuery(out io.Writer, q *query, json bool) { Build: &buildContext, Scope: []string{pkg}, Reflection: true, - Output: output, + Output: outputFn, } if err := guru.Run(q.verb, &query); err != nil { @@ -189,14 +193,18 @@ func doQuery(out io.Writer, q *query, json bool) { return } - if json { - if q.verb == "referrers" { - sort.Strings(jsons[1:]) // for determinism - } - for _, json := range jsons { - fmt.Fprintf(out, "%s\n", json) - } - } else { + // In a "referrers" query, references are sorted within each + // package but packages are visited in arbitrary order, + // so for determinism we sort them. Line 0 is a caption. + if q.verb == "referrers" { + sort.Strings(outputs[1:]) + } + + for _, output := range outputs { + fmt.Fprintf(out, "%s\n", output) + } + + if !json { io.WriteString(out, "\n") } } @@ -226,6 +234,7 @@ func TestGuru(t *testing.T) { // TODO(adonovan): most of these are very similar; combine them. "testdata/src/calls-json/main.go", "testdata/src/peers-json/main.go", + "testdata/src/definition-json/main.go", "testdata/src/describe-json/main.go", "testdata/src/implements-json/main.go", "testdata/src/implements-methods-json/main.go", diff --git a/vendor/golang.org/x/tools/cmd/guru/implements.go b/vendor/golang.org/x/tools/cmd/guru/implements.go index e852441e96..b7c29626ab 100644 --- a/vendor/golang.org/x/tools/cmd/guru/implements.go +++ b/vendor/golang.org/x/tools/cmd/guru/implements.go @@ -88,11 +88,17 @@ func implements(q *Query) error { T = recv.Type() } } + + // If not a method, use the expression's type. + if T == nil { + T = qpos.info.TypeOf(path[0].(ast.Expr)) + } + case actionType: T = qpos.info.TypeOf(path[0].(ast.Expr)) } if T == nil { - return fmt.Errorf("no type or method here") + return fmt.Errorf("not a type, method, or value") } // Find all named types, even local types (which can have diff --git a/vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main.go b/vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main.go new file mode 100644 index 0000000000..ba13b6ba57 --- /dev/null +++ b/vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main.go @@ -0,0 +1,44 @@ +package definition + +// Tests of 'definition' query, -json output. +// See go.tools/guru/guru_test.go for explanation. +// See definition.golden for expected query results. + +// TODO(adonovan): test: selection of member of same package defined in another file. + +import ( + "lib" + lib2 "lib" + "nosuchpkg" +) + +func main() { + var _ int // @definition builtin "int" + + var _ undef // @definition lexical-undef "undef" + var x lib.T // @definition lexical-pkgname "lib" + f() // @definition lexical-func "f" + print(x) // @definition lexical-var "x" + if x := ""; x == "" { // @definition lexical-shadowing "x" + } + + var _ lib.Type // @definition qualified-type "Type" + var _ lib.Func // @definition qualified-func "Func" + var _ lib.Var // @definition qualified-var "Var" + var _ lib.Const // @definition qualified-const "Const" + var _ lib2.Type // @definition qualified-type-renaming "Type" + var _ lib.Nonesuch // @definition qualified-nomember "Nonesuch" + var _ nosuchpkg.T // @definition qualified-nopkg "nosuchpkg" + + var u U + print(u.field) // @definition select-field "field" + u.method() // @definition select-method "method" +} + +func f() + +type T struct{ field int } + +func (T) method() + +type U struct{ T } diff --git a/vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main.golden b/vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main.golden new file mode 100644 index 0000000000..c2e1349ce2 --- /dev/null +++ b/vendor/golang.org/x/tools/cmd/guru/testdata/src/definition-json/main.golden @@ -0,0 +1,67 @@ +-------- @definition builtin -------- + +Error: int is built in +-------- @definition lexical-undef -------- + +Error: no object for identifier +-------- @definition lexical-pkgname -------- +{ + "objpos": "testdata/src/definition-json/main.go:10:2", + "desc": "package lib" +} +-------- @definition lexical-func -------- +{ + "objpos": "$GOPATH/src/definition-json/main.go:38:6", + "desc": "func f" +} +-------- @definition lexical-var -------- +{ + "objpos": "$GOPATH/src/definition-json/main.go:19:6", + "desc": "var x" +} +-------- @definition lexical-shadowing -------- +{ + "objpos": "$GOPATH/src/definition-json/main.go:22:5", + "desc": "var x" +} +-------- @definition qualified-type -------- +{ + "objpos": "testdata/src/lib/lib.go:3:6", + "desc": "type lib.Type" +} +-------- @definition qualified-func -------- +{ + "objpos": "testdata/src/lib/lib.go:9:6", + "desc": "func lib.Func" +} +-------- @definition qualified-var -------- +{ + "objpos": "testdata/src/lib/lib.go:14:5", + "desc": "var lib.Var" +} +-------- @definition qualified-const -------- +{ + "objpos": "testdata/src/lib/lib.go:12:7", + "desc": "const lib.Const" +} +-------- @definition qualified-type-renaming -------- +{ + "objpos": "testdata/src/lib/lib.go:3:6", + "desc": "type lib.Type" +} +-------- @definition qualified-nomember -------- + +Error: couldn't find declaration of Nonesuch in "lib" +-------- @definition qualified-nopkg -------- + +Error: no object for identifier +-------- @definition select-field -------- +{ + "objpos": "testdata/src/definition-json/main.go:40:16", + "desc": "field field int" +} +-------- @definition select-method -------- +{ + "objpos": "testdata/src/definition-json/main.go:42:10", + "desc": "func (T).method()" +} diff --git a/vendor/golang.org/x/tools/cmd/guru/testdata/src/implements/main.go b/vendor/golang.org/x/tools/cmd/guru/testdata/src/implements/main.go index 22457d9983..fea9006ec9 100644 --- a/vendor/golang.org/x/tools/cmd/guru/testdata/src/implements/main.go +++ b/vendor/golang.org/x/tools/cmd/guru/testdata/src/implements/main.go @@ -37,3 +37,8 @@ func (sorter) Swap(i, j int) {} type I interface { // @implements I "I" Method(*int) *int } + +func _() { + var d D + _ = d // @implements var_d "d" +} diff --git a/vendor/golang.org/x/tools/cmd/guru/testdata/src/implements/main.golden b/vendor/golang.org/x/tools/cmd/guru/testdata/src/implements/main.golden index ee00f3d9fe..1077c9827b 100644 --- a/vendor/golang.org/x/tools/cmd/guru/testdata/src/implements/main.golden +++ b/vendor/golang.org/x/tools/cmd/guru/testdata/src/implements/main.golden @@ -42,3 +42,9 @@ slice type sorter interface type I is implemented by basic type lib.Type +-------- @implements var_d -------- +struct type D + implements F +pointer type *D + implements FG + diff --git a/vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers-json/main.golden b/vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers-json/main.golden index 3fcd288a18..9c3ee98b77 100644 --- a/vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers-json/main.golden +++ b/vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers-json/main.golden @@ -2,6 +2,39 @@ { "desc": "package lib" } +{ + "package": "definition-json", + "refs": [ + { + "pos": "testdata/src/definition-json/main.go:19:8", + "text": "\tvar x lib.T // @definition lexical-pkgname \"lib\"" + }, + { + "pos": "testdata/src/definition-json/main.go:25:8", + "text": "\tvar _ lib.Type // @definition qualified-type \"Type\"" + }, + { + "pos": "testdata/src/definition-json/main.go:26:8", + "text": "\tvar _ lib.Func // @definition qualified-func \"Func\"" + }, + { + "pos": "testdata/src/definition-json/main.go:27:8", + "text": "\tvar _ lib.Var // @definition qualified-var \"Var\"" + }, + { + "pos": "testdata/src/definition-json/main.go:28:8", + "text": "\tvar _ lib.Const // @definition qualified-const \"Const\"" + }, + { + "pos": "testdata/src/definition-json/main.go:29:8", + "text": "\tvar _ lib2.Type // @definition qualified-type-renaming \"Type\"" + }, + { + "pos": "testdata/src/definition-json/main.go:30:8", + "text": "\tvar _ lib.Nonesuch // @definition qualified-nomember \"Nonesuch\"" + } + ] +} { "package": "describe", "refs": [ diff --git a/vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers/main.golden b/vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers/main.golden index 044c54c7f7..9cac5040ca 100644 --- a/vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers/main.golden +++ b/vendor/golang.org/x/tools/cmd/guru/testdata/src/referrers/main.golden @@ -9,35 +9,42 @@ references to type s struct{f int} -------- @referrers ref-package -------- references to package lib - var v lib.Type = lib.Const // @referrers ref-package "lib" - var v lib.Type = lib.Const // @referrers ref-package "lib" - var v lib.Type = lib.Const // @referrers ref-package "lib" - var v lib.Type = lib.Const // @referrers ref-package "lib" - var _ lib.Outer // @describe lib-outer "Outer" + _ = (lib.Type).Method // ref from external test package + _ = (lib.Type).Method // ref from internal test package const c = lib.Const // @describe ref-const "Const" lib.Func() // @describe ref-func "Func" lib.Var++ // @describe ref-var "Var" - var t lib.Type // @describe ref-type "Type" + var _ lib.Const // @definition qualified-const "Const" + var _ lib.Func // @definition qualified-func "Func" + var _ lib.Nonesuch // @definition qualified-nomember "Nonesuch" + var _ lib.Outer // @describe lib-outer "Outer" + var _ lib.Type // @definition qualified-type "Type" var _ lib.Type // @describe ref-pkg "lib" - _ = (lib.Type).Method // ref from internal test package - _ = (lib.Type).Method // ref from external test package + var _ lib.Var // @definition qualified-var "Var" + var _ lib2.Type // @definition qualified-type-renaming "Type" + var t lib.Type // @describe ref-type "Type" + var v lib.Type = lib.Const // @referrers ref-package "lib" + var v lib.Type = lib.Const // @referrers ref-package "lib" + var v lib.Type = lib.Const // @referrers ref-package "lib" + var v lib.Type = lib.Const // @referrers ref-package "lib" + var x lib.T // @definition lexical-pkgname "lib" -------- @referrers ref-method -------- references to func (Type).Method(x *int) *int - _ = v.Method // @referrers ref-method "Method" + _ = (lib.Type).Method // ref from external test package + _ = (lib.Type).Method // ref from internal test package _ = v.Method - _ = v.Method // @referrers ref-method "Method" _ = v.Method + _ = v.Method // @referrers ref-method "Method" + _ = v.Method // @referrers ref-method "Method" p := t.Method(&a) // @describe ref-method "Method" - _ = (lib.Type).Method // ref from internal test package - _ = (lib.Type).Method // ref from external test package -------- @referrers ref-local -------- references to var v lib.Type - _ = v.Method // @referrers ref-method "Method" _ = v.Method - v++ //@referrers ref-local "v" + _ = v.Method // @referrers ref-method "Method" v++ + v++ //@referrers ref-local "v" -------- @referrers ref-field -------- references to field f int diff --git a/vendor/golang.org/x/tools/cmd/html2article/conv.go b/vendor/golang.org/x/tools/cmd/html2article/conv.go index d3267d7a08..419505f60b 100644 --- a/vendor/golang.org/x/tools/cmd/html2article/conv.go +++ b/vendor/golang.org/x/tools/cmd/html2article/conv.go @@ -7,7 +7,6 @@ package main // import "golang.org/x/tools/cmd/html2article" import ( - "bufio" "bytes" "errors" "flag" @@ -39,7 +38,9 @@ func convert(w io.Writer, r io.Reader) error { } style := find(root, isTag(atom.Style)) - parseStyles(style) + if err := parseStyles(style); err != nil { + log.Printf("couldn't parse all styles: %v", err) + } body := find(root, isTag(atom.Body)) if body == nil { @@ -60,59 +61,44 @@ const ( var cssRules = make(map[string]Style) -func parseStyles(style *html.Node) { +func parseStyles(style *html.Node) error { if style == nil || style.FirstChild == nil { - log.Println("couldn't find styles") - return + return errors.New("couldn't find styles") } - s := bufio.NewScanner(strings.NewReader(style.FirstChild.Data)) - findRule := func(b []byte, atEOF bool) (advance int, token []byte, err error) { - if i := bytes.Index(b, []byte("{")); i >= 0 { - token = bytes.TrimSpace(b[:i]) - advance = i + styles := style.FirstChild.Data + readUntil := func(end rune) (string, bool) { + i := strings.IndexRune(styles, end) + if i < 0 { + return "", false } - return - } - findBody := func(b []byte, atEOF bool) (advance int, token []byte, err error) { - if len(b) == 0 { - return - } - if b[0] != '{' { - err = fmt.Errorf("expected {, got %c", b[0]) - return - } - if i := bytes.Index(b, []byte("}")); i < 0 { - err = fmt.Errorf("can't find closing }") - return - } else { - token = b[1:i] - advance = i + 1 - } - return + s := styles[:i] + styles = styles[i:] + return s, true } - s.Split(findRule) - for s.Scan() { - rule := s.Text() - s.Split(findBody) - if !s.Scan() { + for { + sel, ok := readUntil('{') + if !ok && sel == "" { break + } else if !ok { + return fmt.Errorf("could not parse selector %q", styles) + } + + value, ok := readUntil('}') + if !ok { + return fmt.Errorf("couldn't parse style body for %s", sel) } - b := strings.ToLower(s.Text()) switch { - case strings.Contains(b, "italic"): - cssRules[rule] = Italic - case strings.Contains(b, "bold"): - cssRules[rule] = Bold - case strings.Contains(b, "Consolas") || strings.Contains(b, "Courier New"): - cssRules[rule] = Code + case strings.Contains(value, "italic"): + cssRules[sel] = Italic + case strings.Contains(value, "bold"): + cssRules[sel] = Bold + case strings.Contains(value, "Consolas") || strings.Contains(value, "Courier New"): + cssRules[sel] = Code } - s.Split(findRule) - } - if err := s.Err(); err != nil { - log.Println(err) } + return nil } var newlineRun = regexp.MustCompile(`\n\n+`) diff --git a/vendor/golang.org/x/tools/cmd/tip/Dockerfile b/vendor/golang.org/x/tools/cmd/tip/Dockerfile index 8364cdb8a3..d258f7f733 100644 --- a/vendor/golang.org/x/tools/cmd/tip/Dockerfile +++ b/vendor/golang.org/x/tools/cmd/tip/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.5 +FROM golang:1.6 RUN apt-get update && apt-get install --no-install-recommends -y -q build-essential git @@ -10,4 +10,4 @@ ADD . /go/src/tip RUN go install tip ENTRYPOINT ["/go/bin/tip"] # Kubernetes expects us to listen on port 8080 -EXPOSE 8080 +EXPOSE 8080 diff --git a/vendor/golang.org/x/tools/cmd/tip/talks.go b/vendor/golang.org/x/tools/cmd/tip/talks.go index 15167e1c97..6a1b8c2989 100644 --- a/vendor/golang.org/x/tools/cmd/tip/talks.go +++ b/vendor/golang.org/x/tools/cmd/tip/talks.go @@ -21,7 +21,7 @@ func (b talksBuilder) Signature(heads map[string]string) string { return heads["talks"] } -const talksToolsRev = "ac6d9c1d842f9b6482f39f7a172e0251a0f7cbc0" +const talksToolsRev = "1f1b3322f67af76803c942fd237291538ec68262" func (b talksBuilder) Init(dir, hostport string, heads map[string]string) (*exec.Cmd, error) { toolsDir := filepath.Join(dir, "gopath/src/golang.org/x/tools") diff --git a/vendor/golang.org/x/tools/cmd/tip/tip.go b/vendor/golang.org/x/tools/cmd/tip/tip.go index 43894144d6..c3de65cc42 100644 --- a/vendor/golang.org/x/tools/cmd/tip/tip.go +++ b/vendor/golang.org/x/tools/cmd/tip/tip.go @@ -44,7 +44,7 @@ func main() { p := &Proxy{builder: b} go p.run() - http.Handle("/", p) + http.Handle("/", httpsOnlyHandler{p}) http.HandleFunc("/_ah/health", p.serveHealthCheck) log.Print("Starting up") @@ -323,3 +323,23 @@ func getOK(url string) (body []byte, err error) { } return body, nil } + +// httpsOnlyHandler redirects requests to "http://example.com/foo?bar" +// to "https://example.com/foo?bar" +type httpsOnlyHandler struct { + h http.Handler +} + +func (h httpsOnlyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("X-Appengine-Https") == "off" { + r.URL.Scheme = "https" + r.URL.Host = r.Host + http.Redirect(w, r, r.URL.String(), http.StatusFound) + return + } + if r.Header.Get("X-Appengine-Https") == "on" { + // Only set this header when we're actually in production. + w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload") + } + h.h.ServeHTTP(w, r) +} diff --git a/vendor/golang.org/x/tools/go/gcimporter15/bexport.go b/vendor/golang.org/x/tools/go/gcimporter15/bexport.go index e8ac1babbc..1ceae1377d 100644 --- a/vendor/golang.org/x/tools/go/gcimporter15/bexport.go +++ b/vendor/golang.org/x/tools/go/gcimporter15/bexport.go @@ -52,8 +52,9 @@ type exporter struct { typIndex map[types.Type]int // position encoding - prevFile string - prevLine int + posInfoFormat bool + prevFile string + prevLine int // debugging support written int // bytes written @@ -64,10 +65,11 @@ type exporter struct { // If no file set is provided, position info will be missing. func BExportData(fset *token.FileSet, pkg *types.Package) []byte { p := exporter{ - fset: fset, - strIndex: map[string]int{"": 0}, // empty string is mapped to 0 - pkgIndex: make(map[*types.Package]int), - typIndex: make(map[types.Type]int), + fset: fset, + strIndex: map[string]int{"": 0}, // empty string is mapped to 0 + pkgIndex: make(map[*types.Package]int), + typIndex: make(map[types.Type]int), + posInfoFormat: true, // TODO(gri) might become a flag, eventually } // first byte indicates low-level encoding format @@ -77,6 +79,9 @@ func BExportData(fset *token.FileSet, pkg *types.Package) []byte { } p.rawByte(format) + // posInfo exported or not? + p.bool(p.posInfoFormat) + // --- generic export data --- if trace { @@ -200,27 +205,58 @@ func (p *exporter) obj(obj types.Object) { } func (p *exporter) pos(obj types.Object) { - var file string - var line int - if p.fset != nil { - pos := p.fset.Position(obj.Pos()) - file = pos.Filename - line = pos.Line + if !p.posInfoFormat { + return } - if file == p.prevFile && line != p.prevLine { - // common case: write delta-encoded line number - p.int(line - p.prevLine) // != 0 + file, line := p.fileLine(obj) + if file == p.prevFile { + // common case: write line delta + // delta == 0 means different file or no line change + delta := line - p.prevLine + p.int(delta) + if delta == 0 { + p.int(-1) // -1 means no file change + } } else { - // uncommon case: filename changed, or line didn't change + // different file p.int(0) - p.string(file) - p.int(line) + // Encode filename as length of common prefix with previous + // filename, followed by (possibly empty) suffix. Filenames + // frequently share path prefixes, so this can save a lot + // of space and make export data size less dependent on file + // path length. The suffix is unlikely to be empty because + // file names tend to end in ".go". + n := commonPrefixLen(p.prevFile, file) + p.int(n) // n >= 0 + p.string(file[n:]) // write suffix only p.prevFile = file + p.int(line) } p.prevLine = line } +func (p *exporter) fileLine(obj types.Object) (file string, line int) { + if p.fset != nil { + pos := p.fset.Position(obj.Pos()) + file = pos.Filename + line = pos.Line + } + return +} + +func commonPrefixLen(a, b string) int { + if len(a) > len(b) { + a, b = b, a + } + // len(a) <= len(b) + i := 0 + for i < len(a) && a[i] == b[i] { + i++ + } + return i +} + func (p *exporter) qualifiedName(obj types.Object) { p.string(obj.Name()) p.pkg(obj.Pkg(), false) @@ -454,8 +490,11 @@ func (p *exporter) paramList(params *types.Tuple, variadic bool) { } p.typ(t) if n > 0 { - p.string(q.Name()) - p.pkg(q.Pkg(), false) + name := q.Name() + p.string(name) + if name != "_" { + p.pkg(q.Pkg(), false) + } } p.string("") // no compiler-specific info } @@ -563,6 +602,20 @@ func valueToRat(x constant.Value) *big.Rat { return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) } +func (p *exporter) bool(b bool) bool { + if trace { + p.tracef("[") + defer p.tracef("= %v] ", b) + } + + x := 0 + if b { + x = 1 + } + p.int(x) + return b +} + // ---------------------------------------------------------------------------- // Low-level encoders diff --git a/vendor/golang.org/x/tools/go/gcimporter15/bimport.go b/vendor/golang.org/x/tools/go/gcimporter15/bimport.go index fd77daf926..a742dbfd28 100644 --- a/vendor/golang.org/x/tools/go/gcimporter15/bimport.go +++ b/vendor/golang.org/x/tools/go/gcimporter15/bimport.go @@ -6,10 +6,6 @@ // This file is a copy of $GOROOT/src/go/internal/gcimporter/bimport.go, tagged for go1.5. -// 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 gcimporter import ( @@ -19,6 +15,7 @@ import ( "go/token" "go/types" "sort" + "strings" "unicode" "unicode/utf8" ) @@ -35,8 +32,9 @@ type importer struct { typList []types.Type // in order of appearance // position encoding - prevFile string - prevLine int + posInfoFormat bool + prevFile string + prevLine int // debugging support debugFormat bool @@ -65,6 +63,8 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i return p.read, nil, fmt.Errorf("invalid encoding format in export data: got %q; want 'c' or 'd'", format) } + p.posInfoFormat = p.int() != 0 + // --- generic export data --- if v := p.string(); v != "v0" { @@ -202,15 +202,20 @@ func (p *importer) obj(tag int) { } func (p *importer) pos() { + if !p.posInfoFormat { + return + } + file := p.prevFile line := p.prevLine - if delta := p.int(); delta != 0 { + // line changed line += delta - } else { - file = p.string() - line = p.int() + } else if n := p.int(); n >= 0 { + // file changed + file = p.prevFile[:n] + p.string() p.prevFile = file + line = p.int() } p.prevLine = line @@ -505,7 +510,12 @@ func (p *importer) param(named bool) (*types.Var, bool) { if name == "" { panic("expected named parameter") } - pkg = p.pkg() + if name != "_" { + pkg = p.pkg() + } + if i := strings.Index(name, "·"); i > 0 { + name = name[:i] // cut off gc-specific parameter numbering + } } // read and discard compiler-specific info diff --git a/vendor/golang.org/x/tools/go/ssa/builder_test.go b/vendor/golang.org/x/tools/go/ssa/builder_test.go index fdb58ff90c..5f4ff9f272 100644 --- a/vendor/golang.org/x/tools/go/ssa/builder_test.go +++ b/vendor/golang.org/x/tools/go/ssa/builder_test.go @@ -69,8 +69,9 @@ func main() { deps := []string{ // directly imported dependencies: "bytes", "io", "testing", - // indirect dependencies (partial list): - "errors", "fmt", "os", "runtime", + // indirect dependencies mentioned by + // the direct imports' export data + "sync", "unicode", "time", } prog := mainPkg.Prog diff --git a/vendor/golang.org/x/tools/go/ssa/interp/external.go b/vendor/golang.org/x/tools/go/ssa/interp/external.go index 9c02e54745..905e2ebcbd 100644 --- a/vendor/golang.org/x/tools/go/ssa/interp/external.go +++ b/vendor/golang.org/x/tools/go/ssa/interp/external.go @@ -15,6 +15,7 @@ import ( "os" "runtime" "strings" + "sync/atomic" "syscall" "time" "unsafe" @@ -104,6 +105,7 @@ func init() { "runtime.Gosched": ext۰runtime۰Gosched, "runtime.init": ext۰runtime۰init, "runtime.NumCPU": ext۰runtime۰NumCPU, + "runtime.NumGoroutine": ext۰runtime۰NumGoroutine, "runtime.ReadMemStats": ext۰runtime۰ReadMemStats, "runtime.SetFinalizer": ext۰runtime۰SetFinalizer, "(*runtime.Func).Entry": ext۰runtime۰Func۰Entry, @@ -387,6 +389,10 @@ func ext۰runtime۰NumCPU(fr *frame, args []value) value { return runtime.NumCPU() } +func ext۰runtime۰NumGoroutine(fr *frame, args []value) value { + return int(atomic.LoadInt32(&fr.i.goroutines)) +} + func ext۰runtime۰ReadMemStats(fr *frame, args []value) value { // TODO(adonovan): populate args[0].(Struct) return nil diff --git a/vendor/golang.org/x/tools/go/ssa/interp/interp.go b/vendor/golang.org/x/tools/go/ssa/interp/interp.go index b855645f32..730d0f55ca 100644 --- a/vendor/golang.org/x/tools/go/ssa/interp/interp.go +++ b/vendor/golang.org/x/tools/go/ssa/interp/interp.go @@ -53,6 +53,7 @@ import ( "os" "reflect" "runtime" + "sync/atomic" "golang.org/x/tools/go/ssa" ) @@ -86,6 +87,7 @@ type interpreter struct { rtypeMethods methodSet // the method set of rtype, which implements the reflect.Type interface. runtimeErrorString types.Type // the runtime.errorString type sizes types.Sizes // the effective type-sizing function + goroutines int32 // atomically updated } type deferred struct { @@ -269,7 +271,11 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation { case *ssa.Go: fn, args := prepareCall(fr, &instr.Call) - go call(fr.i, nil, instr.Pos(), fn, args) + atomic.AddInt32(&fr.i.goroutines, 1) + go func() { + call(fr.i, nil, instr.Pos(), fn, args) + atomic.AddInt32(&fr.i.goroutines, -1) + }() case *ssa.MakeChan: fr.env[instr] = make(chan value, asInt(fr.get(instr.Size))) @@ -660,10 +666,11 @@ func deleteBodies(pkg *ssa.Package, except ...string) { // func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) { i := &interpreter{ - prog: mainpkg.Prog, - globals: make(map[ssa.Value]*value), - mode: mode, - sizes: sizes, + prog: mainpkg.Prog, + globals: make(map[ssa.Value]*value), + mode: mode, + sizes: sizes, + goroutines: 1, } runtimePkg := i.prog.ImportedPackage("runtime") if runtimePkg == nil { diff --git a/vendor/golang.org/x/tools/godoc/godoc.go b/vendor/golang.org/x/tools/godoc/godoc.go index dda1f49cb3..7bb4829a00 100644 --- a/vendor/golang.org/x/tools/godoc/godoc.go +++ b/vendor/golang.org/x/tools/godoc/godoc.go @@ -587,7 +587,7 @@ func startsWithUppercase(s string) bool { return unicode.IsUpper(r) } -var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`) +var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`) // stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name // while keeping uppercase Braz in Foo_Braz. diff --git a/vendor/golang.org/x/tools/imports/imports.go b/vendor/golang.org/x/tools/imports/imports.go index 9260823ce6..c26c1946a6 100644 --- a/vendor/golang.org/x/tools/imports/imports.go +++ b/vendor/golang.org/x/tools/imports/imports.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:generate go run mkstdlib.go + // Package imports implements a Go pretty-printer (like package "go/format") // that also adds or removes import statements as necessary. package imports // import "golang.org/x/tools/imports" diff --git a/vendor/golang.org/x/tools/imports/mkstdlib.go b/vendor/golang.org/x/tools/imports/mkstdlib.go index e2dca76006..3a2e39d7a3 100644 --- a/vendor/golang.org/x/tools/imports/mkstdlib.go +++ b/vendor/golang.org/x/tools/imports/mkstdlib.go @@ -11,6 +11,7 @@ import ( "fmt" "go/format" "io" + "io/ioutil" "log" "os" "path" @@ -49,6 +50,7 @@ func main() { mustOpen(api("go1.3.txt")), mustOpen(api("go1.4.txt")), mustOpen(api("go1.5.txt")), + mustOpen(api("go1.6.txt")), ) sc := bufio.NewScanner(f) fullImport := map[string]string{} // "zip.NewReader" => "archive/zip" @@ -89,5 +91,8 @@ func main() { if err != nil { log.Fatal(err) } - os.Stdout.Write(fmtbuf) + err = ioutil.WriteFile("zstdlib.go", fmtbuf, 0666) + if err != nil { + log.Fatal(err) + } } diff --git a/vendor/golang.org/x/tools/imports/zstdlib.go b/vendor/golang.org/x/tools/imports/zstdlib.go index 39eb3c74b1..6fe21b6b70 100644 --- a/vendor/golang.org/x/tools/imports/zstdlib.go +++ b/vendor/golang.org/x/tools/imports/zstdlib.go @@ -16,6 +16,10 @@ var stdlib = map[string]string{ "ascii85.NewDecoder": "encoding/ascii85", "ascii85.NewEncoder": "encoding/ascii85", "asn1.BitString": "encoding/asn1", + "asn1.ClassApplication": "encoding/asn1", + "asn1.ClassContextSpecific": "encoding/asn1", + "asn1.ClassPrivate": "encoding/asn1", + "asn1.ClassUniversal": "encoding/asn1", "asn1.Enumerated": "encoding/asn1", "asn1.Flag": "encoding/asn1", "asn1.Marshal": "encoding/asn1", @@ -24,6 +28,21 @@ var stdlib = map[string]string{ "asn1.RawValue": "encoding/asn1", "asn1.StructuralError": "encoding/asn1", "asn1.SyntaxError": "encoding/asn1", + "asn1.TagBitString": "encoding/asn1", + "asn1.TagBoolean": "encoding/asn1", + "asn1.TagEnum": "encoding/asn1", + "asn1.TagGeneralString": "encoding/asn1", + "asn1.TagGeneralizedTime": "encoding/asn1", + "asn1.TagIA5String": "encoding/asn1", + "asn1.TagInteger": "encoding/asn1", + "asn1.TagOID": "encoding/asn1", + "asn1.TagOctetString": "encoding/asn1", + "asn1.TagPrintableString": "encoding/asn1", + "asn1.TagSequence": "encoding/asn1", + "asn1.TagSet": "encoding/asn1", + "asn1.TagT61String": "encoding/asn1", + "asn1.TagUTCTime": "encoding/asn1", + "asn1.TagUTF8String": "encoding/asn1", "asn1.Unmarshal": "encoding/asn1", "asn1.UnmarshalWithParams": "encoding/asn1", "ast.ArrayType": "go/ast", @@ -212,6 +231,7 @@ var stdlib = map[string]string{ "binary.Write": "encoding/binary", "bufio.ErrAdvanceTooFar": "bufio", "bufio.ErrBufferFull": "bufio", + "bufio.ErrFinalToken": "bufio", "bufio.ErrInvalidUnreadByte": "bufio", "bufio.ErrInvalidUnreadRune": "bufio", "bufio.ErrNegativeAdvance": "bufio", @@ -238,6 +258,7 @@ var stdlib = map[string]string{ "build.Context": "go/build", "build.Default": "go/build", "build.FindOnly": "go/build", + "build.IgnoreVendor": "go/build", "build.Import": "go/build", "build.ImportComment": "go/build", "build.ImportDir": "go/build", @@ -363,6 +384,8 @@ var stdlib = map[string]string{ "color.NRGBA64": "image/color", "color.NRGBA64Model": "image/color", "color.NRGBAModel": "image/color", + "color.NYCbCrA": "image/color", + "color.NYCbCrAModel": "image/color", "color.Opaque": "image/color", "color.Palette": "image/color", "color.RGBA": "image/color", @@ -406,6 +429,9 @@ var stdlib = map[string]string{ "constant.Sign": "go/constant", "constant.String": "go/constant", "constant.StringVal": "go/constant", + "constant.ToComplex": "go/constant", + "constant.ToFloat": "go/constant", + "constant.ToInt": "go/constant", "constant.Uint64Val": "go/constant", "constant.UnaryOp": "go/constant", "constant.Unknown": "go/constant", @@ -473,6 +499,7 @@ var stdlib = map[string]string{ "debug.SetMaxStack": "runtime/debug", "debug.SetMaxThreads": "runtime/debug", "debug.SetPanicOnFault": "runtime/debug", + "debug.SetTraceback": "runtime/debug", "debug.Stack": "runtime/debug", "debug.WriteHeapDump": "runtime/debug", "des.BlockSize": "crypto/des", @@ -634,6 +661,7 @@ var stdlib = map[string]string{ "dwarf.ClassReferenceSig": "debug/dwarf", "dwarf.ClassString": "debug/dwarf", "dwarf.ClassStringAlt": "debug/dwarf", + "dwarf.ClassUnknown": "debug/dwarf", "dwarf.CommonType": "debug/dwarf", "dwarf.ComplexType": "debug/dwarf", "dwarf.Data": "debug/dwarf", @@ -732,7 +760,15 @@ var stdlib = map[string]string{ "ecdsa.Sign": "crypto/ecdsa", "ecdsa.Verify": "crypto/ecdsa", "elf.ARM_MAGIC_TRAMP_NUMBER": "debug/elf", + "elf.COMPRESS_HIOS": "debug/elf", + "elf.COMPRESS_HIPROC": "debug/elf", + "elf.COMPRESS_LOOS": "debug/elf", + "elf.COMPRESS_LOPROC": "debug/elf", + "elf.COMPRESS_ZLIB": "debug/elf", + "elf.Chdr32": "debug/elf", + "elf.Chdr64": "debug/elf", "elf.Class": "debug/elf", + "elf.CompressionType": "debug/elf", "elf.DF_BIND_NOW": "debug/elf", "elf.DF_ORIGIN": "debug/elf", "elf.DF_STATIC_TLS": "debug/elf", @@ -1129,6 +1165,55 @@ var stdlib = map[string]string{ "elf.R_ARM_XPC25": "debug/elf", "elf.R_INFO": "debug/elf", "elf.R_INFO32": "debug/elf", + "elf.R_MIPS": "debug/elf", + "elf.R_MIPS_16": "debug/elf", + "elf.R_MIPS_26": "debug/elf", + "elf.R_MIPS_32": "debug/elf", + "elf.R_MIPS_64": "debug/elf", + "elf.R_MIPS_ADD_IMMEDIATE": "debug/elf", + "elf.R_MIPS_CALL16": "debug/elf", + "elf.R_MIPS_CALL_HI16": "debug/elf", + "elf.R_MIPS_CALL_LO16": "debug/elf", + "elf.R_MIPS_DELETE": "debug/elf", + "elf.R_MIPS_GOT16": "debug/elf", + "elf.R_MIPS_GOT_DISP": "debug/elf", + "elf.R_MIPS_GOT_HI16": "debug/elf", + "elf.R_MIPS_GOT_LO16": "debug/elf", + "elf.R_MIPS_GOT_OFST": "debug/elf", + "elf.R_MIPS_GOT_PAGE": "debug/elf", + "elf.R_MIPS_GPREL16": "debug/elf", + "elf.R_MIPS_GPREL32": "debug/elf", + "elf.R_MIPS_HI16": "debug/elf", + "elf.R_MIPS_HIGHER": "debug/elf", + "elf.R_MIPS_HIGHEST": "debug/elf", + "elf.R_MIPS_INSERT_A": "debug/elf", + "elf.R_MIPS_INSERT_B": "debug/elf", + "elf.R_MIPS_JALR": "debug/elf", + "elf.R_MIPS_LITERAL": "debug/elf", + "elf.R_MIPS_LO16": "debug/elf", + "elf.R_MIPS_NONE": "debug/elf", + "elf.R_MIPS_PC16": "debug/elf", + "elf.R_MIPS_PJUMP": "debug/elf", + "elf.R_MIPS_REL16": "debug/elf", + "elf.R_MIPS_REL32": "debug/elf", + "elf.R_MIPS_RELGOT": "debug/elf", + "elf.R_MIPS_SCN_DISP": "debug/elf", + "elf.R_MIPS_SHIFT5": "debug/elf", + "elf.R_MIPS_SHIFT6": "debug/elf", + "elf.R_MIPS_SUB": "debug/elf", + "elf.R_MIPS_TLS_DTPMOD32": "debug/elf", + "elf.R_MIPS_TLS_DTPMOD64": "debug/elf", + "elf.R_MIPS_TLS_DTPREL32": "debug/elf", + "elf.R_MIPS_TLS_DTPREL64": "debug/elf", + "elf.R_MIPS_TLS_DTPREL_HI16": "debug/elf", + "elf.R_MIPS_TLS_DTPREL_LO16": "debug/elf", + "elf.R_MIPS_TLS_GD": "debug/elf", + "elf.R_MIPS_TLS_GOTTPREL": "debug/elf", + "elf.R_MIPS_TLS_LDM": "debug/elf", + "elf.R_MIPS_TLS_TPREL32": "debug/elf", + "elf.R_MIPS_TLS_TPREL64": "debug/elf", + "elf.R_MIPS_TLS_TPREL_HI16": "debug/elf", + "elf.R_MIPS_TLS_TPREL_LO16": "debug/elf", "elf.R_PPC": "debug/elf", "elf.R_PPC64": "debug/elf", "elf.R_PPC64_ADDR14": "debug/elf", @@ -1382,6 +1467,7 @@ var stdlib = map[string]string{ "elf.Rela32": "debug/elf", "elf.Rela64": "debug/elf", "elf.SHF_ALLOC": "debug/elf", + "elf.SHF_COMPRESSED": "debug/elf", "elf.SHF_EXECINSTR": "debug/elf", "elf.SHF_GROUP": "debug/elf", "elf.SHF_INFO_LINK": "debug/elf", @@ -1714,6 +1800,7 @@ var stdlib = map[string]string{ "http.ErrNotMultipart": "net/http", "http.ErrNotSupported": "net/http", "http.ErrShortBody": "net/http", + "http.ErrSkipAltProtocol": "net/http", "http.ErrUnexpectedTrailer": "net/http", "http.ErrWriteAfterFlush": "net/http", "http.Error": "net/http", @@ -1732,6 +1819,15 @@ var stdlib = map[string]string{ "http.ListenAndServe": "net/http", "http.ListenAndServeTLS": "net/http", "http.MaxBytesReader": "net/http", + "http.MethodConnect": "net/http", + "http.MethodDelete": "net/http", + "http.MethodGet": "net/http", + "http.MethodHead": "net/http", + "http.MethodOptions": "net/http", + "http.MethodPatch": "net/http", + "http.MethodPost": "net/http", + "http.MethodPut": "net/http", + "http.MethodTrace": "net/http", "http.NewFileTransport": "net/http", "http.NewRequest": "net/http", "http.NewServeMux": "net/http", @@ -1780,6 +1876,7 @@ var stdlib = map[string]string{ "http.StatusMethodNotAllowed": "net/http", "http.StatusMovedPermanently": "net/http", "http.StatusMultipleChoices": "net/http", + "http.StatusNetworkAuthenticationRequired": "net/http", "http.StatusNoContent": "net/http", "http.StatusNonAuthoritativeInfo": "net/http", "http.StatusNotAcceptable": "net/http", @@ -1790,8 +1887,10 @@ var stdlib = map[string]string{ "http.StatusPartialContent": "net/http", "http.StatusPaymentRequired": "net/http", "http.StatusPreconditionFailed": "net/http", + "http.StatusPreconditionRequired": "net/http", "http.StatusProxyAuthRequired": "net/http", "http.StatusRequestEntityTooLarge": "net/http", + "http.StatusRequestHeaderFieldsTooLarge": "net/http", "http.StatusRequestTimeout": "net/http", "http.StatusRequestURITooLong": "net/http", "http.StatusRequestedRangeNotSatisfiable": "net/http", @@ -1802,7 +1901,9 @@ var stdlib = map[string]string{ "http.StatusTeapot": "net/http", "http.StatusTemporaryRedirect": "net/http", "http.StatusText": "net/http", + "http.StatusTooManyRequests": "net/http", "http.StatusUnauthorized": "net/http", + "http.StatusUnavailableForLegalReasons": "net/http", "http.StatusUnsupportedMediaType": "net/http", "http.StatusUseProxy": "net/http", "http.StripPrefix": "net/http", @@ -1816,6 +1917,7 @@ var stdlib = map[string]string{ "httptest.NewUnstartedServer": "net/http/httptest", "httptest.ResponseRecorder": "net/http/httptest", "httptest.Server": "net/http/httptest", + "httputil.BufferPool": "net/http/httputil", "httputil.ClientConn": "net/http/httputil", "httputil.DumpRequest": "net/http/httputil", "httputil.DumpRequestOut": "net/http/httputil", @@ -1845,6 +1947,7 @@ var stdlib = map[string]string{ "image.Image": "image", "image.NRGBA": "image", "image.NRGBA64": "image", + "image.NYCbCrA": "image", "image.NewAlpha": "image", "image.NewAlpha16": "image", "image.NewCMYK": "image", @@ -1852,6 +1955,7 @@ var stdlib = map[string]string{ "image.NewGray16": "image", "image.NewNRGBA": "image", "image.NewNRGBA64": "image", + "image.NewNYCbCrA": "image", "image.NewPaletted": "image", "image.NewRGBA": "image", "image.NewRGBA64": "image", @@ -2549,19 +2653,19 @@ var stdlib = map[string]string{ "rand.Float32": "math/rand", "rand.Float64": "math/rand", // "rand.Int" is ambiguous - "rand.Int31": "math/rand", - "rand.Int31n": "math/rand", - "rand.Int63": "math/rand", - "rand.Int63n": "math/rand", - "rand.Intn": "math/rand", - "rand.New": "math/rand", - "rand.NewSource": "math/rand", - "rand.NewZipf": "math/rand", - "rand.NormFloat64": "math/rand", - "rand.Perm": "math/rand", - "rand.Prime": "crypto/rand", - "rand.Rand": "math/rand", - "rand.Read": "crypto/rand", + "rand.Int31": "math/rand", + "rand.Int31n": "math/rand", + "rand.Int63": "math/rand", + "rand.Int63n": "math/rand", + "rand.Intn": "math/rand", + "rand.New": "math/rand", + "rand.NewSource": "math/rand", + "rand.NewZipf": "math/rand", + "rand.NormFloat64": "math/rand", + "rand.Perm": "math/rand", + "rand.Prime": "crypto/rand", + "rand.Rand": "math/rand", + // "rand.Read" is ambiguous "rand.Reader": "crypto/rand", "rand.Seed": "math/rand", "rand.Source": "math/rand", @@ -2837,7 +2941,9 @@ var stdlib = map[string]string{ "strconv.AppendQuote": "strconv", "strconv.AppendQuoteRune": "strconv", "strconv.AppendQuoteRuneToASCII": "strconv", + "strconv.AppendQuoteRuneToGraphic": "strconv", "strconv.AppendQuoteToASCII": "strconv", + "strconv.AppendQuoteToGraphic": "strconv", "strconv.AppendUint": "strconv", "strconv.Atoi": "strconv", "strconv.CanBackquote": "strconv", @@ -2848,6 +2954,7 @@ var stdlib = map[string]string{ "strconv.FormatInt": "strconv", "strconv.FormatUint": "strconv", "strconv.IntSize": "strconv", + "strconv.IsGraphic": "strconv", "strconv.IsPrint": "strconv", "strconv.Itoa": "strconv", "strconv.NumError": "strconv", @@ -2858,7 +2965,9 @@ var stdlib = map[string]string{ "strconv.Quote": "strconv", "strconv.QuoteRune": "strconv", "strconv.QuoteRuneToASCII": "strconv", + "strconv.QuoteRuneToGraphic": "strconv", "strconv.QuoteToASCII": "strconv", + "strconv.QuoteToGraphic": "strconv", "strconv.Unquote": "strconv", "strconv.UnquoteChar": "strconv", "strings.Compare": "strings", @@ -8206,12 +8315,14 @@ var stdlib = map[string]string{ "template.ErrSlashAmbig": "html/template", "template.Error": "html/template", "template.ErrorCode": "html/template", + "template.ExecError": "text/template", // "template.FuncMap" is ambiguous "template.HTML": "html/template", "template.HTMLAttr": "html/template", // "template.HTMLEscape" is ambiguous // "template.HTMLEscapeString" is ambiguous // "template.HTMLEscaper" is ambiguous + // "template.IsTrue" is ambiguous "template.JS": "html/template", // "template.JSEscape" is ambiguous // "template.JSEscapeString" is ambiguous @@ -8345,6 +8456,7 @@ var stdlib = map[string]string{ "tls.NewLRUClientSessionCache": "crypto/tls", "tls.NewListener": "crypto/tls", "tls.NoClientCert": "crypto/tls", + "tls.RecordHeaderError": "crypto/tls", "tls.RequestClientCert": "crypto/tls", "tls.RequireAndVerifyClientCert": "crypto/tls", "tls.RequireAnyClientCert": "crypto/tls", @@ -8363,7 +8475,9 @@ var stdlib = map[string]string{ "tls.TLS_FALLBACK_SCSV": "crypto/tls", "tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA": "crypto/tls", "tls.TLS_RSA_WITH_AES_128_CBC_SHA": "crypto/tls", + "tls.TLS_RSA_WITH_AES_128_GCM_SHA256": "crypto/tls", "tls.TLS_RSA_WITH_AES_256_CBC_SHA": "crypto/tls", + "tls.TLS_RSA_WITH_AES_256_GCM_SHA384": "crypto/tls", "tls.TLS_RSA_WITH_RC4_128_SHA": "crypto/tls", "tls.VerifyClientCertIfGiven": "crypto/tls", "tls.VersionSSL30": "crypto/tls", @@ -8494,7 +8608,9 @@ var stdlib = map[string]string{ "types.Id": "go/types", "types.Identical": "go/types", "types.Implements": "go/types", + "types.ImportMode": "go/types", "types.Importer": "go/types", + "types.ImporterFrom": "go/types", "types.Info": "go/types", "types.Initializer": "go/types", "types.Int": "go/types", @@ -8850,6 +8966,7 @@ var stdlib = map[string]string{ "unicode.Zs": "unicode", "url.Error": "net/url", "url.EscapeError": "net/url", + "url.InvalidHostError": "net/url", "url.Parse": "net/url", "url.ParseQuery": "net/url", "url.ParseRequestURI": "net/url", @@ -8925,6 +9042,7 @@ var stdlib = map[string]string{ "x509.HostnameError": "crypto/x509", "x509.IncompatibleUsage": "crypto/x509", "x509.IncorrectPasswordError": "crypto/x509", + "x509.InsecureAlgorithmError": "crypto/x509", "x509.InvalidReason": "crypto/x509", "x509.IsEncryptedPEMBlock": "crypto/x509", "x509.KeyUsage": "crypto/x509", diff --git a/vendor/golang.org/x/tools/playground/common.go b/vendor/golang.org/x/tools/playground/common.go index d8c28052d7..186537a6e3 100644 --- a/vendor/golang.org/x/tools/playground/common.go +++ b/vendor/golang.org/x/tools/playground/common.go @@ -15,7 +15,7 @@ import ( "net/http" ) -const baseURL = "http://play.golang.org" +const baseURL = "https://golang.org" func init() { http.HandleFunc("/compile", bounce) From 81459dbade18d94297186c1e8afd9d54e6db1cb5 Mon Sep 17 00:00:00 2001 From: Torin Sandall Date: Tue, 3 May 2016 13:40:54 -0700 Subject: [PATCH 2/2] Add basic indexing support Non-ground references are lazily indexed during evaluation if they are contained in an equality expression where the other side is ground (or ground after plugging the current bindings). Updates to data that is indexed cause the built index to be dropped from the indices. The indexing supports exact match, reverse lookup using the ground value in the expression. The result of the lookup is a set of bindings that when plugged into the reference, result in the equality evaluating to true. In this way, the indexing is not "lossy", i.e., it does not produce false matches (positive or negative). --- eval/hashmap.go | 51 ++++- eval/hashmap_test.go | 35 +++- eval/index.go | 363 +++++++++++++++++++++++++++++++++++ eval/index_test.go | 125 ++++++++++++ eval/storage.go | 441 ++++++++++++++++++++++++++----------------- eval/storage_test.go | 79 +++++++- eval/topdown.go | 162 ++++++++++++++-- eval/topdown_test.go | 42 +++-- runtime/repl_test.go | 18 +- runtime/runtime.go | 2 +- 10 files changed, 1108 insertions(+), 210 deletions(-) create mode 100644 eval/index.go create mode 100644 eval/index_test.go diff --git a/eval/hashmap.go b/eval/hashmap.go index 9d364b3843..cde248ba1f 100644 --- a/eval/hashmap.go +++ b/eval/hashmap.go @@ -17,10 +17,33 @@ type hashEntry struct { type hashMap struct { table map[int]*hashEntry + size int } func newHashMap() *hashMap { - return &hashMap{make(map[int]*hashEntry)} + return &hashMap{make(map[int]*hashEntry), 0} +} + +func (hm *hashMap) Copy() *hashMap { + cpy := newHashMap() + hm.Iter(func(k, v opalog.Value) bool { + cpy.Put(k, v) + return false + }) + return cpy +} + +func (hm *hashMap) Equal(other *hashMap) bool { + if hm.Len() != other.Len() { + return false + } + return !hm.Iter(func(k, v opalog.Value) bool { + ov := other.Get(k) + if ov == nil { + return true + } + return !v.Equal(ov) + }) } func (hm *hashMap) Get(k opalog.Value) opalog.Value { @@ -33,6 +56,15 @@ func (hm *hashMap) Get(k opalog.Value) opalog.Value { return nil } +func (hm *hashMap) Hash() int { + var hash int + hm.Iter(func(k, v opalog.Value) bool { + hash += k.Hash() + v.Hash() + return false + }) + return hash +} + // Iter invokes the iter function for each element in the hashMap. // If the iter function returns true, iteration stops and the return value is true. // If the iter function never returns true, iteration proceeds through all elements @@ -48,6 +80,10 @@ func (hm *hashMap) Iter(iter func(opalog.Value, opalog.Value) bool) bool { return false } +func (hm *hashMap) Len() int { + return hm.size +} + func (hm *hashMap) Put(k opalog.Value, v opalog.Value) { hash := k.Hash() head := hm.table[hash] @@ -58,6 +94,7 @@ func (hm *hashMap) Put(k opalog.Value, v opalog.Value) { } } hm.table[hash] = &hashEntry{k: k, v: v, next: head} + hm.size++ } func (hm *hashMap) String() string { @@ -68,3 +105,15 @@ func (hm *hashMap) String() string { }) return "{" + strings.Join(buf, ", ") + "}" } + +// Update returns a new hashMap with elements from the other hashMap put into this hashMap. +// If the other hashMap contains elements with the same key as this hashMap, the value +// from the other hashMap overwrites the value from this hashMap. +func (hm *hashMap) Update(other *hashMap) *hashMap { + updated := hm.Copy() + other.Iter(func(k, v opalog.Value) bool { + updated.Put(k, v) + return false + }) + return updated +} diff --git a/eval/hashmap_test.go b/eval/hashmap_test.go index c73908b262..99cc491fe2 100644 --- a/eval/hashmap_test.go +++ b/eval/hashmap_test.go @@ -24,7 +24,7 @@ func TestHashmapOverwrite(t *testing.T) { } } -func TestIter(t *testing.T) { +func TestHashmapIter(t *testing.T) { m := newHashMap() keys := []opalog.Number{opalog.Number(1), opalog.Number(2), opalog.Number(1.4)} value := opalog.Null{} @@ -49,3 +49,36 @@ func TestIter(t *testing.T) { t.Errorf("Expected %v but got %v", expected, results) } } + +func TestHashmapCompare(t *testing.T) { + m := newHashMap() + n := newHashMap() + k1 := opalog.String("k1") + k2 := opalog.String("k2") + k3 := opalog.String("k3") + v1 := parseTerm(`[{"a": 1, "b": 2}, {"c": 3}]`).Value + v2 := parseTerm(`[{"a": 1, "b": 2}, {"c": 4}]`).Value + m.Put(k1, v1) + if m.Equal(n) { + t.Errorf("Expected hash maps of different size to be non-equal for %v and %v", m, n) + return + } + n.Put(k1, v1) + if m.Hash() != n.Hash() { + t.Errorf("Expected hashes to equal for %v and %v", m, n) + return + } + if !m.Equal(n) { + t.Errorf("Expected hash maps to be equal for %v and %v", m, n) + return + } + m.Put(k2, v2) + n.Put(k3, v2) + if m.Hash() == n.Hash() { + t.Errorf("Did not expect hashes to equal for %v and %v", m, n) + return + } + if m.Equal(n) { + t.Errorf("Did not expect hash maps to be equal for %v and %v", m, n) + } +} diff --git a/eval/index.go b/eval/index.go new file mode 100644 index 0000000000..e1c889d56b --- /dev/null +++ b/eval/index.go @@ -0,0 +1,363 @@ +// Copyright 2016 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package eval + +import ( + "fmt" + "strings" + + "github.com/open-policy-agent/opa/opalog" +) + +// Indices contains a mapping of non-ground references to values to sets of bindings. +// +// +------+------------------------------------+ +// | ref1 | val1 | bindings-1, bindings-2, ... | +// | +------+-----------------------------+ +// | | val2 | bindings-m, bindings-m, ... | +// | +------+-----------------------------+ +// | | .... | ... | +// +------+------+-----------------------------+ +// | ref2 | .... | ... | +// +------+------+-----------------------------+ +// | ... | +// +-------------------------------------------+ +// +// The "value" is the data value stored at the location referred to by the ground +// reference obtained by plugging bindings into the non-ground reference that is the +// index key. +// +type Indices struct { + table map[int]*indicesNode +} + +type indicesNode struct { + key opalog.Ref + val *Index + next *indicesNode +} + +// NewIndices returns an empty indices. +func NewIndices() *Indices { + return &Indices{ + table: map[int]*indicesNode{}, + } +} + +// Build initializes the references' index by walking the store for the reference and +// creating the index that maps values to bindings. +func (ind *Indices) Build(store *Storage, ref opalog.Ref) error { + index := NewIndex() + err := iterStorage(store, ref, opalog.EmptyRef(), newHashMap(), func(bindings *hashMap, val interface{}) { + index.Add(val, bindings) + }) + if err != nil { + return err + } + hashCode := ref.Hash() + head := ind.table[hashCode] + entry := &indicesNode{ + key: ref, + val: index, + next: head, + } + ind.table[hashCode] = entry + return nil +} + +// Drop removes the index for the reference. +func (ind *Indices) Drop(ref opalog.Ref) { + hashCode := ref.Hash() + var prev *indicesNode + for entry := ind.table[hashCode]; entry != nil; entry = entry.next { + if entry.key.Equal(ref) { + if prev == nil { + ind.table[hashCode] = entry.next + } else { + prev.next = entry.next + } + return + } + } +} + +// Get returns the reference's index. +func (ind *Indices) Get(ref opalog.Ref) *Index { + node := ind.getNode(ref) + if node != nil { + return node.val + } + return nil +} + +// Iter calls the iter function for each of the indices. +func (ind *Indices) Iter(iter func(opalog.Ref, *Index) error) error { + for _, head := range ind.table { + for entry := head; entry != nil; entry = entry.next { + if err := iter(entry.key, entry.val); err != nil { + return err + } + } + } + return nil +} + +func (ind *Indices) String() string { + buf := []string{} + for _, head := range ind.table { + for entry := head; entry != nil; entry = entry.next { + str := fmt.Sprintf("%v: %v", entry.key, entry.val) + buf = append(buf, str) + } + } + return "{" + strings.Join(buf, ", ") + "}" +} + +func (ind *Indices) getNode(ref opalog.Ref) *indicesNode { + hashCode := ref.Hash() + for entry := ind.table[hashCode]; entry != nil; entry = entry.next { + if entry.key.Equal(ref) { + return entry + } + } + return nil +} + +// Index contains a mapping of values to bindings. +// +type Index struct { + table map[int]*indexNode +} + +type indexNode struct { + key interface{} + val *bindingSet + next *indexNode +} + +// NewIndex returns a new empty index. +func NewIndex() *Index { + return &Index{ + table: map[int]*indexNode{}, + } +} + +// Add updates the index to include new bindings for the value. +// If the bindings already exist for the value, no change is made. +func (ind *Index) Add(val interface{}, bindings *hashMap) { + + node := ind.getNode(val) + if node != nil { + node.val.Add(bindings) + return + } + + hashCode := hash(val) + bindingsSet := newBindingSet() + bindingsSet.Add(bindings) + + entry := &indexNode{ + key: val, + val: bindingsSet, + next: ind.table[hashCode], + } + + ind.table[hashCode] = entry +} + +// Iter calls the iter function for each set of bindings for the value. +func (ind *Index) Iter(val interface{}, iter func(*hashMap) error) error { + node := ind.getNode(val) + if node == nil { + return nil + } + return node.val.Iter(iter) +} + +func (ind *Index) getNode(val interface{}) *indexNode { + hashCode := hash(val) + head := ind.table[hashCode] + for entry := head; entry != nil; entry = entry.next { + if Compare(entry.key, val) == 0 { + return entry + } + } + return nil +} + +func (ind *Index) String() string { + + buf := []string{} + + for _, head := range ind.table { + for entry := head; entry != nil; entry = entry.next { + str := fmt.Sprintf("%v: %v", entry.key, entry.val) + buf = append(buf, str) + } + } + + return "{" + strings.Join(buf, ", ") + "}" +} + +type bindingSetNode struct { + val *hashMap + next *bindingSetNode +} + +type bindingSet struct { + table map[int]*bindingSetNode +} + +func newBindingSet() *bindingSet { + return &bindingSet{ + table: map[int]*bindingSetNode{}, + } +} + +func (set *bindingSet) Add(val *hashMap) { + node := set.getNode(val) + if node != nil { + return + } + hashCode := val.Hash() + head := set.table[hashCode] + set.table[hashCode] = &bindingSetNode{val, head} +} + +func (set *bindingSet) Iter(iter func(*hashMap) error) error { + for _, head := range set.table { + for entry := head; entry != nil; entry = entry.next { + if err := iter(entry.val); err != nil { + return err + } + } + } + return nil +} + +func (set *bindingSet) String() string { + buf := []string{} + set.Iter(func(bindings *hashMap) error { + buf = append(buf, bindings.String()) + return nil + }) + return "{" + strings.Join(buf, ", ") + "}" +} + +func (set *bindingSet) getNode(val *hashMap) *bindingSetNode { + hashCode := val.Hash() + for entry := set.table[hashCode]; entry != nil; entry = entry.next { + if entry.val.Equal(val) { + return entry + } + } + return nil +} + +func hash(v interface{}) int { + switch v := v.(type) { + case []interface{}: + var h int + for _, e := range v { + h += hash(e) + } + return h + case map[string]interface{}: + var h int + for k, v := range v { + h += hash(k) + hash(v) + } + return h + case string: + // TOOD(tsandall) move to utils package + // FNV-1a hashing + var h uint32 + for i := 0; i < len(v); i++ { + h ^= uint32(v[i]) + h *= 16777619 + } + return int(h) + case bool: + if v { + return 1 + } + return 0 + case nil: + return 0 + case float64: + return int(v) + } + panic(fmt.Sprintf("illegal argument: %v (%T)", v, v)) +} + +func iterStorage(store *Storage, ref opalog.Ref, path opalog.Ref, bindings *hashMap, iter func(*hashMap, interface{})) error { + + if len(ref) == 0 { + + p, _ := path.Underlying() + node, err := store.Get(p) + if err != nil { + switch err := err.(type) { + case *StorageError: + if err.Code == StorageNotFoundErr { + return nil + } + } + return err + } + + iter(bindings, node) + return nil + } + + head := ref[0] + tail := ref[1:] + + headVar, isVar := head.Value.(opalog.Var) + + if !isVar || len(path) == 0 { + path = append(path, head) + return iterStorage(store, tail, path, bindings, iter) + } + + p, _ := path.Underlying() + node, err := store.Get(p) + if err != nil { + switch err := err.(type) { + case *StorageError: + if err.Code == StorageNotFoundErr { + return nil + } + } + return err + } + + switch node := node.(type) { + case map[string]interface{}: + for key := range node { + path = append(path, opalog.StringTerm(key)) + cpy := bindings.Copy() + cpy.Put(headVar, opalog.String(key)) + err := iterStorage(store, tail, path, cpy, iter) + if err != nil { + return err + } + path = path[:len(path)-1] + } + case []interface{}: + for i := range node { + path = append(path, opalog.NumberTerm(float64(i))) + cpy := bindings.Copy() + cpy.Put(headVar, opalog.Number(float64(i))) + err := iterStorage(store, tail, path, cpy, iter) + if err != nil { + return err + } + path = path[:len(path)-1] + } + } + + return nil +} diff --git a/eval/index_test.go b/eval/index_test.go new file mode 100644 index 0000000000..937c4db661 --- /dev/null +++ b/eval/index_test.go @@ -0,0 +1,125 @@ +// Copyright 2016 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package eval + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" +) + +func TestIndicesBuild(t *testing.T) { + + tests := []struct { + note string + ref string + value interface{} + expected string + }{ + {"single var", "a[i]", float64(2), `[{"i": 1}]`}, + {"two var", "d[x][y]", "baz", `[{"x": "e", "y": 1}]`}, + {"partial ground", `c[i]["y"][j]`, nil, `[{"i": 0, "j": 0}]`}, + {"multiple bindings", "g[x][y]", float64(0), `[ + {"x": "a", "y": 1}, + {"x": "a", "y": 2}, + {"x": "a", "y": 3}, + {"x": "b", "y": 0}, + {"x": "b", "y": 2}, + {"x": "b", "y": 3}, + {"x": "c", "y": 0}, + {"x": "c", "y": 1}, + {"x": "c", "y": 2} + ]`}, + } + + for i, tc := range tests { + runIndexBuildTestCase(t, i+1, tc.note, tc.ref, tc.expected, tc.value) + } + +} + +func TestIndicesAdd(t *testing.T) { + + indices := NewIndices() + data := loadSmallTestData() + store := NewStorageFromJSONObject(data) + + ref := parseRef("d[x][y]") + + indices.Build(store, ref) + index := indices.Get(ref) + + // new value to add + var val1 interface{} + err := json.Unmarshal([]byte(`{"x":[1,true]}`), &val1) + if err != nil { + panic(err) + } + bindings1 := loadExpectedBindings(`[{"x": "e", "y": 2}]`)[0] + index.Add(val1, bindings1) + assertBindingsEqual(t, "new value", index, val1, `[{"x": "e", "y": 2}]`) + + // existing value + val2 := "baz" + bindings2 := loadExpectedBindings(`[{"x": "e", "y": 3}]`)[0] + index.Add(val2, bindings2) + assertBindingsEqual(t, "existing value", index, val2, `[{"x": "e", "y": 1}, {"x": "e", "y": 3}]`) + index.Add(val2, bindings2) + assertBindingsEqual(t, "same value (no change)", index, val2, `[{"x": "e", "y": 1}, {"x": "e", "y": 3}]`) +} + +func runIndexBuildTestCase(t *testing.T, i int, note string, refStr string, expectedStr string, value interface{}) { + + indices := NewIndices() + data := loadSmallTestData() + store := NewStorageFromJSONObject(data) + ref := parseRef(refStr) + + if indices.Get(ref) != nil { + t.Errorf("Test case %d (%v): Did not expect indices to contain %v yet", i, note, ref) + return + } + + err := indices.Build(store, ref) + if err != nil { + t.Errorf("Test case %d (%v): Did not expect error from build: %v", i, note, err) + return + } + + index := indices.Get(ref) + if index == nil { + t.Errorf("Test case %d (%v): Did not expect nil index for %v", i, note, ref) + return + } + + assertBindingsEqual(t, fmt.Sprintf("Test case %d (%v)", i+1, note), index, value, expectedStr) +} + +func assertBindingsEqual(t *testing.T, note string, index *Index, value interface{}, expectedStr string) { + + expected := loadExpectedBindings(expectedStr) + + err := index.Iter(value, func(bindings *hashMap) error { + for j := range expected { + if reflect.DeepEqual(expected[j], bindings) { + tmp := expected[:j] + expected = append(tmp, expected[j+1:]...) + return nil + } + } + return fmt.Errorf("unexpected bindings: %v", bindings) + }) + + if err != nil { + t.Errorf("%v: Did not expect error from index iteration: %v", note, err) + return + } + + if len(expected) > 0 { + t.Errorf("%v: Missing expected bindings: %v", note, expected) + return + } +} diff --git a/eval/storage.go b/eval/storage.go index 24c4103328..36836251e9 100644 --- a/eval/storage.go +++ b/eval/storage.go @@ -8,6 +8,8 @@ import ( "encoding/json" "fmt" "os" + + "github.com/open-policy-agent/opa/opalog" ) // StorageErrorCode represents the collection of error types that can be @@ -39,7 +41,7 @@ var nonEmptyMsg = "path must be non-empty" var stringHeadMsg = "path must begin with string" func arrayIndexTypeMsg(v interface{}) string { - return fmt.Sprintf("array index must be string, not %T", v) + return fmt.Sprintf("array index must be integer, not %T", v) } func objectKeyTypeMsg(v interface{}) string { @@ -47,11 +49,11 @@ func objectKeyTypeMsg(v interface{}) string { } func nonCollectionMsg(v interface{}) string { - return fmt.Sprintf("path refers to non-object/non-array document with element %v (%T)", v, v) + return fmt.Sprintf("path refers to non-object/non-array document with element %v", v) } func nonArrayMsg(v interface{}) string { - return fmt.Sprintf("path refers to non-array document with element %v (%T)", v, v) + return fmt.Sprintf("path refers to non-array document with element %v", v) } func notFoundError(path []interface{}, f string, a ...interface{}) *StorageError { @@ -66,15 +68,21 @@ func notFoundError(path []interface{}, f string, a ...interface{}) *StorageError } // Storage is the backend containing rules and data. -type Storage map[interface{}]interface{} +type Storage struct { + Indices *Indices + data map[string]interface{} +} // NewStorage is a helper for creating a new, empty Storage. func NewStorage() Storage { - return Storage(map[interface{}]interface{}{}) + return Storage{ + Indices: NewIndices(), + data: map[string]interface{}{}, + } } // NewStorageFromJSONFiles is a helper for creating a new Storage containing documents stored in files. -func NewStorageFromJSONFiles(files []string) (Storage, error) { +func NewStorageFromJSONFiles(files []string) (*Storage, error) { store := NewStorage() for _, file := range files { f, err := os.Open(file) @@ -97,73 +105,32 @@ func NewStorageFromJSONFiles(files []string) (Storage, error) { } } - return store, nil + return &store, nil } // NewStorageFromJSONObject returns Storage by converting from map[string]interface{} -func NewStorageFromJSONObject(data map[string]interface{}) Storage { +func NewStorageFromJSONObject(data map[string]interface{}) *Storage { store := NewStorage() for k, v := range data { if err := store.Patch(StorageAdd, []interface{}{k}, v); err != nil { panic(err) } } - return store + return &store } // Get returns the value in Storage referenced by path. // If the lookup fails, an error is returned with a message indicating // why the failure occurred. -func (store Storage) Get(path []interface{}) (interface{}, error) { - - if len(path) == 0 { - return nil, notFoundError(path, nonEmptyMsg) - } - - head, ok := path[0].(string) - if !ok { - return nil, notFoundError(path, stringHeadMsg) - } - - node, ok := store[head] - if !ok { - return nil, notFoundError(path, doesNotExistMsg) - - } - - for _, v := range path[1:] { - switch n := node.(type) { - - case map[string]interface{}: - k, err := checkObjectKey(path, n, v) - if err != nil { - return nil, err - } - node = n[k] +func (store *Storage) Get(path []interface{}) (interface{}, error) { - case []interface{}: - idx, err := checkArrayIndex(path, n, v) - if err != nil { - return nil, err - } - node = n[idx] - - default: - return nil, notFoundError(path, nonCollectionMsg(v)) - } - } - - return node, nil + return get(store.data, path) } // MustGet returns the value in Storage reference by path. // If the lookup fails, the function will panic. -func (store Storage) MustGet(path []interface{}) interface{} { - node, err := store.Get(path) - if err != nil { - panic(err) - } - return node +func (store *Storage) MustGet(path []interface{}) interface{} { + return mustGet(store.data, path) } // StorageOp is the enumeration of supposed modifications. @@ -182,7 +149,7 @@ const ( ) // Patch modifies the store by performing the associated add/remove/replace operation on the given path. -func (store Storage) Patch(op StorageOp, path []interface{}, value interface{}) error { +func (store *Storage) Patch(op StorageOp, path []interface{}, value interface{}) error { if len(path) == 0 { return notFoundError(path, nonEmptyMsg) @@ -193,261 +160,381 @@ func (store Storage) Patch(op StorageOp, path []interface{}, value interface{}) return notFoundError(path, stringHeadMsg) } + // Drop the indices that are affected by this patch. This is inefficient for mixed read-write + // access patterns, but it's easy and simple for now. Once we have more information about + // the use cases, we can optimize this. + r := []opalog.Ref{} + + err := store.Indices.Iter(func(ref opalog.Ref, index *Index) error { + if !commonPrefix(ref, path) { + return nil + } + r = append(r, ref) + return nil + }) + + if err != nil { + return err + } + + for _, ref := range r { + store.Indices.Drop(ref) + } + + // Perform in-place update on data. switch op { case StorageAdd: - return store.add(path, value) + return add(store.data, path, value) case StorageRemove: - return store.remove(path) + return remove(store.data, path) case StorageReplace: - return store.replace(path, value) - default: - return &StorageError{Code: StorageInternalErr, Message: fmt.Sprintf("invalid operation: %v", op)} + return replace(store.data, path, value) } + + // Unreachable. + return nil +} + +func (store *Storage) String() string { + return fmt.Sprintf("%v", store.data) } -func (store Storage) add(path []interface{}, value interface{}) error { +// commonPrefix returns true the reference is a prefix of the path or vice versa. +// The shorter value is the prefix of the other if all of the ground elements +// are equal to the elements at the same indices in the other. +func commonPrefix(ref opalog.Ref, path []interface{}) bool { + + min := len(ref) + if len(path) < min { + min = len(path) + } + + cmp := func(a opalog.Value, b interface{}) bool { + switch a := a.(type) { + case opalog.Null: + if b == nil { + return true + } + case opalog.Boolean: + if b, ok := b.(bool); ok { + return b == bool(a) + } + case opalog.Number: + if b, ok := b.(float64); ok { + return b == float64(a) + } + case opalog.String: + if b, ok := b.(string); ok { + return b == string(a) + } + } + return false + } + + v := opalog.String(ref[0].Value.(opalog.Var)) + + if !cmp(v, path[0]) { + return false + } + + for i := 1; i < min; i++ { + if !ref[i].IsGround() { + continue + } + if cmp(ref[i].Value, path[i]) { + continue + } + return false + } + + return true +} + +func add(data map[string]interface{}, path []interface{}, value interface{}) error { // Special case for adding a new root. if len(path) == 1 { - return store.addRoot(path[0], value) + return addRoot(data, path[0].(string), value) } // Special case for appending to an array. switch v := path[len(path)-1].(type) { case string: if v == "-" { - return store.addAppend(path[:len(path)-1], value) + return addAppend(data, path[:len(path)-1], value) } } - node, err := store.Get(path[:len(path)-1]) + node, err := get(data, path[:len(path)-1]) if err != nil { return err } switch node := node.(type) { case map[string]interface{}: - return store.addInsertObject(path, node, value) + return addInsertObject(data, path, node, value) case []interface{}: - return store.addInsertArray(path, node, value) + return addInsertArray(data, path, node, value) default: return notFoundError(path, nonCollectionMsg(path[len(path)-2])) } } -func (store Storage) addAppend(path []interface{}, value interface{}) error { +func addAppend(data map[string]interface{}, path []interface{}, value interface{}) error { + + var parent interface{} = data - var nodeParent interface{} = store if len(path) > 1 { - r, err := store.Get(path[:len(path)-1]) + r, err := get(data, path[:len(path)-1]) if err != nil { return err } - nodeParent = r + parent = r } - node, err := store.Get(path) + n, err := get(data, path) if err != nil { return err } - switch n := node.(type) { - case []interface{}: - node = append(n, value) - default: + a, ok := n.([]interface{}) + if !ok { return notFoundError(path, nonArrayMsg(path[len(path)-1])) } - switch nodeParent := nodeParent.(type) { - case Storage: - nodeParent[path[len(path)-1]] = node + a = append(a, value) + e := path[len(path)-1] + + switch parent := parent.(type) { case []interface{}: - // This is safe because it was validated by the lookup above. - idx := int(path[len(path)-1].(float64)) - nodeParent[idx] = node + i := int(e.(float64)) + parent[i] = a case map[string]interface{}: - // This is safe because it was validated by the lookup above. - key := path[len(path)-1].(string) - nodeParent[key] = node + k := e.(string) + parent[k] = a default: - // "node" exists, therefore this is not reachable. - panic(fmt.Sprintf("illegal value: %v %v", nodeParent, path)) + panic(fmt.Sprintf("illegal value: %v %v", parent, path)) // "node" exists, therefore this is not reachable. } return nil } -func (store Storage) addInsertArray(path []interface{}, node []interface{}, value interface{}) error { +func addInsertArray(data map[string]interface{}, path []interface{}, node []interface{}, value interface{}) error { - idx, err := checkArrayIndex(path, node, path[len(path)-1]) + i, err := checkArrayIndex(path, node, path[len(path)-1]) if err != nil { return err } - var nodeParent interface{} = store + var parent interface{} = data if len(path) > 2 { - // "node" exists, therefore parent must exist. - n := store.MustGet(path[:len(path)-2]) - nodeParent = n - } - - switch nodeParent := nodeParent.(type) { - case Storage: - node = append(node, 0) - copy(node[idx+1:], node[idx:]) - node[idx] = value - key := path[len(path)-2] - nodeParent[key] = node - return nil + parent = mustGet(data, path[:len(path)-2]) // "node" exists, therefore parent must exist. + } + + node = append(node, 0) + copy(node[i+1:], node[i:]) + node[i] = value + e := path[len(path)-2] + + switch parent := parent.(type) { case map[string]interface{}: - node = append(node, 0) - copy(node[idx+1:], node[idx:]) - node[idx] = value - key := path[len(path)-2].(string) - nodeParent[key] = node - return nil + k := e.(string) + parent[k] = node case []interface{}: - node = append(node, 0) - copy(node[idx+1:], node[idx:]) - node[idx] = value - idx = int(path[len(path)-2].(float64)) - nodeParent[idx] = node - return nil + i = int(e.(float64)) + parent[i] = node default: - // "node" exists, therefore this is not reachable. - panic(fmt.Sprintf("illegal value: %v %v", nodeParent, path)) + panic(fmt.Sprintf("illegal value: %v %v", parent, path)) // "node" exists, therefore this is not reachable. } + + return nil } -func (store Storage) addInsertObject(path []interface{}, node map[string]interface{}, value interface{}) error { +func addInsertObject(data map[string]interface{}, path []interface{}, node map[string]interface{}, value interface{}) error { + + var k string + switch last := path[len(path)-1].(type) { case string: - node[last] = value - return nil + k = last default: return notFoundError(path, objectKeyTypeMsg(last)) } + + node[k] = value + return nil } -func (store Storage) addRoot(key interface{}, value interface{}) error { - store[key] = value +func addRoot(data map[string]interface{}, key string, value interface{}) error { + data[key] = value return nil } -func (store Storage) remove(path []interface{}) error { +func get(data map[string]interface{}, path []interface{}) (interface{}, error) { + if len(path) == 0 { + return nil, notFoundError(path, nonEmptyMsg) + } - // Special case for removing a root. - if len(path) == 1 { - delete(store, path[0]) - return nil + head, ok := path[0].(string) + if !ok { + return nil, notFoundError(path, stringHeadMsg) } - node, err := store.Get(path[:len(path)-1]) + node, ok := data[head] + if !ok { + return nil, notFoundError(path, doesNotExistMsg) + + } + + for _, v := range path[1:] { + switch n := node.(type) { + + case map[string]interface{}: + k, err := checkObjectKey(path, n, v) + if err != nil { + return nil, err + } + node = n[k] + + case []interface{}: + idx, err := checkArrayIndex(path, n, v) + if err != nil { + return nil, err + } + node = n[idx] + + default: + return nil, notFoundError(path, nonCollectionMsg(v)) + } + } + + return node, nil +} + +func mustGet(data map[string]interface{}, path []interface{}) interface{} { + r, err := get(data, path) if err != nil { + panic(err) + } + return r +} + +func remove(data map[string]interface{}, path []interface{}) error { + + if _, err := get(data, path); err != nil { return err } + // Special case for removing a root. + if len(path) == 1 { + return removeRoot(data, path[0].(string)) + } + + node := mustGet(data, path[:len(path)-1]) + switch node := node.(type) { case []interface{}: - return store.removeArray(path, node) + return removeArray(data, path, node) case map[string]interface{}: - return store.removeObject(path, node) + return removeObject(data, path, node) default: return notFoundError(path, nonCollectionMsg(path[len(path)-2])) } } -func (store Storage) removeArray(path []interface{}, node []interface{}) error { +func removeArray(data map[string]interface{}, path []interface{}, node []interface{}) error { - idx, err := checkArrayIndex(path, node, path[len(path)-1]) + i, err := checkArrayIndex(path, node, path[len(path)-1]) if err != nil { return err } - var nodeParent interface{} = store + var parent interface{} = data if len(path) > 2 { - // "node" exists, therefore parent must exist. - n := store.MustGet(path[:len(path)-2]) - nodeParent = n + parent = mustGet(data, path[:len(path)-2]) // "node" exists, therefore parent must exist. } - node = append(node[:idx], node[idx+1:]...) + node = append(node[:i], node[i+1:]...) + e := path[len(path)-2] - switch nodeParent := nodeParent.(type) { - case Storage: - key := path[len(path)-2] - nodeParent[key] = node - return nil + switch parent := parent.(type) { case map[string]interface{}: - key := path[len(path)-2].(string) - nodeParent[key] = node - return nil + k := e.(string) + parent[k] = node case []interface{}: - idx = int(path[len(path)-2].(float64)) - nodeParent[idx] = node - return nil + i = int(e.(float64)) + parent[i] = node default: - // "node" exists, therefore this is not reachable. - panic(fmt.Sprintf("illegal value: %v %v", nodeParent, path)) + panic(fmt.Sprintf("illegal value: %v %v", parent, path)) // "node" exists, therefore this is not reachable. } + return nil } -func (store Storage) removeObject(path []interface{}, node map[string]interface{}) error { - key, err := checkObjectKey(path, node, path[len(path)-1]) +func removeObject(data map[string]interface{}, path []interface{}, node map[string]interface{}) error { + k, err := checkObjectKey(path, node, path[len(path)-1]) if err != nil { return err } - delete(node, key) + + delete(node, k) return nil } -func (store Storage) replace(path []interface{}, value interface{}) error { - - if len(path) == 1 { - root := path[0] - if _, ok := store[root]; !ok { - return notFoundError(path, doesNotExistMsg) - } - store[root] = value - return nil - } +func removeRoot(data map[string]interface{}, root string) error { + delete(data, root) + return nil +} - node, err := store.Get(path[:len(path)-1]) +func replace(data map[string]interface{}, path []interface{}, value interface{}) error { - if err != nil { + if _, err := get(data, path); err != nil { return err } + if len(path) == 1 { + return replaceRoot(data, path, value) + } + + node := mustGet(data, path[:len(path)-1]) + switch node := node.(type) { case map[string]interface{}: - return store.replaceObject(path, node, value) + return replaceObject(data, path, node, value) case []interface{}: - return store.replaceArray(path, node, value) + return replaceArray(data, path, node, value) default: return notFoundError(path, nonCollectionMsg(path[len(path)-2])) } } -func (store Storage) replaceObject(path []interface{}, node map[string]interface{}, value interface{}) error { - key, err := checkObjectKey(path, node, path[len(path)-1]) +func replaceObject(data map[string]interface{}, path []interface{}, node map[string]interface{}, value interface{}) error { + k, err := checkObjectKey(path, node, path[len(path)-1]) if err != nil { return err } - node[key] = value + + node[k] = value return nil } -func (store Storage) replaceArray(path []interface{}, node []interface{}, value interface{}) error { - idx, err := checkArrayIndex(path, node, path[len(path)-1]) +func replaceRoot(data map[string]interface{}, path []interface{}, value interface{}) error { + root := path[0].(string) + data[root] = value + return nil +} + +func replaceArray(data map[string]interface{}, path []interface{}, node []interface{}, value interface{}) error { + i, err := checkArrayIndex(path, node, path[len(path)-1]) if err != nil { return err } - node[idx] = value + + node[i] = value return nil } @@ -468,14 +555,14 @@ func checkArrayIndex(path []interface{}, node []interface{}, v interface{}) (int if !isFloat { return 0, notFoundError(path, arrayIndexTypeMsg(v)) } - idx := int(f) - if float64(idx) != f { + i := int(f) + if float64(i) != f { return 0, notFoundError(path, arrayIndexTypeMsg(v)) } - if idx >= len(node) { + if i >= len(node) { return 0, notFoundError(path, outOfRangeMsg) - } else if idx < 0 { + } else if i < 0 { return 0, notFoundError(path, outOfRangeMsg) } - return idx, nil + return i, nil } diff --git a/eval/storage_test.go b/eval/storage_test.go index 821c137d83..4cdde05e17 100644 --- a/eval/storage_test.go +++ b/eval/storage_test.go @@ -5,6 +5,7 @@ package eval import ( + "encoding/json" "fmt" "io/ioutil" "os" @@ -134,6 +135,7 @@ func TestStoragePatch(t *testing.T) { {"add arr/arr", "add", path("h[1][2]"), `"x"`, nil, path("h"), `[[1,2,3], [2,3,"x",4]]`}, {"add obj/arr", "add", path("d.e[1]"), `"x"`, nil, path("d"), `{"e": ["bar", "x", "baz"]}`}, {"add obj", "add", path("b.vNew"), `"x"`, nil, path("b"), `{"v1": "hello", "v2": "goodbye", "vNew": "x"}`}, + {"add obj (existing)", "add", path("b.v2"), `"x"`, nil, path("b"), `{"v1": "hello", "v2": "x"}`}, {"append root/arr", "add", path(`a["-"]`), `"x"`, nil, path("a"), `[1,2,3,4,"x"]`}, {"append obj/arr", "add", path(`c[0].x["-"]`), `"x"`, nil, path("c[0].x"), `[true,false,"foo","x"]`}, @@ -162,11 +164,11 @@ func TestStoragePatch(t *testing.T) { {"err: append obj/arr", "add", path(`c[0].deadbeef["-"]`), `"x"`, notFoundError(path("c[0].deadbeef"), doesNotExistMsg), nil, nil}, {"err: append arr/arr (out of range)", "add", path(`h[9999]["-"]`), `"x"`, notFoundError(path("h[9999]"), outOfRangeMsg), nil, nil}, {"err: append arr/arr (non-array)", "add", path(`b.v1["-"]`), "1", notFoundError(path("b.v1"), nonArrayMsg("v1")), nil, nil}, - {"err: remove missing", "remove", path("dead.beef[0]"), "", notFoundError(path("dead.beef"), doesNotExistMsg), nil, nil}, + {"err: remove missing", "remove", path("dead.beef[0]"), "", notFoundError(path("dead.beef[0]"), doesNotExistMsg), nil, nil}, {"err: remove obj (non string)", "remove", path("b[100]"), "", notFoundError(path("b[100]"), objectKeyTypeMsg(float64(100))), nil, nil}, {"err: remove obj (missing)", "remove", path("b.deadbeef"), "", notFoundError(path("b.deadbeef"), doesNotExistMsg), nil, nil}, {"err: replace root (missing)", "replace", path("deadbeef"), "1", notFoundError(path("deadbeef"), doesNotExistMsg), nil, nil}, - {"err: replace missing", "replace", "dead.beef[1]", "1", notFoundError(path("dead.beef"), doesNotExistMsg), nil, nil}, + {"err: replace missing", "replace", "dead.beef[1]", "1", notFoundError(path("dead.beef[1]"), doesNotExistMsg), nil, nil}, } for i, tc := range tests { @@ -239,6 +241,79 @@ func TestStoragePatch(t *testing.T) { } +func TestStorageIndexingBasicUpdate(t *testing.T) { + refA := parseRef("a[i]") + refB := parseRef("b[x]") + store := newStorageWithIndices(refA, refB) + + mustPatch(store, StorageAdd, path(`a["-"]`), float64(100)) + + index := store.Indices.Get(refA) + if index != nil { + t.Errorf("Expected index to be removed after patch: %v", index) + } + + index = store.Indices.Get(refB) + if index == nil { + t.Errorf("Expected index to be intact after patch: %v", refB) + } +} + +func TestStorageIndexingAddDeepPath(t *testing.T) { + ref := parseRef("i[x]") + refD := parseRef("i[x].d") + store := newStorageWithIndices(ref, refD) + + mustPatch(store, StorageAdd, path(`i[0].c["-"]`), float64(5)) + + index := store.Indices.Get(ref) + if index != nil { + t.Errorf("Expected index to be removed after patch: %v", index) + } + + index = store.Indices.Get(refD) + if index == nil { + t.Errorf("Expected index to be intact after patch: %v", refD) + } +} + +func TestStorageIndexingAddDeepRef(t *testing.T) { + ref := parseRef("i[x].a") + store := newStorageWithIndices(ref) + var data interface{} + json.Unmarshal([]byte(`{"a": "eve", "b": 100, "c": [999,999,999]}`), &data) + + mustPatch(store, StorageAdd, path(`i["-"]`), data) + + index := store.Indices.Get(ref) + if index != nil { + t.Errorf("Expected index to be removed after patch: %v", index) + } +} + +func newStorageWithIndices(r ...opalog.Ref) *Storage { + data := loadSmallTestData() + store := NewStorageFromJSONObject(data) + for _, x := range r { + mustBuild(store, x) + } + return store +} + +func mustBuild(store *Storage, ref opalog.Ref) { + err := store.Indices.Build(store, ref) + if err != nil { + panic(err) + } +} + +func mustPatch(store *Storage, op StorageOp, path []interface{}, value interface{}) { + err := store.Patch(op, path, value) + if err != nil { + panic(err) + } +} + func path(input interface{}) []interface{} { switch input := input.(type) { case []interface{}: diff --git a/eval/topdown.go b/eval/topdown.go index 95ee8dff82..916a4be221 100644 --- a/eval/topdown.go +++ b/eval/topdown.go @@ -4,8 +4,12 @@ package eval -import "github.com/open-policy-agent/opa/opalog" -import "fmt" +import ( + "fmt" + + "github.com/open-policy-agent/opa/opalog" + "github.com/pkg/errors" +) // TopDownContext contains the state of the evaluation process. // @@ -18,12 +22,12 @@ type TopDownContext struct { Bindings *hashMap Index int Previous *TopDownContext - Store Storage + Store *Storage Tracer Tracer } // NewTopDownContext creates a new TopDownContext with no bindings. -func NewTopDownContext(query opalog.Body, store Storage) *TopDownContext { +func NewTopDownContext(query opalog.Body, store *Storage) *TopDownContext { return &TopDownContext{ Query: query, Bindings: newHashMap(), @@ -34,11 +38,7 @@ func NewTopDownContext(query opalog.Body, store Storage) *TopDownContext { // BindRef returns a new TopDownContext with bindings that map the reference to the value. func (ctx *TopDownContext) BindRef(ref opalog.Ref, value opalog.Value) *TopDownContext { cpy := *ctx - cpy.Bindings = newHashMap() - ctx.Bindings.Iter(func(k opalog.Value, v opalog.Value) bool { - cpy.Bindings.Put(k, v) - return false - }) + cpy.Bindings = ctx.Bindings.Copy() cpy.Bindings.Put(ref, value) return &cpy } @@ -143,7 +143,7 @@ func TopDown(ctx *TopDownContext, iter TopDownIterator) error { // TopDownQueryParams defines input parameters for the query interface. type TopDownQueryParams struct { - Store Storage + Store *Storage Tracer Tracer Path []string } @@ -258,8 +258,12 @@ func ValueToInterface(v opalog.Value, ctx *TopDownContext) (interface{}, error) type builtinFunction func(*TopDownContext, *opalog.Expr, TopDownIterator) error +const ( + equalityBuiltin = opalog.Var("=") +) + var builtinFunctions = map[opalog.Var]builtinFunction{ - opalog.Var("="): evalEq, + equalityBuiltin: evalEq, } // dereferenceVar is used to lookup the variable binding and convert the value to @@ -952,6 +956,7 @@ func evalRefRuleResult(ctx *TopDownContext, ref opalog.Ref, suffix opalog.Ref, r // // TODO(tsandall): extend to support indexing. func evalTerms(ctx *TopDownContext, iter TopDownIterator) error { + expr := ctx.Current() var ts []*opalog.Term switch t := expr.Terms.(type) { @@ -962,9 +967,66 @@ func evalTerms(ctx *TopDownContext, iter TopDownIterator) error { default: panic(fmt.Sprintf("illegal argument: %v", t)) } + + if indexAvailable(ctx, ts) { + + ref, isRef := ts[1].Value.(opalog.Ref) + + if isRef { + ok, err := indexBuildLazy(ctx, ref) + if err != nil { + return errors.Wrapf(err, "index build failed on %v", ref) + } + if ok { + return evalTermsIndexed(ctx, iter, ref, ts[2]) + } + } + + ref, isRef = ts[2].Value.(opalog.Ref) + + if isRef { + ok, err := indexBuildLazy(ctx, ref) + if err != nil { + return errors.Wrapf(err, "index build failed on %v", ref) + } + if ok { + return evalTermsIndexed(ctx, iter, ref, ts[1]) + } + } + } + return evalTermsRec(ctx, iter, ts) } +func evalTermsIndexed(ctx *TopDownContext, iter TopDownIterator, indexed opalog.Ref, nonIndexed *opalog.Term) error { + + iterateIndex := func(ctx *TopDownContext) error { + + // Evaluate the non-indexed term. + plugged := plugTerm(nonIndexed, ctx.Bindings) + nonIndexedValue, err := ValueToInterface(plugged.Value, ctx) + if err != nil { + return err + } + + // Get the index for the indexed term. If the term is indexed, this should not fail. + index := ctx.Store.Indices.Get(indexed) + if index == nil { + return fmt.Errorf("missing index: %v", indexed) + } + + // Iterate the bindings for the indexed term that when applied to the reference + // would locate the non-indexed value obtained above. + return index.Iter(nonIndexedValue, func(bindings *hashMap) error { + ctx.Bindings = ctx.Bindings.Update(bindings) + return iter(ctx) + }) + + } + + return evalTermsRec(ctx, iterateIndex, []*opalog.Term{nonIndexed}) +} + func evalTermsRec(ctx *TopDownContext, iter TopDownIterator, ts []*opalog.Term) error { if len(ts) == 0 { @@ -1058,7 +1120,83 @@ func evalTermsRecObject(ctx *TopDownContext, obj opalog.Object, idx int, iter To } } -func lookup(store Storage, ref opalog.Ref) (interface{}, error) { +// indexAvailable returns true if the index can/should be used when evaluating this expression. +// Indexing is used on equality expressions where both sides are non-ground refs (to base docs) or one +// side is a non-ground ref (to a base doc) and the other side is any ground term. In the future, indexing +// may be used on references embedded inside array/object values. +func indexAvailable(ctx *TopDownContext, terms []*opalog.Term) bool { + + // Indexing can only be used when evaluating equality expressions. + if !terms[0].Value.Equal(equalityBuiltin) { + return false + } + + pluggedA := plugTerm(terms[1], ctx.Bindings) + pluggedB := plugTerm(terms[2], ctx.Bindings) + + _, isRefA := pluggedA.Value.(opalog.Ref) + _, isRefB := pluggedB.Value.(opalog.Ref) + + if isRefA && !pluggedA.IsGround() { + return pluggedB.IsGround() || isRefB + } + + if isRefB && !pluggedB.IsGround() { + return pluggedA.IsGround() || isRefA + } + + return false +} + +// indexBuildLazy returns true if there is an index built for this term. If there is no index +// currently built for the term, but the term is a candidate for indexing, ther index will be +// built on the fly. +func indexBuildLazy(ctx *TopDownContext, ref opalog.Ref) (bool, error) { + + if ref.IsGround() { + return false, nil + } + + // Check if index was already built. + if ctx.Store.Indices.Get(ref) != nil { + return true, nil + } + + // Ignore refs against variables. + if ctx.Bindings.Get(ref[0].Value) != nil { + return false, nil + } + + // Ignore refs against virtual docs. + tmp := opalog.Ref{ref[0]} + for _, p := range ref[1:] { + + path, _ := tmp.Underlying() + r, err := ctx.Store.Get(path) + if err != nil { + return false, err + } + + switch r.(type) { + case ([]*opalog.Rule): + return false, nil + } + + if !p.Value.IsGround() { + break + } + + tmp = append(tmp, p) + } + + if err := ctx.Store.Indices.Build(ctx.Store, ref); err != nil { + return false, err + } + + return true, nil +} + +func lookup(store *Storage, ref opalog.Ref) (interface{}, error) { path, err := ref.Underlying() if err != nil { return nil, err diff --git a/eval/topdown_test.go b/eval/topdown_test.go index dcc35775b7..afcf8f0379 100644 --- a/eval/topdown_test.go +++ b/eval/topdown_test.go @@ -114,18 +114,15 @@ func TestEvalTerms(t *testing.T) { {"i": 0, "j": "z", "k": "p"}, {"i": 0, "j": "z", "k": "q"} ]`}, - {"d[x][y] = a[i]", `[ - {"x": "e", "y": 0, "i": 0}, - {"x": "e", "y": 0, "i": 1}, - {"x": "e", "y": 0, "i": 2}, - {"x": "e", "y": 0, "i": 3}, - {"x": "e", "y": 1, "i": 0}, - {"x": "e", "y": 1, "i": 1}, - {"x": "e", "y": 1, "i": 2}, - {"x": "e", "y": 1, "i": 3} + {"a[i] = h[j][k]", `[ + {"i": 0, "j": 0, "k": 0}, + {"i": 1, "j": 0, "k": 1}, + {"i": 1, "j": 1, "k": 0}, + {"i": 2, "j": 0, "k": 2}, + {"i": 2, "j": 1, "k": 1}, + {"i": 3, "j": 1, "k": 2} ]`}, - {"d[x][y] = {1: 2}", `[ - {"x": "e", "y": 0}, + {`d[x][y] = "baz"`, `[ {"x": "e", "y": 1} ]`}, {"d[x][y] = d[x][y]", `[ @@ -460,6 +457,7 @@ func TestTopDownVarReferences(t *testing.T) { {"embedded ref binding", []string{"p[x] :- v = c[i][j], w = [v[0], v[1]], x = w[y]"}, "[null, false, true, 3.14159]"}, {"array: ground var", []string{"p[y] :- a = [1,2,3,4], b = [1,2,999], b[i] = x, a[x] = y"}, "[2,3]"}, {"object: ground var", []string{`p[y] :- a = {"a": 1, "b": 2, "c": 3}, b = ["a", "c", "deadbeef"], b[i] = x, a[x] = y`}, "[1, 3]"}, + {"avoids indexer", []string{"p = true :- somevar = [1,2,3], somevar[i] = 2"}, "true"}, } data := loadSmallTestData() @@ -588,6 +586,19 @@ func loadSmallTestData() map[string]interface{} { [1,2,3], [2,3,4] ], + "i": [ + { + "a": "bob", + "b": -1, + "c": [1,2,3,4] + }, + { + "a": "alice", + "b": 1, + "c": [2,3,4,5], + "d": null + } + ], "z": [] }`), &data) if err != nil { @@ -596,7 +607,7 @@ func loadSmallTestData() map[string]interface{} { return data } -func newStorage(data map[string]interface{}, rules []*opalog.Rule) Storage { +func newStorage(data map[string]interface{}, rules []*opalog.Rule) *Storage { byName := map[opalog.Var][]*opalog.Rule{} for _, rule := range rules { s, ok := byName[rule.Name] @@ -608,7 +619,10 @@ func newStorage(data map[string]interface{}, rules []*opalog.Rule) Storage { } store := NewStorageFromJSONObject(data) for name, rules := range byName { - store[string(name)] = rules + err := store.Patch(StorageAdd, []interface{}{string(name)}, rules) + if err != nil { + panic(err) + } } return store } @@ -662,7 +676,7 @@ func runTopDownTestCase(t *testing.T, data map[string]interface{}, i int, note s t.Errorf("Test case %d (%v): unexpected error: %v", i+1, note, err) return } - switch store["p"].([]*opalog.Rule)[0].DocKind() { + switch store.MustGet([]interface{}{"p"}).([]*opalog.Rule)[0].DocKind() { case opalog.PartialSetDoc: sort.Sort(ResultSet(result.([]interface{}))) } diff --git a/runtime/repl_test.go b/runtime/repl_test.go index d97ad98b79..ca69a40cb4 100644 --- a/runtime/repl_test.go +++ b/runtime/repl_test.go @@ -14,6 +14,20 @@ import ( "github.com/open-policy-agent/opa/opalog" ) +func TestDump(t *testing.T) { + input := `{"a": [1,2,3,4]}` + var data map[string]interface{} + err := json.Unmarshal([]byte(input), &data) + if err != nil { + panic(err) + } + store := eval.NewStorageFromJSONObject(data) + var buffer bytes.Buffer + repl := newRepl(store, &buffer) + repl.cmdDump() + expectOutput(t, buffer.String(), "map[a:[1 2 3 4]]\n") +} + func TestOneShotEmptyBufferOneExpr(t *testing.T) { store := newTestStorage() var buffer bytes.Buffer @@ -80,13 +94,13 @@ func expectOutput(t *testing.T, output string, expected string) { } } -func newRepl(store eval.Storage, buffer *bytes.Buffer) *Repl { +func newRepl(store *eval.Storage, buffer *bytes.Buffer) *Repl { runtime := &Runtime{Store: store} repl := NewRepl(runtime, "", buffer) return repl } -func newTestStorage() eval.Storage { +func newTestStorage() *eval.Storage { input := ` { "a": [ diff --git a/runtime/runtime.go b/runtime/runtime.go index b3a7c62829..0a53d973af 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -20,7 +20,7 @@ type Params struct { // Runtime represents a single OPA instance. type Runtime struct { - Store eval.Storage + Store *eval.Storage } // Start is the entry point of an OPA instance.