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