mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-10 13:11:29 +08:00
Merge branch 'feature/v2-mac' of github.com:wailsapp/wails into feature/v2-mac
This commit is contained in:
commit
cd03b4b633
1
.gitignore
vendored
1
.gitignore
vendored
@ -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
|
||||
|
91
v2/cmd/wails/internal/commands/generate/generate.go
Normal file
91
v2/cmd/wails/internal/commands/generate/generate.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
@ -83,10 +87,11 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
// Create Template Options
|
||||
options := &templates.Options{
|
||||
ProjectName: projectName,
|
||||
TargetDir: projectDirectory,
|
||||
TemplateName: templateName,
|
||||
Logger: logger,
|
||||
ProjectName: projectName,
|
||||
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("")
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
19
v2/go.sum
19
v2/go.sum
@ -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=
|
||||
|
@ -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++ {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
29
v2/internal/system/operatingsystem/os_windows.go
Normal file
29
v2/internal/system/operatingsystem/os_windows.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
27
v2/internal/templates/ides/vscode/launch.json.tmpl
Normal file
27
v2/internal/templates/ides/vscode/launch.json.tmpl
Normal 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": []
|
||||
},
|
||||
]
|
||||
}
|
23
v2/internal/templates/ides/vscode/tasks.json.tmpl
Normal file
23
v2/internal/templates/ides/vscode/tasks.json.tmpl
Normal 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"
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/gosod"
|
||||
@ -31,11 +32,14 @@ type Data struct {
|
||||
|
||||
// Options for installing a template
|
||||
type Options struct {
|
||||
ProjectName string
|
||||
TemplateName string
|
||||
BinaryName string
|
||||
TargetDir string
|
||||
Logger *clilogger.CLILogger
|
||||
ProjectName string
|
||||
TemplateName string
|
||||
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 == "." {
|
||||
// Yes - use cwd
|
||||
options.TargetDir = cwd
|
||||
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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
6
v2/pkg/commands/build/packager_windows.go
Normal file
6
v2/pkg/commands/build/packager_windows.go
Normal file
@ -0,0 +1,6 @@
|
||||
package build
|
||||
|
||||
func packageApplication(options *Options) error {
|
||||
// TBD
|
||||
return nil
|
||||
}
|
50
v2/pkg/parser/applicationVariableName.go
Normal file
50
v2/pkg/parser/applicationVariableName.go
Normal 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
21
v2/pkg/parser/comments.go
Normal 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
156
v2/pkg/parser/conversion.go
Normal 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
311
v2/pkg/parser/field.go
Normal 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
|
||||
}
|
152
v2/pkg/parser/findBoundStructs.go
Normal file
152
v2/pkg/parser/findBoundStructs.go
Normal 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
248
v2/pkg/parser/generate.go
Normal 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(),
|
||||
}
|
||||
}
|
69
v2/pkg/parser/getFunctionReturnType.go
Normal file
69
v2/pkg/parser/getFunctionReturnType.go
Normal 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
|
||||
}
|
27
v2/pkg/parser/globals.d.template
Normal file
27
v2/pkg/parser/globals.d.template
Normal 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}}
|
||||
}
|
||||
}
|
7
v2/pkg/parser/index.d.template
Normal file
7
v2/pkg/parser/index.d.template
Normal 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}}
|
13
v2/pkg/parser/index.template
Normal file
13
v2/pkg/parser/index.template
Normal 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
179
v2/pkg/parser/method.go
Normal 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, ", ")
|
||||
}
|
31
v2/pkg/parser/package.d copy.template
Normal file
31
v2/pkg/parser/package.d copy.template
Normal 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}}
|
||||
|
||||
}
|
||||
|
42
v2/pkg/parser/package.d.template
Normal file
42
v2/pkg/parser/package.d.template
Normal 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
152
v2/pkg/parser/package.go
Normal 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
|
||||
}
|
13
v2/pkg/parser/package.json
Normal file
13
v2/pkg/parser/package.json
Normal 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"
|
||||
}
|
44
v2/pkg/parser/package.template
Normal file
44
v2/pkg/parser/package.template
Normal 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}}
|
75
v2/pkg/parser/parseBoundStructs.go
Normal file
75
v2/pkg/parser/parseBoundStructs.go
Normal 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
|
||||
}
|
35
v2/pkg/parser/parseStructFields.go
Normal file
35
v2/pkg/parser/parseStructFields.go
Normal 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
122
v2/pkg/parser/parser.go
Normal 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
|
||||
}
|
35
v2/pkg/parser/resolvePackageReferences.go
Normal file
35
v2/pkg/parser/resolvePackageReferences.go
Normal 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
68
v2/pkg/parser/struct.go
Normal 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
|
||||
}
|
66
v2/pkg/parser/testproject/basic.go
Normal file
66
v2/pkg/parser/testproject/basic.go
Normal 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
|
||||
}
|
9
v2/pkg/parser/testproject/go.mod
Normal file
9
v2/pkg/parser/testproject/go.mod
Normal 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
|
83
v2/pkg/parser/testproject/go.sum
Normal file
83
v2/pkg/parser/testproject/go.sum
Normal 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=
|
21
v2/pkg/parser/testproject/main.go
Normal file
21
v2/pkg/parser/testproject/main.go
Normal 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{})
|
||||
|
||||
}
|
36
v2/pkg/parser/testproject/mypackage/mypackage.go
Normal file
36
v2/pkg/parser/testproject/mypackage/mypackage.go
Normal 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{}
|
||||
// }
|
Loading…
Reference in New Issue
Block a user