diff --git a/v3/examples/binding/bindings.js b/v3/examples/binding/bindings_main.js similarity index 72% rename from v3/examples/binding/bindings.js rename to v3/examples/binding/bindings_main.js index 1f8ce7495..aad1983c0 100644 --- a/v3/examples/binding/bindings.js +++ b/v3/examples/binding/bindings_main.js @@ -1,3 +1,7 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + function GreetService(method) { return { packageName: "main", @@ -9,17 +13,17 @@ function GreetService(method) { /** * GreetService.Greet - * Greet someone + * * @param name {string} * @returns {Promise} - */ + **/ function Greet(name) { return wails.Call(GreetService("Greet", name)); } window.go = window.go || {}; -Object.window.go.main = { +window.go.main = { GreetService: { Greet, - } + }, }; diff --git a/v3/internal/commands/bindings.go b/v3/internal/commands/bindings.go index cf20bed49..1c89544da 100644 --- a/v3/internal/commands/bindings.go +++ b/v3/internal/commands/bindings.go @@ -1,34 +1,15 @@ package commands +import "github.com/wailsapp/wails/v3/internal/parser" + type GenerateBindingsOptions struct { + Silent bool `name:"silent" description:"Silent mode"` ModelsFilename string `name:"m" description:"The filename for the models file" default:"models.ts"` BindingsFilename string `name:"b" description:"The filename for the bindings file" default:"bindings.js"` - ProjectDirectory string `name:"d" description:"The project directory" default:"."` + ProjectDirectory string `name:"p" description:"The project directory" default:"."` + OutputDirectory string `name:"d" description:"The output directory" default:"."` } func GenerateBindings(options *GenerateBindingsOptions) error { - - // parserContext, err := parser.ParseDirectory(options.ProjectDirectory) - // if err != nil { - // return fmt.Errorf("error parsing project: %v", err) - // } - // - // // Generate models - // modelsData, err := parser.GenerateModels(parserContext) - // if err != nil { - // return fmt.Errorf("error generating models: %v", err) - // } - // - // var modelsFileData bytes.Buffer - // modelsFileData.WriteString(`// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL - //// This file is automatically generated. DO NOT EDIT - // - //`) - // modelsFileData.Write(modelsData) - // err = os.WriteFile(options.ModelsFilename, modelsFileData.Bytes(), 0755) - // if err != nil { - // return fmt.Errorf("error writing models file: %v", err) - // } - // println("Generated models file '" + options.ModelsFilename + "'") - return nil + return parser.GenerateBindingsAndModels(options.ProjectDirectory, options.OutputDirectory) } diff --git a/v3/internal/parser/bindings.go b/v3/internal/parser/bindings.go index 25101c1b2..793c83635 100644 --- a/v3/internal/parser/bindings.go +++ b/v3/internal/parser/bindings.go @@ -153,70 +153,66 @@ func normalisePackageNames(packageNames []string) map[string]string { return result } -func GenerateBindings(bindings map[string]map[string][]*BoundMethod) string { +func GenerateBindings(bindings map[string]map[string][]*BoundMethod) map[string]string { + + var result = make(map[string]string) - var result string - var allModels []string var normalisedPackageNames = normalisePackageNames(lo.Keys(bindings)) // sort the bindings keys packageNames := lo.Keys(bindings) sort.Strings(packageNames) for _, packageName := range packageNames { + var allModels []string + packageBindings := bindings[packageName] structNames := lo.Keys(packageBindings) sort.Strings(structNames) for _, structName := range structNames { - result += GenerateHelper(normalisedPackageNames[packageName], structName) + result[normalisedPackageNames[packageName]] += GenerateHelper(normalisedPackageNames[packageName], structName) methods := packageBindings[structName] sort.Slice(methods, func(i, j int) bool { return methods[i].Name < methods[j].Name }) for _, method := range methods { thisBinding, models := GenerateBinding(structName, method) - result += thisBinding + result[normalisedPackageNames[packageName]] += thisBinding allModels = append(allModels, models...) } } - } - result += ` + result[normalisedPackageNames[packageName]] += ` window.go = window.go || {}; ` - // Iterate over the sorted bindings keys - packageNames = lo.Keys(bindings) - for _, packageName := range packageNames { - packageBindings := bindings[packageName] - structNames := lo.Keys(packageBindings) - sort.Strings(structNames) - result += "window.go." + normalisedPackageNames[packageName] + " = {\n" + // Iterate over the sorted struct keys + result[normalisedPackageNames[packageName]] += "window.go." + normalisedPackageNames[packageName] + " = {\n" for _, structName := range structNames { - result += " " + structName + ": {\n" + result[normalisedPackageNames[packageName]] += " " + structName + ": {\n" methods := packageBindings[structName] sort.Slice(methods, func(i, j int) bool { return methods[i].Name < methods[j].Name }) for _, method := range methods { - result += " " + method.Name + ",\n" + result[normalisedPackageNames[packageName]] += " " + method.Name + ",\n" } - result += " },\n" + result[normalisedPackageNames[packageName]] += " },\n" } - result += "};\n" - } + result[normalisedPackageNames[packageName]] += "};\n" - // add imports - if len(allModels) > 0 { - allModels := lo.Uniq(allModels) - var models []string - for _, model := range allModels { - models = append(models, normalisedPackageNames[model]) + // add imports + if len(allModels) > 0 { + allModels := lo.Uniq(allModels) + var models []string + for _, model := range allModels { + models = append(models, normalisedPackageNames[model]) + } + sort.Strings(models) + result[normalisedPackageNames[packageName]] += "\n" + imports := "import {" + strings.Join(models, ", ") + "} from './models';\n" + result[normalisedPackageNames[packageName]] = imports + "\n" + result[normalisedPackageNames[packageName]] } - sort.Strings(models) - result += "\n" - imports := "import {" + strings.Join(models, ", ") + "} from './models';\n" - result = imports + "\n" + result - } - result = header + result + result[normalisedPackageNames[packageName]] = header + result[normalisedPackageNames[packageName]] + } return result } diff --git a/v3/internal/parser/bindings_test.go b/v3/internal/parser/bindings_test.go index 9b9054431..f6f309b10 100644 --- a/v3/internal/parser/bindings_test.go +++ b/v3/internal/parser/bindings_test.go @@ -1,30 +1,94 @@ package parser import ( + "embed" + "io/fs" "os" "testing" "github.com/google/go-cmp/cmp" ) +//go:embed testdata +var testdata embed.FS + +func getFile(filename string) string { + // get the file from the testdata FS + file, err := fs.ReadFile(testdata, filename) + if err != nil { + panic(err) + } + return string(file) +} + func TestGenerateBindings(t *testing.T) { - tests := []string{ - "struct_literal_single", - "struct_literal_multiple", - "struct_literal_multiple_other", - "struct_literal_multiple_files", - "function_single", - "function_from_imported_package", - "variable_single", - "variable_single_from_function", - "variable_single_from_other_function", + tests := []struct { + dir string + want map[string]string + }{ + { + "testdata/function_single", + map[string]string{ + "main": getFile("testdata/function_single/bindings_main.js"), + }, + }, + { + "testdata/function_from_imported_package", + map[string]string{ + "main": getFile("testdata/function_from_imported_package/bindings_main.js"), + "services": getFile("testdata/function_from_imported_package/bindings_services.js"), + }, + }, + { + "testdata/variable_single", + map[string]string{ + "main": getFile("testdata/variable_single/bindings_main.js"), + }, + }, + { + "testdata/variable_single_from_function", + map[string]string{ + "main": getFile("testdata/variable_single_from_function/bindings_main.js"), + }, + }, + { + "testdata/variable_single_from_other_function", + map[string]string{ + "main": getFile("testdata/variable_single_from_other_function/bindings_main.js"), + "services": getFile("testdata/variable_single_from_other_function/bindings_services.js"), + }, + }, + { + "testdata/struct_literal_single", + map[string]string{ + "main": getFile("testdata/struct_literal_single/bindings_main.js"), + }, + }, + { + "testdata/struct_literal_multiple", + map[string]string{ + "main": getFile("testdata/struct_literal_multiple/bindings_main.js"), + }, + }, + { + "testdata/struct_literal_multiple_other", + map[string]string{ + "main": getFile("testdata/struct_literal_multiple_other/bindings_main.js"), + "services": getFile("testdata/struct_literal_multiple_other/bindings_services.js"), + }, + }, + { + "testdata/struct_literal_multiple_files", + map[string]string{ + "main": getFile("testdata/struct_literal_multiple_files/bindings_main.js"), + }, + }, } - for _, projectDir := range tests { - t.Run(projectDir, func(t *testing.T) { - projectDir = "testdata/" + projectDir + for _, tt := range tests { + t.Run(tt.dir, func(t *testing.T) { // Run parser on directory - project, err := ParseProject(projectDir) + project, err := ParseProject(tt.dir) if err != nil { t.Errorf("ParseProject() error = %v", err) return @@ -33,28 +97,27 @@ func TestGenerateBindings(t *testing.T) { // Generate Bindings got := GenerateBindings(project.BoundMethods) - // Load bindings.js from project directory - expected, err := os.ReadFile(projectDir + "/bindings.js") - if err != nil { - // Write file to project directory - err = os.WriteFile(projectDir+"/bindings.got.js", []byte(got), 0644) - if err != nil { - t.Errorf("os.WriteFile() error = %v", err) + for name, binding := range got { + // check if the binding is in the expected bindings + expected, ok := tt.want[name] + if !ok { + err = os.WriteFile(tt.dir+"/bindings_"+name+".got.js", []byte(binding), 0644) + if err != nil { + t.Errorf("os.WriteFile() error = %v", err) + return + } + t.Errorf("GenerateBindings() unexpected binding = %v", name) return } - t.Errorf("os.ReadFile() error = %v", err) - return - } - - // Compare - if diff := cmp.Diff(string(expected), got); diff != "" { - // Write file to project directory - err = os.WriteFile(projectDir+"/bindings.got.js", []byte(got), 0644) - if err != nil { - t.Errorf("os.WriteFile() error = %v", err) - return + // compare the binding + if diff := cmp.Diff(expected, binding); diff != "" { + err = os.WriteFile(tt.dir+"/bindings_"+name+".got.js", []byte(binding), 0644) + if err != nil { + t.Errorf("os.WriteFile() error = %v", err) + return + } + t.Fatalf("GenerateBindings() mismatch (-want +got):\n%s", diff) } - t.Fatalf("GenerateService() mismatch (-want +got):\n%s", diff) } }) } diff --git a/v3/internal/parser/models.go b/v3/internal/parser/models.go index 4d589bc71..bd0022315 100644 --- a/v3/internal/parser/models.go +++ b/v3/internal/parser/models.go @@ -25,6 +25,10 @@ func GenerateModel(wr io.Writer, def *ModelDefinitions) error { return nil } +func GenerateModels(models map[packagePath]map[structName]*StructDef) string { + return "" +} + //func GenerateClass(wr io.Writer, def *StructDef) error { // tmpl, err := template.New("class.ts.tmpl").ParseFiles("templates/class.ts.tmpl") // if err != nil { diff --git a/v3/internal/parser/parser.go b/v3/internal/parser/parser.go index d69fbe061..ba9ef2c1d 100644 --- a/v3/internal/parser/parser.go +++ b/v3/internal/parser/parser.go @@ -126,7 +126,6 @@ func ParseProject(projectPath string) (*Project, error) { if err != nil { return nil, err } - println("Parsed " + projectPath) err = result.findApplicationNewCalls(pkgs) if err != nil { return nil, err @@ -142,6 +141,51 @@ func ParseProject(projectPath string) (*Project, error) { return result, nil } +func GenerateBindingsAndModels(projectDir string, outputDir string) error { + p, err := ParseProject(projectDir) + if err != nil { + return err + } + + if p.BoundMethods == nil { + return nil + } + err = os.MkdirAll(outputDir, 0755) + if err != nil { + return err + } + generatedMethods := GenerateBindings(p.BoundMethods) + for pkg, text := range generatedMethods { + // Write the file + err = os.WriteFile(filepath.Join(outputDir, "bindings_"+pkg+".js"), []byte(text), 0644) + if err != nil { + return err + } + } + + // Generate Models + if len(p.Models) > 0 { + generatedModels := GenerateModels(p.Models) + err = os.WriteFile(filepath.Join(outputDir, "models.js"), []byte(generatedModels), 0644) + if err != nil { + return err + } + } + + absPath, err := filepath.Abs(projectDir) + if err != nil { + return err + } + println("Generated bindings and models for project: " + absPath) + absPath, err = filepath.Abs(outputDir) + if err != nil { + return err + } + println("Output directory: " + absPath) + + return nil +} + func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) { if p.packageCache[dir] != nil { return map[string]*ParsedPackage{dir: p.packageCache[dir]}, nil diff --git a/v3/internal/parser/testdata/function_from_imported_package/bindings.js b/v3/internal/parser/testdata/function_from_imported_package/bindings_main.js similarity index 64% rename from v3/internal/parser/testdata/function_from_imported_package/bindings.js rename to v3/internal/parser/testdata/function_from_imported_package/bindings_main.js index 574988d22..53a819885 100644 --- a/v3/internal/parser/testdata/function_from_imported_package/bindings.js +++ b/v3/internal/parser/testdata/function_from_imported_package/bindings_main.js @@ -2,26 +2,8 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT -import {main, services} from './models'; +import {main} from './models'; -function OtherService(method) { - return { - packageName: "services", - serviceName: "OtherService", - methodName: method, - args: Array.prototype.slice.call(arguments, 1), - }; -} - -/** - * OtherService.Yay - * - * - * @returns {Promise} - **/ -function Yay() { - return wails.Call(OtherService("Yay")); -} function GreetService(method) { return { packageName: "main", @@ -58,9 +40,4 @@ window.go.main = { NewPerson, }, }; -window.go.services = { - OtherService: { - Yay, - }, -}; diff --git a/v3/internal/parser/testdata/function_from_imported_package/bindings_services.js b/v3/internal/parser/testdata/function_from_imported_package/bindings_services.js new file mode 100644 index 000000000..48e7ed652 --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/bindings_services.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {services} from './models'; + +function OtherService(method) { + return { + packageName: "services", + serviceName: "OtherService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * OtherService.Yay + * + * + * @returns {Promise} + **/ +function Yay() { + return wails.Call(OtherService("Yay")); +} + +window.go = window.go || {}; +window.go.services = { + OtherService: { + Yay, + }, +}; + diff --git a/v3/internal/parser/testdata/function_single/bindings.js b/v3/internal/parser/testdata/function_single/bindings_main.js similarity index 100% rename from v3/internal/parser/testdata/function_single/bindings.js rename to v3/internal/parser/testdata/function_single/bindings_main.js diff --git a/v3/internal/parser/testdata/struct_literal_multiple/bindings.js b/v3/internal/parser/testdata/struct_literal_multiple/bindings_main.js similarity index 100% rename from v3/internal/parser/testdata/struct_literal_multiple/bindings.js rename to v3/internal/parser/testdata/struct_literal_multiple/bindings_main.js diff --git a/v3/internal/parser/testdata/struct_literal_multiple_files/bindings.js b/v3/internal/parser/testdata/struct_literal_multiple_files/bindings_main.js similarity index 100% rename from v3/internal/parser/testdata/struct_literal_multiple_files/bindings.js rename to v3/internal/parser/testdata/struct_literal_multiple_files/bindings_main.js diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/bindings.js b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_main.js similarity index 64% rename from v3/internal/parser/testdata/struct_literal_multiple_other/bindings.js rename to v3/internal/parser/testdata/struct_literal_multiple_other/bindings_main.js index 574988d22..53a819885 100644 --- a/v3/internal/parser/testdata/struct_literal_multiple_other/bindings.js +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_main.js @@ -2,26 +2,8 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT -import {main, services} from './models'; +import {main} from './models'; -function OtherService(method) { - return { - packageName: "services", - serviceName: "OtherService", - methodName: method, - args: Array.prototype.slice.call(arguments, 1), - }; -} - -/** - * OtherService.Yay - * - * - * @returns {Promise} - **/ -function Yay() { - return wails.Call(OtherService("Yay")); -} function GreetService(method) { return { packageName: "main", @@ -58,9 +40,4 @@ window.go.main = { NewPerson, }, }; -window.go.services = { - OtherService: { - Yay, - }, -}; diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_services.js b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_services.js new file mode 100644 index 000000000..48e7ed652 --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_services.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {services} from './models'; + +function OtherService(method) { + return { + packageName: "services", + serviceName: "OtherService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * OtherService.Yay + * + * + * @returns {Promise} + **/ +function Yay() { + return wails.Call(OtherService("Yay")); +} + +window.go = window.go || {}; +window.go.services = { + OtherService: { + Yay, + }, +}; + diff --git a/v3/internal/parser/testdata/struct_literal_single/bindings.js b/v3/internal/parser/testdata/struct_literal_single/bindings_main.js similarity index 100% rename from v3/internal/parser/testdata/struct_literal_single/bindings.js rename to v3/internal/parser/testdata/struct_literal_single/bindings_main.js diff --git a/v3/internal/parser/testdata/variable_single/bindings.js b/v3/internal/parser/testdata/variable_single/bindings_main.js similarity index 100% rename from v3/internal/parser/testdata/variable_single/bindings.js rename to v3/internal/parser/testdata/variable_single/bindings_main.js diff --git a/v3/internal/parser/testdata/variable_single_from_function/bindings.js b/v3/internal/parser/testdata/variable_single_from_function/bindings_main.js similarity index 100% rename from v3/internal/parser/testdata/variable_single_from_function/bindings.js rename to v3/internal/parser/testdata/variable_single_from_function/bindings_main.js diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/bindings.js b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_main.js similarity index 64% rename from v3/internal/parser/testdata/variable_single_from_other_function/bindings.js rename to v3/internal/parser/testdata/variable_single_from_other_function/bindings_main.js index 574988d22..53a819885 100644 --- a/v3/internal/parser/testdata/variable_single_from_other_function/bindings.js +++ b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_main.js @@ -2,26 +2,8 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT -import {main, services} from './models'; +import {main} from './models'; -function OtherService(method) { - return { - packageName: "services", - serviceName: "OtherService", - methodName: method, - args: Array.prototype.slice.call(arguments, 1), - }; -} - -/** - * OtherService.Yay - * - * - * @returns {Promise} - **/ -function Yay() { - return wails.Call(OtherService("Yay")); -} function GreetService(method) { return { packageName: "main", @@ -58,9 +40,4 @@ window.go.main = { NewPerson, }, }; -window.go.services = { - OtherService: { - Yay, - }, -}; diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/bindings_services.js b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_services.js new file mode 100644 index 000000000..48e7ed652 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_services.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {services} from './models'; + +function OtherService(method) { + return { + packageName: "services", + serviceName: "OtherService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * OtherService.Yay + * + * + * @returns {Promise} + **/ +function Yay() { + return wails.Call(OtherService("Yay")); +} + +window.go = window.go || {}; +window.go.services = { + OtherService: { + Yay, + }, +}; +