package gojs import ( "apigo.cloud/git/apigo/plugin" "github.com/ssgo/u" "path" "reflect" "regexp" "runtime" "strings" ) type ArgInfo struct { index int isVariadic bool isOutArg bool isFunc bool isSkip bool isOptional bool funcInArgs []ArgInfo funcOutArgs []ArgInfo Name string Type string } var numberMatcher = regexp.MustCompile("[a-z0-9]*(int|float)[a-z0-9]*") var mapMatcher = regexp.MustCompile("map\\[(\\w+)](\\w+)") func makeArgTypeString(argType string) string { if strings.Contains(argType, "interface {}") { argType = strings.ReplaceAll(argType, "interface {}", "any") } if strings.HasPrefix(argType, "*") { argType = argType[1:] } if strings.HasPrefix(argType, "[]") { argType = argType[2:] + "[]" } if strings.Contains(argType, "int") || strings.Contains(argType, "float") { argType = numberMatcher.ReplaceAllString(argType, "number") } if strings.Contains(argType, "bool") { argType = strings.ReplaceAll(argType, "bool", "boolean") } if strings.Contains(argType, "booleanean") { argType = strings.ReplaceAll(argType, "booleanean", "boolean") } if strings.HasPrefix(argType, "map[") { argType = mapMatcher.ReplaceAllString(argType, "Map<$1, $2>") } //if strings.ContainsRune(argType, '.') { // argType = argType[strings.LastIndexByte(argType, '.')+1:] //} if strings.HasPrefix(argType, "<-") { argType = "any" } if argType == "" { argType = "void" } return argType } func (argInfo *ArgInfo) String() string { argType := argInfo.Type if argInfo.isFunc { argType = "(" + makeInArgsString(argInfo.funcInArgs) + ") => " + makeOutArgsString(argInfo.funcOutArgs) } else { argType = makeArgTypeString(argType) } if argInfo.isOutArg { return argType } else { argName := argInfo.Name if argName == "" { argName = "arg" + u.String(argInfo.index+1) } if argInfo.isVariadic { argName = "..." + argName } return argName + u.StringIf(argInfo.isOptional, "?: ", ": ") + argType } } func makeInArgs(args []ArgInfo) []string { arr := make([]string, 0) for _, arg := range args { if !arg.isSkip { arr = append(arr, arg.String()) } } return arr } func makeInArgsString(args []ArgInfo) string { return strings.Join(makeInArgs(args), ", ") } func makeOutArgsString(args []ArgInfo) string { arr := makeInArgs(args) if len(arr) == 0 { return "void" } else if len(arr) == 1 { return arr[0] } else { return "[" + strings.Join(arr, ", ") + "]" } } func makeFuncArgsNames(v reflect.Value, inArgs []ArgInfo, isMethod bool) { var fp *runtime.Func if v.IsValid() { fp = runtime.FuncForPC(v.Pointer()) } else { // no name for Interface return } file, lineNo := fp.FileLine(fp.Entry()) if file != "" && u.FileExists(file) { lines, _ := u.ReadFileLines(file) if len(lines) >= lineNo { line := lines[lineNo-1] if !strings.Contains(line, "func") && lineNo-2 >= 0 && strings.Contains(lines[lineNo-2], "func") { line = lines[lineNo-2] } line = strings.ReplaceAll(line, "interface{}", "any") pos := strings.Index(line, "func") if pos != -1 { line = strings.TrimSpace(line[pos+4:]) if isMethod { // skip method this arg pos = strings.Index(line, "(") if pos != -1 { line = strings.TrimSpace(line[pos+1:]) makeFuncArgsName(line, inArgs[1:]) } } else { makeFuncArgsName(line, inArgs) } } } } } func makeFuncArgsName(line string, inArgs []ArgInfo) { pos := strings.Index(line, "(") if pos != -1 { line = strings.TrimSpace(line[pos+1:]) for i := 0; i < len(inArgs); i++ { // find param name pos = strings.Index(line, " ") // support combined args a, b string pos1 := strings.Index(line, ",") if pos1 != -1 && pos1 < pos { inArgs[i].Name = line[0:pos1] line = strings.TrimSpace(line[pos1+1:]) continue } if pos != -1 { inArgs[i].Name = line[0:pos] line = strings.TrimSpace(line[pos+1:]) } // skip inline func if strings.HasPrefix(line, "func") { line = strings.TrimSpace(line[4:]) leftQuotes := 0 quoteStarted := false for pos = 0; pos < len(line); pos++ { if line[pos] == '(' { leftQuotes++ quoteStarted = true continue } if quoteStarted && line[pos] == ')' { leftQuotes-- } if quoteStarted && leftQuotes == 0 { break } } makeFuncArgsName(line, inArgs[i].funcInArgs) line = strings.TrimSpace(line[pos+1:]) } // skip , pos = strings.Index(line, ",") if pos != -1 { line = strings.TrimSpace(line[pos+1:]) } } } } func fixFieldElemType(argType string) string { if strings.HasPrefix(argType, "Map<") { argType = "Object/*" + argType + "*/" } return argType } func makeFieldElemType(t reflect.Type, existsClasses *map[string]string, sameClassIndex *map[string]int, classes *[]string, isSkip bool) string { originT := t if t.Kind() == reflect.Pointer { t = t.Elem() } if t.Kind() == reflect.Map { if t.Elem().Kind() == reflect.Struct && !isSkip { makeClass(t.Elem(), existsClasses, sameClassIndex, classes) } et := t.Elem() if et.Kind() == reflect.Pointer { et = et.Elem() } if et.Kind() == reflect.Struct || et.Kind() == reflect.Map || (et.Kind() == reflect.Slice && et.Elem().Kind() != reflect.Uint8) { return "Map<" + makeArgTypeString(t.Key().String()) + ", " + makeFieldElemType(t.Elem(), existsClasses, sameClassIndex, classes, isSkip) + ">" } return "Object" } else if t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 { if t.Elem().Kind() == reflect.Struct && !isSkip { makeClass(t.Elem(), existsClasses, sameClassIndex, classes) } return "Array<" + makeFieldElemType(t.Elem(), existsClasses, sameClassIndex, classes, isSkip) + ">" } else if t.Kind() == reflect.Struct { if !isSkip { makeClass(originT, existsClasses, sameClassIndex, classes) } return makeArgTypeString(getFixedClassName(t.String(), existsClasses)) //return makeArgTypeString(strings.ReplaceAll(t.String(), ".", "_")) } else if t.Kind() == reflect.Interface { if !isSkip { makeClass(originT, existsClasses, sameClassIndex, classes) } return makeArgTypeString(getFixedClassName(t.String(), existsClasses)) } else if t.Kind() == reflect.Func { inArgs, outArgs := makeFuncInOutArgs(t, existsClasses, sameClassIndex, classes) //makeFuncArgsNames(reflect.MakeFunc(t, func(args []reflect.Value) (results []reflect.Value) { // return nil //}), inArgs, false) return "(" + makeInArgsString(inArgs) + ") => " + makeOutArgsString(outArgs) } else { return makeArgTypeString(getFixedTypeName(t)) } } func getFixedClassName(className string, existsClasses *map[string]string) string { if (*existsClasses)[className] != "" { return (*existsClasses)[className] } return strings.ReplaceAll(className, ".", "_") } func getFixedTypeName(t reflect.Type) string { if t.String() != t.Kind().String() && !strings.HasPrefix(t.String(), "[]") { if t.Kind().String() == "slice" { return "[]any" } if t.Kind().String() == "chan" { return "any" } if t.Kind().String() == "func" { return "()=>void" } //fmt.Println(">>>>>", u.BYellow(t.String()), u.BYellow(t.String()), u.BYellow(t.Kind().String())) return makeArgTypeString(t.Kind().String()) } else { return makeArgTypeString(t.String()) } } func makeClass(t reflect.Type, existsClasses *map[string]string, sameClassIndex *map[string]int, classes *[]string) { originT := t if t.Kind() == reflect.Pointer { t = t.Elem() } if t.Name() == "" { return } fullTypeName := t.String() useTypeName := t.Name() //fmt.Println(fullTypeName, useTypeName) if (*existsClasses)[fullTypeName] == "" { (*sameClassIndex)[useTypeName]++ if (*sameClassIndex)[useTypeName] > 1 { useTypeName += u.String((*sameClassIndex)[useTypeName]) } (*existsClasses)[fullTypeName] = useTypeName classItems := make([]string, 0) implements := make([]string, 0) if t.Kind() != reflect.Interface { for i := 0; i < t.NumField(); i++ { if !t.Field(i).IsExported() { continue } ft := t.Field(i).Type if ft.Kind() == reflect.Pointer { ft = ft.Elem() } ftStr := fixFieldElemType(makeFieldElemType(ft, existsClasses, sameClassIndex, classes, false)) if t.Field(i).Anonymous { implements = append(implements, ftStr) continue } classItems = append(classItems, " "+u.GetLowerName(t.Field(i).Name)+": "+ftStr) } } for i := 0; i < originT.NumMethod(); i++ { if !originT.Method(i).IsExported() { continue } inArgs, outArgs := makeFuncInOutArgs(originT.Method(i).Type, existsClasses, sameClassIndex, classes) if len(inArgs) > 0 { inArgs[0].isSkip = true } //fmt.Println(">>>>>", u.BYellow(originT.Method(i))) makeFuncArgsNames(originT.Method(i).Func, inArgs, true) classItems = append(classItems, " "+u.GetLowerName(originT.Method(i).Name)+"("+makeInArgsString(inArgs)+"): "+makeOutArgsString(outArgs)) } implementsStr := "" if len(implements) > 0 { implementsStr = " extends " + strings.Join(implements, ", ") } newClass := append([]string{}, "export interface "+useTypeName+implementsStr+" {") newClass = append(newClass, classItems...) newClass = append(newClass, "}") *classes = append(*classes, strings.Join(newClass, "\n")) } } func makeFuncInOutArgs(t reflect.Type, existsClasses *map[string]string, sameClassIndex *map[string]int, classes *[]string) (inArgs []ArgInfo, outArgs []ArgInfo) { inArgs = make([]ArgInfo, t.NumIn()) outArgs = make([]ArgInfo, t.NumOut()) isOptional := true // *plugin.Context for i := t.NumIn() - 1; i >= 0; i-- { arg := t.In(i) isSkip := false if arg.String() == "*plugin.Context" { isSkip = true } if isOptional && arg.Kind() != reflect.Pointer { isOptional = false } argInfo := ArgInfo{ index: i, isOutArg: false, isFunc: false, isSkip: isSkip, isOptional: isOptional, isVariadic: t.IsVariadic() && i == t.NumIn()-1, Name: "", Type: fixFieldElemType(makeFieldElemType(arg, existsClasses, sameClassIndex, classes, isSkip)), } //originArg := arg //if arg.Kind() == reflect.Pointer { // arg = arg.Elem() //} if arg.Kind() == reflect.Func { argInfo.isFunc = true argInfo.funcInArgs, argInfo.funcOutArgs = makeFuncInOutArgs(arg, existsClasses, sameClassIndex, classes) } //if !argInfo.isSkip && arg.Kind() == reflect.Struct { // makeClass(originArg, existsClasses, sameClassIndex, classes) //} inArgs[i] = argInfo } for i := t.NumOut() - 1; i >= 0; i-- { arg := t.Out(i) isSkip := false if arg.String() == "error" { isSkip = true } argInfo := ArgInfo{ index: i, isOutArg: true, isFunc: false, isSkip: isSkip, isVariadic: false, Name: "", Type: fixFieldElemType(makeFieldElemType(arg, existsClasses, sameClassIndex, classes, isSkip)), } //originArg := arg //if arg.Kind() == reflect.Pointer { // arg = arg.Elem() //} //if !argInfo.isSkip && arg.Kind() == reflect.Struct { // makeClass(originArg, existsClasses, sameClassIndex, classes) //} outArgs[i] = argInfo } return inArgs, outArgs } func findObject(v reflect.Value, level int, existsClasses *map[string]string, sameClassIndex *map[string]int) (codes []string, classes []string) { codes = make([]string, 0) classes = make([]string, 0) if v.Kind() == reflect.Interface { v = v.Elem() } originT := v.Type() v = u.FinalValue(v) t := originT if t.Kind() == reflect.Pointer { t = t.Elem() } indent := strings.Repeat(" ", level) if t.Kind() == reflect.Map { codes = append(codes, "{") for _, k := range v.MapKeys() { codes2, classes2 := findObject(v.MapIndex(k), level+1, existsClasses, sameClassIndex) classes = append(classes, classes2...) codes = append(codes, indent+" \""+k.String()+"\": "+strings.Join(codes2, "\n")+",") } codes = append(codes, indent+"}") } else if t.Kind() == reflect.Struct { makeClass(originT, existsClasses, sameClassIndex, &classes) codes = append(codes, "null as "+getFixedClassName(t.String(), existsClasses)) } else if t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 { codes = append(codes, "[") for i := 0; i < v.Len(); i++ { codes2, classes2 := findObject(v.Index(i), level+1, existsClasses, sameClassIndex) classes = append(classes, classes2...) codes = append(codes, indent+" "+strings.Join(codes2, "\n")+",") } codes = append(codes, indent+"]") } else if t.Kind() == reflect.Func { inArgs, outArgs := makeFuncInOutArgs(t, existsClasses, sameClassIndex, &classes) makeFuncArgsNames(v, inArgs, false) codes = append(codes, "function ("+makeInArgsString(inArgs)+"): "+makeOutArgsString(outArgs)+" {return}") } else { codes = append(codes, u.Json(v.Interface())) } return codes, classes } func MakeAllPluginCode() { for _, plg := range plugin.List() { exportCode, interfaceCode := MakePluginCode(&plg) if interfaceCode != "" { _ = u.WriteFile(path.Join("plugins", plg.Id+"Type.ts"), interfaceCode) } _ = u.WriteFile(path.Join("plugins", plg.Id+".ts"), exportCode) } } func MakePluginCode(plg *plugin.Plugin) (exportCode, interfaceCode string) { if plg == nil { return "", "" } existsClasses := make(map[string]string) sameClassIndex := make(map[string]int) codes, classes := findObject(reflect.ValueOf(plg.Objects), 0, &existsClasses, &sameClassIndex) interfaceExports := make([]string, 0) for _, existsClassName := range existsClasses { interfaceExports = append(interfaceExports, existsClassName) } exportCode = "export default " + strings.Join(codes, "\n") interfaceCode = "" if len(interfaceExports) > 0 { exportCode = "import {" + strings.Join(interfaceExports, ", ") + "} from '" + plg.Id + "Type'\n\n" + exportCode interfaceCode = strings.Join(classes, "\n\n") // + "\n\nexport default {" + strings.Join(interfaceExports, ", ") + "," } return }