Merge pull request #30 from tsandall/basic-indexing

Add basic indexing support
This commit is contained in:
Torin Sandall
2016-05-06 08:22:11 -07:00
63 changed files with 3063 additions and 684 deletions
+50 -1
View File
@@ -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
}
+34 -1
View File
@@ -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)
}
}
+363
View File
@@ -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
}
+125
View File
@@ -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
}
}
+333 -246
View File
@@ -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,25 +105,274 @@ 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) {
func (store *Storage) Get(path []interface{}) (interface{}, error) {
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{} {
return mustGet(store.data, path)
}
// StorageOp is the enumeration of supposed modifications.
type StorageOp int
const (
// StorageAdd represents an additive operation.
StorageAdd StorageOp = iota
// StorageRemove represents a removal operation.
StorageRemove = iota
// StorageReplace represents a replacement operation.
StorageReplace = iota
)
// 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 {
if len(path) == 0 {
return notFoundError(path, nonEmptyMsg)
}
_, isString := path[0].(string)
if !isString {
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 add(store.data, path, value)
case StorageRemove:
return remove(store.data, path)
case StorageReplace:
return replace(store.data, path, value)
}
// Unreachable.
return nil
}
func (store *Storage) String() string {
return fmt.Sprintf("%v", store.data)
}
// 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 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 addAppend(data, path[:len(path)-1], value)
}
}
node, err := get(data, path[:len(path)-1])
if err != nil {
return err
}
switch node := node.(type) {
case map[string]interface{}:
return addInsertObject(data, path, node, value)
case []interface{}:
return addInsertArray(data, path, node, value)
default:
return notFoundError(path, nonCollectionMsg(path[len(path)-2]))
}
}
func addAppend(data map[string]interface{}, path []interface{}, value interface{}) error {
var parent interface{} = data
if len(path) > 1 {
r, err := get(data, path[:len(path)-1])
if err != nil {
return err
}
parent = r
}
n, err := get(data, path)
if err != nil {
return err
}
a, ok := n.([]interface{})
if !ok {
return notFoundError(path, nonArrayMsg(path[len(path)-1]))
}
a = append(a, value)
e := path[len(path)-1]
switch parent := parent.(type) {
case []interface{}:
i := int(e.(float64))
parent[i] = a
case map[string]interface{}:
k := e.(string)
parent[k] = a
default:
panic(fmt.Sprintf("illegal value: %v %v", parent, path)) // "node" exists, therefore this is not reachable.
}
return nil
}
func addInsertArray(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
}
var parent interface{} = data
if len(path) > 2 {
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{}:
k := e.(string)
parent[k] = node
case []interface{}:
i = int(e.(float64))
parent[i] = node
default:
panic(fmt.Sprintf("illegal value: %v %v", parent, path)) // "node" exists, therefore this is not reachable.
}
return nil
}
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:
k = last
default:
return notFoundError(path, objectKeyTypeMsg(last))
}
node[k] = value
return nil
}
func addRoot(data map[string]interface{}, key string, value interface{}) error {
data[key] = value
return nil
}
func get(data map[string]interface{}, path []interface{}) (interface{}, error) {
if len(path) == 0 {
return nil, notFoundError(path, nonEmptyMsg)
}
@@ -125,7 +382,7 @@ func (store Storage) Get(path []interface{}) (interface{}, error) {
return nil, notFoundError(path, stringHeadMsg)
}
node, ok := store[head]
node, ok := data[head]
if !ok {
return nil, notFoundError(path, doesNotExistMsg)
@@ -156,298 +413,128 @@ func (store Storage) Get(path []interface{}) (interface{}, error) {
return node, nil
}
// 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)
func mustGet(data map[string]interface{}, path []interface{}) interface{} {
r, err := get(data, path)
if err != nil {
panic(err)
}
return node
return r
}
// StorageOp is the enumeration of supposed modifications.
type StorageOp int
func remove(data map[string]interface{}, path []interface{}) error {
const (
// StorageAdd represents an additive operation.
StorageAdd StorageOp = iota
// StorageRemove represents a removal operation.
StorageRemove = iota
// StorageReplace represents a replacement operation.
StorageReplace = iota
)
// 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 {
if len(path) == 0 {
return notFoundError(path, nonEmptyMsg)
}
_, isString := path[0].(string)
if !isString {
return notFoundError(path, stringHeadMsg)
}
switch op {
case StorageAdd:
return store.add(path, value)
case StorageRemove:
return store.remove(path)
case StorageReplace:
return store.replace(path, value)
default:
return &StorageError{Code: StorageInternalErr, Message: fmt.Sprintf("invalid operation: %v", op)}
}
}
func (store Storage) add(path []interface{}, value interface{}) error {
// Special case for adding a new root.
if len(path) == 1 {
return store.addRoot(path[0], 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)
}
}
node, err := store.Get(path[:len(path)-1])
if err != nil {
if _, err := get(data, path); err != nil {
return err
}
switch node := node.(type) {
case map[string]interface{}:
return store.addInsertObject(path, node, value)
case []interface{}:
return store.addInsertArray(path, node, value)
default:
return notFoundError(path, nonCollectionMsg(path[len(path)-2]))
}
}
func (store Storage) addAppend(path []interface{}, value interface{}) error {
var nodeParent interface{} = store
if len(path) > 1 {
r, err := store.Get(path[:len(path)-1])
if err != nil {
return err
}
nodeParent = r
}
node, err := store.Get(path)
if err != nil {
return err
}
switch n := node.(type) {
case []interface{}:
node = append(n, value)
default:
return notFoundError(path, nonArrayMsg(path[len(path)-1]))
}
switch nodeParent := nodeParent.(type) {
case Storage:
nodeParent[path[len(path)-1]] = node
case []interface{}:
// This is safe because it was validated by the lookup above.
idx := int(path[len(path)-1].(float64))
nodeParent[idx] = node
case map[string]interface{}:
// This is safe because it was validated by the lookup above.
key := path[len(path)-1].(string)
nodeParent[key] = node
default:
// "node" exists, therefore this is not reachable.
panic(fmt.Sprintf("illegal value: %v %v", nodeParent, path))
}
return nil
}
func (store Storage) addInsertArray(path []interface{}, node []interface{}, value interface{}) error {
idx, err := checkArrayIndex(path, node, path[len(path)-1])
if err != nil {
return err
}
var nodeParent interface{} = store
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
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
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
default:
// "node" exists, therefore this is not reachable.
panic(fmt.Sprintf("illegal value: %v %v", nodeParent, path))
}
}
func (store Storage) addInsertObject(path []interface{}, node map[string]interface{}, value interface{}) error {
switch last := path[len(path)-1].(type) {
case string:
node[last] = value
return nil
default:
return notFoundError(path, objectKeyTypeMsg(last))
}
}
func (store Storage) addRoot(key interface{}, value interface{}) error {
store[key] = value
return nil
}
func (store Storage) remove(path []interface{}) error {
// Special case for removing a root.
if len(path) == 1 {
delete(store, path[0])
return nil
return removeRoot(data, path[0].(string))
}
node, err := store.Get(path[:len(path)-1])
if err != nil {
return err
}
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.
}
}
func (store Storage) removeObject(path []interface{}, node map[string]interface{}) error {
key, err := checkObjectKey(path, node, path[len(path)-1])
if err != nil {
return err
}
delete(node, key)
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
}
node, err := store.Get(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, k)
return nil
}
func removeRoot(data map[string]interface{}, root string) error {
delete(data, root)
return nil
}
func replace(data map[string]interface{}, path []interface{}, value interface{}) error {
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
}
+77 -2
View File
@@ -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{}:
+150 -12
View File
@@ -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
+28 -14
View File
@@ -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{})))
}
Generated
+12 -7
View File
@@ -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: []
+1
View File
@@ -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
+16 -2
View File
@@ -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": [
+1 -1
View File
@@ -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.
+11 -1
View File
@@ -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++
}
}
}
+11
View File
@@ -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, "_") {
+4
View File
@@ -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
+6
View File
@@ -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.
+2
View File
@@ -37,3 +37,5 @@ type termios struct {
Ispeed int32
Ospeed int32
}
const cursorColumn = false
+1 -2
View File
@@ -67,8 +67,7 @@ func NewLiner() *State {
}
if !s.outputRedirected {
s.getColumns()
s.outputRedirected = s.columns <= 0
s.outputRedirected = !s.getColumns()
}
return &s
+4
View File
@@ -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
+2
View File
@@ -24,3 +24,5 @@ const (
type termios struct {
syscall.Termios
}
const cursorColumn = false
+3
View File
@@ -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 {
+7 -3
View File
@@ -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() {
+4
View File
@@ -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--
}
}
+24
View File
@@ -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
+10
View File
@@ -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 ./...
+24
View File
@@ -0,0 +1,24 @@
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+52
View File
@@ -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
+248
View File
@@ -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()
}
}
+189
View File
@@ -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)
}
}
}
+71
View File
@@ -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
}
+16 -1
View File
@@ -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 <spf@spf13.com>
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.
+9
View File
@@ -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"))
}
+39 -20
View File
@@ -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)
+315 -64
View File
@@ -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 <http://www.gnu.org/licenses/>.
along with {{ .appName }}. If not, see <http://www.gnu.org/licenses/>.`,
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 <http://www.gnu.org/licenses/>.
`,
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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 <http://www.gnu.org/licenses/>.
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:
<program> Copyright (C) <year> <name of author>
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
<http://www.gnu.org/licenses/>.
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
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
`,
}
+1 -13
View File
@@ -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 <EMAIL ADDRESS>")
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.
+28 -4
View File
@@ -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)
+1 -1
View File
@@ -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.
+3
View File
@@ -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]
+1
View File
@@ -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)
}
+99
View File
@@ -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
+166 -152
View File
@@ -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.
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))))
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)
(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
+24 -15
View File
@@ -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",
+7 -1
View File
@@ -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
@@ -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 }
@@ -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()"
}
+5
View File
@@ -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"
}
@@ -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
@@ -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": [
+23 -16
View File
@@ -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"
_ = v.Method
_ = v.Method // @referrers ref-method "Method"
_ = v.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
_ = (lib.Type).Method // ref from internal test package
_ = v.Method
_ = v.Method
_ = v.Method // @referrers ref-method "Method"
_ = v.Method // @referrers ref-method "Method"
p := t.Method(&a) // @describe ref-method "Method"
-------- @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
+33 -47
View File
@@ -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
}
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
}
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
return errors.New("couldn't find styles")
}
s.Split(findRule)
for s.Scan() {
rule := s.Text()
s.Split(findBody)
if !s.Scan() {
styles := style.FirstChild.Data
readUntil := func(end rune) (string, bool) {
i := strings.IndexRune(styles, end)
if i < 0 {
return "", false
}
s := styles[:i]
styles = styles[i:]
return s, true
}
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+`)
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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")
+21 -1
View File
@@ -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)
}
+73 -20
View File
@@ -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,25 +205,56 @@ func (p *exporter) obj(obj types.Object) {
}
func (p *exporter) pos(obj types.Object) {
var file string
var line int
if !p.posInfoFormat {
return
}
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 {
// different file
p.int(0)
// 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
}
if file == p.prevFile && line != p.prevLine {
// common case: write delta-encoded line number
p.int(line - p.prevLine) // != 0
} else {
// uncommon case: filename changed, or line didn't change
p.int(0)
p.string(file)
p.int(line)
p.prevFile = file
func commonPrefixLen(a, b string) int {
if len(a) > len(b) {
a, b = b, a
}
p.prevLine = line
// len(a) <= len(b)
i := 0
for i < len(a) && a[i] == b[i] {
i++
}
return i
}
func (p *exporter) qualifiedName(obj types.Object) {
@@ -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
+21 -11
View File
@@ -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
+3 -2
View File
@@ -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
+6
View File
@@ -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
+12 -5
View File
@@ -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 {
+1 -1
View File
@@ -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.
+2
View File
@@ -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"
+6 -1
View File
@@ -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)
}
}
+131 -13
View File
@@ -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",
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"net/http"
)
const baseURL = "http://play.golang.org"
const baseURL = "https://golang.org"
func init() {
http.HandleFunc("/compile", bounce)