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:] //} 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) { fp := runtime.FuncForPC(v.Pointer()) 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 makeFieldElemType(t reflect.Type, existsClasses *map[string]bool, 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, 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, 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, classes) } return "Array<" + makeFieldElemType(t.Elem(), existsClasses, classes, isSkip) + ">" } else if t.Kind() == reflect.Struct { if !isSkip { makeClass(originT, existsClasses, classes) } return makeArgTypeString(t.Name()) } else { return makeArgTypeString(t.String()) } } func makeClass(t reflect.Type, existsClasses *map[string]bool, classes *[]string) { originT := t if t.Kind() == reflect.Pointer { t = t.Elem() } if !(*existsClasses)[t.Name()] { (*existsClasses)[t.Name()] = true classItems := make([]string, 0) implements := make([]string, 0) 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 := makeFieldElemType(ft, existsClasses, 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, classes) if len(inArgs) > 0 { inArgs[0].isSkip = true } 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{}, "interface "+t.Name()+implementsStr+" {") newClass = append(newClass, classItems...) newClass = append(newClass, "}") *classes = append(*classes, strings.Join(newClass, "\n")) } } func makeFuncInOutArgs(t reflect.Type, existsClasses *map[string]bool, classes *[]string) (inArgs []ArgInfo, outArgs []ArgInfo) { inArgs = make([]ArgInfo, t.NumIn()) outArgs = make([]ArgInfo, t.NumOut()) // *plugin.Context for i := t.NumIn() - 1; i >= 0; i-- { arg := t.In(i) isSkip := false if arg.String() == "*plugin.Context" { isSkip = true } argInfo := ArgInfo{ index: i, isOutArg: false, isFunc: false, isSkip: isSkip, isOptional: arg.Kind() == reflect.Pointer, isVariadic: t.IsVariadic() && i == t.NumIn()-1, Name: "", Type: makeFieldElemType(arg, existsClasses, 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, classes) } //if !argInfo.isSkip && arg.Kind() == reflect.Struct { // makeClass(originArg, existsClasses, 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: makeFieldElemType(arg, existsClasses, classes, isSkip), } //originArg := arg //if arg.Kind() == reflect.Pointer { // arg = arg.Elem() //} //if !argInfo.isSkip && arg.Kind() == reflect.Struct { // makeClass(originArg, existsClasses, classes) //} outArgs[i] = argInfo } return inArgs, outArgs } func findObject(v reflect.Value, level int, existsClasses *map[string]bool) (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) 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, &classes) codes = append(codes, "null as "+t.Name()) } 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) 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, &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() { code := MakePluginCode(&plg) _ = u.WriteFile(path.Join("plugins", plg.Id+".ts"), code) } } func MakePluginCode(plg *plugin.Plugin) string { if plg == nil { return "" } existsClasses := make(map[string]bool) codes, classes := findObject(reflect.ValueOf(plg.Objects), 0, &existsClasses) return strings.Join(classes, "\n\n") + "\n\nexport default " + strings.Join(codes, "\n") }