github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/store/layer_generators/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/format"
    11  	"go/parser"
    12  	"go/token"
    13  	"io/ioutil"
    14  	"log"
    15  	"os"
    16  	"path"
    17  	"strings"
    18  	"text/template"
    19  )
    20  
    21  const OPEN_TRACING_PARAMS_MARKER = "@openTracingParams"
    22  
    23  func main() {
    24  	if err := buildTimerLayer(); err != nil {
    25  		log.Fatal(err)
    26  	}
    27  	if err := buildOpenTracingLayer(); err != nil {
    28  		log.Fatal(err)
    29  	}
    30  }
    31  
    32  func buildTimerLayer() error {
    33  	code, err := generateLayer("TimerLayer", "timer_layer.go.tmpl")
    34  	if err != nil {
    35  		return err
    36  	}
    37  	formatedCode, err := format.Source(code)
    38  	if err != nil {
    39  		return err
    40  	}
    41  
    42  	return ioutil.WriteFile(path.Join("timer_layer.go"), formatedCode, 0644)
    43  }
    44  
    45  func buildOpenTracingLayer() error {
    46  	code, err := generateLayer("OpenTracingLayer", "opentracing_layer.go.tmpl")
    47  	if err != nil {
    48  		return err
    49  	}
    50  	formatedCode, err := format.Source(code)
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	return ioutil.WriteFile(path.Join("opentracing_layer.go"), formatedCode, 0644)
    56  }
    57  
    58  type methodParam struct {
    59  	Name string
    60  	Type string
    61  }
    62  
    63  type methodData struct {
    64  	Params        []methodParam
    65  	Results       []string
    66  	ParamsToTrace map[string]bool
    67  }
    68  
    69  type subStore struct {
    70  	Methods map[string]methodData
    71  }
    72  
    73  type storeMetadata struct {
    74  	Name      string
    75  	SubStores map[string]subStore
    76  	Methods   map[string]methodData
    77  }
    78  
    79  func extractMethodMetadata(method *ast.Field, src []byte) methodData {
    80  	params := []methodParam{}
    81  	results := []string{}
    82  	paramsToTrace := map[string]bool{}
    83  	ast.Inspect(method.Type, func(expr ast.Node) bool {
    84  		switch e := expr.(type) {
    85  		case *ast.FuncType:
    86  			if method.Doc != nil {
    87  				for _, comment := range method.Doc.List {
    88  					s := comment.Text
    89  					if idx := strings.Index(s, OPEN_TRACING_PARAMS_MARKER); idx != -1 {
    90  						for _, p := range strings.Split(s[idx+len(OPEN_TRACING_PARAMS_MARKER):], ",") {
    91  							paramsToTrace[strings.TrimSpace(p)] = true
    92  						}
    93  					}
    94  				}
    95  			}
    96  			if e.Params != nil {
    97  				for _, param := range e.Params.List {
    98  					for _, paramName := range param.Names {
    99  						params = append(params, methodParam{Name: paramName.Name, Type: string(src[param.Type.Pos()-1 : param.Type.End()-1])})
   100  					}
   101  				}
   102  			}
   103  			if e.Results != nil {
   104  				for _, result := range e.Results.List {
   105  					results = append(results, string(src[result.Type.Pos()-1:result.Type.End()-1]))
   106  				}
   107  			}
   108  
   109  			for paramName := range paramsToTrace {
   110  				found := false
   111  				for _, param := range params {
   112  					if param.Name == paramName {
   113  						found = true
   114  						break
   115  					}
   116  				}
   117  				if !found {
   118  					log.Fatalf("Unable to find a parameter called '%s' (method '%s') that is mentioned in the '%s' comment. Maybe it was renamed?", paramName, method.Names[0].Name, OPEN_TRACING_PARAMS_MARKER)
   119  				}
   120  			}
   121  		}
   122  		return true
   123  	})
   124  	return methodData{Params: params, Results: results, ParamsToTrace: paramsToTrace}
   125  }
   126  
   127  func extractStoreMetadata() (*storeMetadata, error) {
   128  	// Create the AST by parsing src.
   129  	fset := token.NewFileSet() // positions are relative to fset
   130  
   131  	file, err := os.Open("store.go")
   132  	if err != nil {
   133  		return nil, fmt.Errorf("Unable to open store/store.go file: %w", err)
   134  	}
   135  	src, err := ioutil.ReadAll(file)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	file.Close()
   140  	f, err := parser.ParseFile(fset, "", src, parser.AllErrors|parser.ParseComments)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	topLevelFunctions := map[string]bool{
   146  		"MarkSystemRanUnitTests":   false,
   147  		"Close":                    false,
   148  		"LockToMaster":             false,
   149  		"UnlockFromMaster":         false,
   150  		"DropAllTables":            false,
   151  		"TotalMasterDbConnections": true,
   152  		"TotalReadDbConnections":   true,
   153  		"SetContext":               true,
   154  		"TotalSearchDbConnections": true,
   155  		"GetCurrentSchemaVersion":  true,
   156  	}
   157  
   158  	metadata := storeMetadata{Methods: map[string]methodData{}, SubStores: map[string]subStore{}}
   159  
   160  	ast.Inspect(f, func(n ast.Node) bool {
   161  		switch x := n.(type) {
   162  		case *ast.TypeSpec:
   163  			if x.Name.Name == "Store" {
   164  				for _, method := range x.Type.(*ast.InterfaceType).Methods.List {
   165  					methodName := method.Names[0].Name
   166  					if _, ok := topLevelFunctions[methodName]; ok {
   167  						metadata.Methods[methodName] = extractMethodMetadata(method, src)
   168  					}
   169  				}
   170  			} else if strings.HasSuffix(x.Name.Name, "Store") {
   171  				subStoreName := strings.TrimSuffix(x.Name.Name, "Store")
   172  				metadata.SubStores[subStoreName] = subStore{Methods: map[string]methodData{}}
   173  				for _, method := range x.Type.(*ast.InterfaceType).Methods.List {
   174  					methodName := method.Names[0].Name
   175  					metadata.SubStores[subStoreName].Methods[methodName] = extractMethodMetadata(method, src)
   176  				}
   177  			}
   178  		}
   179  		return true
   180  	})
   181  
   182  	return &metadata, nil
   183  }
   184  
   185  func generateLayer(name, templateFile string) ([]byte, error) {
   186  	out := bytes.NewBufferString("")
   187  	metadata, err := extractStoreMetadata()
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	metadata.Name = name
   192  
   193  	myFuncs := template.FuncMap{
   194  		"joinResults": func(results []string) string {
   195  			return strings.Join(results, ", ")
   196  		},
   197  		"joinResultsForSignature": func(results []string) string {
   198  			if len(results) == 0 {
   199  				return ""
   200  			}
   201  			if len(results) == 1 {
   202  				return strings.Join(results, ", ")
   203  			}
   204  			return fmt.Sprintf("(%s)", strings.Join(results, ", "))
   205  		},
   206  		"genResultsVars": func(results []string) string {
   207  			vars := []string{}
   208  			for i := range results {
   209  				vars = append(vars, fmt.Sprintf("resultVar%d", i))
   210  			}
   211  			return strings.Join(vars, ", ")
   212  		},
   213  		"errorToBoolean": func(results []string) string {
   214  			for i, typeName := range results {
   215  				if typeName == "*model.AppError" {
   216  					return fmt.Sprintf("resultVar%d == nil", i)
   217  				}
   218  			}
   219  			return "true"
   220  		},
   221  		"errorPresent": func(results []string) bool {
   222  			for _, typeName := range results {
   223  				if typeName == "*model.AppError" {
   224  					return true
   225  				}
   226  			}
   227  			return false
   228  		},
   229  		"errorVar": func(results []string) string {
   230  			for i, typeName := range results {
   231  				if typeName == "*model.AppError" {
   232  					return fmt.Sprintf("resultVar%d", i)
   233  				}
   234  			}
   235  			return ""
   236  		},
   237  		"joinParams": func(params []methodParam) string {
   238  			paramsNames := make([]string, 0, len(params))
   239  			for _, param := range params {
   240  				paramsNames = append(paramsNames, param.Name)
   241  			}
   242  			return strings.Join(paramsNames, ", ")
   243  		},
   244  		"joinParamsWithType": func(params []methodParam) string {
   245  			paramsWithType := []string{}
   246  			for _, param := range params {
   247  				paramsWithType = append(paramsWithType, fmt.Sprintf("%s %s", param.Name, param.Type))
   248  			}
   249  			return strings.Join(paramsWithType, ", ")
   250  		},
   251  	}
   252  
   253  	t := template.Must(template.New(templateFile).Funcs(myFuncs).ParseFiles("layer_generators/" + templateFile))
   254  	if err = t.Execute(out, metadata); err != nil {
   255  		return nil, err
   256  	}
   257  	return out.Bytes(), nil
   258  }