mirror of https://github.com/ethereum/go-ethereum
Merge pull request #2357 from karalabe/abi-binding-generator
accounts/abi/bind: Go ABI binding generatorpull/2381/head
commit
cfb3a8ea8f
@ -0,0 +1,27 @@ |
||||
Copyright (c) 2009 The Go Authors. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following disclaimer |
||||
in the documentation and/or other materials provided with the |
||||
distribution. |
||||
* Neither the name of Google Inc. nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,22 @@ |
||||
Additional IP Rights Grant (Patents) |
||||
|
||||
"This implementation" means the copyrightable works distributed by |
||||
Google as part of the Go project. |
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive, |
||||
no-charge, royalty-free, irrevocable (except as stated in this section) |
||||
patent license to make, have made, use, offer to sell, sell, import, |
||||
transfer and otherwise run, modify and propagate the contents of this |
||||
implementation of Go, where such license applies only to those patent |
||||
claims, both currently owned or controlled by Google and acquired in |
||||
the future, licensable by Google that are necessarily infringed by this |
||||
implementation of Go. This grant does not include claims that would be |
||||
infringed only as a consequence of further modification of this |
||||
implementation. If you or your agent or exclusive licensee institute or |
||||
order or agree to the institution of patent litigation against any |
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging |
||||
that this implementation of Go or any code incorporated within this |
||||
implementation of Go constitutes direct or contributory patent |
||||
infringement, or inducement of patent infringement, then any patent |
||||
rights granted to you under this License for this implementation of Go |
||||
shall terminate as of the date such litigation is filed. |
@ -0,0 +1,624 @@ |
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package astutil |
||||
|
||||
// This file defines utilities for working with source positions.
|
||||
|
||||
import ( |
||||
"fmt" |
||||
"go/ast" |
||||
"go/token" |
||||
"sort" |
||||
) |
||||
|
||||
// PathEnclosingInterval returns the node that encloses the source
|
||||
// interval [start, end), and all its ancestors up to the AST root.
|
||||
//
|
||||
// The definition of "enclosing" used by this function considers
|
||||
// additional whitespace abutting a node to be enclosed by it.
|
||||
// In this example:
|
||||
//
|
||||
// z := x + y // add them
|
||||
// <-A->
|
||||
// <----B----->
|
||||
//
|
||||
// the ast.BinaryExpr(+) node is considered to enclose interval B
|
||||
// even though its [Pos()..End()) is actually only interval A.
|
||||
// This behaviour makes user interfaces more tolerant of imperfect
|
||||
// input.
|
||||
//
|
||||
// This function treats tokens as nodes, though they are not included
|
||||
// in the result. e.g. PathEnclosingInterval("+") returns the
|
||||
// enclosing ast.BinaryExpr("x + y").
|
||||
//
|
||||
// If start==end, the 1-char interval following start is used instead.
|
||||
//
|
||||
// The 'exact' result is true if the interval contains only path[0]
|
||||
// and perhaps some adjacent whitespace. It is false if the interval
|
||||
// overlaps multiple children of path[0], or if it contains only
|
||||
// interior whitespace of path[0].
|
||||
// In this example:
|
||||
//
|
||||
// z := x + y // add them
|
||||
// <--C--> <---E-->
|
||||
// ^
|
||||
// D
|
||||
//
|
||||
// intervals C, D and E are inexact. C is contained by the
|
||||
// z-assignment statement, because it spans three of its children (:=,
|
||||
// x, +). So too is the 1-char interval D, because it contains only
|
||||
// interior whitespace of the assignment. E is considered interior
|
||||
// whitespace of the BlockStmt containing the assignment.
|
||||
//
|
||||
// Precondition: [start, end) both lie within the same file as root.
|
||||
// TODO(adonovan): return (nil, false) in this case and remove precond.
|
||||
// Requires FileSet; see loader.tokenFileContainsPos.
|
||||
//
|
||||
// Postcondition: path is never nil; it always contains at least 'root'.
|
||||
//
|
||||
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) { |
||||
// fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging
|
||||
|
||||
// Precondition: node.[Pos..End) and adjoining whitespace contain [start, end).
|
||||
var visit func(node ast.Node) bool |
||||
visit = func(node ast.Node) bool { |
||||
path = append(path, node) |
||||
|
||||
nodePos := node.Pos() |
||||
nodeEnd := node.End() |
||||
|
||||
// fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging
|
||||
|
||||
// Intersect [start, end) with interval of node.
|
||||
if start < nodePos { |
||||
start = nodePos |
||||
} |
||||
if end > nodeEnd { |
||||
end = nodeEnd |
||||
} |
||||
|
||||
// Find sole child that contains [start, end).
|
||||
children := childrenOf(node) |
||||
l := len(children) |
||||
for i, child := range children { |
||||
// [childPos, childEnd) is unaugmented interval of child.
|
||||
childPos := child.Pos() |
||||
childEnd := child.End() |
||||
|
||||
// [augPos, augEnd) is whitespace-augmented interval of child.
|
||||
augPos := childPos |
||||
augEnd := childEnd |
||||
if i > 0 { |
||||
augPos = children[i-1].End() // start of preceding whitespace
|
||||
} |
||||
if i < l-1 { |
||||
nextChildPos := children[i+1].Pos() |
||||
// Does [start, end) lie between child and next child?
|
||||
if start >= augEnd && end <= nextChildPos { |
||||
return false // inexact match
|
||||
} |
||||
augEnd = nextChildPos // end of following whitespace
|
||||
} |
||||
|
||||
// fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n",
|
||||
// i, augPos, augEnd, start, end) // debugging
|
||||
|
||||
// Does augmented child strictly contain [start, end)?
|
||||
if augPos <= start && end <= augEnd { |
||||
_, isToken := child.(tokenNode) |
||||
return isToken || visit(child) |
||||
} |
||||
|
||||
// Does [start, end) overlap multiple children?
|
||||
// i.e. left-augmented child contains start
|
||||
// but LR-augmented child does not contain end.
|
||||
if start < childEnd && end > augEnd { |
||||
break |
||||
} |
||||
} |
||||
|
||||
// No single child contained [start, end),
|
||||
// so node is the result. Is it exact?
|
||||
|
||||
// (It's tempting to put this condition before the
|
||||
// child loop, but it gives the wrong result in the
|
||||
// case where a node (e.g. ExprStmt) and its sole
|
||||
// child have equal intervals.)
|
||||
if start == nodePos && end == nodeEnd { |
||||
return true // exact match
|
||||
} |
||||
|
||||
return false // inexact: overlaps multiple children
|
||||
} |
||||
|
||||
if start > end { |
||||
start, end = end, start |
||||
} |
||||
|
||||
if start < root.End() && end > root.Pos() { |
||||
if start == end { |
||||
end = start + 1 // empty interval => interval of size 1
|
||||
} |
||||
exact = visit(root) |
||||
|
||||
// Reverse the path:
|
||||
for i, l := 0, len(path); i < l/2; i++ { |
||||
path[i], path[l-1-i] = path[l-1-i], path[i] |
||||
} |
||||
} else { |
||||
// Selection lies within whitespace preceding the
|
||||
// first (or following the last) declaration in the file.
|
||||
// The result nonetheless always includes the ast.File.
|
||||
path = append(path, root) |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
// tokenNode is a dummy implementation of ast.Node for a single token.
|
||||
// They are used transiently by PathEnclosingInterval but never escape
|
||||
// this package.
|
||||
//
|
||||
type tokenNode struct { |
||||
pos token.Pos |
||||
end token.Pos |
||||
} |
||||
|
||||
func (n tokenNode) Pos() token.Pos { |
||||
return n.pos |
||||
} |
||||
|
||||
func (n tokenNode) End() token.Pos { |
||||
return n.end |
||||
} |
||||
|
||||
func tok(pos token.Pos, len int) ast.Node { |
||||
return tokenNode{pos, pos + token.Pos(len)} |
||||
} |
||||
|
||||
// childrenOf returns the direct non-nil children of ast.Node n.
|
||||
// It may include fake ast.Node implementations for bare tokens.
|
||||
// it is not safe to call (e.g.) ast.Walk on such nodes.
|
||||
//
|
||||
func childrenOf(n ast.Node) []ast.Node { |
||||
var children []ast.Node |
||||
|
||||
// First add nodes for all true subtrees.
|
||||
ast.Inspect(n, func(node ast.Node) bool { |
||||
if node == n { // push n
|
||||
return true // recur
|
||||
} |
||||
if node != nil { // push child
|
||||
children = append(children, node) |
||||
} |
||||
return false // no recursion
|
||||
}) |
||||
|
||||
// Then add fake Nodes for bare tokens.
|
||||
switch n := n.(type) { |
||||
case *ast.ArrayType: |
||||
children = append(children, |
||||
tok(n.Lbrack, len("[")), |
||||
tok(n.Elt.End(), len("]"))) |
||||
|
||||
case *ast.AssignStmt: |
||||
children = append(children, |
||||
tok(n.TokPos, len(n.Tok.String()))) |
||||
|
||||
case *ast.BasicLit: |
||||
children = append(children, |
||||
tok(n.ValuePos, len(n.Value))) |
||||
|
||||
case *ast.BinaryExpr: |
||||
children = append(children, tok(n.OpPos, len(n.Op.String()))) |
||||
|
||||
case *ast.BlockStmt: |
||||
children = append(children, |
||||
tok(n.Lbrace, len("{")), |
||||
tok(n.Rbrace, len("}"))) |
||||
|
||||
case *ast.BranchStmt: |
||||
children = append(children, |
||||
tok(n.TokPos, len(n.Tok.String()))) |
||||
|
||||
case *ast.CallExpr: |
||||
children = append(children, |
||||
tok(n.Lparen, len("(")), |
||||
tok(n.Rparen, len(")"))) |
||||
if n.Ellipsis != 0 { |
||||
children = append(children, tok(n.Ellipsis, len("..."))) |
||||
} |
||||
|
||||
case *ast.CaseClause: |
||||
if n.List == nil { |
||||
children = append(children, |
||||
tok(n.Case, len("default"))) |
||||
} else { |
||||
children = append(children, |
||||
tok(n.Case, len("case"))) |
||||
} |
||||
children = append(children, tok(n.Colon, len(":"))) |
||||
|
||||
case *ast.ChanType: |
||||
switch n.Dir { |
||||
case ast.RECV: |
||||
children = append(children, tok(n.Begin, len("<-chan"))) |
||||
case ast.SEND: |
||||
children = append(children, tok(n.Begin, len("chan<-"))) |
||||
case ast.RECV | ast.SEND: |
||||
children = append(children, tok(n.Begin, len("chan"))) |
||||
} |
||||
|
||||
case *ast.CommClause: |
||||
if n.Comm == nil { |
||||
children = append(children, |
||||
tok(n.Case, len("default"))) |
||||
} else { |
||||
children = append(children, |
||||
tok(n.Case, len("case"))) |
||||
} |
||||
children = append(children, tok(n.Colon, len(":"))) |
||||
|
||||
case *ast.Comment: |
||||
// nop
|
||||
|
||||
case *ast.CommentGroup: |
||||
// nop
|
||||
|
||||
case *ast.CompositeLit: |
||||
children = append(children, |
||||
tok(n.Lbrace, len("{")), |
||||
tok(n.Rbrace, len("{"))) |
||||
|
||||
case *ast.DeclStmt: |
||||
// nop
|
||||
|
||||
case *ast.DeferStmt: |
||||
children = append(children, |
||||
tok(n.Defer, len("defer"))) |
||||
|
||||
case *ast.Ellipsis: |
||||
children = append(children, |
||||
tok(n.Ellipsis, len("..."))) |
||||
|
||||
case *ast.EmptyStmt: |
||||
// nop
|
||||
|
||||
case *ast.ExprStmt: |
||||
// nop
|
||||
|
||||
case *ast.Field: |
||||
// TODO(adonovan): Field.{Doc,Comment,Tag}?
|
||||
|
||||
case *ast.FieldList: |
||||
children = append(children, |
||||
tok(n.Opening, len("(")), |
||||
tok(n.Closing, len(")"))) |
||||
|
||||
case *ast.File: |
||||
// TODO test: Doc
|
||||
children = append(children, |
||||
tok(n.Package, len("package"))) |
||||
|
||||
case *ast.ForStmt: |
||||
children = append(children, |
||||
tok(n.For, len("for"))) |
||||
|
||||
case *ast.FuncDecl: |
||||
// TODO(adonovan): FuncDecl.Comment?
|
||||
|
||||
// Uniquely, FuncDecl breaks the invariant that
|
||||
// preorder traversal yields tokens in lexical order:
|
||||
// in fact, FuncDecl.Recv precedes FuncDecl.Type.Func.
|
||||
//
|
||||
// As a workaround, we inline the case for FuncType
|
||||
// here and order things correctly.
|
||||
//
|
||||
children = nil // discard ast.Walk(FuncDecl) info subtrees
|
||||
children = append(children, tok(n.Type.Func, len("func"))) |
||||
if n.Recv != nil { |
||||
children = append(children, n.Recv) |
||||
} |
||||
children = append(children, n.Name) |
||||
if n.Type.Params != nil { |
||||
children = append(children, n.Type.Params) |
||||
} |
||||
if n.Type.Results != nil { |
||||
children = append(children, n.Type.Results) |
||||
} |
||||
if n.Body != nil { |
||||
children = append(children, n.Body) |
||||
} |
||||
|
||||
case *ast.FuncLit: |
||||
// nop
|
||||
|
||||
case *ast.FuncType: |
||||
if n.Func != 0 { |
||||
children = append(children, |
||||
tok(n.Func, len("func"))) |
||||
} |
||||
|
||||
case *ast.GenDecl: |
||||
children = append(children, |
||||
tok(n.TokPos, len(n.Tok.String()))) |
||||
if n.Lparen != 0 { |
||||
children = append(children, |
||||
tok(n.Lparen, len("(")), |
||||
tok(n.Rparen, len(")"))) |
||||
} |
||||
|
||||
case *ast.GoStmt: |
||||
children = append(children, |
||||
tok(n.Go, len("go"))) |
||||
|
||||
case *ast.Ident: |
||||
children = append(children, |
||||
tok(n.NamePos, len(n.Name))) |
||||
|
||||
case *ast.IfStmt: |
||||
children = append(children, |
||||
tok(n.If, len("if"))) |
||||
|
||||
case *ast.ImportSpec: |
||||
// TODO(adonovan): ImportSpec.{Doc,EndPos}?
|
||||
|
||||
case *ast.IncDecStmt: |
||||
children = append(children, |
||||
tok(n.TokPos, len(n.Tok.String()))) |
||||
|
||||
case *ast.IndexExpr: |
||||
children = append(children, |
||||
tok(n.Lbrack, len("{")), |
||||
tok(n.Rbrack, len("}"))) |
||||
|
||||
case *ast.InterfaceType: |
||||
children = append(children, |
||||
tok(n.Interface, len("interface"))) |
||||
|
||||
case *ast.KeyValueExpr: |
||||
children = append(children, |
||||
tok(n.Colon, len(":"))) |
||||
|
||||
case *ast.LabeledStmt: |
||||
children = append(children, |
||||
tok(n.Colon, len(":"))) |
||||
|
||||
case *ast.MapType: |
||||
children = append(children, |
||||
tok(n.Map, len("map"))) |
||||
|
||||
case *ast.ParenExpr: |
||||
children = append(children, |
||||
tok(n.Lparen, len("(")), |
||||
tok(n.Rparen, len(")"))) |
||||
|
||||
case *ast.RangeStmt: |
||||
children = append(children, |
||||
tok(n.For, len("for")), |
||||
tok(n.TokPos, len(n.Tok.String()))) |
||||
|
||||
case *ast.ReturnStmt: |
||||
children = append(children, |
||||
tok(n.Return, len("return"))) |
||||
|
||||
case *ast.SelectStmt: |
||||
children = append(children, |
||||
tok(n.Select, len("select"))) |
||||
|
||||
case *ast.SelectorExpr: |
||||
// nop
|
||||
|
||||
case *ast.SendStmt: |
||||
children = append(children, |
||||
tok(n.Arrow, len("<-"))) |
||||
|
||||
case *ast.SliceExpr: |
||||
children = append(children, |
||||
tok(n.Lbrack, len("[")), |
||||
tok(n.Rbrack, len("]"))) |
||||
|
||||
case *ast.StarExpr: |
||||
children = append(children, tok(n.Star, len("*"))) |
||||
|
||||
case *ast.StructType: |
||||
children = append(children, tok(n.Struct, len("struct"))) |
||||
|
||||
case *ast.SwitchStmt: |
||||
children = append(children, tok(n.Switch, len("switch"))) |
||||
|
||||
case *ast.TypeAssertExpr: |
||||
children = append(children, |
||||
tok(n.Lparen-1, len(".")), |
||||
tok(n.Lparen, len("(")), |
||||
tok(n.Rparen, len(")"))) |
||||
|
||||
case *ast.TypeSpec: |
||||
// TODO(adonovan): TypeSpec.{Doc,Comment}?
|
||||
|
||||
case *ast.TypeSwitchStmt: |
||||
children = append(children, tok(n.Switch, len("switch"))) |
||||
|
||||
case *ast.UnaryExpr: |
||||
children = append(children, tok(n.OpPos, len(n.Op.String()))) |
||||
|
||||
case *ast.ValueSpec: |
||||
// TODO(adonovan): ValueSpec.{Doc,Comment}?
|
||||
|
||||
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt: |
||||
// nop
|
||||
} |
||||
|
||||
// TODO(adonovan): opt: merge the logic of ast.Inspect() into
|
||||
// the switch above so we can make interleaved callbacks for
|
||||
// both Nodes and Tokens in the right order and avoid the need
|
||||
// to sort.
|
||||
sort.Sort(byPos(children)) |
||||
|
||||
return children |
||||
} |
||||
|
||||
type byPos []ast.Node |
||||
|
||||
func (sl byPos) Len() int { |
||||
return len(sl) |
||||
} |
||||
func (sl byPos) Less(i, j int) bool { |
||||
return sl[i].Pos() < sl[j].Pos() |
||||
} |
||||
func (sl byPos) Swap(i, j int) { |
||||
sl[i], sl[j] = sl[j], sl[i] |
||||
} |
||||
|
||||
// NodeDescription returns a description of the concrete type of n suitable
|
||||
// for a user interface.
|
||||
//
|
||||
// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident,
|
||||
// StarExpr) we could be much more specific given the path to the AST
|
||||
// root. Perhaps we should do that.
|
||||
//
|
||||
func NodeDescription(n ast.Node) string { |
||||
switch n := n.(type) { |
||||
case *ast.ArrayType: |
||||
return "array type" |
||||
case *ast.AssignStmt: |
||||
return "assignment" |
||||
case *ast.BadDecl: |
||||
return "bad declaration" |
||||
case *ast.BadExpr: |
||||
return "bad expression" |
||||
case *ast.BadStmt: |
||||
return "bad statement" |
||||
case *ast.BasicLit: |
||||
return "basic literal" |
||||
case *ast.BinaryExpr: |
||||
return fmt.Sprintf("binary %s operation", n.Op) |
||||
case *ast.BlockStmt: |
||||
return "block" |
||||
case *ast.BranchStmt: |
||||
switch n.Tok { |
||||
case token.BREAK: |
||||
return "break statement" |
||||
case token.CONTINUE: |
||||
return "continue statement" |
||||
case token.GOTO: |
||||
return "goto statement" |
||||
case token.FALLTHROUGH: |
||||
return "fall-through statement" |
||||
} |
||||
case *ast.CallExpr: |
||||
return "function call (or conversion)" |
||||
case *ast.CaseClause: |
||||
return "case clause" |
||||
case *ast.ChanType: |
||||
return "channel type" |
||||
case *ast.CommClause: |
||||
return "communication clause" |
||||
case *ast.Comment: |
||||
return "comment" |
||||
case *ast.CommentGroup: |
||||
return "comment group" |
||||
case *ast.CompositeLit: |
||||
return "composite literal" |
||||
case *ast.DeclStmt: |
||||
return NodeDescription(n.Decl) + " statement" |
||||
case *ast.DeferStmt: |
||||
return "defer statement" |
||||
case *ast.Ellipsis: |
||||
return "ellipsis" |
||||
case *ast.EmptyStmt: |
||||
return "empty statement" |
||||
case *ast.ExprStmt: |
||||
return "expression statement" |
||||
case *ast.Field: |
||||
// Can be any of these:
|
||||
// struct {x, y int} -- struct field(s)
|
||||
// struct {T} -- anon struct field
|
||||
// interface {I} -- interface embedding
|
||||
// interface {f()} -- interface method
|
||||
// func (A) func(B) C -- receiver, param(s), result(s)
|
||||
return "field/method/parameter" |
||||
case *ast.FieldList: |
||||
return "field/method/parameter list" |
||||
case *ast.File: |
||||
return "source file" |
||||
case *ast.ForStmt: |
||||
return "for loop" |
||||
case *ast.FuncDecl: |
||||
return "function declaration" |
||||
case *ast.FuncLit: |
||||
return "function literal" |
||||
case *ast.FuncType: |
||||
return "function type" |
||||
case *ast.GenDecl: |
||||
switch n.Tok { |
||||
case token.IMPORT: |
||||
return "import declaration" |
||||
case token.CONST: |
||||
return "constant declaration" |
||||
case token.TYPE: |
||||
return "type declaration" |
||||
case token.VAR: |
||||
return "variable declaration" |
||||
} |
||||
case *ast.GoStmt: |
||||
return "go statement" |
||||
case *ast.Ident: |
||||
return "identifier" |
||||
case *ast.IfStmt: |
||||
return "if statement" |
||||
case *ast.ImportSpec: |
||||
return "import specification" |
||||
case *ast.IncDecStmt: |
||||
if n.Tok == token.INC { |
||||
return "increment statement" |
||||
} |
||||
return "decrement statement" |
||||
case *ast.IndexExpr: |
||||
return "index expression" |
||||
case *ast.InterfaceType: |
||||
return "interface type" |
||||
case *ast.KeyValueExpr: |
||||
return "key/value association" |
||||
case *ast.LabeledStmt: |
||||
return "statement label" |
||||
case *ast.MapType: |
||||
return "map type" |
||||
case *ast.Package: |
||||
return "package" |
||||
case *ast.ParenExpr: |
||||
return "parenthesized " + NodeDescription(n.X) |
||||
case *ast.RangeStmt: |
||||
return "range loop" |
||||
case *ast.ReturnStmt: |
||||
return "return statement" |
||||
case *ast.SelectStmt: |
||||
return "select statement" |
||||
case *ast.SelectorExpr: |
||||
return "selector" |
||||
case *ast.SendStmt: |
||||
return "channel send" |
||||
case *ast.SliceExpr: |
||||
return "slice expression" |
||||
case *ast.StarExpr: |
||||
return "*-operation" // load/store expr or pointer type
|
||||
case *ast.StructType: |
||||
return "struct type" |
||||
case *ast.SwitchStmt: |
||||
return "switch statement" |
||||
case *ast.TypeAssertExpr: |
||||
return "type assertion" |
||||
case *ast.TypeSpec: |
||||
return "type specification" |
||||
case *ast.TypeSwitchStmt: |
||||
return "type switch" |
||||
case *ast.UnaryExpr: |
||||
return fmt.Sprintf("unary %s operation", n.Op) |
||||
case *ast.ValueSpec: |
||||
return "value specification" |
||||
|
||||
} |
||||
panic(fmt.Sprintf("unexpected node type: %T", n)) |
||||
} |
@ -0,0 +1,376 @@ |
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package astutil contains common utilities for working with the Go AST.
|
||||
package astutil |
||||
|
||||
import ( |
||||
"fmt" |
||||
"go/ast" |
||||
"go/token" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
// AddImport adds the import path to the file f, if absent.
|
||||
func AddImport(fset *token.FileSet, f *ast.File, ipath string) (added bool) { |
||||
return AddNamedImport(fset, f, "", ipath) |
||||
} |
||||
|
||||
// AddNamedImport adds the import path to the file f, if absent.
|
||||
// If name is not empty, it is used to rename the import.
|
||||
//
|
||||
// For example, calling
|
||||
// AddNamedImport(fset, f, "pathpkg", "path")
|
||||
// adds
|
||||
// import pathpkg "path"
|
||||
func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added bool) { |
||||
if imports(f, ipath) { |
||||
return false |
||||
} |
||||
|
||||
newImport := &ast.ImportSpec{ |
||||
Path: &ast.BasicLit{ |
||||
Kind: token.STRING, |
||||
Value: strconv.Quote(ipath), |
||||
}, |
||||
} |
||||
if name != "" { |
||||
newImport.Name = &ast.Ident{Name: name} |
||||
} |
||||
|
||||
// Find an import decl to add to.
|
||||
// The goal is to find an existing import
|
||||
// whose import path has the longest shared
|
||||
// prefix with ipath.
|
||||
var ( |
||||
bestMatch = -1 // length of longest shared prefix
|
||||
lastImport = -1 // index in f.Decls of the file's final import decl
|
||||
impDecl *ast.GenDecl // import decl containing the best match
|
||||
impIndex = -1 // spec index in impDecl containing the best match
|
||||
) |
||||
for i, decl := range f.Decls { |
||||
gen, ok := decl.(*ast.GenDecl) |
||||
if ok && gen.Tok == token.IMPORT { |
||||
lastImport = i |
||||
// Do not add to import "C", to avoid disrupting the
|
||||
// association with its doc comment, breaking cgo.
|
||||
if declImports(gen, "C") { |
||||
continue |
||||
} |
||||
|
||||
// Match an empty import decl if that's all that is available.
|
||||
if len(gen.Specs) == 0 && bestMatch == -1 { |
||||
impDecl = gen |
||||
} |
||||
|
||||
// Compute longest shared prefix with imports in this group.
|
||||
for j, spec := range gen.Specs { |
||||
impspec := spec.(*ast.ImportSpec) |
||||
n := matchLen(importPath(impspec), ipath) |
||||
if n > bestMatch { |
||||
bestMatch = n |
||||
impDecl = gen |
||||
impIndex = j |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// If no import decl found, add one after the last import.
|
||||
if impDecl == nil { |
||||
impDecl = &ast.GenDecl{ |
||||
Tok: token.IMPORT, |
||||
} |
||||
if lastImport >= 0 { |
||||
impDecl.TokPos = f.Decls[lastImport].End() |
||||
} else { |
||||
// There are no existing imports.
|
||||
// Our new import goes after the package declaration and after
|
||||
// the comment, if any, that starts on the same line as the
|
||||
// package declaration.
|
||||
impDecl.TokPos = f.Package |
||||
|
||||
file := fset.File(f.Package) |
||||
pkgLine := file.Line(f.Package) |
||||
for _, c := range f.Comments { |
||||
if file.Line(c.Pos()) > pkgLine { |
||||
break |
||||
} |
||||
impDecl.TokPos = c.End() |
||||
} |
||||
} |
||||
f.Decls = append(f.Decls, nil) |
||||
copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:]) |
||||
f.Decls[lastImport+1] = impDecl |
||||
} |
||||
|
||||
// Insert new import at insertAt.
|
||||
insertAt := 0 |
||||
if impIndex >= 0 { |
||||
// insert after the found import
|
||||
insertAt = impIndex + 1 |
||||
} |
||||
impDecl.Specs = append(impDecl.Specs, nil) |
||||
copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:]) |
||||
impDecl.Specs[insertAt] = newImport |
||||
pos := impDecl.Pos() |
||||
if insertAt > 0 { |
||||
// If there is a comment after an existing import, preserve the comment
|
||||
// position by adding the new import after the comment.
|
||||
if spec, ok := impDecl.Specs[insertAt-1].(*ast.ImportSpec); ok && spec.Comment != nil { |
||||
pos = spec.Comment.End() |
||||
} else { |
||||
// Assign same position as the previous import,
|
||||
// so that the sorter sees it as being in the same block.
|
||||
pos = impDecl.Specs[insertAt-1].Pos() |
||||
} |
||||
} |
||||
if newImport.Name != nil { |
||||
newImport.Name.NamePos = pos |
||||
} |
||||
newImport.Path.ValuePos = pos |
||||
newImport.EndPos = pos |
||||
|
||||
// Clean up parens. impDecl contains at least one spec.
|
||||
if len(impDecl.Specs) == 1 { |
||||
// Remove unneeded parens.
|
||||
impDecl.Lparen = token.NoPos |
||||
} else if !impDecl.Lparen.IsValid() { |
||||
// impDecl needs parens added.
|
||||
impDecl.Lparen = impDecl.Specs[0].Pos() |
||||
} |
||||
|
||||
f.Imports = append(f.Imports, newImport) |
||||
return true |
||||
} |
||||
|
||||
// DeleteImport deletes the import path from the file f, if present.
|
||||
func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) { |
||||
return DeleteNamedImport(fset, f, "", path) |
||||
} |
||||
|
||||
// DeleteNamedImport deletes the import with the given name and path from the file f, if present.
|
||||
func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (deleted bool) { |
||||
var delspecs []*ast.ImportSpec |
||||
|
||||
// Find the import nodes that import path, if any.
|
||||
for i := 0; i < len(f.Decls); i++ { |
||||
decl := f.Decls[i] |
||||
gen, ok := decl.(*ast.GenDecl) |
||||
if !ok || gen.Tok != token.IMPORT { |
||||
continue |
||||
} |
||||
for j := 0; j < len(gen.Specs); j++ { |
||||
spec := gen.Specs[j] |
||||
impspec := spec.(*ast.ImportSpec) |
||||
if impspec.Name == nil && name != "" { |
||||
continue |
||||
} |
||||
if impspec.Name != nil && impspec.Name.Name != name { |
||||
continue |
||||
} |
||||
if importPath(impspec) != path { |
||||
continue |
||||
} |
||||
|
||||
// We found an import spec that imports path.
|
||||
// Delete it.
|
||||
delspecs = append(delspecs, impspec) |
||||
deleted = true |
||||
copy(gen.Specs[j:], gen.Specs[j+1:]) |
||||
gen.Specs = gen.Specs[:len(gen.Specs)-1] |
||||
|
||||
// If this was the last import spec in this decl,
|
||||
// delete the decl, too.
|
||||
if len(gen.Specs) == 0 { |
||||
copy(f.Decls[i:], f.Decls[i+1:]) |
||||
f.Decls = f.Decls[:len(f.Decls)-1] |
||||
i-- |
||||
break |
||||
} else if len(gen.Specs) == 1 { |
||||
gen.Lparen = token.NoPos // drop parens
|
||||
} |
||||
if j > 0 { |
||||
lastImpspec := gen.Specs[j-1].(*ast.ImportSpec) |
||||
lastLine := fset.Position(lastImpspec.Path.ValuePos).Line |
||||
line := fset.Position(impspec.Path.ValuePos).Line |
||||
|
||||
// We deleted an entry but now there may be
|
||||
// a blank line-sized hole where the import was.
|
||||
if line-lastLine > 1 { |
||||
// There was a blank line immediately preceding the deleted import,
|
||||
// so there's no need to close the hole.
|
||||
// Do nothing.
|
||||
} else { |
||||
// There was no blank line. Close the hole.
|
||||
fset.File(gen.Rparen).MergeLine(line) |
||||
} |
||||
} |
||||
j-- |
||||
} |
||||
} |
||||
|
||||
// Delete them from f.Imports.
|
||||
for i := 0; i < len(f.Imports); i++ { |
||||
imp := f.Imports[i] |
||||
for j, del := range delspecs { |
||||
if imp == del { |
||||
copy(f.Imports[i:], f.Imports[i+1:]) |
||||
f.Imports = f.Imports[:len(f.Imports)-1] |
||||
copy(delspecs[j:], delspecs[j+1:]) |
||||
delspecs = delspecs[:len(delspecs)-1] |
||||
i-- |
||||
break |
||||
} |
||||
} |
||||
} |
||||
|
||||
if len(delspecs) > 0 { |
||||
panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs)) |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
// RewriteImport rewrites any import of path oldPath to path newPath.
|
||||
func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) { |
||||
for _, imp := range f.Imports { |
||||
if importPath(imp) == oldPath { |
||||
rewrote = true |
||||
// record old End, because the default is to compute
|
||||
// it using the length of imp.Path.Value.
|
||||
imp.EndPos = imp.End() |
||||
imp.Path.Value = strconv.Quote(newPath) |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// UsesImport reports whether a given import is used.
|
||||
func UsesImport(f *ast.File, path string) (used bool) { |
||||
spec := importSpec(f, path) |
||||
if spec == nil { |
||||
return |
||||
} |
||||
|
||||
name := spec.Name.String() |
||||
switch name { |
||||
case "<nil>": |
||||
// If the package name is not explicitly specified,
|
||||
// make an educated guess. This is not guaranteed to be correct.
|
||||
lastSlash := strings.LastIndex(path, "/") |
||||
if lastSlash == -1 { |
||||
name = path |
||||
} else { |
||||
name = path[lastSlash+1:] |
||||
} |
||||
case "_", ".": |
||||
// Not sure if this import is used - err on the side of caution.
|
||||
return true |
||||
} |
||||
|
||||
ast.Walk(visitFn(func(n ast.Node) { |
||||
sel, ok := n.(*ast.SelectorExpr) |
||||
if ok && isTopName(sel.X, name) { |
||||
used = true |
||||
} |
||||
}), f) |
||||
|
||||
return |
||||
} |
||||
|
||||
type visitFn func(node ast.Node) |
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor { |
||||
fn(node) |
||||
return fn |
||||
} |
||||
|
||||
// imports returns true if f imports path.
|
||||
func imports(f *ast.File, path string) bool { |
||||
return importSpec(f, path) != nil |
||||
} |
||||
|
||||
// importSpec returns the import spec if f imports path,
|
||||
// or nil otherwise.
|
||||
func importSpec(f *ast.File, path string) *ast.ImportSpec { |
||||
for _, s := range f.Imports { |
||||
if importPath(s) == path { |
||||
return s |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// importPath returns the unquoted import path of s,
|
||||
// or "" if the path is not properly quoted.
|
||||
func importPath(s *ast.ImportSpec) string { |
||||
t, err := strconv.Unquote(s.Path.Value) |
||||
if err == nil { |
||||
return t |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
// declImports reports whether gen contains an import of path.
|
||||
func declImports(gen *ast.GenDecl, path string) bool { |
||||
if gen.Tok != token.IMPORT { |
||||
return false |
||||
} |
||||
for _, spec := range gen.Specs { |
||||
impspec := spec.(*ast.ImportSpec) |
||||
if importPath(impspec) == path { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// matchLen returns the length of the longest path segment prefix shared by x and y.
|
||||
func matchLen(x, y string) int { |
||||
n := 0 |
||||
for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ { |
||||
if x[i] == '/' { |
||||
n++ |
||||
} |
||||
} |
||||
return n |
||||
} |
||||
|
||||
// isTopName returns true if n is a top-level unresolved identifier with the given name.
|
||||
func isTopName(n ast.Expr, name string) bool { |
||||
id, ok := n.(*ast.Ident) |
||||
return ok && id.Name == name && id.Obj == nil |
||||
} |
||||
|
||||
// Imports returns the file imports grouped by paragraph.
|
||||
func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec { |
||||
var groups [][]*ast.ImportSpec |
||||
|
||||
for _, decl := range f.Decls { |
||||
genDecl, ok := decl.(*ast.GenDecl) |
||||
if !ok || genDecl.Tok != token.IMPORT { |
||||
break |
||||
} |
||||
|
||||
group := []*ast.ImportSpec{} |
||||
|
||||
var lastLine int |
||||
for _, spec := range genDecl.Specs { |
||||
importSpec := spec.(*ast.ImportSpec) |
||||
pos := importSpec.Path.ValuePos |
||||
line := fset.Position(pos).Line |
||||
if lastLine > 0 && pos > 0 && line-lastLine > 1 { |
||||
groups = append(groups, group) |
||||
group = []*ast.ImportSpec{} |
||||
} |
||||
group = append(group, importSpec) |
||||
lastLine = line |
||||
} |
||||
groups = append(groups, group) |
||||
} |
||||
|
||||
return groups |
||||
} |
@ -0,0 +1,14 @@ |
||||
package astutil |
||||
|
||||
import "go/ast" |
||||
|
||||
// Unparen returns e with any enclosing parentheses stripped.
|
||||
func Unparen(e ast.Expr) ast.Expr { |
||||
for { |
||||
p, ok := e.(*ast.ParenExpr) |
||||
if !ok { |
||||
return e |
||||
} |
||||
e = p.X |
||||
} |
||||
} |
@ -0,0 +1,419 @@ |
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package imports |
||||
|
||||
import ( |
||||
"fmt" |
||||
"go/ast" |
||||
"go/build" |
||||
"go/parser" |
||||
"go/token" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"strings" |
||||
"sync" |
||||
|
||||
"golang.org/x/tools/go/ast/astutil" |
||||
) |
||||
|
||||
// importToGroup is a list of functions which map from an import path to
|
||||
// a group number.
|
||||
var importToGroup = []func(importPath string) (num int, ok bool){ |
||||
func(importPath string) (num int, ok bool) { |
||||
if strings.HasPrefix(importPath, "appengine") { |
||||
return 2, true |
||||
} |
||||
return |
||||
}, |
||||
func(importPath string) (num int, ok bool) { |
||||
if strings.Contains(importPath, ".") { |
||||
return 1, true |
||||
} |
||||
return |
||||
}, |
||||
} |
||||
|
||||
func importGroup(importPath string) int { |
||||
for _, fn := range importToGroup { |
||||
if n, ok := fn(importPath); ok { |
||||
return n |
||||
} |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
func fixImports(fset *token.FileSet, f *ast.File, filename string) (added []string, err error) { |
||||
// refs are a set of possible package references currently unsatisfied by imports.
|
||||
// first key: either base package (e.g. "fmt") or renamed package
|
||||
// second key: referenced package symbol (e.g. "Println")
|
||||
refs := make(map[string]map[string]bool) |
||||
|
||||
// decls are the current package imports. key is base package or renamed package.
|
||||
decls := make(map[string]*ast.ImportSpec) |
||||
|
||||
// collect potential uses of packages.
|
||||
var visitor visitFn |
||||
visitor = visitFn(func(node ast.Node) ast.Visitor { |
||||
if node == nil { |
||||
return visitor |
||||
} |
||||
switch v := node.(type) { |
||||
case *ast.ImportSpec: |
||||
if v.Name != nil { |
||||
decls[v.Name.Name] = v |
||||
} else { |
||||
local := importPathToName(strings.Trim(v.Path.Value, `\"`)) |
||||
decls[local] = v |
||||
} |
||||
case *ast.SelectorExpr: |
||||
xident, ok := v.X.(*ast.Ident) |
||||
if !ok { |
||||
break |
||||
} |
||||
if xident.Obj != nil { |
||||
// if the parser can resolve it, it's not a package ref
|
||||
break |
||||
} |
||||
pkgName := xident.Name |
||||
if refs[pkgName] == nil { |
||||
refs[pkgName] = make(map[string]bool) |
||||
} |
||||
if decls[pkgName] == nil { |
||||
refs[pkgName][v.Sel.Name] = true |
||||
} |
||||
} |
||||
return visitor |
||||
}) |
||||
ast.Walk(visitor, f) |
||||
|
||||
// Nil out any unused ImportSpecs, to be removed in following passes
|
||||
unusedImport := map[string]string{} |
||||
for pkg, is := range decls { |
||||
if refs[pkg] == nil && pkg != "_" && pkg != "." { |
||||
name := "" |
||||
if is.Name != nil { |
||||
name = is.Name.Name |
||||
} |
||||
unusedImport[strings.Trim(is.Path.Value, `"`)] = name |
||||
} |
||||
} |
||||
for ipath, name := range unusedImport { |
||||
if ipath == "C" { |
||||
// Don't remove cgo stuff.
|
||||
continue |
||||
} |
||||
astutil.DeleteNamedImport(fset, f, name, ipath) |
||||
} |
||||
|
||||
// Search for imports matching potential package references.
|
||||
searches := 0 |
||||
type result struct { |
||||
ipath string |
||||
name string |
||||
err error |
||||
} |
||||
results := make(chan result) |
||||
for pkgName, symbols := range refs { |
||||
if len(symbols) == 0 { |
||||
continue // skip over packages already imported
|
||||
} |
||||
go func(pkgName string, symbols map[string]bool) { |
||||
ipath, rename, err := findImport(pkgName, symbols, filename) |
||||
r := result{ipath: ipath, err: err} |
||||
if rename { |
||||
r.name = pkgName |
||||
} |
||||
results <- r |
||||
}(pkgName, symbols) |
||||
searches++ |
||||
} |
||||
for i := 0; i < searches; i++ { |
||||
result := <-results |
||||
if result.err != nil { |
||||
return nil, result.err |
||||
} |
||||
if result.ipath != "" { |
||||
if result.name != "" { |
||||
astutil.AddNamedImport(fset, f, result.name, result.ipath) |
||||
} else { |
||||
astutil.AddImport(fset, f, result.ipath) |
||||
} |
||||
added = append(added, result.ipath) |
||||
} |
||||
} |
||||
|
||||
return added, nil |
||||
} |
||||
|
||||
// importPathToName returns the package name for the given import path.
|
||||
var importPathToName = importPathToNameGoPath |
||||
|
||||
// importPathToNameBasic assumes the package name is the base of import path.
|
||||
func importPathToNameBasic(importPath string) (packageName string) { |
||||
return path.Base(importPath) |
||||
} |
||||
|
||||
// importPathToNameGoPath finds out the actual package name, as declared in its .go files.
|
||||
// If there's a problem, it falls back to using importPathToNameBasic.
|
||||
func importPathToNameGoPath(importPath string) (packageName string) { |
||||
if buildPkg, err := build.Import(importPath, "", 0); err == nil { |
||||
return buildPkg.Name |
||||
} else { |
||||
return importPathToNameBasic(importPath) |
||||
} |
||||
} |
||||
|
||||
type pkg struct { |
||||
importpath string // full pkg import path, e.g. "net/http"
|
||||
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
|
||||
} |
||||
|
||||
var pkgIndexOnce sync.Once |
||||
|
||||
var pkgIndex struct { |
||||
sync.Mutex |
||||
m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
|
||||
} |
||||
|
||||
// gate is a semaphore for limiting concurrency.
|
||||
type gate chan struct{} |
||||
|
||||
func (g gate) enter() { g <- struct{}{} } |
||||
func (g gate) leave() { <-g } |
||||
|
||||
// fsgate protects the OS & filesystem from too much concurrency.
|
||||
// Too much disk I/O -> too many threads -> swapping and bad scheduling.
|
||||
var fsgate = make(gate, 8) |
||||
|
||||
func loadPkgIndex() { |
||||
pkgIndex.Lock() |
||||
pkgIndex.m = make(map[string][]pkg) |
||||
pkgIndex.Unlock() |
||||
|
||||
var wg sync.WaitGroup |
||||
for _, path := range build.Default.SrcDirs() { |
||||
fsgate.enter() |
||||
f, err := os.Open(path) |
||||
if err != nil { |
||||
fsgate.leave() |
||||
fmt.Fprint(os.Stderr, err) |
||||
continue |
||||
} |
||||
children, err := f.Readdir(-1) |
||||
f.Close() |
||||
fsgate.leave() |
||||
if err != nil { |
||||
fmt.Fprint(os.Stderr, err) |
||||
continue |
||||
} |
||||
for _, child := range children { |
||||
if child.IsDir() { |
||||
wg.Add(1) |
||||
go func(path, name string) { |
||||
defer wg.Done() |
||||
loadPkg(&wg, path, name) |
||||
}(path, child.Name()) |
||||
} |
||||
} |
||||
} |
||||
wg.Wait() |
||||
} |
||||
|
||||
func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) { |
||||
importpath := filepath.ToSlash(pkgrelpath) |
||||
dir := filepath.Join(root, importpath) |
||||
|
||||
fsgate.enter() |
||||
defer fsgate.leave() |
||||
pkgDir, err := os.Open(dir) |
||||
if err != nil { |
||||
return |
||||
} |
||||
children, err := pkgDir.Readdir(-1) |
||||
pkgDir.Close() |
||||
if err != nil { |
||||
return |
||||
} |
||||
// hasGo tracks whether a directory actually appears to be a
|
||||
// Go source code directory. If $GOPATH == $HOME, and
|
||||
// $HOME/src has lots of other large non-Go projects in it,
|
||||
// then the calls to importPathToName below can be expensive.
|
||||
hasGo := false |
||||
for _, child := range children { |
||||
// Avoid .foo, _foo, and testdata directory trees.
|
||||
name := child.Name() |
||||
if name == "" || name[0] == '.' || name[0] == '_' || name == "testdata" { |
||||
continue |
||||
} |
||||
if strings.HasSuffix(name, ".go") { |
||||
hasGo = true |
||||
} |
||||
if child.IsDir() { |
||||
wg.Add(1) |
||||
go func(root, name string) { |
||||
defer wg.Done() |
||||
loadPkg(wg, root, name) |
||||
}(root, filepath.Join(importpath, name)) |
||||
} |
||||
} |
||||
if hasGo { |
||||
shortName := importPathToName(importpath) |
||||
pkgIndex.Lock() |
||||
pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{ |
||||
importpath: importpath, |
||||
dir: dir, |
||||
}) |
||||
pkgIndex.Unlock() |
||||
} |
||||
|
||||
} |
||||
|
||||
// loadExports returns a list exports for a package.
|
||||
var loadExports = loadExportsGoPath |
||||
|
||||
func loadExportsGoPath(dir string) map[string]bool { |
||||
exports := make(map[string]bool) |
||||
buildPkg, err := build.ImportDir(dir, 0) |
||||
if err != nil { |
||||
if strings.Contains(err.Error(), "no buildable Go source files in") { |
||||
return nil |
||||
} |
||||
fmt.Fprintf(os.Stderr, "could not import %q: %v\n", dir, err) |
||||
return nil |
||||
} |
||||
fset := token.NewFileSet() |
||||
for _, files := range [...][]string{buildPkg.GoFiles, buildPkg.CgoFiles} { |
||||
for _, file := range files { |
||||
f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0) |
||||
if err != nil { |
||||
fmt.Fprintf(os.Stderr, "could not parse %q: %v\n", file, err) |
||||
continue |
||||
} |
||||
for name := range f.Scope.Objects { |
||||
if ast.IsExported(name) { |
||||
exports[name] = true |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return exports |
||||
} |
||||
|
||||
// findImport searches for a package with the given symbols.
|
||||
// If no package is found, findImport returns "".
|
||||
// Declared as a variable rather than a function so goimports can be easily
|
||||
// extended by adding a file with an init function.
|
||||
var findImport = findImportGoPath |
||||
|
||||
func findImportGoPath(pkgName string, symbols map[string]bool, filename string) (string, bool, error) { |
||||
// Fast path for the standard library.
|
||||
// In the common case we hopefully never have to scan the GOPATH, which can
|
||||
// be slow with moving disks.
|
||||
if pkg, rename, ok := findImportStdlib(pkgName, symbols); ok { |
||||
return pkg, rename, nil |
||||
} |
||||
|
||||
// TODO(sameer): look at the import lines for other Go files in the
|
||||
// local directory, since the user is likely to import the same packages
|
||||
// in the current Go file. Return rename=true when the other Go files
|
||||
// use a renamed package that's also used in the current file.
|
||||
|
||||
pkgIndexOnce.Do(loadPkgIndex) |
||||
|
||||
// Collect exports for packages with matching names.
|
||||
var ( |
||||
wg sync.WaitGroup |
||||
mu sync.Mutex |
||||
shortest string |
||||
) |
||||
pkgIndex.Lock() |
||||
for _, pkg := range pkgIndex.m[pkgName] { |
||||
if !canUse(filename, pkg.dir) { |
||||
continue |
||||
} |
||||
wg.Add(1) |
||||
go func(importpath, dir string) { |
||||
defer wg.Done() |
||||
exports := loadExports(dir) |
||||
if exports == nil { |
||||
return |
||||
} |
||||
// If it doesn't have the right symbols, stop.
|
||||
for symbol := range symbols { |
||||
if !exports[symbol] { |
||||
return |
||||
} |
||||
} |
||||
|
||||
// Devendorize for use in import statement.
|
||||
if i := strings.LastIndex(importpath, "/vendor/"); i >= 0 { |
||||
importpath = importpath[i+len("/vendor/"):] |
||||
} else if strings.HasPrefix(importpath, "vendor/") { |
||||
importpath = importpath[len("vendor/"):] |
||||
} |
||||
|
||||
// Save as the answer.
|
||||
// If there are multiple candidates, the shortest wins,
|
||||
// to prefer "bytes" over "github.com/foo/bytes".
|
||||
mu.Lock() |
||||
if shortest == "" || len(importpath) < len(shortest) || len(importpath) == len(shortest) && importpath < shortest { |
||||
shortest = importpath |
||||
} |
||||
mu.Unlock() |
||||
}(pkg.importpath, pkg.dir) |
||||
} |
||||
pkgIndex.Unlock() |
||||
wg.Wait() |
||||
|
||||
return shortest, false, nil |
||||
} |
||||
|
||||
func canUse(filename, dir string) bool { |
||||
dirSlash := filepath.ToSlash(dir) |
||||
if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") { |
||||
return true |
||||
} |
||||
// Vendor or internal directory only visible from children of parent.
|
||||
// That means the path from the current directory to the target directory
|
||||
// can contain ../vendor or ../internal but not ../foo/vendor or ../foo/internal
|
||||
// or bar/vendor or bar/internal.
|
||||
// After stripping all the leading ../, the only okay place to see vendor or internal
|
||||
// is at the very beginning of the path.
|
||||
abs, err := filepath.Abs(filename) |
||||
if err != nil { |
||||
return false |
||||
} |
||||
rel, err := filepath.Rel(abs, dir) |
||||
if err != nil { |
||||
return false |
||||
} |
||||
relSlash := filepath.ToSlash(rel) |
||||
if i := strings.LastIndex(relSlash, "../"); i >= 0 { |
||||
relSlash = relSlash[i+len("../"):] |
||||
} |
||||
return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal") |
||||
} |
||||
|
||||
type visitFn func(node ast.Node) ast.Visitor |
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor { |
||||
return fn(node) |
||||
} |
||||
|
||||
func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename, ok bool) { |
||||
for symbol := range symbols { |
||||
path := stdlib[shortPkg+"."+symbol] |
||||
if path == "" { |
||||
return "", false, false |
||||
} |
||||
if importPath != "" && importPath != path { |
||||
// Ambiguous. Symbols pointed to different things.
|
||||
return "", false, false |
||||
} |
||||
importPath = path |
||||
} |
||||
return importPath, false, importPath != "" |
||||
} |
@ -0,0 +1,283 @@ |
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package imports implements a Go pretty-printer (like package "go/format")
|
||||
// that also adds or removes import statements as necessary.
|
||||
package imports |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"fmt" |
||||
"go/ast" |
||||
"go/format" |
||||
"go/parser" |
||||
"go/printer" |
||||
"go/token" |
||||
"io" |
||||
"regexp" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"golang.org/x/tools/go/ast/astutil" |
||||
) |
||||
|
||||
// Options specifies options for processing files.
|
||||
type Options struct { |
||||
Fragment bool // Accept fragment of a source file (no package statement)
|
||||
AllErrors bool // Report all errors (not just the first 10 on different lines)
|
||||
|
||||
Comments bool // Print comments (true if nil *Options provided)
|
||||
TabIndent bool // Use tabs for indent (true if nil *Options provided)
|
||||
TabWidth int // Tab width (8 if nil *Options provided)
|
||||
} |
||||
|
||||
// Process formats and adjusts imports for the provided file.
|
||||
// If opt is nil the defaults are used.
|
||||
//
|
||||
// Note that filename's directory influences which imports can be chosen,
|
||||
// so it is important that filename be accurate.
|
||||
// To process data ``as if'' it were in filename, pass the data as a non-nil src.
|
||||
func Process(filename string, src []byte, opt *Options) ([]byte, error) { |
||||
if opt == nil { |
||||
opt = &Options{Comments: true, TabIndent: true, TabWidth: 8} |
||||
} |
||||
|
||||
fileSet := token.NewFileSet() |
||||
file, adjust, err := parse(fileSet, filename, src, opt) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
_, err = fixImports(fileSet, file, filename) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
sortImports(fileSet, file) |
||||
imps := astutil.Imports(fileSet, file) |
||||
|
||||
var spacesBefore []string // import paths we need spaces before
|
||||
for _, impSection := range imps { |
||||
// Within each block of contiguous imports, see if any
|
||||
// import lines are in different group numbers. If so,
|
||||
// we'll need to put a space between them so it's
|
||||
// compatible with gofmt.
|
||||
lastGroup := -1 |
||||
for _, importSpec := range impSection { |
||||
importPath, _ := strconv.Unquote(importSpec.Path.Value) |
||||
groupNum := importGroup(importPath) |
||||
if groupNum != lastGroup && lastGroup != -1 { |
||||
spacesBefore = append(spacesBefore, importPath) |
||||
} |
||||
lastGroup = groupNum |
||||
} |
||||
|
||||
} |
||||
|
||||
printerMode := printer.UseSpaces |
||||
if opt.TabIndent { |
||||
printerMode |= printer.TabIndent |
||||
} |
||||
printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth} |
||||
|
||||
var buf bytes.Buffer |
||||
err = printConfig.Fprint(&buf, fileSet, file) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
out := buf.Bytes() |
||||
if adjust != nil { |
||||
out = adjust(src, out) |
||||
} |
||||
if len(spacesBefore) > 0 { |
||||
out = addImportSpaces(bytes.NewReader(out), spacesBefore) |
||||
} |
||||
|
||||
out, err = format.Source(out) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return out, nil |
||||
} |
||||
|
||||
// parse parses src, which was read from filename,
|
||||
// as a Go source file or statement list.
|
||||
func parse(fset *token.FileSet, filename string, src []byte, opt *Options) (*ast.File, func(orig, src []byte) []byte, error) { |
||||
parserMode := parser.Mode(0) |
||||
if opt.Comments { |
||||
parserMode |= parser.ParseComments |
||||
} |
||||
if opt.AllErrors { |
||||
parserMode |= parser.AllErrors |
||||
} |
||||
|
||||
// Try as whole source file.
|
||||
file, err := parser.ParseFile(fset, filename, src, parserMode) |
||||
if err == nil { |
||||
return file, nil, nil |
||||
} |
||||
// If the error is that the source file didn't begin with a
|
||||
// package line and we accept fragmented input, fall through to
|
||||
// try as a source fragment. Stop and return on any other error.
|
||||
if !opt.Fragment || !strings.Contains(err.Error(), "expected 'package'") { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
// If this is a declaration list, make it a source file
|
||||
// by inserting a package clause.
|
||||
// Insert using a ;, not a newline, so that the line numbers
|
||||
// in psrc match the ones in src.
|
||||
psrc := append([]byte("package main;"), src...) |
||||
file, err = parser.ParseFile(fset, filename, psrc, parserMode) |
||||
if err == nil { |
||||
// If a main function exists, we will assume this is a main
|
||||
// package and leave the file.
|
||||
if containsMainFunc(file) { |
||||
return file, nil, nil |
||||
} |
||||
|
||||
adjust := func(orig, src []byte) []byte { |
||||
// Remove the package clause.
|
||||
// Gofmt has turned the ; into a \n.
|
||||
src = src[len("package main\n"):] |
||||
return matchSpace(orig, src) |
||||
} |
||||
return file, adjust, nil |
||||
} |
||||
// If the error is that the source file didn't begin with a
|
||||
// declaration, fall through to try as a statement list.
|
||||
// Stop and return on any other error.
|
||||
if !strings.Contains(err.Error(), "expected declaration") { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
// If this is a statement list, make it a source file
|
||||
// by inserting a package clause and turning the list
|
||||
// into a function body. This handles expressions too.
|
||||
// Insert using a ;, not a newline, so that the line numbers
|
||||
// in fsrc match the ones in src.
|
||||
fsrc := append(append([]byte("package p; func _() {"), src...), '}') |
||||
file, err = parser.ParseFile(fset, filename, fsrc, parserMode) |
||||
if err == nil { |
||||
adjust := func(orig, src []byte) []byte { |
||||
// Remove the wrapping.
|
||||
// Gofmt has turned the ; into a \n\n.
|
||||
src = src[len("package p\n\nfunc _() {"):] |
||||
src = src[:len(src)-len("}\n")] |
||||
// Gofmt has also indented the function body one level.
|
||||
// Remove that indent.
|
||||
src = bytes.Replace(src, []byte("\n\t"), []byte("\n"), -1) |
||||
return matchSpace(orig, src) |
||||
} |
||||
return file, adjust, nil |
||||
} |
||||
|
||||
// Failed, and out of options.
|
||||
return nil, nil, err |
||||
} |
||||
|
||||
// containsMainFunc checks if a file contains a function declaration with the
|
||||
// function signature 'func main()'
|
||||
func containsMainFunc(file *ast.File) bool { |
||||
for _, decl := range file.Decls { |
||||
if f, ok := decl.(*ast.FuncDecl); ok { |
||||
if f.Name.Name != "main" { |
||||
continue |
||||
} |
||||
|
||||
if len(f.Type.Params.List) != 0 { |
||||
continue |
||||
} |
||||
|
||||
if f.Type.Results != nil && len(f.Type.Results.List) != 0 { |
||||
continue |
||||
} |
||||
|
||||
return true |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
func cutSpace(b []byte) (before, middle, after []byte) { |
||||
i := 0 |
||||
for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\n') { |
||||
i++ |
||||
} |
||||
j := len(b) |
||||
for j > 0 && (b[j-1] == ' ' || b[j-1] == '\t' || b[j-1] == '\n') { |
||||
j-- |
||||
} |
||||
if i <= j { |
||||
return b[:i], b[i:j], b[j:] |
||||
} |
||||
return nil, nil, b[j:] |
||||
} |
||||
|
||||
// matchSpace reformats src to use the same space context as orig.
|
||||
// 1) If orig begins with blank lines, matchSpace inserts them at the beginning of src.
|
||||
// 2) matchSpace copies the indentation of the first non-blank line in orig
|
||||
// to every non-blank line in src.
|
||||
// 3) matchSpace copies the trailing space from orig and uses it in place
|
||||
// of src's trailing space.
|
||||
func matchSpace(orig []byte, src []byte) []byte { |
||||
before, _, after := cutSpace(orig) |
||||
i := bytes.LastIndex(before, []byte{'\n'}) |
||||
before, indent := before[:i+1], before[i+1:] |
||||
|
||||
_, src, _ = cutSpace(src) |
||||
|
||||
var b bytes.Buffer |
||||
b.Write(before) |
||||
for len(src) > 0 { |
||||
line := src |
||||
if i := bytes.IndexByte(line, '\n'); i >= 0 { |
||||
line, src = line[:i+1], line[i+1:] |
||||
} else { |
||||
src = nil |
||||
} |
||||
if len(line) > 0 && line[0] != '\n' { // not blank
|
||||
b.Write(indent) |
||||
} |
||||
b.Write(line) |
||||
} |
||||
b.Write(after) |
||||
return b.Bytes() |
||||
} |
||||
|
||||
var impLine = regexp.MustCompile(`^\s+(?:[\w\.]+\s+)?"(.+)"`) |
||||
|
||||
func addImportSpaces(r io.Reader, breaks []string) []byte { |
||||
var out bytes.Buffer |
||||
sc := bufio.NewScanner(r) |
||||
inImports := false |
||||
done := false |
||||
for sc.Scan() { |
||||
s := sc.Text() |
||||
|
||||
if !inImports && !done && strings.HasPrefix(s, "import") { |
||||
inImports = true |
||||
} |
||||
if inImports && (strings.HasPrefix(s, "var") || |
||||
strings.HasPrefix(s, "func") || |
||||
strings.HasPrefix(s, "const") || |
||||
strings.HasPrefix(s, "type")) { |
||||
done = true |
||||
inImports = false |
||||
} |
||||
if inImports && len(breaks) > 0 { |
||||
if m := impLine.FindStringSubmatch(s); m != nil { |
||||
if m[1] == string(breaks[0]) { |
||||
out.WriteByte('\n') |
||||
breaks = breaks[1:] |
||||
} |
||||
} |
||||
} |
||||
|
||||
fmt.Fprintln(&out, s) |
||||
} |
||||
return out.Bytes() |
||||
} |
@ -0,0 +1,173 @@ |
||||
// +build ignore
|
||||
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Command mkindex creates the file "pkgindex.go" containing an index of the Go
|
||||
// standard library. The file is intended to be built as part of the imports
|
||||
// package, so that the package may be used in environments where a GOROOT is
|
||||
// not available (such as App Engine).
|
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"go/ast" |
||||
"go/build" |
||||
"go/format" |
||||
"go/parser" |
||||
"go/token" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"strings" |
||||
) |
||||
|
||||
var ( |
||||
pkgIndex = make(map[string][]pkg) |
||||
exports = make(map[string]map[string]bool) |
||||
) |
||||
|
||||
func main() { |
||||
// Don't use GOPATH.
|
||||
ctx := build.Default |
||||
ctx.GOPATH = "" |
||||
|
||||
// Populate pkgIndex global from GOROOT.
|
||||
for _, path := range ctx.SrcDirs() { |
||||
f, err := os.Open(path) |
||||
if err != nil { |
||||
log.Print(err) |
||||
continue |
||||
} |
||||
children, err := f.Readdir(-1) |
||||
f.Close() |
||||
if err != nil { |
||||
log.Print(err) |
||||
continue |
||||
} |
||||
for _, child := range children { |
||||
if child.IsDir() { |
||||
loadPkg(path, child.Name()) |
||||
} |
||||
} |
||||
} |
||||
// Populate exports global.
|
||||
for _, ps := range pkgIndex { |
||||
for _, p := range ps { |
||||
e := loadExports(p.dir) |
||||
if e != nil { |
||||
exports[p.dir] = e |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Construct source file.
|
||||
var buf bytes.Buffer |
||||
fmt.Fprint(&buf, pkgIndexHead) |
||||
fmt.Fprintf(&buf, "var pkgIndexMaster = %#v\n", pkgIndex) |
||||
fmt.Fprintf(&buf, "var exportsMaster = %#v\n", exports) |
||||
src := buf.Bytes() |
||||
|
||||
// Replace main.pkg type name with pkg.
|
||||
src = bytes.Replace(src, []byte("main.pkg"), []byte("pkg"), -1) |
||||
// Replace actual GOROOT with "/go".
|
||||
src = bytes.Replace(src, []byte(ctx.GOROOT), []byte("/go"), -1) |
||||
// Add some line wrapping.
|
||||
src = bytes.Replace(src, []byte("}, "), []byte("},\n"), -1) |
||||
src = bytes.Replace(src, []byte("true, "), []byte("true,\n"), -1) |
||||
|
||||
var err error |
||||
src, err = format.Source(src) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
|
||||
// Write out source file.
|
||||
err = ioutil.WriteFile("pkgindex.go", src, 0644) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
const pkgIndexHead = `package imports |
||||
|
||||
func init() { |
||||
pkgIndexOnce.Do(func() { |
||||
pkgIndex.m = pkgIndexMaster |
||||
}) |
||||
loadExports = func(dir string) map[string]bool { |
||||
return exportsMaster[dir] |
||||
} |
||||
} |
||||
` |
||||
|
||||
type pkg struct { |
||||
importpath string // full pkg import path, e.g. "net/http"
|
||||
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
|
||||
} |
||||
|
||||
var fset = token.NewFileSet() |
||||
|
||||
func loadPkg(root, importpath string) { |
||||
shortName := path.Base(importpath) |
||||
if shortName == "testdata" { |
||||
return |
||||
} |
||||
|
||||
dir := filepath.Join(root, importpath) |
||||
pkgIndex[shortName] = append(pkgIndex[shortName], pkg{ |
||||
importpath: importpath, |
||||
dir: dir, |
||||
}) |
||||
|
||||
pkgDir, err := os.Open(dir) |
||||
if err != nil { |
||||
return |
||||
} |
||||
children, err := pkgDir.Readdir(-1) |
||||
pkgDir.Close() |
||||
if err != nil { |
||||
return |
||||
} |
||||
for _, child := range children { |
||||
name := child.Name() |
||||
if name == "" { |
||||
continue |
||||
} |
||||
if c := name[0]; c == '.' || ('0' <= c && c <= '9') { |
||||
continue |
||||
} |
||||
if child.IsDir() { |
||||
loadPkg(root, filepath.Join(importpath, name)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func loadExports(dir string) map[string]bool { |
||||
exports := make(map[string]bool) |
||||
buildPkg, err := build.ImportDir(dir, 0) |
||||
if err != nil { |
||||
if strings.Contains(err.Error(), "no buildable Go source files in") { |
||||
return nil |
||||
} |
||||
log.Printf("could not import %q: %v", dir, err) |
||||
return nil |
||||
} |
||||
for _, file := range buildPkg.GoFiles { |
||||
f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0) |
||||
if err != nil { |
||||
log.Printf("could not parse %q: %v", file, err) |
||||
continue |
||||
} |
||||
for name := range f.Scope.Objects { |
||||
if ast.IsExported(name) { |
||||
exports[name] = true |
||||
} |
||||
} |
||||
} |
||||
return exports |
||||
} |
@ -0,0 +1,93 @@ |
||||
// +build ignore
|
||||
|
||||
// mkstdlib generates the zstdlib.go file, containing the Go standard
|
||||
// library API symbols. It's baked into the binary to avoid scanning
|
||||
// GOPATH in the common case.
|
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"fmt" |
||||
"go/format" |
||||
"io" |
||||
"log" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"regexp" |
||||
"sort" |
||||
"strings" |
||||
) |
||||
|
||||
func mustOpen(name string) io.Reader { |
||||
f, err := os.Open(name) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
return f |
||||
} |
||||
|
||||
func api(base string) string { |
||||
return filepath.Join(os.Getenv("GOROOT"), "api", base) |
||||
} |
||||
|
||||
var sym = regexp.MustCompile(`^pkg (\S+).*?, (?:var|func|type|const) ([A-Z]\w*)`) |
||||
|
||||
func main() { |
||||
var buf bytes.Buffer |
||||
outf := func(format string, args ...interface{}) { |
||||
fmt.Fprintf(&buf, format, args...) |
||||
} |
||||
outf("// AUTO-GENERATED BY mkstdlib.go\n\n") |
||||
outf("package imports\n") |
||||
outf("var stdlib = map[string]string{\n") |
||||
f := io.MultiReader( |
||||
mustOpen(api("go1.txt")), |
||||
mustOpen(api("go1.1.txt")), |
||||
mustOpen(api("go1.2.txt")), |
||||
mustOpen(api("go1.3.txt")), |
||||
mustOpen(api("go1.4.txt")), |
||||
mustOpen(api("go1.5.txt")), |
||||
) |
||||
sc := bufio.NewScanner(f) |
||||
fullImport := map[string]string{} // "zip.NewReader" => "archive/zip"
|
||||
ambiguous := map[string]bool{} |
||||
var keys []string |
||||
for sc.Scan() { |
||||
l := sc.Text() |
||||
has := func(v string) bool { return strings.Contains(l, v) } |
||||
if has("struct, ") || has("interface, ") || has(", method (") { |
||||
continue |
||||
} |
||||
if m := sym.FindStringSubmatch(l); m != nil { |
||||
full := m[1] |
||||
key := path.Base(full) + "." + m[2] |
||||
if exist, ok := fullImport[key]; ok { |
||||
if exist != full { |
||||
ambiguous[key] = true |
||||
} |
||||
} else { |
||||
fullImport[key] = full |
||||
keys = append(keys, key) |
||||
} |
||||
} |
||||
} |
||||
if err := sc.Err(); err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
sort.Strings(keys) |
||||
for _, key := range keys { |
||||
if ambiguous[key] { |
||||
outf("\t// %q is ambiguous\n", key) |
||||
} else { |
||||
outf("\t%q: %q,\n", key, fullImport[key]) |
||||
} |
||||
} |
||||
outf("}\n") |
||||
fmtbuf, err := format.Source(buf.Bytes()) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
os.Stdout.Write(fmtbuf) |
||||
} |
@ -0,0 +1,212 @@ |
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Hacked up copy of go/ast/import.go
|
||||
|
||||
package imports |
||||
|
||||
import ( |
||||
"go/ast" |
||||
"go/token" |
||||
"sort" |
||||
"strconv" |
||||
) |
||||
|
||||
// sortImports sorts runs of consecutive import lines in import blocks in f.
|
||||
// It also removes duplicate imports when it is possible to do so without data loss.
|
||||
func sortImports(fset *token.FileSet, f *ast.File) { |
||||
for i, d := range f.Decls { |
||||
d, ok := d.(*ast.GenDecl) |
||||
if !ok || d.Tok != token.IMPORT { |
||||
// Not an import declaration, so we're done.
|
||||
// Imports are always first.
|
||||
break |
||||
} |
||||
|
||||
if len(d.Specs) == 0 { |
||||
// Empty import block, remove it.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) |
||||
} |
||||
|
||||
if !d.Lparen.IsValid() { |
||||
// Not a block: sorted by default.
|
||||
continue |
||||
} |
||||
|
||||
// Identify and sort runs of specs on successive lines.
|
||||
i := 0 |
||||
specs := d.Specs[:0] |
||||
for j, s := range d.Specs { |
||||
if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line { |
||||
// j begins a new run. End this one.
|
||||
specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) |
||||
i = j |
||||
} |
||||
} |
||||
specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) |
||||
d.Specs = specs |
||||
|
||||
// Deduping can leave a blank line before the rparen; clean that up.
|
||||
if len(d.Specs) > 0 { |
||||
lastSpec := d.Specs[len(d.Specs)-1] |
||||
lastLine := fset.Position(lastSpec.Pos()).Line |
||||
if rParenLine := fset.Position(d.Rparen).Line; rParenLine > lastLine+1 { |
||||
fset.File(d.Rparen).MergeLine(rParenLine - 1) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func importPath(s ast.Spec) string { |
||||
t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value) |
||||
if err == nil { |
||||
return t |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
func importName(s ast.Spec) string { |
||||
n := s.(*ast.ImportSpec).Name |
||||
if n == nil { |
||||
return "" |
||||
} |
||||
return n.Name |
||||
} |
||||
|
||||
func importComment(s ast.Spec) string { |
||||
c := s.(*ast.ImportSpec).Comment |
||||
if c == nil { |
||||
return "" |
||||
} |
||||
return c.Text() |
||||
} |
||||
|
||||
// collapse indicates whether prev may be removed, leaving only next.
|
||||
func collapse(prev, next ast.Spec) bool { |
||||
if importPath(next) != importPath(prev) || importName(next) != importName(prev) { |
||||
return false |
||||
} |
||||
return prev.(*ast.ImportSpec).Comment == nil |
||||
} |
||||
|
||||
type posSpan struct { |
||||
Start token.Pos |
||||
End token.Pos |
||||
} |
||||
|
||||
func sortSpecs(fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec { |
||||
// Can't short-circuit here even if specs are already sorted,
|
||||
// since they might yet need deduplication.
|
||||
// A lone import, however, may be safely ignored.
|
||||
if len(specs) <= 1 { |
||||
return specs |
||||
} |
||||
|
||||
// Record positions for specs.
|
||||
pos := make([]posSpan, len(specs)) |
||||
for i, s := range specs { |
||||
pos[i] = posSpan{s.Pos(), s.End()} |
||||
} |
||||
|
||||
// Identify comments in this range.
|
||||
// Any comment from pos[0].Start to the final line counts.
|
||||
lastLine := fset.Position(pos[len(pos)-1].End).Line |
||||
cstart := len(f.Comments) |
||||
cend := len(f.Comments) |
||||
for i, g := range f.Comments { |
||||
if g.Pos() < pos[0].Start { |
||||
continue |
||||
} |
||||
if i < cstart { |
||||
cstart = i |
||||
} |
||||
if fset.Position(g.End()).Line > lastLine { |
||||
cend = i |
||||
break |
||||
} |
||||
} |
||||
comments := f.Comments[cstart:cend] |
||||
|
||||
// Assign each comment to the import spec preceding it.
|
||||
importComment := map[*ast.ImportSpec][]*ast.CommentGroup{} |
||||
specIndex := 0 |
||||
for _, g := range comments { |
||||
for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { |
||||
specIndex++ |
||||
} |
||||
s := specs[specIndex].(*ast.ImportSpec) |
||||
importComment[s] = append(importComment[s], g) |
||||
} |
||||
|
||||
// Sort the import specs by import path.
|
||||
// Remove duplicates, when possible without data loss.
|
||||
// Reassign the import paths to have the same position sequence.
|
||||
// Reassign each comment to abut the end of its spec.
|
||||
// Sort the comments by new position.
|
||||
sort.Sort(byImportSpec(specs)) |
||||
|
||||
// Dedup. Thanks to our sorting, we can just consider
|
||||
// adjacent pairs of imports.
|
||||
deduped := specs[:0] |
||||
for i, s := range specs { |
||||
if i == len(specs)-1 || !collapse(s, specs[i+1]) { |
||||
deduped = append(deduped, s) |
||||
} else { |
||||
p := s.Pos() |
||||
fset.File(p).MergeLine(fset.Position(p).Line) |
||||
} |
||||
} |
||||
specs = deduped |
||||
|
||||
// Fix up comment positions
|
||||
for i, s := range specs { |
||||
s := s.(*ast.ImportSpec) |
||||
if s.Name != nil { |
||||
s.Name.NamePos = pos[i].Start |
||||
} |
||||
s.Path.ValuePos = pos[i].Start |
||||
s.EndPos = pos[i].End |
||||
for _, g := range importComment[s] { |
||||
for _, c := range g.List { |
||||
c.Slash = pos[i].End |
||||
} |
||||
} |
||||
} |
||||
|
||||
sort.Sort(byCommentPos(comments)) |
||||
|
||||
return specs |
||||
} |
||||
|
||||
type byImportSpec []ast.Spec // slice of *ast.ImportSpec
|
||||
|
||||
func (x byImportSpec) Len() int { return len(x) } |
||||
func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |
||||
func (x byImportSpec) Less(i, j int) bool { |
||||
ipath := importPath(x[i]) |
||||
jpath := importPath(x[j]) |
||||
|
||||
igroup := importGroup(ipath) |
||||
jgroup := importGroup(jpath) |
||||
if igroup != jgroup { |
||||
return igroup < jgroup |
||||
} |
||||
|
||||
if ipath != jpath { |
||||
return ipath < jpath |
||||
} |
||||
iname := importName(x[i]) |
||||
jname := importName(x[j]) |
||||
|
||||
if iname != jname { |
||||
return iname < jname |
||||
} |
||||
return importComment(x[i]) < importComment(x[j]) |
||||
} |
||||
|
||||
type byCommentPos []*ast.CommentGroup |
||||
|
||||
func (x byCommentPos) Len() int { return len(x) } |
||||
func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |
||||
func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() } |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,59 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library 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.
|
||||
//
|
||||
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind |
||||
|
||||
import ( |
||||
"errors" |
||||
"io" |
||||
"io/ioutil" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
) |
||||
|
||||
// NewTransactor is a utility method to easily create a transaction signer from
|
||||
// an encrypted json key stream and the associated passphrase.
|
||||
func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { |
||||
json, err := ioutil.ReadAll(keyin) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
key, err := crypto.DecryptKey(json, passphrase) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return NewKeyedTransactor(key), nil |
||||
} |
||||
|
||||
// NewKeyedTransactor is a utility method to easily create a transaction signer
|
||||
// from a plain go-ethereum crypto key.
|
||||
func NewKeyedTransactor(key *crypto.Key) *TransactOpts { |
||||
return &TransactOpts{ |
||||
From: key.Address, |
||||
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { |
||||
if address != key.Address { |
||||
return nil, errors.New("not authorized to sign this account") |
||||
} |
||||
signature, err := crypto.Sign(tx.SigHash().Bytes(), key.PrivateKey) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return tx.WithSignature(signature) |
||||
}, |
||||
} |
||||
} |
@ -0,0 +1,63 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library 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.
|
||||
//
|
||||
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
// ContractCaller defines the methods needed to allow operating with contract on a read
|
||||
// only basis.
|
||||
type ContractCaller interface { |
||||
// ContractCall executes an Ethereum contract call with the specified data as
|
||||
// the input. The pending flag requests execution against the pending block, not
|
||||
// the stable head of the chain.
|
||||
ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) |
||||
} |
||||
|
||||
// ContractTransactor defines the methods needed to allow operating with contract
|
||||
// on a write only basis. Beside the transacting method, the remainder are helpers
|
||||
// used when the user does not provide some needed values, but rather leaves it up
|
||||
// to the transactor to decide.
|
||||
type ContractTransactor interface { |
||||
// Nonce retrieves the current pending nonce associated with an account.
|
||||
PendingAccountNonce(account common.Address) (uint64, error) |
||||
|
||||
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
|
||||
// execution of a transaction.
|
||||
SuggestGasPrice() (*big.Int, error) |
||||
|
||||
// EstimateGasLimit tries to estimate the gas needed to execute a specific
|
||||
// transaction based on the current pending state of the backend blockchain.
|
||||
// There is no guarantee that this is the true gas limit requirement as other
|
||||
// transactions may be added or removed by miners, but it should provide a basis
|
||||
// for setting a reasonable default.
|
||||
EstimateGasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) |
||||
|
||||
// SendTransaction injects the transaction into the pending pool for execution.
|
||||
SendTransaction(*types.Transaction) error |
||||
} |
||||
|
||||
// ContractBackend defines the methods needed to allow operating with contract
|
||||
// on a read-write basis.
|
||||
type ContractBackend interface { |
||||
ContractCaller |
||||
ContractTransactor |
||||
} |
@ -0,0 +1,49 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library 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.
|
||||
//
|
||||
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package backends |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
// This nil assignment ensures compile time that nilBackend implements bind.ContractBackend.
|
||||
var _ bind.ContractBackend = (*nilBackend)(nil) |
||||
|
||||
// nilBackend implements bind.ContractBackend, but panics on any method call.
|
||||
// Its sole purpose is to support the binding tests to construct the generated
|
||||
// wrappers without calling any methods on them.
|
||||
type nilBackend struct{} |
||||
|
||||
func (*nilBackend) ContractCall(common.Address, []byte, bool) ([]byte, error) { |
||||
panic("not implemented") |
||||
} |
||||
func (*nilBackend) EstimateGasLimit(common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) { |
||||
panic("not implemented") |
||||
} |
||||
func (*nilBackend) SuggestGasPrice() (*big.Int, error) { panic("not implemented") } |
||||
func (*nilBackend) PendingAccountNonce(common.Address) (uint64, error) { panic("not implemented") } |
||||
func (*nilBackend) SendTransaction(*types.Transaction) error { panic("not implemented") } |
||||
|
||||
// NewNilBackend creates a new binding backend that can be used for instantiation
|
||||
// but will panic on any invocation. Its sole purpose is to help testing.
|
||||
func NewNilBackend() bind.ContractBackend { |
||||
return new(nilBackend) |
||||
} |
@ -0,0 +1,216 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library 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.
|
||||
//
|
||||
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package backends |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"math/big" |
||||
"sync" |
||||
"sync/atomic" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
"github.com/ethereum/go-ethereum/rpc" |
||||
) |
||||
|
||||
// This nil assignment ensures compile time that rpcBackend implements bind.ContractBackend.
|
||||
var _ bind.ContractBackend = (*rpcBackend)(nil) |
||||
|
||||
// rpcBackend implements bind.ContractBackend, and acts as the data provider to
|
||||
// Ethereum contracts bound to Go structs. It uses an RPC connection to delegate
|
||||
// all its functionality.
|
||||
//
|
||||
// Note: The current implementation is a blocking one. This should be replaced
|
||||
// by a proper async version when a real RPC client is created.
|
||||
type rpcBackend struct { |
||||
client rpc.Client // RPC client connection to interact with an API server
|
||||
autoid uint32 // ID number to use for the next API request
|
||||
lock sync.Mutex // Singleton access until we get to request multiplexing
|
||||
} |
||||
|
||||
// NewRPCBackend creates a new binding backend to an RPC provider that can be
|
||||
// used to interact with remote contracts.
|
||||
func NewRPCBackend(client rpc.Client) bind.ContractBackend { |
||||
return &rpcBackend{ |
||||
client: client, |
||||
} |
||||
} |
||||
|
||||
// request is a JSON RPC request package assembled internally from the client
|
||||
// method calls.
|
||||
type request struct { |
||||
JSONRPC string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
|
||||
ID int `json:"id"` // Auto incrementing ID number for this request
|
||||
Method string `json:"method"` // Remote procedure name to invoke on the server
|
||||
Params []interface{} `json:"params"` // List of parameters to pass through (keep types simple)
|
||||
} |
||||
|
||||
// response is a JSON RPC response package sent back from the API server.
|
||||
type response struct { |
||||
JSONRPC string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
|
||||
ID int `json:"id"` // Auto incrementing ID number for this request
|
||||
Error json.RawMessage `json:"error"` // Any error returned by the remote side
|
||||
Result json.RawMessage `json:"result"` // Whatever the remote side sends us in reply
|
||||
} |
||||
|
||||
// request forwards an API request to the RPC server, and parses the response.
|
||||
//
|
||||
// This is currently painfully non-concurrent, but it will have to do until we
|
||||
// find the time for niceties like this :P
|
||||
func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessage, error) { |
||||
b.lock.Lock() |
||||
defer b.lock.Unlock() |
||||
|
||||
// Ugly hack to serialize an empty list properly
|
||||
if params == nil { |
||||
params = []interface{}{} |
||||
} |
||||
// Assemble the request object
|
||||
req := &request{ |
||||
JSONRPC: "2.0", |
||||
ID: int(atomic.AddUint32(&b.autoid, 1)), |
||||
Method: method, |
||||
Params: params, |
||||
} |
||||
if err := b.client.Send(req); err != nil { |
||||
return nil, err |
||||
} |
||||
res := new(response) |
||||
if err := b.client.Recv(res); err != nil { |
||||
return nil, err |
||||
} |
||||
if len(res.Error) > 0 { |
||||
return nil, fmt.Errorf("remote error: %s", string(res.Error)) |
||||
} |
||||
return res.Result, nil |
||||
} |
||||
|
||||
// ContractCall implements ContractCaller.ContractCall, delegating the execution of
|
||||
// a contract call to the remote node, returning the reply to for local processing.
|
||||
func (b *rpcBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) { |
||||
// Pack up the request into an RPC argument
|
||||
args := struct { |
||||
To common.Address `json:"to"` |
||||
Data string `json:"data"` |
||||
}{ |
||||
To: contract, |
||||
Data: common.ToHex(data), |
||||
} |
||||
// Execute the RPC call and retrieve the response
|
||||
block := "latest" |
||||
if pending { |
||||
block = "pending" |
||||
} |
||||
res, err := b.request("eth_call", []interface{}{args, block}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var hex string |
||||
if err := json.Unmarshal(res, &hex); err != nil { |
||||
return nil, err |
||||
} |
||||
// Convert the response back to a Go byte slice and return
|
||||
return common.FromHex(hex), nil |
||||
} |
||||
|
||||
// PendingAccountNonce implements ContractTransactor.PendingAccountNonce, delegating
|
||||
// the current account nonce retrieval to the remote node.
|
||||
func (b *rpcBackend) PendingAccountNonce(account common.Address) (uint64, error) { |
||||
res, err := b.request("eth_getTransactionCount", []interface{}{account.Hex(), "pending"}) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
var hex string |
||||
if err := json.Unmarshal(res, &hex); err != nil { |
||||
return 0, err |
||||
} |
||||
nonce, ok := new(big.Int).SetString(hex, 0) |
||||
if !ok { |
||||
return 0, fmt.Errorf("invalid nonce hex: %s", hex) |
||||
} |
||||
return nonce.Uint64(), nil |
||||
} |
||||
|
||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice, delegating the
|
||||
// gas price oracle request to the remote node.
|
||||
func (b *rpcBackend) SuggestGasPrice() (*big.Int, error) { |
||||
res, err := b.request("eth_gasPrice", nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var hex string |
||||
if err := json.Unmarshal(res, &hex); err != nil { |
||||
return nil, err |
||||
} |
||||
price, ok := new(big.Int).SetString(hex, 0) |
||||
if !ok { |
||||
return nil, fmt.Errorf("invalid price hex: %s", hex) |
||||
} |
||||
return price, nil |
||||
} |
||||
|
||||
// EstimateGasLimit implements ContractTransactor.EstimateGasLimit, delegating
|
||||
// the gas estimation to the remote node.
|
||||
func (b *rpcBackend) EstimateGasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) { |
||||
// Pack up the request into an RPC argument
|
||||
args := struct { |
||||
From common.Address `json:"from"` |
||||
To *common.Address `json:"to"` |
||||
Value *rpc.HexNumber `json:"value"` |
||||
Data string `json:"data"` |
||||
}{ |
||||
From: sender, |
||||
To: contract, |
||||
Data: common.ToHex(data), |
||||
Value: rpc.NewHexNumber(value), |
||||
} |
||||
// Execute the RPC call and retrieve the response
|
||||
res, err := b.request("eth_estimateGas", []interface{}{args}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var hex string |
||||
if err := json.Unmarshal(res, &hex); err != nil { |
||||
return nil, err |
||||
} |
||||
estimate, ok := new(big.Int).SetString(hex, 0) |
||||
if !ok { |
||||
return nil, fmt.Errorf("invalid estimate hex: %s", hex) |
||||
} |
||||
return estimate, nil |
||||
} |
||||
|
||||
// SendTransaction implements ContractTransactor.SendTransaction, delegating the
|
||||
// raw transaction injection to the remote node.
|
||||
func (b *rpcBackend) SendTransaction(tx *types.Transaction) error { |
||||
data, err := rlp.EncodeToBytes(tx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
res, err := b.request("eth_sendRawTransaction", []interface{}{common.ToHex(data)}) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
var hex string |
||||
if err := json.Unmarshal(res, &hex); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,187 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library 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.
|
||||
//
|
||||
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package backends |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core" |
||||
"github.com/ethereum/go-ethereum/core/state" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
) |
||||
|
||||
// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
|
||||
var _ bind.ContractBackend = (*SimulatedBackend)(nil) |
||||
|
||||
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
|
||||
// the background. Its main purpose is to allow easily testing contract bindings.
|
||||
type SimulatedBackend struct { |
||||
database ethdb.Database // In memory database to store our testing data
|
||||
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
|
||||
|
||||
pendingBlock *types.Block // Currently pending block that will be imported on request
|
||||
pendingState *state.StateDB // Currently pending state that will be the active on on request
|
||||
} |
||||
|
||||
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
||||
// for testing purposes.
|
||||
func NewSimulatedBackend(accounts ...core.GenesisAccount) *SimulatedBackend { |
||||
database, _ := ethdb.NewMemDatabase() |
||||
core.WriteGenesisBlockForTesting(database, accounts...) |
||||
blockchain, _ := core.NewBlockChain(database, new(core.FakePow), new(event.TypeMux)) |
||||
|
||||
backend := &SimulatedBackend{ |
||||
database: database, |
||||
blockchain: blockchain, |
||||
} |
||||
backend.Rollback() |
||||
|
||||
return backend |
||||
} |
||||
|
||||
// Commit imports all the pending transactions as a single block and starts a
|
||||
// fresh new state.
|
||||
func (b *SimulatedBackend) Commit() { |
||||
if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil { |
||||
panic(err) // This cannot happen unless the simulator is wrong, fail in that case
|
||||
} |
||||
b.Rollback() |
||||
} |
||||
|
||||
// Rollback aborts all pending transactions, reverting to the last committed state.
|
||||
func (b *SimulatedBackend) Rollback() { |
||||
blocks, _ := core.GenerateChain(b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {}) |
||||
|
||||
b.pendingBlock = blocks[0] |
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database) |
||||
} |
||||
|
||||
// ContractCall implements ContractCaller.ContractCall, executing the specified
|
||||
// contract with the given input data.
|
||||
func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) { |
||||
// Create a copy of the current state db to screw around with
|
||||
var ( |
||||
block *types.Block |
||||
statedb *state.StateDB |
||||
) |
||||
if pending { |
||||
block, statedb = b.pendingBlock, b.pendingState.Copy() |
||||
} else { |
||||
block = b.blockchain.CurrentBlock() |
||||
statedb, _ = b.blockchain.State() |
||||
} |
||||
// Set infinite balance to the a fake caller account
|
||||
from := statedb.GetOrNewStateObject(common.Address{}) |
||||
from.SetBalance(common.MaxBig) |
||||
|
||||
// Assemble the call invocation to measure the gas usage
|
||||
msg := callmsg{ |
||||
from: from, |
||||
to: &contract, |
||||
gasPrice: new(big.Int), |
||||
gasLimit: common.MaxBig, |
||||
value: new(big.Int), |
||||
data: data, |
||||
} |
||||
// Execute the call and return
|
||||
vmenv := core.NewEnv(statedb, b.blockchain, msg, block.Header(), nil) |
||||
gaspool := new(core.GasPool).AddGas(common.MaxBig) |
||||
|
||||
out, _, err := core.ApplyMessage(vmenv, msg, gaspool) |
||||
return out, err |
||||
} |
||||
|
||||
// PendingAccountNonce implements ContractTransactor.PendingAccountNonce, retrieving
|
||||
// the nonce currently pending for the account.
|
||||
func (b *SimulatedBackend) PendingAccountNonce(account common.Address) (uint64, error) { |
||||
return b.pendingState.GetOrNewStateObject(account).Nonce(), nil |
||||
} |
||||
|
||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
||||
// chain doens't have miners, we just return a gas price of 1 for any call.
|
||||
func (b *SimulatedBackend) SuggestGasPrice() (*big.Int, error) { |
||||
return big.NewInt(1), nil |
||||
} |
||||
|
||||
// EstimateGasLimit implements ContractTransactor.EstimateGasLimit, executing the
|
||||
// requested code against the currently pending block/state and returning the used
|
||||
// gas.
|
||||
func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) { |
||||
// Create a copy of the currently pending state db to screw around with
|
||||
var ( |
||||
block = b.pendingBlock |
||||
statedb = b.pendingState.Copy() |
||||
) |
||||
|
||||
// Set infinite balance to the a fake caller account
|
||||
from := statedb.GetOrNewStateObject(sender) |
||||
from.SetBalance(common.MaxBig) |
||||
|
||||
// Assemble the call invocation to measure the gas usage
|
||||
msg := callmsg{ |
||||
from: from, |
||||
to: contract, |
||||
gasPrice: new(big.Int), |
||||
gasLimit: common.MaxBig, |
||||
value: value, |
||||
data: data, |
||||
} |
||||
// Execute the call and return
|
||||
vmenv := core.NewEnv(statedb, b.blockchain, msg, block.Header(), nil) |
||||
gaspool := new(core.GasPool).AddGas(common.MaxBig) |
||||
|
||||
_, gas, err := core.ApplyMessage(vmenv, msg, gaspool) |
||||
return gas, err |
||||
} |
||||
|
||||
// SendTransaction implements ContractTransactor.SendTransaction, delegating the raw
|
||||
// transaction injection to the remote node.
|
||||
func (b *SimulatedBackend) SendTransaction(tx *types.Transaction) error { |
||||
blocks, _ := core.GenerateChain(b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) { |
||||
for _, tx := range b.pendingBlock.Transactions() { |
||||
block.AddTx(tx) |
||||
} |
||||
block.AddTx(tx) |
||||
}) |
||||
b.pendingBlock = blocks[0] |
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// callmsg implements core.Message to allow passing it as a transaction simulator.
|
||||
type callmsg struct { |
||||
from *state.StateObject |
||||
to *common.Address |
||||
gasLimit *big.Int |
||||
gasPrice *big.Int |
||||
value *big.Int |
||||
data []byte |
||||
} |
||||
|
||||
func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } |
||||
func (m callmsg) FromFrontier() (common.Address, error) { return m.from.Address(), nil } |
||||
func (m callmsg) Nonce() uint64 { return m.from.Nonce() } |
||||
func (m callmsg) To() *common.Address { return m.to } |
||||
func (m callmsg) GasPrice() *big.Int { return m.gasPrice } |
||||
func (m callmsg) Gas() *big.Int { return m.gasLimit } |
||||
func (m callmsg) Value() *big.Int { return m.value } |
||||
func (m callmsg) Data() []byte { return m.data } |
@ -0,0 +1,174 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library 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.
|
||||
//
|
||||
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
) |
||||
|
||||
// SignerFn is a signer function callback when a contract requires a method to
|
||||
// sign the transaction before submission.
|
||||
type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) |
||||
|
||||
// CallOpts is the collection of options to fine tune a contract call request.
|
||||
type CallOpts struct { |
||||
Pending bool // Whether to operate on the pending state or the last known one
|
||||
} |
||||
|
||||
// TransactOpts is the collection of authorization data required to create a
|
||||
// valid Ethereum transaction.
|
||||
type TransactOpts struct { |
||||
From common.Address // Ethereum account to send the transaction from
|
||||
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
|
||||
Signer SignerFn // Method to use for signing the transaction (mandatory)
|
||||
|
||||
Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds)
|
||||
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
|
||||
GasLimit *big.Int // Gas limit to set for the transaction execution (nil = estimate + 10%)
|
||||
} |
||||
|
||||
// BoundContract is the base wrapper object that reflects a contract on the
|
||||
// Ethereum network. It contains a collection of methods that are used by the
|
||||
// higher level contract bindings to operate.
|
||||
type BoundContract struct { |
||||
address common.Address // Deployment address of the contract on the Ethereum blockchain
|
||||
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
|
||||
caller ContractCaller // Read interface to interact with the blockchain
|
||||
transactor ContractTransactor // Write interface to interact with the blockchain
|
||||
} |
||||
|
||||
// NewBoundContract creates a low level contract interface through which calls
|
||||
// and transactions may be made through.
|
||||
func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor) *BoundContract { |
||||
return &BoundContract{ |
||||
address: address, |
||||
abi: abi, |
||||
caller: caller, |
||||
transactor: transactor, |
||||
} |
||||
} |
||||
|
||||
// DeployContract deploys a contract onto the Ethereum blockchain and binds the
|
||||
// deployment address with a Go wrapper.
|
||||
func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) { |
||||
// Otherwise try to deploy the contract
|
||||
c := NewBoundContract(common.Address{}, abi, backend, backend) |
||||
|
||||
input, err := c.abi.Pack("", params...) |
||||
if err != nil { |
||||
return common.Address{}, nil, nil, err |
||||
} |
||||
tx, err := c.transact(opts, nil, append(bytecode, input...)) |
||||
if err != nil { |
||||
return common.Address{}, nil, nil, err |
||||
} |
||||
c.address = crypto.CreateAddress(opts.From, tx.Nonce()) |
||||
return c.address, tx, c, nil |
||||
} |
||||
|
||||
// Call invokes the (constant) contract method with params as input values and
|
||||
// sets the output to result. The result type might be a single field for simple
|
||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||
// returns.
|
||||
func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error { |
||||
// Don't crash on a lazy user
|
||||
if opts == nil { |
||||
opts = new(CallOpts) |
||||
} |
||||
// Pack the input, call and unpack the results
|
||||
input, err := c.abi.Pack(method, params...) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
output, err := c.caller.ContractCall(c.address, input, opts.Pending) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return c.abi.Unpack(result, method, output) |
||||
} |
||||
|
||||
// Transact invokes the (paid) contract method with params as input values and
|
||||
// value as the fund transfer to the contract.
|
||||
func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { |
||||
// Otherwise pack up the parameters and invoke the contract
|
||||
input, err := c.abi.Pack(method, params...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return c.transact(opts, &c.address, input) |
||||
} |
||||
|
||||
// transact executes an actual transaction invocation, first deriving any missing
|
||||
// authorization fields, and then scheduling the transaction for execution.
|
||||
func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) { |
||||
var err error |
||||
|
||||
// Ensure a valid value field and resolve the account nonce
|
||||
value := opts.Value |
||||
if value == nil { |
||||
value = new(big.Int) |
||||
} |
||||
nonce := uint64(0) |
||||
if opts.Nonce == nil { |
||||
nonce, err = c.transactor.PendingAccountNonce(opts.From) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to retrieve account nonce: %v", err) |
||||
} |
||||
} else { |
||||
nonce = opts.Nonce.Uint64() |
||||
} |
||||
// Figure out the gas allowance and gas price values
|
||||
gasPrice := opts.GasPrice |
||||
if gasPrice == nil { |
||||
gasPrice, err = c.transactor.SuggestGasPrice() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to suggest gas price: %v", err) |
||||
} |
||||
} |
||||
gasLimit := opts.GasLimit |
||||
if gasLimit == nil { |
||||
gasLimit, err = c.transactor.EstimateGasLimit(opts.From, contract, value, input) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to exstimate gas needed: %v", err) |
||||
} |
||||
} |
||||
// Create the transaction, sign it and schedule it for execution
|
||||
var rawTx *types.Transaction |
||||
if contract == nil { |
||||
rawTx = types.NewContractCreation(nonce, value, gasLimit, gasPrice, input) |
||||
} else { |
||||
rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input) |
||||
} |
||||
if opts.Signer == nil { |
||||
return nil, errors.New("no signer to authorize the transaction with") |
||||
} |
||||
signedTx, err := opts.Signer(opts.From, rawTx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if err := c.transactor.SendTransaction(signedTx); err != nil { |
||||
return nil, err |
||||
} |
||||
return signedTx, nil |
||||
} |
@ -0,0 +1,173 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library 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.
|
||||
//
|
||||
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package bind generates Ethereum contract Go bindings.
|
||||
//
|
||||
// Detailed usage document and tutorial available on the go-ethereum Wiki page:
|
||||
// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts
|
||||
package bind |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"strings" |
||||
"text/template" |
||||
"unicode" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi" |
||||
"golang.org/x/tools/imports" |
||||
) |
||||
|
||||
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
||||
// to be used as is in client code, but rather as an intermediate struct which
|
||||
// enforces compile time type safety and naming convention opposed to having to
|
||||
// manually maintain hard coded strings that break on runtime.
|
||||
func Bind(types []string, abis []string, bytecodes []string, pkg string) (string, error) { |
||||
// Process each individual contract requested binding
|
||||
contracts := make(map[string]*tmplContract) |
||||
|
||||
for i := 0; i < len(types); i++ { |
||||
// Parse the actual ABI to generate the binding for
|
||||
evmABI, err := abi.JSON(strings.NewReader(abis[i])) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
// Strip any whitespace from the JSON ABI
|
||||
strippedABI := strings.Map(func(r rune) rune { |
||||
if unicode.IsSpace(r) { |
||||
return -1 |
||||
} |
||||
return r |
||||
}, abis[i]) |
||||
|
||||
// Extract the call and transact methods, and sort them alphabetically
|
||||
var ( |
||||
calls = make(map[string]*tmplMethod) |
||||
transacts = make(map[string]*tmplMethod) |
||||
) |
||||
for _, original := range evmABI.Methods { |
||||
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
||||
normalized := original |
||||
normalized.Name = capitalise(original.Name) |
||||
|
||||
normalized.Inputs = make([]abi.Argument, len(original.Inputs)) |
||||
copy(normalized.Inputs, original.Inputs) |
||||
for j, input := range normalized.Inputs { |
||||
if input.Name == "" { |
||||
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) |
||||
} |
||||
} |
||||
normalized.Outputs = make([]abi.Argument, len(original.Outputs)) |
||||
copy(normalized.Outputs, original.Outputs) |
||||
for j, output := range normalized.Outputs { |
||||
if output.Name != "" { |
||||
normalized.Outputs[j].Name = capitalise(output.Name) |
||||
} |
||||
} |
||||
// Append the methos to the call or transact lists
|
||||
if original.Const { |
||||
calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} |
||||
} else { |
||||
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} |
||||
} |
||||
} |
||||
contracts[types[i]] = &tmplContract{ |
||||
Type: capitalise(types[i]), |
||||
InputABI: strippedABI, |
||||
InputBin: strings.TrimSpace(bytecodes[i]), |
||||
Constructor: evmABI.Constructor, |
||||
Calls: calls, |
||||
Transacts: transacts, |
||||
} |
||||
} |
||||
// Generate the contract template data content and render it
|
||||
data := &tmplData{ |
||||
Package: pkg, |
||||
Contracts: contracts, |
||||
} |
||||
buffer := new(bytes.Buffer) |
||||
|
||||
funcs := map[string]interface{}{ |
||||
"bindtype": bindType, |
||||
} |
||||
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource)) |
||||
if err := tmpl.Execute(buffer, data); err != nil { |
||||
return "", err |
||||
} |
||||
// Pass the code through goimports to clean it up and double check
|
||||
code, err := imports.Process("", buffer.Bytes(), nil) |
||||
if err != nil { |
||||
return "", fmt.Errorf("%v\n%s", err, buffer) |
||||
} |
||||
return string(code), nil |
||||
} |
||||
|
||||
// bindType converts a Solidity type to a Go one. Since there is no clear mapping
|
||||
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
|
||||
// mapped will use an upscaled type (e.g. *big.Int).
|
||||
func bindType(kind abi.Type) string { |
||||
stringKind := kind.String() |
||||
|
||||
switch { |
||||
case stringKind == "address": |
||||
return "common.Address" |
||||
|
||||
case stringKind == "hash": |
||||
return "common.Hash" |
||||
|
||||
case strings.HasPrefix(stringKind, "bytes"): |
||||
if stringKind == "bytes" { |
||||
return "[]byte" |
||||
} |
||||
return fmt.Sprintf("[%s]byte", stringKind[5:]) |
||||
|
||||
case strings.HasPrefix(stringKind, "int"): |
||||
switch stringKind[:3] { |
||||
case "8", "16", "32", "64": |
||||
return stringKind |
||||
} |
||||
return "*big.Int" |
||||
|
||||
case strings.HasPrefix(stringKind, "uint"): |
||||
switch stringKind[:4] { |
||||
case "8", "16", "32", "64": |
||||
return stringKind |
||||
} |
||||
return "*big.Int" |
||||
|
||||
default: |
||||
return stringKind |
||||
} |
||||
} |
||||
|
||||
// capitalise makes the first character of a string upper case.
|
||||
func capitalise(input string) string { |
||||
return strings.ToUpper(input[:1]) + input[1:] |
||||
} |
||||
|
||||
// structured checks whether a method has enough information to return a proper
|
||||
// Go struct ot if flat returns are needed.
|
||||
func structured(method abi.Method) bool { |
||||
if len(method.Outputs) < 2 { |
||||
return false |
||||
} |
||||
for _, out := range method.Outputs { |
||||
if out.Name == "" { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,212 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library 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.
|
||||
//
|
||||
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind |
||||
|
||||
import "github.com/ethereum/go-ethereum/accounts/abi" |
||||
|
||||
// tmplData is the data structure required to fill the binding template.
|
||||
type tmplData struct { |
||||
Package string // Name of the package to place the generated file in
|
||||
Contracts map[string]*tmplContract // List of contracts to generate into this file
|
||||
} |
||||
|
||||
// tmplContract contains the data needed to generate an individual contract binding.
|
||||
type tmplContract struct { |
||||
Type string // Type name of the main contract binding
|
||||
InputABI string // JSON ABI used as the input to generate the binding from
|
||||
InputBin string // Optional EVM bytecode used to denetare deploy code from
|
||||
Constructor abi.Method // Contract constructor for deploy parametrization
|
||||
Calls map[string]*tmplMethod // Contract calls that only read state data
|
||||
Transacts map[string]*tmplMethod // Contract calls that write state data
|
||||
} |
||||
|
||||
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
||||
// and cached data fields.
|
||||
type tmplMethod struct { |
||||
Original abi.Method // Original method as parsed by the abi package
|
||||
Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns)
|
||||
Structured bool // Whether the returns should be accumulated into a contract
|
||||
} |
||||
|
||||
// tmplSource is the Go source template use to generate the contract binding
|
||||
// based on.
|
||||
const tmplSource = ` |
||||
// This file is an automatically generated Go binding. Do not modify as any
|
||||
// change will likely be lost upon the next re-generation!
|
||||
|
||||
package {{.Package}} |
||||
|
||||
{{range $contract := .Contracts}} |
||||
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
||||
const {{.Type}}ABI = ` + "`" + `{{.InputABI}}` + "`" + ` |
||||
|
||||
{{if .InputBin}} |
||||
// {{.Type}}Bin is the compiled bytecode used for deploying new contracts.
|
||||
const {{.Type}}Bin = ` + "`" + `{{.InputBin}}` + "`" + ` |
||||
|
||||
// Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
||||
func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) { |
||||
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI)) |
||||
if err != nil { |
||||
return common.Address{}, nil, nil, err |
||||
} |
||||
address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}}) |
||||
if err != nil { |
||||
return common.Address{}, nil, nil, err |
||||
} |
||||
return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract} }, nil |
||||
} |
||||
{{end}} |
||||
|
||||
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
||||
type {{.Type}} struct { |
||||
{{.Type}}Caller // Read-only binding to the contract
|
||||
{{.Type}}Transactor // Write-only binding to the contract
|
||||
} |
||||
|
||||
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
|
||||
type {{.Type}}Caller struct { |
||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||
} |
||||
|
||||
// {{.Type}}Transactor is an auto generated write-only Go binding around an Ethereum contract.
|
||||
type {{.Type}}Transactor struct { |
||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||
} |
||||
|
||||
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
|
||||
// with pre-set call and transact options.
|
||||
type {{.Type}}Session struct { |
||||
Contract *{{.Type}} // Generic contract binding to set the session for
|
||||
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||
} |
||||
|
||||
// {{.Type}}CallerSession is an auto generated read-only Go binding around an Ethereum contract,
|
||||
// with pre-set call options.
|
||||
type {{.Type}}CallerSession struct { |
||||
Contract *{{.Type}}Caller // Generic contract caller binding to set the session for
|
||||
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||
} |
||||
|
||||
// {{.Type}}TransactorSession is an auto generated write-only Go binding around an Ethereum contract,
|
||||
// with pre-set transact options.
|
||||
type {{.Type}}TransactorSession struct { |
||||
Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for
|
||||
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||
} |
||||
|
||||
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) { |
||||
contract, err := bind{{.Type}}(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract} }, nil |
||||
} |
||||
|
||||
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) { |
||||
contract, err := bind{{.Type}}(address, caller, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &{{.Type}}Caller{contract: contract}, nil |
||||
} |
||||
|
||||
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) { |
||||
contract, err := bind{{.Type}}(address, nil, transactor) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &{{.Type}}Transactor{contract: contract}, nil |
||||
} |
||||
|
||||
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
|
||||
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { |
||||
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return bind.NewBoundContract(address, parsed, caller, transactor), nil |
||||
} |
||||
|
||||
{{range .Calls}} |
||||
{{if .Structured}} |
||||
// {{.Normalized.Name}}Result is the result of the {{.Normalized.Name}} invocation."
|
||||
type {{.Normalized.Name}}Result struct { |
||||
{{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}} |
||||
{{end}} |
||||
} |
||||
{{end}} |
||||
|
||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}*{{.Normalized.Name}}Result,{{else}}{{range .Normalized.Outputs}}{{bindtype .Type}},{{end}}{{end}} error) { |
||||
var ( |
||||
{{if .Structured}}ret = new(*{{.Normalized.Name}}Result){{else}}{{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type}}) |
||||
{{end}}{{end}} |
||||
) |
||||
out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}[]interface{}{ |
||||
{{range $i, $_ := .Normalized.Outputs}}ret{{$i}}, |
||||
{{end}} |
||||
}{{end}}{{end}} |
||||
err := _{{$contract.Type}}.contract.Call(opts, out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) |
||||
return {{if .Structured}}*ret,{{else}}{{range $i, $_ := .Normalized.Outputs}}*ret{{$i}},{{end}}{{end}} err |
||||
} |
||||
|
||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}*{{.Normalized.Name}}Result, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type}},{{end}} {{end}} error) { |
||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) |
||||
} |
||||
|
||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}*{{.Normalized.Name}}Result, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type}},{{end}} {{end}} error) { |
||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) |
||||
} |
||||
{{end}} |
||||
|
||||
{{range .Transacts}} |
||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) { |
||||
return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) |
||||
} |
||||
|
||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) { |
||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) |
||||
} |
||||
|
||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) { |
||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) |
||||
} |
||||
{{end}} |
||||
{{end}} |
||||
` |
@ -0,0 +1,124 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library 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.
|
||||
//
|
||||
// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"flag" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||
"github.com/ethereum/go-ethereum/common/compiler" |
||||
) |
||||
|
||||
var ( |
||||
abiFlag = flag.String("abi", "", "Path to the Ethereum contract ABI json to bind") |
||||
binFlag = flag.String("bin", "", "Path to the Ethereum contract bytecode (generate deploy method)") |
||||
typFlag = flag.String("type", "", "Go struct name for the binding (default = package name)") |
||||
|
||||
solFlag = flag.String("sol", "", "Path to the Ethereum contract Solidity source to build and bind") |
||||
solcFlag = flag.String("solc", "solc", "Solidity compiler to use if source builds are requested") |
||||
|
||||
pkgFlag = flag.String("pkg", "", "Go package name to generate the binding into") |
||||
outFlag = flag.String("out", "", "Output file for the generated binding (default = stdout)") |
||||
) |
||||
|
||||
func main() { |
||||
// Parse and ensure all needed inputs are specified
|
||||
flag.Parse() |
||||
|
||||
if *abiFlag == "" && *solFlag == "" { |
||||
fmt.Printf("No contract ABI (--abi) or Solidity source (--sol) specified\n") |
||||
os.Exit(-1) |
||||
} else if (*abiFlag != "" || *binFlag != "" || *typFlag != "") && *solFlag != "" { |
||||
fmt.Printf("Contract ABI (--abi), bytecode (--bin) and type (--type) flags are mutually exclusive with the Solidity source (--sol) flag\n") |
||||
os.Exit(-1) |
||||
} |
||||
if *pkgFlag == "" { |
||||
fmt.Printf("No destination Go package specified (--pkg)\n") |
||||
os.Exit(-1) |
||||
} |
||||
// If the entire solidity code was specified, build and bind based on that
|
||||
var ( |
||||
abis []string |
||||
bins []string |
||||
types []string |
||||
) |
||||
if *solFlag != "" { |
||||
solc, err := compiler.New(*solcFlag) |
||||
if err != nil { |
||||
fmt.Printf("Failed to locate Solidity compiler: %v\n", err) |
||||
os.Exit(-1) |
||||
} |
||||
source, err := ioutil.ReadFile(*solFlag) |
||||
if err != nil { |
||||
fmt.Printf("Failed to read Soldity source code: %v\n", err) |
||||
os.Exit(-1) |
||||
} |
||||
contracts, err := solc.Compile(string(source)) |
||||
if err != nil { |
||||
fmt.Printf("Failed to build Solidity contract: %v\n", err) |
||||
os.Exit(-1) |
||||
} |
||||
for name, contract := range contracts { |
||||
abi, _ := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse
|
||||
abis = append(abis, string(abi)) |
||||
bins = append(bins, contract.Code) |
||||
types = append(types, name) |
||||
} |
||||
} else { |
||||
// Otherwise load up the ABI, optional bytecode and type name from the parameters
|
||||
abi, err := ioutil.ReadFile(*abiFlag) |
||||
if err != nil { |
||||
fmt.Printf("Failed to read input ABI: %v\n", err) |
||||
os.Exit(-1) |
||||
} |
||||
abis = append(abis, string(abi)) |
||||
|
||||
bin := []byte{} |
||||
if *binFlag != "" { |
||||
if bin, err = ioutil.ReadFile(*binFlag); err != nil { |
||||
fmt.Printf("Failed to read input bytecode: %v\n", err) |
||||
os.Exit(-1) |
||||
} |
||||
} |
||||
bins = append(bins, string(bin)) |
||||
|
||||
kind := *typFlag |
||||
if kind == "" { |
||||
kind = *pkgFlag |
||||
} |
||||
types = append(types, kind) |
||||
} |
||||
// Generate the contract binding
|
||||
code, err := bind.Bind(types, abis, bins, *pkgFlag) |
||||
if err != nil { |
||||
fmt.Printf("Failed to generate ABI binding: %v\n", err) |
||||
os.Exit(-1) |
||||
} |
||||
// Either flush it out to a file or display on the standard output
|
||||
if *outFlag == "" { |
||||
fmt.Printf("%s\n", code) |
||||
return |
||||
} |
||||
if err := ioutil.WriteFile(*outFlag, []byte(code), 0600); err != nil { |
||||
fmt.Printf("Failed to write ABI binding: %v\n", err) |
||||
os.Exit(-1) |
||||
} |
||||
} |
Loading…
Reference in new issue