5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-10 15:39:46 +08:00

Merge branch 'feature/v2-mac' of github.com:wailsapp/wails into feature/v2-mac

This commit is contained in:
Lea Anthony 2020-11-15 10:18:33 +11:00
commit cd03b4b633
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
42 changed files with 2405 additions and 38 deletions

1
.gitignore vendored
View File

@ -25,3 +25,4 @@ v2/test/hidden/icon.png
v2/internal/ffenestri/runtime.c
v2/internal/runtime/assets/desktop.js
v2/test/kitchensink/frontend/public/bundle.*
v2/pkg/parser/testproject/frontend/wails

View File

@ -0,0 +1,91 @@
package generate
import (
"io"
"time"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/parser"
)
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("generate", "Code Generation Tools")
// Backend API
backendAPI := command.NewSubCommand("module", "Generates a JS module for the frontend to interface with the backend")
// Quiet Init
quiet := false
backendAPI.BoolFlag("q", "Supress output to console", &quiet)
backendAPI.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
app.PrintBanner()
logger.Print("Generating Javascript module for Go code...")
// Start Time
start := time.Now()
p, err := parser.GenerateWailsFrontendPackage()
if err != nil {
return err
}
logger.Println("done.")
logger.Println("")
elapsed := time.Since(start)
packages := p.Packages
// Print report
for _, pkg := range p.Packages {
if pkg.ShouldGenerate() {
logPackage(pkg, logger)
}
}
logger.Println("%d packages parsed in %s.", len(packages), elapsed)
return nil
})
return nil
}
func logPackage(pkg *parser.Package, logger *clilogger.CLILogger) {
logger.Println("Processed Go package '" + pkg.Gopackage.Name + "' as '" + pkg.Name + "'")
for _, strct := range pkg.Structs() {
logger.Println("")
logger.Println(" Processed struct '" + strct.Name + "'")
if strct.IsBound {
for _, method := range strct.Methods {
logger.Println(" Bound method '" + method.Name + "'")
}
}
if strct.IsUsedAsData {
for _, field := range strct.Fields {
if !field.Ignored {
logger.Print(" Processed ")
if field.IsOptional {
logger.Print("optional ")
}
logger.Println("field '" + field.Name + "' as '" + field.JSName() + "'")
}
}
}
}
logger.Println("")
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
}

View File

@ -32,13 +32,17 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
command.StringFlag("n", "Name of project", &projectName)
// Setup project directory
projectDirectory := "."
projectDirectory := ""
command.StringFlag("d", "Project directory", &projectDirectory)
// Quiet Init
quiet := false
command.BoolFlag("q", "Supress output to console", &quiet)
// VSCode project files
vscode := false
command.BoolFlag("vscode", "Generate VSCode project files", &vscode)
// List templates
list := false
command.BoolFlag("l", "List templates", &list)
@ -87,6 +91,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
GenerateVSCode: vscode,
}
return initProject(options)
@ -110,6 +115,14 @@ func initProject(options *templates.Options) error {
// Output stats
elapsed := time.Since(start)
options.Logger.Println("")
options.Logger.Println("Project Name: " + options.ProjectName)
options.Logger.Println("Project Directory: " + options.TargetDir)
options.Logger.Println("Project Template: " + options.TemplateName)
options.Logger.Println("")
if options.GenerateVSCode {
options.Logger.Println("VSCode config files generated.")
}
options.Logger.Println("")
options.Logger.Println(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
options.Logger.Println("")

View File

@ -7,6 +7,7 @@ import (
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
)
@ -37,6 +38,11 @@ func main() {
fatal(err.Error())
}
err = generate.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = app.Run()
if err != nil {
println("\n\nERROR: " + err.Error())

View File

@ -3,19 +3,23 @@ module github.com/wailsapp/wails/v2
go 1.13
require (
github.com/davecgh/go-spew v1.1.1
github.com/fatih/structtag v1.2.0
github.com/fsnotify/fsnotify v1.4.9
github.com/imdario/mergo v0.3.11
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/gosod v0.0.4
github.com/leaanthony/slicer v1.4.1
github.com/leaanthony/slicer v1.5.0
github.com/matryer/is v1.4.0
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/olekukonko/tablewriter v0.0.4
github.com/pkg/errors v0.9.1
github.com/tdewolff/minify v2.3.6+incompatible
github.com/tdewolff/minify/v2 v2.9.5
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/test v1.0.6 // indirect
github.com/xyproto/xpm v1.2.1
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
nhooyr.io/websocket v1.8.6

View File

@ -1,8 +1,8 @@
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@ -44,13 +44,12 @@ github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/slicer v1.4.1 h1:X/SmRIDhkUAolP79mSTO0jTcVX1k504PJBqvV6TwP0w=
github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
@ -63,23 +62,19 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tdewolff/minify v1.1.0 h1:nxHQi1ML+g3ZbZHffiZ6eC7vMqNvSRfX3KB5Y5y/kfw=
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/minify/v2 v2.9.5 h1:+fHvqLencVdv14B+zgxQGhetF9qXl/nRTN/1mcyQwpM=
github.com/tdewolff/minify/v2 v2.9.5/go.mod h1:jshtBj/uUJH6JX1fuxTLnnHOA1RVJhF5MM+leJzDKb4=
github.com/tdewolff/parse v1.1.0 h1:tMjj9GCK8zzwjWyxdZ4pabzdWO1VG+G3bvCnG6aUIyQ=
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/parse/v2 v2.5.3 h1:fnPIstKgEfxd3+wwHnH73sAYydsR0o/jYhcQ6c5PkrA=
github.com/tdewolff/parse/v2 v2.5.3/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=

View File

@ -68,6 +68,8 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
boundMethod.Inputs = inputs
// Iterate outputs
// TODO: Determine what to do about limiting return types
// especially around errors.
outputParamCount := methodType.NumOut()
var outputs []*Parameter
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {

View File

@ -20,6 +20,17 @@ func LocalDirectory() string {
return filepath.Dir(thisFile)
}
// RelativeToCwd returns an absolute path based on the cwd
// and the given relative path
func RelativeToCwd(relativePath string) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(cwd, relativePath), nil
}
// Mkdir will create the given directory
func Mkdir(dirname string) error {
return os.Mkdir(dirname, 0755)
@ -169,3 +180,23 @@ func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
})
return &result, err
}
func DirIsEmpty(dir string) (bool, error) {
if !DirExists(dir) {
return false, fmt.Errorf("DirIsEmpty called with a non-existant directory: %s", dir)
}
// CREDIT: https://stackoverflow.com/a/30708914/8325411
f, err := os.Open(dir)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1) // Or f.Readdir(1)
if err == io.EOF {
return true, nil
}
return false, err // Either not empty or error, suits both cases
}

View File

@ -65,7 +65,6 @@ func ParseProject(projectPath string) (BoundStructs, error) {
var wailsPkgVar = ""
ast.Inspect(file, func(n ast.Node) bool {
var s string
switch x := n.(type) {
// Parse import declarations
case *ast.ImportSpec:

View File

@ -0,0 +1,29 @@
package operatingsystem
import (
"fmt"
"golang.org/x/sys/windows/registry"
)
func platformInfo() (*OS, error) {
// Default value
var result OS
result.ID = "Unknown"
result.Name = "Windows"
result.Version = "Unknown"
// Credit: https://stackoverflow.com/a/33288328
// Ignore errors as it isn't a showstopper
key, _ := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
defer key.Close()
fmt.Printf("%+v\n", key)
// Ignore errors as it isn't a showstopper
productName, _, _ := key.GetStringValue("ProductName")
fmt.Println(productName)
return nil, nil
}

View File

@ -2,14 +2,20 @@
package system
import (
"fmt"
"syscall"
)
import "github.com/wailsapp/wails/v2/internal/system/operatingsystem"
func (i *Info) discover() {
dll := syscall.MustLoadDLL("kernel32.dll")
p := dll.MustFindProc("GetVersion")
v, _, _ := p.Call()
fmt.Printf("Windows version %d.%d (Build %d)\n", byte(v), uint8(v>>8), uint16(v>>16))
func (i *Info) discover() error {
var err error
osinfo, err := operatingsystem.Info()
if err != nil {
return err
}
i.OS = osinfo
// dll := syscall.MustLoadDLL("kernel32.dll")
// p := dll.MustFindProc("GetVersion")
// v, _, _ := p.Call()
// fmt.Printf("Windows version %d.%d (Build %d)\n", byte(v), uint8(v>>8), uint16(v>>16))
return nil
}

View File

@ -0,0 +1,27 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Wails: Debug {{.ProjectName}} (Desktop)",
"type": "go",
"request": "launch",
"mode": "exec",
"program": "${workspaceFolder}/{{.PathToDesktopBinary}}",
"preLaunchTask": "build_desktop",
"cwd": "",
"env": {},
"args": []
},
{
"name": "Wails: Debug {{.ProjectName}} (Server)",
"type": "go",
"request": "launch",
"mode": "exec",
"program": "${workspaceFolder}/{{.PathToServerBinary}}",
"preLaunchTask": "build_server",
"cwd": "",
"env": {},
"args": []
},
]
}

View File

@ -0,0 +1,23 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build_desktop",
"type": "shell",
"options": {
"cwd": "{{.TargetDir}}"
},
"command": "wails build"
},
{
"label": "build_server",
"type": "shell",
"options": {
"cwd": "{{.TargetDir}}"
},
"command": "wails build -t server"
},
]
}

View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/leaanthony/gosod"
@ -36,6 +37,9 @@ type Options struct {
BinaryName string
TargetDir string
Logger *clilogger.CLILogger
GenerateVSCode bool
PathToDesktopBinary string
PathToServerBinary string
}
// Template holds data relating to a template
@ -162,9 +166,24 @@ func Install(options *Options) error {
}
// Did the user want to install in current directory?
if options.TargetDir == "." {
if options.TargetDir == "" {
// If the current directory is empty, use it
isEmpty, err := fs.DirIsEmpty(cwd)
if err != nil {
return err
}
if isEmpty {
// Yes - use cwd
options.TargetDir = cwd
} else {
options.TargetDir = filepath.Join(cwd, options.ProjectName)
if fs.DirExists(options.TargetDir) {
return fmt.Errorf("cannot create project directory. Dir exists: %s", options.TargetDir)
}
}
} else {
// Get the absolute path of the given directory
targetDir, err := filepath.Abs(filepath.Join(cwd, options.TargetDir))
@ -213,7 +232,11 @@ func Install(options *Options) error {
return err
}
// Calculate the directory name
err = generateIDEFiles(options)
if err != nil {
return err
}
return nil
}
@ -243,3 +266,40 @@ func OutputList(logger *clilogger.CLILogger) error {
table.Render()
return nil
}
func generateIDEFiles(options *Options) error {
if options.GenerateVSCode {
return generateVSCodeFiles(options)
}
return nil
}
func generateVSCodeFiles(options *Options) error {
targetDir := filepath.Join(options.TargetDir, ".vscode")
sourceDir := fs.RelativePath(filepath.Join("./ides/vscode"))
// Use Gosod to install the template
installer, err := gosod.TemplateDir(sourceDir)
if err != nil {
return err
}
binaryName := filepath.Base(options.TargetDir)
if runtime.GOOS == "windows" {
// yay windows
binaryName += ".exe"
}
options.PathToDesktopBinary = filepath.Join("build", runtime.GOOS, "desktop", binaryName)
options.PathToServerBinary = filepath.Join("build", runtime.GOOS, "server", binaryName)
err = installer.Extract(targetDir, options)
if err != nil {
return err
}
return nil
}

View File

@ -8,6 +8,7 @@ import (
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/parser"
)
// Mode is the type used to indicate the build modes
@ -89,6 +90,13 @@ func Build(options *Options) (string, error) {
// Initialise Builder
builder.SetProjectData(projectData)
// Generate Frontend JS Package
outputLogger.Println(" - Generating Backend JS Package")
// Ignore the parser report coming back
_, err = parser.GenerateWailsFrontendPackage()
if err != nil {
return "", err
}
if !options.IgnoreFrontend {
outputLogger.Println(" - Building Wails Frontend")
err = builder.BuildFrontend(outputLogger)
@ -115,6 +123,7 @@ func Build(options *Options) (string, error) {
return "", err
}
outputLogger.Println("done.")
// Do we need to pack the app?
if options.Pack {

View File

@ -0,0 +1,6 @@
package build
func packageApplication(options *Options) error {
// TBD
return nil
}

View File

@ -0,0 +1,50 @@
package parser
import "go/ast"
func (p *Package) getApplicationVariableName(file *ast.File, wailsImportName string) string {
// Iterate through the whole file looking for the application name
applicationVariableName := ""
// Inspect the file
ast.Inspect(file, func(n ast.Node) bool {
// Parse Assignments looking for application name
if assignStmt, ok := n.(*ast.AssignStmt); ok {
// Check the RHS is of the form:
// `app := wails.CreateApp()` or
// `app := wails.CreateAppWithOptions`
for _, rhs := range assignStmt.Rhs {
ce, ok := rhs.(*ast.CallExpr)
if !ok {
continue
}
se, ok := ce.Fun.(*ast.SelectorExpr)
if !ok {
continue
}
i, ok := se.X.(*ast.Ident)
if !ok {
continue
}
// Have we found the wails import name?
if i.Name == wailsImportName {
// Check we are calling a function to create the app
if se.Sel.Name == "CreateApp" || se.Sel.Name == "CreateAppWithOptions" {
if len(assignStmt.Lhs) == 1 {
i, ok := assignStmt.Lhs[0].(*ast.Ident)
if ok {
// Found the app variable name
applicationVariableName = i.Name
return false
}
}
}
}
}
}
return true
})
return applicationVariableName
}

21
v2/pkg/parser/comments.go Normal file
View File

@ -0,0 +1,21 @@
package parser
import (
"go/ast"
"strings"
)
func parseComments(comments *ast.CommentGroup) []string {
var result []string
if comments == nil {
return result
}
for _, comment := range comments.List {
commentText := strings.TrimPrefix(comment.Text, "//")
result = append(result, commentText)
}
return result
}

156
v2/pkg/parser/conversion.go Normal file
View File

@ -0,0 +1,156 @@
package parser
import (
"fmt"
"strings"
"github.com/leaanthony/slicer"
)
// JSType represents a javascript type
type JSType string
const (
// JsString is a JS string
JsString JSType = "string"
// JsBoolean is a JS bool
JsBoolean = "boolean"
// JsInt is a JS number
JsInt = "number"
// JsFloat is a JS number
JsFloat = "number"
// JsArray is a JS array
JsArray = "Array"
// JsObject is a JS object
JsObject = "Object"
// JsUnsupported represents a type that cannot be converted
JsUnsupported = "*"
)
func goTypeToJS(input *Field) string {
switch input.Type {
case "string":
return "string"
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
return "number"
case "float32", "float64":
return "number"
case "bool":
return "boolean"
// case reflect.Array, reflect.Slice:
// return JsArray
// case reflect.Ptr, reflect.Struct, reflect.Map, reflect.Interface:
// return JsObject
case "struct":
return input.Struct.Name
default:
fmt.Printf("Unsupported input to goTypeToJS: %+v", input)
return "*"
}
}
// goTypeToTS converts the given field into a Typescript type
// The pkgName is the package that the field is being output in.
// This is used to ensure we don't qualify local structs.
func goTypeToTS(input *Field, pkgName string) string {
var result string
switch input.Type {
case "string":
result = "string"
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
result = "number"
case "float32", "float64":
result = "number"
case "bool":
result = "boolean"
case "struct":
if input.Struct.Package.Name != "" {
if input.Struct.Package.Name != pkgName {
result = input.Struct.Package.Name + "."
}
}
result += input.Struct.Name
// case reflect.Array, reflect.Slice:
// return string(JsArray)
// case reflect.Ptr, reflect.Struct:
// fqt := input.Type().String()
// return strings.Split(fqt, ".")[1]
// case reflect.Map, reflect.Interface:
// return string(JsObject)
default:
fmt.Printf("Unsupported input to goTypeToTS: %+v", input)
return JsUnsupported
}
if input.IsArray {
result = result + "[]"
}
return result
}
func goTypeToTSDeclaration(input *Field, pkgName string) string {
var result string
switch input.Type {
case "string":
result = "string"
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
result = "number"
case "float32", "float64":
result = "number"
case "bool":
result = "boolean"
case "struct":
if input.Struct.Package.Name != "" {
if input.Struct.Package.Name != pkgName {
result = `import("./_` + input.Struct.Package.Name + `").`
}
}
result += input.Struct.Name
// case reflect.Array, reflect.Slice:
// return string(JsArray)
// case reflect.Ptr, reflect.Struct:
// fqt := input.Type().String()
// return strings.Split(fqt, ".")[1]
// case reflect.Map, reflect.Interface:
// return string(JsObject)
default:
fmt.Printf("Unsupported input to goTypeToTS: %+v", input)
return JsUnsupported
}
if input.IsArray {
result = result + "[]"
}
return result
}
func isUnresolvedType(typeName string) bool {
switch typeName {
case "string":
return false
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
return false
case "float32", "float64":
return false
case "bool":
return false
case "struct":
return false
default:
return true
}
}
var reservedJSWords []string = []string{"abstract", "arguments", "await", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "var", "void", "volatile", "while", "with", "yield", "Array", "Date", "eval", "function", "hasOwnProperty", "Infinity", "isFinite", "isNaN", "isPrototypeOf", "length", "Math", "NaN", "Number", "Object", "prototype", "String", "toString", "undefined", "valueOf"}
var jsReservedWords *slicer.StringSlicer = slicer.String(reservedJSWords)
func isJSReservedWord(input string) bool {
return jsReservedWords.Contains(input)
}
func startsWithLowerCaseLetter(input string) bool {
firstLetter := string(input[0])
return strings.ToLower(firstLetter) == firstLetter
}

311
v2/pkg/parser/field.go Normal file
View File

@ -0,0 +1,311 @@
package parser
import (
"fmt"
"go/ast"
"strings"
"github.com/davecgh/go-spew/spew"
"github.com/fatih/structtag"
)
// Field defines a parsed struct field
type Field struct {
// Name of the field
Name string
// The type of the field.
// "struct" if it's a struct
Type string
// A pointer to the struct if the Type is "struct"
Struct *Struct
// User comments on the field
Comments []string
// Indicates if the Field is an array of type "Type"
IsArray bool
// JSON field name defined by a json tag
JSONOptions
}
type JSONOptions struct {
Name string
IsOptional bool
Ignored bool
}
// JSType returns the Javascript type for this field
func (f *Field) JSType() string {
return string(goTypeToJS(f))
}
// JSName returns the Javascript name for this field
func (f *Field) JSName() string {
if f.JSONOptions.Name != "" {
return f.JSONOptions.Name
}
return f.Name
}
// TSName returns the Typescript name for this field
func (f *Field) TSName() string {
result := f.Name
if f.JSONOptions.Name != "" {
result = f.JSONOptions.Name
}
if f.IsOptional {
result += "?"
}
return result
}
// AsTSDeclaration returns a TS definition of a single type field
func (f *Field) AsTSDeclaration(pkgName string) string {
return f.TSName() + ": " + f.TypeAsTSType(pkgName)
}
// NameForPropertyDoc returns a formatted name for the jsdoc @property declaration
func (f *Field) NameForPropertyDoc() string {
if f.IsOptional {
return "[" + f.JSName() + "]"
}
return f.JSName()
}
// TypeForPropertyDoc returns a formatted name for the jsdoc @property declaration
func (f *Field) TypeForPropertyDoc() string {
result := goTypeToJS(f)
if f.IsArray {
result += "[]"
}
return result
}
// TypeAsTSType converts the Field type to something TS wants
func (f *Field) TypeAsTSType(pkgName string) string {
var result = ""
switch f.Type {
case "string":
result = "string"
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
result = "number"
case "float32", "float64":
result = "number"
case "bool":
result = "boolean"
case "struct":
if f.Struct.Package != nil {
if f.Struct.Package.Name != pkgName {
result = f.Struct.Package.Name + "."
}
}
result = result + f.Struct.Name
default:
result = "any"
}
return result
}
func (p *Parser) parseField(file *ast.File, field *ast.Field, pkg *Package) ([]*Field, error) {
var result []*Field
var fieldType string
var strct *Struct
var isArray bool
var jsonOptions JSONOptions
// Determine type
switch t := field.Type.(type) {
case *ast.Ident:
fieldType = t.Name
unresolved := isUnresolvedType(fieldType)
// Check if this type is actually a struct
if unresolved {
// Assume it is a struct
// Parse the struct
var err error
strct, err = p.parseStruct(pkg, t.Name)
if err != nil {
return nil, err
}
if strct == nil {
fieldName := "<anonymous>"
if len(field.Names) > 0 {
fieldName = field.Names[0].Name
}
return nil, fmt.Errorf("unresolved type in field %s: %s", fieldName, fieldType)
}
fieldType = "struct"
}
case *ast.StarExpr:
fieldType = "struct"
packageName, structName, err := parseStructNameFromStarExpr(t)
if err != nil {
return nil, err
}
// If this is an external package, find it
if packageName != "" {
referencedGoPackage := pkg.getImportByName(packageName, file)
referencedPackage := p.getPackageByID(referencedGoPackage.ID)
// If we found the struct, save it as an external package reference
if referencedPackage != nil {
pkg.addExternalReference(referencedPackage)
}
// We save this to pkg anyway, because we want to know if this package
// was NOT found
pkg = referencedPackage
}
// If this is a package in our project, parse the struct!
if pkg != nil {
// Parse the struct
strct, err = p.parseStruct(pkg, structName)
if err != nil {
return nil, err
}
}
case *ast.ArrayType:
isArray = true
// Parse the Elt (There must be a better way!)
switch t := t.Elt.(type) {
case *ast.Ident:
fieldType = t.Name
case *ast.StarExpr:
fieldType = "struct"
packageName, structName, err := parseStructNameFromStarExpr(t)
if err != nil {
return nil, err
}
// If this is an external package, find it
if packageName != "" {
referencedGoPackage := pkg.getImportByName(packageName, file)
referencedPackage := p.getPackageByID(referencedGoPackage.ID)
// If we found the struct, save it as an external package reference
if referencedPackage != nil {
pkg.addExternalReference(referencedPackage)
}
// We save this to pkg anyway, because we want to know if this package
// was NOT found
pkg = referencedPackage
}
// If this is a package in our project, parse the struct!
if pkg != nil {
// Parse the struct
strct, err = p.parseStruct(pkg, structName)
if err != nil {
return nil, err
}
}
default:
// We will default to "Array<any>" for eg nested arrays
fieldType = "any"
}
default:
spew.Dump(t)
return nil, fmt.Errorf("unsupported field found in struct: %+v", t)
}
// Parse json tag if available
if field.Tag != nil {
err := parseJSONOptions(field.Tag.Value, &jsonOptions)
if err != nil {
return nil, err
}
}
// Loop over names if we have
if len(field.Names) > 0 {
for _, name := range field.Names {
// TODO: Check field names are valid in JS
if isJSReservedWord(name.Name) {
return nil, fmt.Errorf("unable to use field name %s - reserved word in Javascript", name.Name)
}
// Create a field per name
thisField := &Field{
Comments: parseComments(field.Doc),
}
thisField.Name = name.Name
thisField.Type = fieldType
thisField.Struct = strct
thisField.IsArray = isArray
thisField.JSONOptions = jsonOptions
result = append(result, thisField)
}
return result, nil
}
// When we have no name
thisField := &Field{
Comments: parseComments(field.Doc),
}
thisField.Type = fieldType
thisField.Struct = strct
thisField.IsArray = isArray
result = append(result, thisField)
return result, nil
}
func parseJSONOptions(fieldTag string, jsonOptions *JSONOptions) error {
// Remove backticks
fieldTag = strings.Trim(fieldTag, "`")
// Parse the tag
tags, err := structtag.Parse(fieldTag)
if err != nil {
return err
}
jsonTag, err := tags.Get("json")
if err != nil {
return err
}
if jsonTag == nil {
return nil
}
// Save the name
jsonOptions.Name = jsonTag.Name
// Check if this field is ignored
if jsonTag.Name == "-" {
jsonOptions.Ignored = true
}
// Check if this field is optional
if jsonTag.HasOption("omitempty") {
jsonOptions.IsOptional = true
}
return nil
}

View File

@ -0,0 +1,152 @@
package parser
import (
"fmt"
"go/ast"
)
// findBoundStructs will search through the Wails project looking
// for which structs have been bound using the `Bind()` method
func (p *Parser) findBoundStructs(pkg *Package) error {
// Iterate through the files in the package looking for the bound structs
for _, fileAst := range pkg.Gopackage.Syntax {
// Find the wails import name
wailsImportName := pkg.getWailsImportName(fileAst)
// If this file doesn't import wails, continue
if wailsImportName == "" {
continue
}
applicationVariableName := pkg.getApplicationVariableName(fileAst, wailsImportName)
if applicationVariableName == "" {
continue
}
var parseError error
ast.Inspect(fileAst, func(n ast.Node) bool {
// Parse Call expressions looking for bind calls
callExpr, ok := n.(*ast.CallExpr)
if !ok {
return true
}
// Check this is the right kind of expression (something.something())
f, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
ident, ok := f.X.(*ast.Ident)
if !ok {
return true
}
if ident.Name != applicationVariableName {
return true
}
if f.Sel.Name != "Bind" {
return true
}
if len(callExpr.Args) != 1 {
return true
}
// Work out what was bound
switch boundItem := callExpr.Args[0].(type) {
// app.Bind( someFunction() )
case *ast.CallExpr:
switch fn := boundItem.Fun.(type) {
case *ast.Ident:
// boundStructs = append(boundStructs, newStruct(pkg.Name, fn.Name))
strct, err := p.getFunctionReturnType(pkg, fn.Name)
if err != nil {
parseError = err
return false
}
if strct == nil {
parseError = fmt.Errorf("unable to resolve function returntype: %s", fn.Name)
return false
}
strct.Package.boundStructs.Add(strct.Name)
case *ast.SelectorExpr:
ident, ok := fn.X.(*ast.Ident)
if !ok {
return true
}
packageName := ident.Name
functionName := fn.Sel.Name
println("Found bound function:", packageName+"."+functionName)
// Get package for package name
externalPackageName := pkg.getImportByName(packageName, fileAst)
externalPackage := p.getPackageByID(externalPackageName.ID)
strct, err := p.getFunctionReturnType(externalPackage, functionName)
if err != nil {
parseError = err
return false
}
if strct == nil {
// Unable to resolve function
return true
}
externalPackage.boundStructs.Add(strct.Name)
}
// Binding struct pointer literals
case *ast.UnaryExpr:
if boundItem.Op.String() != "&" {
return true
}
cl, ok := boundItem.X.(*ast.CompositeLit)
if !ok {
return true
}
switch boundStructExp := cl.Type.(type) {
// app.Bind( &myStruct{} )
case *ast.Ident:
pkg.boundStructs.Add(boundStructExp.Name)
// app.Bind( &mypackage.myStruct{} )
case *ast.SelectorExpr:
var structName = ""
var packageName = ""
switch x := boundStructExp.X.(type) {
case *ast.Ident:
packageName = x.Name
default:
// TODO: Save these warnings
// println("Identifier in binding not supported:")
return true
}
structName = boundStructExp.Sel.Name
referencedPackage := pkg.getImportByName(packageName, fileAst)
packageWrapper := p.getPackageByID(referencedPackage.ID)
packageWrapper.boundStructs.Add(structName)
}
default:
// TODO: Save these warnings
// println("Unsupported bind expression:")
// spew.Dump(boundItem)
}
return true
})
if parseError != nil {
return parseError
}
}
return nil
}

248
v2/pkg/parser/generate.go Normal file
View File

@ -0,0 +1,248 @@
package parser
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"text/template"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/internal/fs"
)
// GenerateWailsFrontendPackage will generate a Javascript/Typescript
// package in `<project>/frontend/wails` that defines which methods
// and structs are bound to your frontend
func GenerateWailsFrontendPackage() (*ParserReport, error) {
dir, err := os.Getwd()
if err != nil {
return nil, err
}
p := NewParser()
err = p.ParseProject(dir)
if err != nil {
return nil, err
}
err = p.generateModule()
return p.parserReport(), err
}
func (p *Parser) generateModule() error {
moduleDir, err := createBackendJSDirectory()
if err != nil {
return err
}
packagesToGenerate := p.packagesToGenerate()
for _, pkg := range packagesToGenerate {
err := generatePackage(pkg, moduleDir)
if err != nil {
return err
}
}
// Copy the standard files
srcFile := fs.RelativePath("./package.json")
tgtFile := filepath.Join(moduleDir, "package.json")
err = fs.CopyFile(srcFile, tgtFile)
if err != nil {
return err
}
// Generate the globals.d.ts file
err = generateGlobalsTS(moduleDir, packagesToGenerate)
if err != nil {
return err
}
// Generate the index.js file
err = generateIndexJS(moduleDir, packagesToGenerate)
if err != nil {
return err
}
// Generate the index.d.ts file
err = generateIndexTS(moduleDir, packagesToGenerate)
if err != nil {
return err
}
return nil
}
func createBackendJSDirectory() (string, error) {
// Calculate the package directory
// Note this is *always* called from the project directory
// so using paths relative to CWD is fine
dir, err := fs.RelativeToCwd("./frontend/backend")
if err != nil {
return "", errors.Wrap(err, "Error creating backend module directory")
}
// Remove directory if it exists - REGENERATION!
err = os.RemoveAll(dir)
if err != nil {
return "", errors.Wrap(err, "Error removing module directory")
}
// Make the directory
err = fs.Mkdir(dir)
return dir, err
}
func generatePackage(pkg *Package, moduledir string) error {
// Get path to local file
typescriptTemplateFile := fs.RelativePath("./package.d.template")
// Load typescript template
typescriptTemplateData := fs.MustLoadString(typescriptTemplateFile)
typescriptTemplate, err := template.New("typescript").Parse(typescriptTemplateData)
if err != nil {
return errors.Wrap(err, "Error creating template")
}
// Execute javascript template
var buffer bytes.Buffer
err = typescriptTemplate.Execute(&buffer, pkg)
if err != nil {
return errors.Wrap(err, "Error generating code")
}
// Save typescript file
err = ioutil.WriteFile(filepath.Join(moduledir, "_"+pkg.Name+".d.ts"), buffer.Bytes(), 0755)
if err != nil {
return errors.Wrap(err, "Error writing backend package file")
}
// Get path to local file
javascriptTemplateFile := fs.RelativePath("./package.template")
// Load javascript template
javascriptTemplateData := fs.MustLoadString(javascriptTemplateFile)
javascriptTemplate, err := template.New("javascript").Parse(javascriptTemplateData)
if err != nil {
return errors.Wrap(err, "Error creating template")
}
// Reset the buffer
buffer.Reset()
err = javascriptTemplate.Execute(&buffer, pkg)
if err != nil {
return errors.Wrap(err, "Error generating code")
}
// Save javascript file
err = ioutil.WriteFile(filepath.Join(moduledir, "_"+pkg.Name+".js"), buffer.Bytes(), 0755)
if err != nil {
return errors.Wrap(err, "Error writing backend package file")
}
return nil
}
func generateIndexJS(dir string, packages []*Package) error {
// Get path to local file
templateFile := fs.RelativePath("./index.template")
// Load template
templateData := fs.MustLoadString(templateFile)
packagesTemplate, err := template.New("index").Parse(templateData)
if err != nil {
return errors.Wrap(err, "Error creating template")
}
// Execute template
var buffer bytes.Buffer
err = packagesTemplate.Execute(&buffer, packages)
if err != nil {
return errors.Wrap(err, "Error generating code")
}
// Calculate target filename
indexJS := filepath.Join(dir, "index.js")
err = ioutil.WriteFile(indexJS, buffer.Bytes(), 0755)
if err != nil {
return errors.Wrap(err, "Error writing backend package index.js file")
}
return nil
}
func generateIndexTS(dir string, packages []*Package) error {
// Get path to local file
templateFile := fs.RelativePath("./index.d.template")
// Load template
templateData := fs.MustLoadString(templateFile)
indexTSTemplate, err := template.New("index.d").Parse(templateData)
if err != nil {
return errors.Wrap(err, "Error creating template")
}
// Execute template
var buffer bytes.Buffer
err = indexTSTemplate.Execute(&buffer, packages)
if err != nil {
return errors.Wrap(err, "Error generating code")
}
// Calculate target filename
indexJS := filepath.Join(dir, "index.d.ts")
err = ioutil.WriteFile(indexJS, buffer.Bytes(), 0755)
if err != nil {
return errors.Wrap(err, "Error writing backend package index.d.ts file")
}
return nil
}
func generateGlobalsTS(dir string, packages []*Package) error {
// Get path to local file
templateFile := fs.RelativePath("./globals.d.template")
// Load template
templateData := fs.MustLoadString(templateFile)
packagesTemplate, err := template.New("globals").Parse(templateData)
if err != nil {
return errors.Wrap(err, "Error creating template")
}
// Execute template
var buffer bytes.Buffer
err = packagesTemplate.Execute(&buffer, packages)
if err != nil {
return errors.Wrap(err, "Error generating code")
}
// Calculate target filename
indexJS := filepath.Join(dir, "globals.d.ts")
err = ioutil.WriteFile(indexJS, buffer.Bytes(), 0755)
if err != nil {
return errors.Wrap(err, "Error writing backend package globals.d.ts file")
}
return nil
}
func (p *Parser) parserReport() *ParserReport {
return &ParserReport{
Packages: p.packagesToGenerate(),
}
}

View File

@ -0,0 +1,69 @@
package parser
import (
"fmt"
"go/ast"
)
func (p *Parser) getFunctionReturnType(pkg *Package, functionName string) (*Struct, error) {
var result *Struct
// Iterate through the files in the package looking for the bound structs
for _, fileAst := range pkg.Gopackage.Syntax {
var parseError error
ast.Inspect(fileAst, func(n ast.Node) bool {
// Parse Call expressions looking for bind calls
funcDecl, ok := n.(*ast.FuncDecl)
if !ok {
return true
}
if funcDecl.Name.Name == functionName {
result, parseError = p.parseFunctionReturnType(fileAst, funcDecl, pkg)
return false
}
return true
})
if parseError != nil {
return nil, parseError
}
if result != nil {
return result, nil
}
}
return result, nil
}
func (p *Parser) parseFunctionReturnType(file *ast.File, funcDecl *ast.FuncDecl, pkg *Package) (*Struct, error) {
var result *Struct
if funcDecl.Type.Results == nil {
return nil, fmt.Errorf("bound function %s has no return values", funcDecl.Name.Name)
}
// We expect only 1 return value for a function return
if len(funcDecl.Type.Results.List) > 1 {
return nil, fmt.Errorf("bound function %s has more than 1 return value", funcDecl.Name.Name)
}
parsedFields, err := p.parseField(file, funcDecl.Type.Results.List[0], pkg)
if err != nil {
return nil, err
}
if len(parsedFields) > 1 {
return nil, fmt.Errorf("bound function %s has more than 1 return value", funcDecl.Name.Name)
}
result = parsedFields[0].Struct
return result, nil
}

View File

@ -0,0 +1,27 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
interface Window {
backend: {
{{- range . }}{{$packageName:=.Name}}
{{- if .HasBoundStructs }}
{{ $packageName }}: {
{{- range .Structs }}
{{- if .IsBound }}
{{if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{end}}
{{.Name}}: {
{{range .Methods}}
{{if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{end}}
{{.Name}}: ({{.InputsAsTSText $packageName}}) => Promise<{{.OutputsAsTSText $packageName}}>,
{{end}}
}
{{- end}}
{{- end}}
}
{{- end}}
{{- end}}
}
}

View File

@ -0,0 +1,7 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
{{- range .}}
export const {{.Name}}: typeof import("./_{{.Name}}");
{{- end}}

View File

@ -0,0 +1,13 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
{{- range .}}
const {{.Name}} = require('./_{{.Name}}');
{{- end}}
module.exports = {
{{- range .}}
{{.Name}}: {{.Name}},
{{- end}}
}

179
v2/pkg/parser/method.go Normal file
View File

@ -0,0 +1,179 @@
package parser
import (
"fmt"
"go/ast"
"strings"
)
// Method defines a struct method
type Method struct {
Name string
Comments []string
Inputs []*Field
Returns []*Field
}
func (p *Parser) parseStructMethods(boundStruct *Struct) error {
for _, fileAst := range boundStruct.Package.Gopackage.Syntax {
// Track errors
var parseError error
ast.Inspect(fileAst, func(n ast.Node) bool {
if funcDecl, ok := n.(*ast.FuncDecl); ok {
if funcDecl.Recv == nil {
return true
}
// This is a struct method
for _, field := range funcDecl.Recv.List {
switch f := field.Type.(type) {
case *ast.StarExpr:
// This is a struct pointer method
ident, ok := f.X.(*ast.Ident) // _ ?
if !ok {
continue
}
// Check this method is for this struct
if ident.Name != boundStruct.Name {
continue
}
// We want to ignore Internal functions
if funcDecl.Name.Name == "WailsInit" || funcDecl.Name.Name == "WailsShutdown" {
continue
}
// If this method is not Public, ignore
if string(funcDecl.Name.Name[0]) != strings.ToUpper((string(funcDecl.Name.Name[0]))) {
continue
}
// Create our struct
structMethod := &Method{
Name: funcDecl.Name.Name,
Comments: parseComments(funcDecl.Doc),
}
// Save the input parameters
if funcDecl.Type.Params != nil {
for _, inputField := range funcDecl.Type.Params.List {
fields, err := p.parseField(fileAst, inputField, boundStruct.Package)
if err != nil {
parseError = err
return false
}
// If this field was a struct, flag that it is used as data
if len(fields) > 0 {
if fields[0].Struct != nil {
fields[0].Struct.IsUsedAsData = true
}
}
structMethod.Inputs = append(structMethod.Inputs, fields...)
}
}
// Save the output parameters
if funcDecl.Type.Results != nil {
for _, outputField := range funcDecl.Type.Results.List {
fields, err := p.parseField(fileAst, outputField, boundStruct.Package)
if err != nil {
parseError = err
return false
}
// If this field was a struct, flag that it is used as data
if len(fields) > 0 {
if fields[0].Struct != nil {
fields[0].Struct.IsUsedAsData = true
}
}
structMethod.Returns = append(structMethod.Returns, fields...)
}
}
// Append this method to the parsed struct
boundStruct.Methods = append(boundStruct.Methods, structMethod)
default:
// Unsupported
continue
}
}
}
return true
})
// If we got an error, return it
if parseError != nil {
return parseError
}
}
return nil
}
// InputsAsTSText generates a string with the method inputs
// formatted in a way acceptable to Typescript
func (m *Method) InputsAsTSText(pkgName string) string {
var inputs []string
for _, input := range m.Inputs {
inputText := fmt.Sprintf("%s: %s", input.Name, goTypeToTS(input, pkgName))
inputs = append(inputs, inputText)
}
return strings.Join(inputs, ", ")
}
// OutputsAsTSText generates a string with the method inputs
// formatted in a way acceptable to Javascript
func (m *Method) OutputsAsTSText(pkgName string) string {
if len(m.Returns) == 0 {
return "void"
}
var result []string
for _, output := range m.Returns {
result = append(result, goTypeToTS(output, pkgName))
}
return strings.Join(result, ", ")
}
// OutputsAsTSDeclarationText generates a string with the method inputs
// formatted in a way acceptable to Javascript
func (m *Method) OutputsAsTSDeclarationText(pkgName string) string {
if len(m.Returns) == 0 {
return "void"
}
var result []string
for _, output := range m.Returns {
result = append(result, goTypeToTSDeclaration(output, pkgName))
}
return strings.Join(result, ", ")
}
// InputsAsJSText generates a string with the method inputs
// formatted in a way acceptable to Javascript
func (m *Method) InputsAsJSText() string {
var inputs []string
for _, input := range m.Inputs {
inputs = append(inputs, input.Name)
}
return strings.Join(inputs, ", ")
}

View File

@ -0,0 +1,31 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
{{- if .DeclarationReferences }}
{{range .DeclarationReferences}}
/// <reference types="../{{.}}" />{{end}}{{- end}}
export namespace {{.Name}} { {{range .Structs}}
{{- if or .IsBound .IsUsedAsData}}
{{if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{- end}}
interface {{.Name}} { {{ if .IsUsedAsData }}
{{- range .Fields}}{{if .Comments }}
{{range .Comments}}//{{ . }}{{end}}{{- end}}
{{.Name}}: {{.TypeAsTSType $.Name}}; {{- end}} {{ end }}
{{- if .IsBound }}
{{- range .Methods}}
/**{{if .Comments }}
{{range .Comments}} * {{ . }}{{end}}
*{{end}}
* @function {{.Name}}
{{range .Inputs}} * @param {{"{"}}{{.JSType}}{{"}"}} {{.Name}}
{{end}} *
* @returns {Promise<{{.OutputsAsTSText $.Name}}>}
*/
{{.Name}}({{.InputsAsTSText $.Name}}): Promise<{{.OutputsAsTSText $.Name}}>;
{{- end}}{{end}}
}{{- end}}
{{end}}
}

View File

@ -0,0 +1,42 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
{{- range .Structs}}
{{- if .IsBound}}
export namespace {{.Name}} {
{{- range .Methods}}
{{- if .Comments }}
{{range .Comments}}
// {{ . }}{{end}}
{{- end}}
function {{.Name}}({{.InputsAsTSText $.Name}}): Promise<{{.OutputsAsTSDeclarationText $.Name}}>;
{{- end}}
}
{{- end}}
{{- if .IsUsedAsData}}
{{if .Comments }}
/**
{{range .Comments}} *{{ . }}{{end}}
*/
export type {{.Name}} = {
{{- range .Fields}}
{{- if not .Ignored}}
{{- if .Comments }}{{range .Comments}}
//{{ . }}{{end}}{{- end}}
{{ .AsTSDeclaration $.Name}}; {{- end}}
{{- end}}
};
/**
{{if .Comments }}{{range .Comments}} *{{ . }}{{end}}{{end}}
* @typedef {object} {{.Name}}
{{- range .Fields}}{{- if not .JSONOptions.Ignored }}
* @property {{"{"}}{{.TypeForPropertyDoc}}{{"}"}} {{.NameForPropertyDoc}} {{- if .Comments}} - {{- range .Comments}}{{ . }}{{- end}}{{- end}}{{- end}}
{{- end}}
*/
export var {{.Name}}: any;
{{- end}}
{{- end}}
{{- end}}

152
v2/pkg/parser/package.go Normal file
View File

@ -0,0 +1,152 @@
package parser
import (
"go/ast"
"strings"
"github.com/leaanthony/slicer"
"golang.org/x/tools/go/packages"
)
// Package is a wrapper around the go parsed package
type Package struct {
// A unique Name for this package.
// This is calculated and may not be the same as the one
// defined in Go - but that's ok!
Name string
// the package we are wrapping
Gopackage *packages.Package
// a list of struct names that are bound in this package
boundStructs slicer.StringSlicer
// Structs used in this package
parsedStructs map[string]*Struct
// A list of external packages we reference from this package
externalReferences slicer.InterfaceSlicer
}
func newPackage(pkg *packages.Package) *Package {
return &Package{
Gopackage: pkg,
parsedStructs: make(map[string]*Struct),
}
}
func (p *Package) getWailsImportName(file *ast.File) string {
// Scan the imports for the wails v2 import
for _, details := range file.Imports {
if details.Path.Value == `"github.com/wailsapp/wails/v2"` {
if details.Name != nil {
return details.Name.Name
}
// Get the import name from the package
imp := p.getImportByPath("github.com/wailsapp/wails/v2")
if imp != nil {
return imp.Name
}
}
}
return ""
}
func (p *Package) getImportByName(importName string, file *ast.File) *packages.Package {
// Check if the file has aliased the import
for _, imp := range file.Imports {
if imp.Name != nil {
if imp.Name.Name == importName {
// Yes it has. Get the import by path
return p.getImportByPath(imp.Path.Value)
}
}
}
// We need to find which package import has this name
for _, imp := range p.Gopackage.Imports {
if imp.Name == importName {
return imp
}
}
// Looks like this package is outside the project...
return nil
}
func (p *Package) getImportByPath(packagePath string) *packages.Package {
packagePath = strings.Trim(packagePath, "\"")
return p.Gopackage.Imports[packagePath]
}
func (p *Package) getStruct(structName string) *Struct {
return p.parsedStructs[structName]
}
func (p *Package) addStruct(strct *Struct) {
p.parsedStructs[strct.Name] = strct
}
// HasBoundStructs returns true if any of its structs
// are bound
func (p *Package) HasBoundStructs() bool {
for _, strct := range p.parsedStructs {
if strct.IsBound {
return true
}
}
return false
}
// HasDataStructs returns true if any of its structs
// are used as data
func (p *Package) HasDataStructs() bool {
for _, strct := range p.parsedStructs {
if strct.IsUsedAsData {
return true
}
}
return false
}
// ShouldGenerate returns true when this package should be generated
func (p *Package) ShouldGenerate() bool {
return p.HasBoundStructs() || p.HasDataStructs()
}
// DeclarationReferences returns a list of external packages
// we reference from this package
func (p *Package) DeclarationReferences() []string {
var referenceNames slicer.StringSlicer
// Generics can't come soon enough!
p.externalReferences.Each(func(p interface{}) {
referenceNames.Add(p.(*Package).Name)
})
return referenceNames.AsSlice()
}
// addExternalReference saves the given package as an external reference
func (p *Package) addExternalReference(pkg *Package) {
p.externalReferences.AddUnique(pkg)
}
// Structs returns the structs that we want to generate
func (p *Package) Structs() []*Struct {
var result []*Struct
for _, elem := range p.parsedStructs {
result = append(result, elem)
}
return result
}

View File

@ -0,0 +1,13 @@
{
"name": "backend",
"version": "1.0.0",
"description": "Auto generated module wrapping your Wails backend",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,44 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
{{- if .DeclarationReferences }}
{{range .DeclarationReferences}}
const {{.}} = require('./_{{.}}');{{end}}{{- end}}
{{- range $struct := .Structs }}
{{- if .IsUsedAsData }}
/**
{{if .Comments }}{{range .Comments}} *{{ . }}{{end}}{{end}}
* @typedef {object} {{.Name}}
{{- range .Fields}}{{- if not .JSONOptions.Ignored }}
* @property {{"{"}}{{.TypeForPropertyDoc}}{{"}"}} {{.NameForPropertyDoc}} {{- if .Comments}} - {{- range .Comments}}{{ . }}{{- end}}{{- end}}{{- end}}
{{- end}}
*/
export var {{.Name}};
{{- end}}
{{- if .IsBound }}
{{- if .Methods }}
{{if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{end}}
export const {{.Name}} = {
{{range .Methods }}
/**{{if .Comments }}
{{range .Comments}} * {{ . }}{{end}}
*{{end}}
* @function {{.Name}}
{{range .Inputs}} * @param {{"{"}}{{.JSType}}{{"}"}} {{.Name}}
{{end}} *
* @returns {Promise<{{.OutputsAsTSText $.Name}}>}
*/
{{.Name}}: function({{.InputsAsJSText}}) {
return window.backend.{{$.Name}}.{{$struct.Name}}.{{.Name}}({{.InputsAsJSText}});
},
{{end}}
}
{{- end}}
{{- end}}
{{- end}}

View File

@ -0,0 +1,75 @@
package parser
import "go/ast"
func (p *Parser) parseBoundStructs(pkg *Package) error {
// Loop over the bound structs
for _, structName := range pkg.boundStructs.AsSlice() {
strct, err := p.parseStruct(pkg, structName)
if err != nil {
return err
}
strct.IsBound = true
}
return nil
}
// ParseStruct will attempt to parse the given struct using
// the package it references
func (p *Parser) parseStruct(pkg *Package, structName string) (*Struct, error) {
// Check the parser cache for this struct
result := pkg.getStruct(structName)
if result != nil {
return result, nil
}
// Iterate through the whole package looking for the bound structs
for _, fileAst := range pkg.Gopackage.Syntax {
// Track errors
var parseError error
ast.Inspect(fileAst, func(n ast.Node) bool {
if genDecl, ok := n.(*ast.GenDecl); ok {
for _, spec := range genDecl.Specs {
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
if structType, ok := typeSpec.Type.(*ast.StructType); ok {
structDefinitionName := typeSpec.Name.Name
if structDefinitionName == structName {
// Create the new struct
result = &Struct{Name: structName, Package: pkg}
// Save comments
result.Comments = parseComments(genDecl.Doc)
parseError = p.parseStructMethods(result)
if parseError != nil {
return false
}
// Parse the struct fields
parseError = p.parseStructFields(fileAst, structType, result)
// Save this struct
pkg.addStruct(result)
return false
}
}
}
}
}
return true
})
// If we got an error, return it
if parseError != nil {
return nil, parseError
}
}
return result, nil
}

View File

@ -0,0 +1,35 @@
package parser
import (
"go/ast"
"github.com/pkg/errors"
)
func (p *Parser) parseStructFields(fileAst *ast.File, structType *ast.StructType, boundStruct *Struct) error {
// Parse the fields
for _, field := range structType.Fields.List {
fields, err := p.parseField(fileAst, field, boundStruct.Package)
if err != nil {
return errors.Wrap(err, "error parsing struct "+boundStruct.Name)
}
// If this field was a struct, flag that it is used as data
if len(fields) > 0 {
if fields[0].Struct != nil {
fields[0].Struct.IsUsedAsData = true
}
}
// If this field name is lowercase, it won't be exported
for _, field := range fields {
if !startsWithLowerCaseLetter(field.Name) {
boundStruct.Fields = append(boundStruct.Fields, field)
}
}
}
return nil
}

122
v2/pkg/parser/parser.go Normal file
View File

@ -0,0 +1,122 @@
// Package parser provides the ability to parse the data that is bound in Wails projects.
// Using this, it can also generate a Javascript module that represents the DTOs used, as
// well as providing wrappers for bound methods.
package parser
import (
"go/token"
"github.com/pkg/errors"
"golang.org/x/tools/go/packages"
)
// Parser is the Wails project parser
type Parser struct {
// Placeholders for Go's parser
fileSet *token.FileSet
// The packages we parse
// The map key is the package ID
packages map[string]*Package
}
// NewParser creates a new Wails project parser
func NewParser() *Parser {
return &Parser{
fileSet: token.NewFileSet(),
packages: make(map[string]*Package),
}
}
// ParseProject will parse the Wails project in the given directory
func (p *Parser) ParseProject(dir string) error {
var err error
err = p.loadPackages(dir)
if err != nil {
return err
}
// Find all the bound structs
for _, pkg := range p.packages {
err = p.findBoundStructs(pkg)
if err != nil {
return err
}
}
// Parse the structs
for _, pkg := range p.packages {
err = p.parseBoundStructs(pkg)
if err != nil {
return err
}
}
// Resolve package names
// We do this because some packages may have the same name
p.resolvePackageNames()
return nil
}
func (p *Parser) loadPackages(projectPath string) error {
mode := packages.NeedName |
packages.NeedFiles |
packages.NeedSyntax |
packages.NeedTypes |
packages.NeedImports |
packages.NeedTypesInfo |
packages.NeedModule
cfg := &packages.Config{Fset: p.fileSet, Mode: mode, Dir: projectPath}
pkgs, err := packages.Load(cfg, "./...")
if err != nil {
return errors.Wrap(err, "Problem loading packages")
}
// Check for errors
var parseError error
for _, pkg := range pkgs {
for _, err := range pkg.Errors {
if parseError == nil {
parseError = errors.New(err.Error())
} else {
parseError = errors.Wrap(parseError, err.Error())
}
}
}
if parseError != nil {
return parseError
}
// Create a map of packages
for _, pkg := range pkgs {
p.packages[pkg.ID] = newPackage(pkg)
}
return nil
}
func (p *Parser) getPackageByID(id string) *Package {
return p.packages[id]
}
func (p *Parser) packagesToGenerate() []*Package {
var result []*Package
for _, pkg := range p.packages {
if pkg.ShouldGenerate() {
result = append(result, pkg)
}
}
return result
}
type ParserReport struct {
Packages []*Package
}

View File

@ -0,0 +1,35 @@
package parser
import (
"fmt"
"github.com/leaanthony/slicer"
)
// resolvePackageNames will deterine the names for the packages, allowing
// us to create a flat structure for the imports in the frontend module
func (p *Parser) resolvePackageNames() {
// A cache for the names
var packageNameCache slicer.StringSlicer
// Process each package
for _, pkg := range p.packages {
pkgName := pkg.Gopackage.Name
// Check for collision
if packageNameCache.Contains(pkgName) {
// https://www.youtube.com/watch?v=otNNGROI0Cs !!!!!
// We start at 2 because having both "pkg" and "pkg1" is 🙄
count := 2
for ok := true; ok; ok = packageNameCache.Contains(pkgName) {
pkgName = fmt.Sprintf("%s%d", pkg.Gopackage.Name, count)
}
}
// Save the name!
packageNameCache.Add(pkgName)
pkg.Name = pkgName
}
}

68
v2/pkg/parser/struct.go Normal file
View File

@ -0,0 +1,68 @@
package parser
import (
"fmt"
"go/ast"
"github.com/pkg/errors"
)
// Struct represents a struct that is used by the frontend
// in a Wails project
type Struct struct {
// The name of the struct
Name string
// The package this was declared in
Package *Package
// Comments for the struct
Comments []string
// The fields used in this struct
Fields []*Field
// The methods available to the front end
Methods []*Method
// Indicates if this struct is bound to the app
IsBound bool
// Indicates if this struct is used as data
IsUsedAsData bool
}
func parseStructNameFromStarExpr(starExpr *ast.StarExpr) (string, string, error) {
pkg := ""
name := ""
// Determine the FQN
switch x := starExpr.X.(type) {
case *ast.SelectorExpr:
switch i := x.X.(type) {
case *ast.Ident:
pkg = i.Name
default:
// TODO: Store warnings?
return "", "", errors.WithStack(fmt.Errorf("unknown type in selector for *ast.SelectorExpr: %+v", i))
}
name = x.Sel.Name
// TODO: IS this used?
case *ast.StarExpr:
switch s := x.X.(type) {
case *ast.Ident:
name = s.Name
default:
// TODO: Store warnings?
return "", "", errors.WithStack(fmt.Errorf("unknown type in selector for *ast.StarExpr: %+v", s))
}
case *ast.Ident:
name = x.Name
default:
// TODO: Store warnings?
return "", "", errors.WithStack(fmt.Errorf("unknown type in selector for *ast.StarExpr: %+v", starExpr))
}
return pkg, name, nil
}

View File

@ -0,0 +1,66 @@
package main
import (
"fmt"
"testproject/mypackage"
wails "github.com/wailsapp/wails/v2"
)
// Basic application struct
type Basic struct {
runtime *wails.Runtime
}
// // Another application struct
// type Another struct {
// runtime *wails.Runtime
// }
// func (a *Another) Doit() {
// }
// // newBasicPointer creates a new Basic application struct
// func newBasicPointer() *Basic {
// return &Basic{}
// }
// // newBasic creates a new Basic application struct
// func newBasic() Basic {
// return Basic{}
// }
// WailsInit is called at application startup
func (b *Basic) WailsInit(runtime *wails.Runtime) error {
// Perform your setup here
b.runtime = runtime
runtime.Window.SetTitle("jsbundle")
return nil
}
// WailsShutdown is called at application termination
func (b *Basic) WailsShutdown() {
// Perform your teardown here
}
// NewPerson creates a new person
func (b *Basic) NewPerson(name string, age int) *mypackage.Person {
return &mypackage.Person{Name: name, Age: age}
}
// Greet returns a greeting for the given name
func (b *Basic) Greet(name string) string {
return fmt.Sprintf("Hello %s!", name)
}
// MultipleGreets returns greetings for the given name
func (b *Basic) MultipleGreets(name string) []string {
return []string{"hi", "hello", "croeso!"}
}
// RemovePerson Removes the given person
func (b *Basic) RemovePerson(p *mypackage.Person) {
// dummy
}

View File

@ -0,0 +1,9 @@
module testproject
go 1.13
require (
github.com/wailsapp/wails/v2 v2.0.0-alpha
)
replace github.com/wailsapp/wails/v2 v2.0.0-alpha => /home/lea/Data/projects/wails/v2

View File

@ -0,0 +1,83 @@
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/minify/v2 v2.9.5/go.mod h1:jshtBj/uUJH6JX1fuxTLnnHOA1RVJhF5MM+leJzDKb4=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/parse/v2 v2.5.3/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=

View File

@ -0,0 +1,21 @@
package main
import (
"testproject/mypackage"
"github.com/wailsapp/wails/v2"
)
func main() {
// Create application with options
app := wails.CreateApp("jsbundle", 1024, 768)
/***** Struct Literal *****/
// Local struct pointer literal *WORKING*
app.Bind(&Basic{})
// External struct pointer literal
app.Bind(&mypackage.Manager{})
}

View File

@ -0,0 +1,36 @@
// Package mypackage does all the things a mypackage can do
package mypackage
type Address struct {
Number int
Street string
Town string
Postcode string
}
// Person defines a Person in the application
type Person struct {
// Name is a name
Name string
Age int
Address *Address
}
// Manager is the Mr Manager
type Manager struct {
Name string
TwoIC *Person
}
// Hire me some peoples!
func (m *Manager) Hire(name, test string, bob int) *Person {
return &Person{Name: name}
}
// func NewManagerPointer() *Manager {
// return &Manager{}
// }
// func NewManager() Manager {
// return Manager{}
// }