github.com/AlpineAIO/wails/v2@v2.0.0-beta.32.0.20240505041856-1047a8fa5fef/internal/binding/generate.go (about) 1 package binding 2 3 import ( 4 "bytes" 5 _ "embed" 6 "fmt" 7 "os" 8 "path/filepath" 9 "regexp" 10 "sort" 11 "strings" 12 13 "github.com/AlpineAIO/wails/v2/internal/fs" 14 15 "github.com/leaanthony/slicer" 16 ) 17 18 var ( 19 mapRegex *regexp.Regexp 20 keyPackageIndex int 21 keyTypeIndex int 22 valueArrayIndex int 23 valuePackageIndex int 24 valueTypeIndex int 25 ) 26 27 func init() { 28 mapRegex = regexp.MustCompile(`(?:map\[(?:(?P<keyPackage>\w+)\.)?(?P<keyType>\w+)])?(?P<valueArray>\[])?(?:\*?(?P<valuePackage>\w+)\.)?(?P<valueType>.+)`) 29 keyPackageIndex = mapRegex.SubexpIndex("keyPackage") 30 keyTypeIndex = mapRegex.SubexpIndex("keyType") 31 valueArrayIndex = mapRegex.SubexpIndex("valueArray") 32 valuePackageIndex = mapRegex.SubexpIndex("valuePackage") 33 valueTypeIndex = mapRegex.SubexpIndex("valueType") 34 } 35 36 func (b *Bindings) GenerateGoBindings(baseDir string) error { 37 store := b.db.store 38 var obfuscatedBindings map[string]int 39 if b.obfuscate { 40 obfuscatedBindings = b.db.UpdateObfuscatedCallMap() 41 } 42 for packageName, structs := range store { 43 packageDir := filepath.Join(baseDir, packageName) 44 err := fs.Mkdir(packageDir) 45 if err != nil { 46 return err 47 } 48 for structName, methods := range structs { 49 var jsoutput bytes.Buffer 50 jsoutput.WriteString(`// @ts-check 51 // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL 52 // This file is automatically generated. DO NOT EDIT 53 `) 54 var tsBody bytes.Buffer 55 var tsContent bytes.Buffer 56 tsContent.WriteString(`// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL 57 // This file is automatically generated. DO NOT EDIT 58 `) 59 // Sort the method names alphabetically 60 methodNames := make([]string, 0, len(methods)) 61 for methodName := range methods { 62 methodNames = append(methodNames, methodName) 63 } 64 sort.Strings(methodNames) 65 66 var importNamespaces slicer.StringSlicer 67 for _, methodName := range methodNames { 68 // Get the method details 69 methodDetails := methods[methodName] 70 71 // Generate JS 72 var args slicer.StringSlicer 73 for count := range methodDetails.Inputs { 74 arg := fmt.Sprintf("arg%d", count+1) 75 args.Add(arg) 76 } 77 argsString := args.Join(", ") 78 jsoutput.WriteString(fmt.Sprintf("\nexport function %s(%s) {", methodName, argsString)) 79 jsoutput.WriteString("\n") 80 if b.obfuscate { 81 id := obfuscatedBindings[strings.Join([]string{packageName, structName, methodName}, ".")] 82 jsoutput.WriteString(fmt.Sprintf(" return ObfuscatedCall(%d, [%s]);", id, argsString)) 83 } else { 84 jsoutput.WriteString(fmt.Sprintf(" return window['go']['%s']['%s']['%s'](%s);", packageName, structName, methodName, argsString)) 85 } 86 jsoutput.WriteString("\n}\n") 87 88 // Generate TS 89 tsBody.WriteString(fmt.Sprintf("\nexport function %s(", methodName)) 90 91 args.Clear() 92 for count, input := range methodDetails.Inputs { 93 arg := fmt.Sprintf("arg%d", count+1) 94 entityName := entityFullReturnType(input.TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces) 95 args.Add(arg + ":" + goTypeToTypescriptType(entityName, &importNamespaces)) 96 } 97 tsBody.WriteString(args.Join(",") + "):") 98 // now build Typescript return types 99 // If there is no return value or only returning error, TS returns Promise<void> 100 // If returning single value, TS returns Promise<type> 101 // If returning single value or error, TS returns Promise<type> 102 // If returning two values, TS returns Promise<type1|type2> 103 // Otherwise, TS returns Promise<type1> (instead of throwing Go error?) 104 var returnType string 105 if methodDetails.OutputCount() == 0 { 106 returnType = "Promise<void>" 107 } else if methodDetails.OutputCount() == 1 && methodDetails.Outputs[0].TypeName == "error" { 108 returnType = "Promise<void>" 109 } else { 110 outputTypeName := entityFullReturnType(methodDetails.Outputs[0].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces) 111 firstType := goTypeToTypescriptType(outputTypeName, &importNamespaces) 112 returnType = "Promise<" + firstType 113 if methodDetails.OutputCount() == 2 && methodDetails.Outputs[1].TypeName != "error" { 114 outputTypeName = entityFullReturnType(methodDetails.Outputs[1].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces) 115 secondType := goTypeToTypescriptType(outputTypeName, &importNamespaces) 116 returnType += "|" + secondType 117 } 118 returnType += ">" 119 } 120 tsBody.WriteString(returnType + ";\n") 121 } 122 123 importNamespaces.Deduplicate() 124 importNamespaces.Each(func(namespace string) { 125 tsContent.WriteString("import {" + namespace + "} from '../models';\n") 126 }) 127 tsContent.WriteString(tsBody.String()) 128 129 jsfilename := filepath.Join(packageDir, structName+".js") 130 err = os.WriteFile(jsfilename, jsoutput.Bytes(), 0o755) 131 if err != nil { 132 return err 133 } 134 tsfilename := filepath.Join(packageDir, structName+".d.ts") 135 err = os.WriteFile(tsfilename, tsContent.Bytes(), 0o755) 136 if err != nil { 137 return err 138 } 139 } 140 } 141 err := b.WriteModels(baseDir) 142 if err != nil { 143 return err 144 } 145 return nil 146 } 147 148 func fullyQualifiedName(packageName string, typeName string) string { 149 if len(packageName) > 0 { 150 return packageName + "." + typeName 151 } 152 153 switch true { 154 case len(typeName) == 0: 155 return "" 156 case typeName == "interface{}" || typeName == "interface {}": 157 return "any" 158 case typeName == "string": 159 return "string" 160 case typeName == "error": 161 return "Error" 162 case 163 strings.HasPrefix(typeName, "int"), 164 strings.HasPrefix(typeName, "uint"), 165 strings.HasPrefix(typeName, "float"): 166 return "number" 167 case typeName == "bool": 168 return "boolean" 169 default: 170 return "any" 171 } 172 } 173 174 func arrayifyValue(valueArray string, valueType string) string { 175 if len(valueArray) == 0 { 176 return valueType 177 } 178 179 return "Array<" + valueType + ">" 180 } 181 182 func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) string { 183 matches := mapRegex.FindStringSubmatch(input) 184 keyPackage := matches[keyPackageIndex] 185 keyType := matches[keyTypeIndex] 186 valueArray := matches[valueArrayIndex] 187 valuePackage := matches[valuePackageIndex] 188 valueType := matches[valueTypeIndex] 189 // fmt.Printf("input=%s, keyPackage=%s, keyType=%s, valueArray=%s, valuePackage=%s, valueType=%s\n", 190 // input, 191 // keyPackage, 192 // keyType, 193 // valueArray, 194 // valuePackage, 195 // valueType) 196 197 // byte array is special case 198 if valueArray == "[]" && valueType == "byte" { 199 return "string" 200 } 201 202 // if any packages, make sure they're saved 203 if len(keyPackage) > 0 { 204 importNamespaces.Add(keyPackage) 205 } 206 207 if len(valuePackage) > 0 { 208 importNamespaces.Add(valuePackage) 209 } 210 211 key := fullyQualifiedName(keyPackage, keyType) 212 var value string 213 if strings.HasPrefix(valueType, "map") { 214 value = goTypeToJSDocType(valueType, importNamespaces) 215 } else { 216 value = fullyQualifiedName(valuePackage, valueType) 217 } 218 219 if len(key) > 0 { 220 return fmt.Sprintf("{[key: %s]: %s}", key, arrayifyValue(valueArray, value)) 221 } 222 223 return arrayifyValue(valueArray, value) 224 } 225 226 func goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer) string { 227 return goTypeToJSDocType(input, importNamespaces) 228 } 229 230 func entityFullReturnType(input, prefix, suffix string, importNamespaces *slicer.StringSlicer) string { 231 if strings.ContainsRune(input, '.') { 232 nameSpace, returnType := getSplitReturn(input) 233 return nameSpace + "." + prefix + returnType + suffix 234 } 235 236 return input 237 }