205 lines
4.9 KiB
Go
205 lines
4.9 KiB
Go
package metadataanalyzer
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"io/fs"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
|
|
// Import the embed package.
|
|
_ "embed"
|
|
)
|
|
|
|
//go:embed analyzer.template
|
|
var tmpl string
|
|
|
|
type PkgInfo struct {
|
|
Method string
|
|
ComponentType string
|
|
}
|
|
|
|
func GenerateMetadataAnalyzer(contribRoot string, componentFolders []string, outputfile string) {
|
|
fset := token.NewFileSet()
|
|
pkgs := make(map[string]PkgInfo)
|
|
|
|
err := filepath.WalkDir(contribRoot, func(path string, file fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if file.IsDir() {
|
|
if file.Name() == "vendor" || file.Name() == "tests" || file.Name() == "internal" {
|
|
return fs.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if filepath.Ext(path) != ".go" {
|
|
return nil
|
|
}
|
|
|
|
componentTypeFolder := ""
|
|
packageName := ""
|
|
skip := true
|
|
dir := filepath.Dir(path)
|
|
for dir != "." && !strings.HasSuffix(dir, "components-contrib") {
|
|
if !skip {
|
|
packageName = filepath.Base(dir) + "/" + packageName
|
|
} else {
|
|
packageName = filepath.Base(dir)
|
|
}
|
|
skip = false
|
|
dir = filepath.Dir(dir)
|
|
|
|
curFolder := filepath.Base(dir)
|
|
|
|
for _, val := range componentFolders {
|
|
if curFolder == val {
|
|
componentTypeFolder = curFolder
|
|
}
|
|
}
|
|
}
|
|
|
|
parsedFile, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
|
if err != nil {
|
|
log.Printf("could not parse %s: %v", path, err)
|
|
return nil
|
|
}
|
|
|
|
var method string
|
|
var methodFinderErr error
|
|
methodFound := false
|
|
|
|
switch componentTypeFolder {
|
|
// Only the component types listed here implement the GetComponentMetadata method today
|
|
// Note: these are folder names not the type of components
|
|
case "secretstores":
|
|
method, methodFinderErr = getConstructorMethod("secretstores.SecretStore", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
}
|
|
case "state":
|
|
method, methodFinderErr = getConstructorMethod("state.Store", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
}
|
|
case "bindings":
|
|
method, methodFinderErr = getConstructorMethod("bindings.InputOutputBinding", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
} else {
|
|
method, methodFinderErr = getConstructorMethod("bindings.OutputBinding", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
} else {
|
|
method, methodFinderErr = getConstructorMethod("bindings.InputBinding", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
}
|
|
}
|
|
}
|
|
case "lock":
|
|
method, methodFinderErr = getConstructorMethod("lock.Store", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
}
|
|
case "workflows":
|
|
method, methodFinderErr = getConstructorMethod("workflows.Workflow", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
}
|
|
case "configuration":
|
|
method, methodFinderErr = getConstructorMethod("configuration.Store", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
}
|
|
case "crypto":
|
|
method, methodFinderErr = getConstructorMethod("contribCrypto.SubtleCrypto", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
}
|
|
case "middleware":
|
|
method, methodFinderErr = getConstructorMethod("middleware.Middleware", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
}
|
|
case "pubsub":
|
|
method, methodFinderErr = getConstructorMethod("pubsub.PubSub", parsedFile)
|
|
if methodFinderErr == nil {
|
|
methodFound = true
|
|
}
|
|
}
|
|
|
|
if methodFound {
|
|
pkgs[packageName] = PkgInfo{
|
|
Method: method,
|
|
ComponentType: componentTypeFolder,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
data := make(map[string][]string)
|
|
|
|
for fullpkg, info := range pkgs {
|
|
sanitizedPkg := strings.ReplaceAll(strings.ReplaceAll(fullpkg, "/", "_"), "-", "_")
|
|
data[fullpkg] = []string{sanitizedPkg, info.Method, info.ComponentType}
|
|
}
|
|
|
|
templateData := struct {
|
|
Pkgs map[string][]string
|
|
}{
|
|
Pkgs: data,
|
|
}
|
|
|
|
// let's try loading the template
|
|
f, err := os.Create(outputfile)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
t := template.Must(template.New("tmpl").Parse(tmpl))
|
|
err = t.Execute(f, templateData)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func getConstructorMethod(componentType string, file *ast.File) (string, error) {
|
|
typeSplit := strings.Split(componentType, ".")
|
|
if len(typeSplit) != 2 {
|
|
return "", fmt.Errorf("invalid component type: %s", componentType)
|
|
}
|
|
|
|
for _, d := range file.Decls {
|
|
if f, ok := d.(*ast.FuncDecl); ok {
|
|
if f.Type.Results != nil && len(f.Type.Results.List) > 0 {
|
|
if selExpr, ok := f.Type.Results.List[0].Type.(*ast.SelectorExpr); ok {
|
|
xIdent, ok := selExpr.X.(*ast.Ident)
|
|
if !ok || xIdent.Name != typeSplit[0] {
|
|
continue
|
|
}
|
|
if selExpr.Sel.Name == typeSplit[1] {
|
|
if len(f.Name.Name) > 3 && f.Name.Name[:3] == "New" {
|
|
return f.Name.Name, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "", errors.New("could not find constructor method")
|
|
}
|