Browse code

Enhance pluginrpc-gen parser

Now handles `package.Type` and `*package.Type`
Fixes parsing issues with slice and map types.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2016/04/10 06:42:24
Showing 9 changed files
... ...
@@ -43,16 +43,6 @@ supplying `--tag`. This flag can be specified multiple times.
43 43
 
44 44
 ## Known issues
45 45
 
46
-The parser can currently only handle types which are not specifically a map or
47
-a slice.  
48
-You can, however, create a type that uses a map or a slice internally, for instance:
49
-
50
-```go
51
-type opts map[string]string
52
-```
53
-
54
-This `opts` type will work, whreas using a `map[string]string` directly will not.
55
-
56 46
 ## go-generate
57 47
 
58 48
 You can also use this with go-generate, which is pretty awesome.  
... ...
@@ -1,5 +1,17 @@
1 1
 package foo
2 2
 
3
+import (
4
+	"fmt"
5
+
6
+	aliasedio "io"
7
+
8
+	"github.com/docker/docker/pkg/plugins/pluginrpc-gen/fixtures/otherfixture"
9
+)
10
+
11
+var (
12
+	errFakeImport = fmt.Errorf("just to import fmt for imports tests")
13
+)
14
+
3 15
 type wobble struct {
4 16
 	Some      string
5 17
 	Val       string
... ...
@@ -22,6 +34,7 @@ type Fooer3 interface {
22 22
 	Qux(a, b string) (val string, err error)
23 23
 	Wobble() (w *wobble)
24 24
 	Wiggle() (w wobble)
25
+	WiggleWobble(a []*wobble, b []wobble, c map[string]*wobble, d map[*wobble]wobble, e map[string][]wobble, f []*otherfixture.Spaceship) (g map[*wobble]wobble, h [][]*wobble, i otherfixture.Spaceship, j *otherfixture.Spaceship, k map[*otherfixture.Spaceship]otherfixture.Spaceship, l []otherfixture.Spaceship)
25 26
 }
26 27
 
27 28
 // Fooer4 is an interface used for tests.
... ...
@@ -39,3 +52,38 @@ type Fooer5 interface {
39 39
 	Foo()
40 40
 	Bar
41 41
 }
42
+
43
+// Fooer6 is an interface used for tests.
44
+type Fooer6 interface {
45
+	Foo(a otherfixture.Spaceship)
46
+}
47
+
48
+// Fooer7 is an interface used for tests.
49
+type Fooer7 interface {
50
+	Foo(a *otherfixture.Spaceship)
51
+}
52
+
53
+// Fooer8 is an interface used for tests.
54
+type Fooer8 interface {
55
+	Foo(a map[string]otherfixture.Spaceship)
56
+}
57
+
58
+// Fooer9 is an interface used for tests.
59
+type Fooer9 interface {
60
+	Foo(a map[string]*otherfixture.Spaceship)
61
+}
62
+
63
+// Fooer10 is an interface used for tests.
64
+type Fooer10 interface {
65
+	Foo(a []otherfixture.Spaceship)
66
+}
67
+
68
+// Fooer11 is an interface used for tests.
69
+type Fooer11 interface {
70
+	Foo(a []*otherfixture.Spaceship)
71
+}
72
+
73
+// Fooer12 is an interface used for tests.
74
+type Fooer12 interface {
75
+	Foo(a aliasedio.Reader)
76
+}
42 77
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+package otherfixture
1
+
2
+// Spaceship is a fixture for tests
3
+type Spaceship struct{}
... ...
@@ -78,7 +78,7 @@ func main() {
78 78
 
79 79
 	errorOut("parser error", generatedTempl.Execute(&buf, analysis))
80 80
 	src, err := format.Source(buf.Bytes())
81
-	errorOut("error formating generated source", err)
81
+	errorOut("error formating generated source:\n"+buf.String(), err)
82 82
 	errorOut("error writing file", ioutil.WriteFile(*outputFile, src, 0644))
83 83
 }
84 84
 
... ...
@@ -6,7 +6,9 @@ import (
6 6
 	"go/ast"
7 7
 	"go/parser"
8 8
 	"go/token"
9
+	"path"
9 10
 	"reflect"
11
+	"strings"
10 12
 )
11 13
 
12 14
 var errBadReturn = errors.New("found return arg with no name: all args must be named")
... ...
@@ -25,6 +27,7 @@ func (e errUnexpectedType) Error() string {
25 25
 type ParsedPkg struct {
26 26
 	Name      string
27 27
 	Functions []function
28
+	Imports   []importSpec
28 29
 }
29 30
 
30 31
 type function struct {
... ...
@@ -35,14 +38,29 @@ type function struct {
35 35
 }
36 36
 
37 37
 type arg struct {
38
-	Name    string
39
-	ArgType string
38
+	Name            string
39
+	ArgType         string
40
+	PackageSelector string
40 41
 }
41 42
 
42 43
 func (a *arg) String() string {
43 44
 	return a.Name + " " + a.ArgType
44 45
 }
45 46
 
47
+type importSpec struct {
48
+	Name string
49
+	Path string
50
+}
51
+
52
+func (s *importSpec) String() string {
53
+	var ss string
54
+	if len(s.Name) != 0 {
55
+		ss += s.Name
56
+	}
57
+	ss += s.Path
58
+	return ss
59
+}
60
+
46 61
 // Parse parses the given file for an interface definition with the given name.
47 62
 func Parse(filePath string, objName string) (*ParsedPkg, error) {
48 63
 	fs := token.NewFileSet()
... ...
@@ -73,6 +91,44 @@ func Parse(filePath string, objName string) (*ParsedPkg, error) {
73 73
 		return nil, err
74 74
 	}
75 75
 
76
+	// figure out what imports will be needed
77
+	imports := make(map[string]importSpec)
78
+	for _, f := range p.Functions {
79
+		args := append(f.Args, f.Returns...)
80
+		for _, arg := range args {
81
+			if len(arg.PackageSelector) == 0 {
82
+				continue
83
+			}
84
+
85
+			for _, i := range pkg.Imports {
86
+				if i.Name != nil {
87
+					if i.Name.Name != arg.PackageSelector {
88
+						continue
89
+					}
90
+					imports[i.Path.Value] = importSpec{Name: arg.PackageSelector, Path: i.Path.Value}
91
+					break
92
+				}
93
+
94
+				_, name := path.Split(i.Path.Value)
95
+				splitName := strings.Split(name, "-")
96
+				if len(splitName) > 1 {
97
+					name = splitName[len(splitName)-1]
98
+				}
99
+				// import paths have quotes already added in, so need to remove them for name comparison
100
+				name = strings.TrimPrefix(name, `"`)
101
+				name = strings.TrimSuffix(name, `"`)
102
+				if name == arg.PackageSelector {
103
+					imports[i.Path.Value] = importSpec{Path: i.Path.Value}
104
+					break
105
+				}
106
+			}
107
+		}
108
+	}
109
+
110
+	for _, spec := range imports {
111
+		p.Imports = append(p.Imports, spec)
112
+	}
113
+
76 114
 	return p, nil
77 115
 }
78 116
 
... ...
@@ -142,22 +198,66 @@ func parseArgs(fields []*ast.Field) ([]arg, error) {
142 142
 			return nil, errBadReturn
143 143
 		}
144 144
 		for _, name := range f.Names {
145
-			var typeName string
146
-			switch argType := f.Type.(type) {
147
-			case *ast.Ident:
148
-				typeName = argType.Name
149
-			case *ast.StarExpr:
150
-				i, ok := argType.X.(*ast.Ident)
151
-				if !ok {
152
-					return nil, errUnexpectedType{"*ast.Ident", f.Type}
153
-				}
154
-				typeName = "*" + i.Name
155
-			default:
156
-				return nil, errUnexpectedType{"*ast.Ident or *ast.StarExpr", f.Type}
145
+			p, err := parseExpr(f.Type)
146
+			if err != nil {
147
+				return nil, err
157 148
 			}
158
-
159
-			args = append(args, arg{name.Name, typeName})
149
+			args = append(args, arg{name.Name, p.value, p.pkg})
160 150
 		}
161 151
 	}
162 152
 	return args, nil
163 153
 }
154
+
155
+type parsedExpr struct {
156
+	value string
157
+	pkg   string
158
+}
159
+
160
+func parseExpr(e ast.Expr) (parsedExpr, error) {
161
+	var parsed parsedExpr
162
+	switch i := e.(type) {
163
+	case *ast.Ident:
164
+		parsed.value += i.Name
165
+	case *ast.StarExpr:
166
+		p, err := parseExpr(i.X)
167
+		if err != nil {
168
+			return parsed, err
169
+		}
170
+		parsed.value += "*"
171
+		parsed.value += p.value
172
+		parsed.pkg = p.pkg
173
+	case *ast.SelectorExpr:
174
+		p, err := parseExpr(i.X)
175
+		if err != nil {
176
+			return parsed, err
177
+		}
178
+		parsed.pkg = p.value
179
+		parsed.value += p.value + "."
180
+		parsed.value += i.Sel.Name
181
+	case *ast.MapType:
182
+		parsed.value += "map["
183
+		p, err := parseExpr(i.Key)
184
+		if err != nil {
185
+			return parsed, err
186
+		}
187
+		parsed.value += p.value
188
+		parsed.value += "]"
189
+		p, err = parseExpr(i.Value)
190
+		if err != nil {
191
+			return parsed, err
192
+		}
193
+		parsed.value += p.value
194
+		parsed.pkg = p.pkg
195
+	case *ast.ArrayType:
196
+		parsed.value += "[]"
197
+		p, err := parseExpr(i.Elt)
198
+		if err != nil {
199
+			return parsed, err
200
+		}
201
+		parsed.value += p.value
202
+		parsed.pkg = p.pkg
203
+	default:
204
+		return parsed, errUnexpectedType{"*ast.Ident or *ast.StarExpr", i}
205
+	}
206
+	return parsed, nil
207
+}
... ...
@@ -47,7 +47,7 @@ func TestParseWithMultipleFuncs(t *testing.T) {
47 47
 	}
48 48
 
49 49
 	assertName(t, "foo", pkg.Name)
50
-	assertNum(t, 6, len(pkg.Functions))
50
+	assertNum(t, 7, len(pkg.Functions))
51 51
 
52 52
 	f := pkg.Functions[0]
53 53
 	assertName(t, "Foo", f.Name)
... ...
@@ -105,6 +105,35 @@ func TestParseWithMultipleFuncs(t *testing.T) {
105 105
 	arg = f.Returns[0]
106 106
 	assertName(t, "w", arg.Name)
107 107
 	assertName(t, "wobble", arg.ArgType)
108
+
109
+	f = pkg.Functions[6]
110
+	assertName(t, "WiggleWobble", f.Name)
111
+	assertNum(t, 6, len(f.Args))
112
+	assertNum(t, 6, len(f.Returns))
113
+	expectedArgs := [][]string{
114
+		{"a", "[]*wobble"},
115
+		{"b", "[]wobble"},
116
+		{"c", "map[string]*wobble"},
117
+		{"d", "map[*wobble]wobble"},
118
+		{"e", "map[string][]wobble"},
119
+		{"f", "[]*otherfixture.Spaceship"},
120
+	}
121
+	for i, arg := range f.Args {
122
+		assertName(t, expectedArgs[i][0], arg.Name)
123
+		assertName(t, expectedArgs[i][1], arg.ArgType)
124
+	}
125
+	expectedReturns := [][]string{
126
+		{"g", "map[*wobble]wobble"},
127
+		{"h", "[][]*wobble"},
128
+		{"i", "otherfixture.Spaceship"},
129
+		{"j", "*otherfixture.Spaceship"},
130
+		{"k", "map[*otherfixture.Spaceship]otherfixture.Spaceship"},
131
+		{"l", "[]otherfixture.Spaceship"},
132
+	}
133
+	for i, ret := range f.Returns {
134
+		assertName(t, expectedReturns[i][0], ret.Name)
135
+		assertName(t, expectedReturns[i][1], ret.ArgType)
136
+	}
108 137
 }
109 138
 
110 139
 func TestParseWithUnamedReturn(t *testing.T) {
... ...
@@ -150,6 +179,31 @@ func TestEmbeddedInterface(t *testing.T) {
150 150
 	assertName(t, "error", arg.ArgType)
151 151
 }
152 152
 
153
+func TestParsedImports(t *testing.T) {
154
+	cases := []string{"Fooer6", "Fooer7", "Fooer8", "Fooer9", "Fooer10", "Fooer11"}
155
+	for _, testCase := range cases {
156
+		pkg, err := Parse(testFixture, testCase)
157
+		if err != nil {
158
+			t.Fatal(err)
159
+		}
160
+
161
+		assertNum(t, 1, len(pkg.Imports))
162
+		importPath := strings.Split(pkg.Imports[0].Path, "/")
163
+		assertName(t, "otherfixture\"", importPath[len(importPath)-1])
164
+		assertName(t, "", pkg.Imports[0].Name)
165
+	}
166
+}
167
+
168
+func TestAliasedImports(t *testing.T) {
169
+	pkg, err := Parse(testFixture, "Fooer12")
170
+	if err != nil {
171
+		t.Fatal(err)
172
+	}
173
+
174
+	assertNum(t, 1, len(pkg.Imports))
175
+	assertName(t, "aliasedio", pkg.Imports[0].Name)
176
+}
177
+
153 178
 func assertName(t *testing.T, expected, actual string) {
154 179
 	if expected != actual {
155 180
 		fatalOut(t, fmt.Sprintf("expected name to be `%s`, got: %s", expected, actual))
... ...
@@ -13,6 +13,19 @@ func printArgs(args []arg) string {
13 13
 	return strings.Join(argStr, ", ")
14 14
 }
15 15
 
16
+func buildImports(specs []importSpec) string {
17
+	if len(specs) == 0 {
18
+		return `import "errors"`
19
+	}
20
+	imports := "import(\n"
21
+	imports += "\t\"errors\"\n"
22
+	for _, i := range specs {
23
+		imports += "\t" + i.String() + "\n"
24
+	}
25
+	imports += ")"
26
+	return imports
27
+}
28
+
16 29
 func marshalType(t string) string {
17 30
 	switch t {
18 31
 	case "error":
... ...
@@ -44,6 +57,7 @@ var templFuncs = template.FuncMap{
44 44
 	"lower":       strings.ToLower,
45 45
 	"title":       title,
46 46
 	"tag":         buildTag,
47
+	"imports":     buildImports,
47 48
 }
48 49
 
49 50
 func title(s string) string {
... ...
@@ -60,7 +74,7 @@ var generatedTempl = template.Must(template.New("rpc_cient").Funcs(templFuncs).P
60 60
 
61 61
 package {{ .Name }}
62 62
 
63
-import "errors"
63
+{{ imports .Imports }}
64 64
 
65 65
 type client interface{
66 66
 	Call(string, interface{}, interface{}) error
... ...
@@ -24,15 +24,12 @@ func NewVolumeDriver(name string, c client) volume.Driver {
24 24
 	return &volumeDriverAdapter{name: name, proxy: proxy}
25 25
 }
26 26
 
27
-type opts map[string]string
28
-type list []*proxyVolume
29
-
30 27
 // volumeDriver defines the available functions that volume plugins must implement.
31 28
 // This interface is only defined to generate the proxy objects.
32 29
 // It's not intended to be public or reused.
33 30
 type volumeDriver interface {
34 31
 	// Create a volume with the given name
35
-	Create(name string, opts opts) (err error)
32
+	Create(name string, opts map[string]string) (err error)
36 33
 	// Remove the volume with the given name
37 34
 	Remove(name string) (err error)
38 35
 	// Get the mountpoint of the given volume
... ...
@@ -42,7 +39,7 @@ type volumeDriver interface {
42 42
 	// Unmount the given volume
43 43
 	Unmount(name, id string) (err error)
44 44
 	// List lists all the volumes known to the driver
45
-	List() (volumes list, err error)
45
+	List() (volumes []*proxyVolume, err error)
46 46
 	// Get retrieves the volume with the requested name
47 47
 	Get(name string) (volume *proxyVolume, err error)
48 48
 }
... ...
@@ -14,14 +14,14 @@ type volumeDriverProxy struct {
14 14
 
15 15
 type volumeDriverProxyCreateRequest struct {
16 16
 	Name string
17
-	Opts opts
17
+	Opts map[string]string
18 18
 }
19 19
 
20 20
 type volumeDriverProxyCreateResponse struct {
21 21
 	Err string
22 22
 }
23 23
 
24
-func (pp *volumeDriverProxy) Create(name string, opts opts) (err error) {
24
+func (pp *volumeDriverProxy) Create(name string, opts map[string]string) (err error) {
25 25
 	var (
26 26
 		req volumeDriverProxyCreateRequest
27 27
 		ret volumeDriverProxyCreateResponse
... ...
@@ -158,11 +158,11 @@ type volumeDriverProxyListRequest struct {
158 158
 }
159 159
 
160 160
 type volumeDriverProxyListResponse struct {
161
-	Volumes list
161
+	Volumes []*proxyVolume
162 162
 	Err     string
163 163
 }
164 164
 
165
-func (pp *volumeDriverProxy) List() (volumes list, err error) {
165
+func (pp *volumeDriverProxy) List() (volumes []*proxyVolume, err error) {
166 166
 	var (
167 167
 		req volumeDriverProxyListRequest
168 168
 		ret volumeDriverProxyListResponse