18
"golang.org/x/tools/go/loader"
24
// - generate exported types if the parameter/response types aren't exported?
25
// - deal with literal interface and struct types.
26
// - copy doc comments from server methods.
30
fmt.Fprintf(os.Stderr, "usage: httprequest-generate server-package server-type client-type\n")
38
serverPkg, serverType, clientType := flag.Arg(0), flag.Arg(1), flag.Arg(2)
40
if err := generate(serverPkg, serverType, clientType); err != nil {
41
fmt.Fprintf(os.Stderr, "%v\n", err)
46
type templateArg struct {
53
var code = template.Must(template.New("").Parse(`
54
// The code in this file was automatically generated by running httprequest-generate-client.
59
{{range .Imports}}{{printf "%q" .}}
63
type {{.ClientType}} struct {
64
Client httprequest.Client
70
func (c *{{$.ClientType}}) {{.Name}}(p *{{.ParamType}}) ({{.RespType}}, error) {
72
err := c.Client.Call(p, &r)
77
func (c *{{$.ClientType}}) {{.Name}}(p *{{.ParamType}}) (error) {
78
return c.Client.Call(p, nil)
84
func generate(serverPkgPath, serverType, clientType string) error {
85
currentDir, err := os.Getwd()
89
localPkg, err := build.Import(".", currentDir, 0)
91
return errgo.Notef(err, "cannot open package in current directory")
93
serverPkg, err := build.Import(serverPkgPath, currentDir, 0)
95
return errgo.Notef(err, "cannot open %q", serverPkgPath)
98
methods, imports, err := serverMethods(serverPkg.ImportPath, serverType, localPkg.ImportPath)
100
return errgo.Mask(err)
105
PkgName: localPkg.Name,
106
ClientType: clientType,
109
if err := code.Execute(&buf, arg); err != nil {
110
return errgo.Mask(err)
112
data, err := format.Source(buf.Bytes())
114
return errgo.Notef(err, "cannot format source")
116
if err := writeOutput(data, clientType); err != nil {
117
return errgo.Mask(err)
122
func writeOutput(data []byte, clientType string) error {
123
filename := strings.ToLower(clientType) + "_generated.go"
124
if err := ioutil.WriteFile(filename, data, 0644); err != nil {
125
return errgo.Mask(err)
137
func serverMethods(serverPkg, serverType, localPkg string) ([]method, []string, error) {
138
cfg := loader.Config{
139
TypeCheckFuncBodies: func(string) bool {
142
ImportPkgs: map[string]bool{
143
serverPkg: false, // false means don't load tests.
145
ParserMode: parser.ParseComments,
147
prog, err := cfg.Load()
149
return nil, nil, errgo.Notef(err, "cannot load %q", serverPkg)
151
pkgInfo := prog.Imported[serverPkg]
153
return nil, nil, errgo.Newf("cannot find %q in imported code", serverPkg)
156
obj := pkg.Scope().Lookup(serverType)
158
return nil, nil, errgo.Newf("type %s not found in %s", serverType, serverPkg)
160
objTypeName, ok := obj.(*types.TypeName)
162
return nil, nil, errgo.Newf("%s is not a type", serverType)
164
// Use the pointer type to get as many methods as possible.
165
ptrObjType := types.NewPointer(objTypeName.Type())
167
imports := map[string]string{
168
"github.com/juju/httprequest": "httprequest",
172
mset := types.NewMethodSet(ptrObjType)
173
for i := 0; i < mset.Len(); i++ {
175
if !sel.Obj().Exported() {
178
name := sel.Obj().Name()
182
ptype, rtype, err := parseMethodType(sel.Type().(*types.Signature))
184
fmt.Fprintf(os.Stderr, "ignoring method %s: %v\n", name, err)
187
comment := docComment(prog, sel)
188
methods = append(methods, method{
191
ParamType: typeStr(ptype, imports),
192
RespType: typeStr(rtype, imports),
195
delete(imports, localPkg)
196
var allImports []string
197
for path := range imports {
198
allImports = append(allImports, path)
200
return methods, allImports, nil
203
// docComment returns the doc comment for the method referred to
204
// by the given selection.
205
func docComment(prog *loader.Program, sel *types.Selection) string {
207
tokFile := prog.Fset.File(obj.Pos())
209
panic("no file found for method")
211
filename := tokFile.Name()
212
for _, pkgInfo := range prog.AllPackages {
213
for _, f := range pkgInfo.Files {
214
if tokFile := prog.Fset.File(f.Pos()); tokFile == nil || tokFile.Name() != filename {
217
// We've found the file we're looking for. Now traverse all
218
// top level declarations looking for the right function declaration.
219
for _, decl := range f.Decls {
220
fdecl, ok := decl.(*ast.FuncDecl)
221
if ok && fdecl.Name.Pos() == obj.Pos() {
223
return commentStr(fdecl.Doc)
228
panic("method declaration not found")
231
func commentStr(c *ast.CommentGroup) string {
236
for i, cc := range c.List {
240
b = append(b, cc.Text...)
245
// typeStr returns the type string to be used when using the
246
// given type. It adds any needed import paths to the given
247
// imports map (map from package path to package id).
248
func typeStr(t types.Type, imports map[string]string) string {
252
qualify := func(pkg *types.Package) string {
253
if name, ok := imports[pkg.Path()]; ok {
257
// Make sure we're not duplicating the name.
258
// TODO if we are, make a new non-duplicated version.
259
for oldPkg, oldName := range imports {
261
panic(errgo.Newf("duplicate package name %s vs %s", pkg.Path(), oldPkg))
264
imports[pkg.Path()] = name
267
return types.TypeString(t, qualify)
270
func parseMethodType(t *types.Signature) (ptype, rtype types.Type, err error) {
272
if mp.Len() != 1 && mp.Len() != 2 {
273
return nil, nil, errgo.New("wrong argument count")
275
ptype0 := mp.At(mp.Len() - 1).Type()
276
ptype1, ok := ptype0.(*types.Pointer)
278
return nil, nil, errgo.New("parameter is not a pointer")
280
ptype = ptype1.Elem()
281
if _, ok := ptype.Underlying().(*types.Struct); !ok {
282
return nil, nil, errgo.Newf("parameter is %s, not a pointer to struct", ptype1.Elem())
286
return nil, nil, errgo.New("wrong result count")
289
rtype = rp.At(0).Type()
291
return ptype, rtype, nil