Create bindings file per package

Improved bindings tests
This commit is contained in:
Lea Anthony 2023-03-07 18:22:33 +11:00
commit 7340247e25
18 changed files with 286 additions and 167 deletions

View file

@ -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<string>}
*/
**/
function Greet(name) {
return wails.Call(GreetService("Greet", name));
}
window.go = window.go || {};
Object.window.go.main = {
window.go.main = {
GreetService: {
Greet,
}
},
};

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}
})
}

View file

@ -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 {

View file

@ -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

View file

@ -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<services.Address | null>}
**/
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,
},
};

View file

@ -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<services.Address | null>}
**/
function Yay() {
return wails.Call(OtherService("Yay"));
}
window.go = window.go || {};
window.go.services = {
OtherService: {
Yay,
},
};

View file

@ -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<services.Address | null>}
**/
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,
},
};

View file

@ -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<services.Address | null>}
**/
function Yay() {
return wails.Call(OtherService("Yay"));
}
window.go = window.go || {};
window.go.services = {
OtherService: {
Yay,
},
};

View file

@ -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<services.Address | null>}
**/
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,
},
};

View file

@ -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<services.Address | null>}
**/
function Yay() {
return wails.Call(OtherService("Yay"));
}
window.go = window.go || {};
window.go.services = {
OtherService: {
Yay,
},
};