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  }