github.com/trigonella/mattermost-server@v5.11.1+incompatible/plugin/interface_generator/main.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package main 5 6 import ( 7 "bytes" 8 "fmt" 9 "go/ast" 10 "go/parser" 11 "go/printer" 12 "go/token" 13 "io/ioutil" 14 "log" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "strings" 19 "text/template" 20 21 "github.com/pkg/errors" 22 ) 23 24 type IHookEntry struct { 25 FuncName string 26 Args *ast.FieldList 27 Results *ast.FieldList 28 } 29 30 type PluginInterfaceInfo struct { 31 Hooks []IHookEntry 32 API []IHookEntry 33 FileSet *token.FileSet 34 } 35 36 func FieldListToFuncList(fieldList *ast.FieldList, fileset *token.FileSet) string { 37 result := []string{} 38 if fieldList == nil || len(fieldList.List) == 0 { 39 return "()" 40 } 41 for _, field := range fieldList.List { 42 typeNameBuffer := &bytes.Buffer{} 43 err := printer.Fprint(typeNameBuffer, fileset, field.Type) 44 if err != nil { 45 panic(err) 46 } 47 typeName := typeNameBuffer.String() 48 names := []string{} 49 for _, name := range field.Names { 50 names = append(names, name.Name) 51 } 52 result = append(result, strings.Join(names, ", ")+" "+typeName) 53 } 54 55 return "(" + strings.Join(result, ", ") + ")" 56 } 57 58 func FieldListToNames(fieldList *ast.FieldList, fileset *token.FileSet) string { 59 result := []string{} 60 if fieldList == nil || len(fieldList.List) == 0 { 61 return "" 62 } 63 for _, field := range fieldList.List { 64 for _, name := range field.Names { 65 result = append(result, name.Name) 66 } 67 } 68 69 return strings.Join(result, ", ") 70 } 71 72 func FieldListToEncodedErrors(structPrefix string, fieldList *ast.FieldList, fileset *token.FileSet) string { 73 result := []string{} 74 if fieldList == nil { 75 return "" 76 } 77 78 nextLetter := 'A' 79 for _, field := range fieldList.List { 80 typeNameBuffer := &bytes.Buffer{} 81 err := printer.Fprint(typeNameBuffer, fileset, field.Type) 82 if err != nil { 83 panic(err) 84 } 85 86 if typeNameBuffer.String() != "error" { 87 nextLetter += 1 88 continue 89 } 90 91 name := "" 92 if len(field.Names) == 0 { 93 name = string(nextLetter) 94 nextLetter += 1 95 } else { 96 for range field.Names { 97 name += string(nextLetter) 98 nextLetter += 1 99 } 100 } 101 102 result = append(result, structPrefix+name+" = encodableError("+structPrefix+name+")") 103 104 } 105 106 return strings.Join(result, "\n") 107 } 108 109 func FieldListDestruct(structPrefix string, fieldList *ast.FieldList, fileset *token.FileSet) string { 110 result := []string{} 111 if fieldList == nil || len(fieldList.List) == 0 { 112 return "" 113 } 114 nextLetter := 'A' 115 for _, field := range fieldList.List { 116 typeNameBuffer := &bytes.Buffer{} 117 err := printer.Fprint(typeNameBuffer, fileset, field.Type) 118 if err != nil { 119 panic(err) 120 } 121 typeName := typeNameBuffer.String() 122 suffix := "" 123 if strings.HasPrefix(typeName, "...") { 124 suffix = "..." 125 } 126 if len(field.Names) == 0 { 127 result = append(result, structPrefix+string(nextLetter)+suffix) 128 nextLetter += 1 129 } else { 130 for range field.Names { 131 result = append(result, structPrefix+string(nextLetter)+suffix) 132 nextLetter += 1 133 } 134 } 135 } 136 137 return strings.Join(result, ", ") 138 } 139 140 func FieldListToStructList(fieldList *ast.FieldList, fileset *token.FileSet) string { 141 result := []string{} 142 if fieldList == nil || len(fieldList.List) == 0 { 143 return "" 144 } 145 nextLetter := 'A' 146 for _, field := range fieldList.List { 147 typeNameBuffer := &bytes.Buffer{} 148 err := printer.Fprint(typeNameBuffer, fileset, field.Type) 149 if err != nil { 150 panic(err) 151 } 152 typeName := typeNameBuffer.String() 153 if strings.HasPrefix(typeName, "...") { 154 typeName = strings.Replace(typeName, "...", "[]", 1) 155 } 156 if len(field.Names) == 0 { 157 result = append(result, string(nextLetter)+" "+typeName) 158 nextLetter += 1 159 } else { 160 for range field.Names { 161 result = append(result, string(nextLetter)+" "+typeName) 162 nextLetter += 1 163 } 164 } 165 } 166 167 return strings.Join(result, "\n\t") 168 } 169 170 func goList(dir string) ([]string, error) { 171 cmd := exec.Command("go", "list", "-f", "{{.Dir}}", dir) 172 bytes, err := cmd.Output() 173 if err != nil { 174 return nil, errors.Wrap(err, "Can't list packages") 175 } 176 177 return strings.Fields(string(bytes)), nil 178 } 179 180 func (info *PluginInterfaceInfo) addHookMethod(method *ast.Field) { 181 info.Hooks = append(info.Hooks, IHookEntry{ 182 FuncName: method.Names[0].Name, 183 Args: method.Type.(*ast.FuncType).Params, 184 Results: method.Type.(*ast.FuncType).Results, 185 }) 186 } 187 188 func (info *PluginInterfaceInfo) addAPIMethod(method *ast.Field) { 189 info.API = append(info.API, IHookEntry{ 190 FuncName: method.Names[0].Name, 191 Args: method.Type.(*ast.FuncType).Params, 192 Results: method.Type.(*ast.FuncType).Results, 193 }) 194 } 195 196 func (info *PluginInterfaceInfo) makeHookInspector() func(node ast.Node) bool { 197 return func(node ast.Node) bool { 198 if typeSpec, ok := node.(*ast.TypeSpec); ok { 199 if typeSpec.Name.Name == "Hooks" { 200 for _, method := range typeSpec.Type.(*ast.InterfaceType).Methods.List { 201 info.addHookMethod(method) 202 } 203 return false 204 } else if typeSpec.Name.Name == "API" { 205 for _, method := range typeSpec.Type.(*ast.InterfaceType).Methods.List { 206 info.addAPIMethod(method) 207 } 208 return false 209 } 210 } 211 return true 212 } 213 } 214 215 func getPluginInfo(dir string) (*PluginInterfaceInfo, error) { 216 pluginInfo := &PluginInterfaceInfo{ 217 Hooks: make([]IHookEntry, 0), 218 FileSet: token.NewFileSet(), 219 } 220 221 packages, err := parser.ParseDir(pluginInfo.FileSet, dir, nil, parser.ParseComments) 222 if err != nil { 223 log.Println("Parser error in dir "+dir+": ", err) 224 } 225 226 for _, pkg := range packages { 227 if pkg.Name != "plugin" { 228 continue 229 } 230 231 for _, file := range pkg.Files { 232 ast.Inspect(file, pluginInfo.makeHookInspector()) 233 } 234 } 235 236 return pluginInfo, nil 237 } 238 239 var hooksTemplate = `// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 240 // See LICENSE.txt for license information. 241 242 // Code generated by "make pluginapi" 243 // DO NOT EDIT 244 245 package plugin 246 247 {{range .HooksMethods}} 248 249 func init() { 250 hookNameToId["{{.Name}}"] = {{.Name}}Id 251 } 252 253 type {{.Name | obscure}}Args struct { 254 {{structStyle .Params}} 255 } 256 257 type {{.Name | obscure}}Returns struct { 258 {{structStyle .Return}} 259 } 260 261 func (g *hooksRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} { 262 _args := &{{.Name | obscure}}Args{ {{valuesOnly .Params}} } 263 _returns := &{{.Name | obscure}}Returns{} 264 if g.implemented[{{.Name}}Id] { 265 if err := g.client.Call("Plugin.{{.Name}}", _args, _returns); err != nil { 266 g.log.Error("RPC call {{.Name}} to plugin failed.", mlog.Err(err)) 267 } 268 } 269 {{ if .Return }} return {{destruct "_returns." .Return}} {{ end }} 270 } 271 272 func (s *hooksRPCServer) {{.Name}}(args *{{.Name | obscure}}Args, returns *{{.Name | obscure}}Returns) error { 273 if hook, ok := s.impl.(interface { 274 {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} 275 }); ok { 276 {{if .Return}}{{destruct "returns." .Return}} = {{end}}hook.{{.Name}}({{destruct "args." .Params}}) 277 {{if .Return}}{{encodeErrors "returns." .Return}}{{end}} 278 } else { 279 return encodableError(fmt.Errorf("Hook {{.Name}} called but not implemented.")) 280 } 281 return nil 282 } 283 {{end}} 284 285 {{range .APIMethods}} 286 287 type {{.Name | obscure}}Args struct { 288 {{structStyle .Params}} 289 } 290 291 type {{.Name | obscure}}Returns struct { 292 {{structStyle .Return}} 293 } 294 295 func (g *apiRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} { 296 _args := &{{.Name | obscure}}Args{ {{valuesOnly .Params}} } 297 _returns := &{{.Name | obscure}}Returns{} 298 if err := g.client.Call("Plugin.{{.Name}}", _args, _returns); err != nil { 299 log.Printf("RPC call to {{.Name}} API failed: %s", err.Error()) 300 } 301 {{ if .Return }} return {{destruct "_returns." .Return}} {{ end }} 302 } 303 304 func (s *apiRPCServer) {{.Name}}(args *{{.Name | obscure}}Args, returns *{{.Name | obscure}}Returns) error { 305 if hook, ok := s.impl.(interface { 306 {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} 307 }); ok { 308 {{if .Return}}{{destruct "returns." .Return}} = {{end}}hook.{{.Name}}({{destruct "args." .Params}}) 309 } else { 310 return encodableError(fmt.Errorf("API {{.Name}} called but not implemented.")) 311 } 312 return nil 313 } 314 {{end}} 315 ` 316 317 type MethodParams struct { 318 Name string 319 Params *ast.FieldList 320 Return *ast.FieldList 321 } 322 323 type HooksTemplateParams struct { 324 HooksMethods []MethodParams 325 APIMethods []MethodParams 326 } 327 328 func generateGlue(info *PluginInterfaceInfo) { 329 templateFunctions := map[string]interface{}{ 330 "funcStyle": func(fields *ast.FieldList) string { return FieldListToFuncList(fields, info.FileSet) }, 331 "structStyle": func(fields *ast.FieldList) string { return FieldListToStructList(fields, info.FileSet) }, 332 "valuesOnly": func(fields *ast.FieldList) string { return FieldListToNames(fields, info.FileSet) }, 333 "encodeErrors": func(structPrefix string, fields *ast.FieldList) string { 334 return FieldListToEncodedErrors(structPrefix, fields, info.FileSet) 335 }, 336 "destruct": func(structPrefix string, fields *ast.FieldList) string { 337 return FieldListDestruct(structPrefix, fields, info.FileSet) 338 }, 339 "obscure": func(name string) string { 340 return "Z_" + name 341 }, 342 } 343 344 hooksTemplate, err := template.New("hooks").Funcs(templateFunctions).Parse(hooksTemplate) 345 if err != nil { 346 panic(err) 347 } 348 349 templateParams := HooksTemplateParams{} 350 for _, hook := range info.Hooks { 351 templateParams.HooksMethods = append(templateParams.HooksMethods, MethodParams{ 352 Name: hook.FuncName, 353 Params: hook.Args, 354 Return: hook.Results, 355 }) 356 } 357 for _, api := range info.API { 358 templateParams.APIMethods = append(templateParams.APIMethods, MethodParams{ 359 Name: api.FuncName, 360 Params: api.Args, 361 Return: api.Results, 362 }) 363 } 364 templateResult := &bytes.Buffer{} 365 hooksTemplate.Execute(templateResult, &templateParams) 366 367 importsBuffer := &bytes.Buffer{} 368 cmd := exec.Command("goimports") 369 cmd.Stdin = templateResult 370 cmd.Stdout = importsBuffer 371 cmd.Stderr = os.Stderr 372 if err := cmd.Run(); err != nil { 373 panic(err) 374 } 375 376 if err := ioutil.WriteFile(filepath.Join(getPluginPackageDir(), "client_rpc_generated.go"), importsBuffer.Bytes(), 0664); err != nil { 377 panic(err) 378 } 379 } 380 381 func getPluginPackageDir() string { 382 dirs, err := goList("github.com/mattermost/mattermost-server/plugin") 383 if err != nil { 384 panic(err) 385 } else if len(dirs) != 1 { 386 panic("More than one package dir, or no dirs!") 387 } 388 389 return dirs[0] 390 } 391 392 func removeExcluded(info *PluginInterfaceInfo) *PluginInterfaceInfo { 393 toBeExcluded := func(item string) bool { 394 excluded := []string{ 395 "OnActivate", 396 "Implemented", 397 "LoadPluginConfiguration", 398 "ServeHTTP", 399 "FileWillBeUploaded", 400 "MessageWillBePosted", 401 "MessageWillBeUpdated", 402 } 403 for _, exclusion := range excluded { 404 if exclusion == item { 405 return true 406 } 407 } 408 return false 409 } 410 hooksResult := make([]IHookEntry, 0, len(info.Hooks)) 411 for _, hook := range info.Hooks { 412 if !toBeExcluded(hook.FuncName) { 413 hooksResult = append(hooksResult, hook) 414 } 415 } 416 info.Hooks = hooksResult 417 418 apiResult := make([]IHookEntry, 0, len(info.API)) 419 for _, api := range info.API { 420 if !toBeExcluded(api.FuncName) { 421 apiResult = append(apiResult, api) 422 } 423 } 424 info.API = apiResult 425 426 return info 427 } 428 429 func main() { 430 pluginPackageDir := getPluginPackageDir() 431 432 log.Println("Generating plugin glue") 433 info, err := getPluginInfo(pluginPackageDir) 434 if err != nil { 435 fmt.Println("Unable to get plugin info: " + err.Error()) 436 } 437 438 info = removeExcluded(info) 439 440 generateGlue(info) 441 }