Compare commits

..

2 Commits

Author SHA1 Message Date
Lea Anthony 261555c191 Init now adds replace lines to generated projects if the CLI was built locally. 2023-03-09 21:26:34 +11:00
Lea Anthony e75e437f6d Fix init project directory 2023-03-09 08:37:53 +11:00
34 changed files with 264 additions and 375 deletions
-1
View File
@@ -2,7 +2,6 @@ package commands
import (
"fmt"
"github.com/wailsapp/wails/v3/internal/flags"
"github.com/wailsapp/wails/v3/internal/templates"
+56
View File
@@ -0,0 +1,56 @@
package debug
import (
"github.com/samber/lo"
"path/filepath"
"runtime"
)
import "runtime/debug"
// Why go doesn't provide this as a map already is beyond me.
var buildSettings = map[string]string{}
var LocalModulePath = ""
func init() {
buildInfo, ok := debug.ReadBuildInfo()
if !ok {
return
}
buildSettings = lo.Associate(buildInfo.Settings, func(setting debug.BuildSetting) (string, string) {
return setting.Key, setting.Value
})
if isLocalBuild() {
modulePath := RelativePath("..", "..", "..")
LocalModulePath, _ = filepath.Abs(modulePath)
}
}
func isLocalBuild() bool {
return buildSettings["vcs.modified"] == "true"
}
// RelativePath returns a qualified path created by joining the
// directory of the calling file and the given relative path.
//
// Example: RelativePath("..") in *this* file would give you '/path/to/wails2/v2/internal`
func RelativePath(relativepath string, optionalpaths ...string) string {
_, thisFile, _, _ := runtime.Caller(1)
localDir := filepath.Dir(thisFile)
// If we have optional paths, join them to the relativepath
if len(optionalpaths) > 0 {
paths := []string{relativepath}
paths = append(paths, optionalpaths...)
relativepath = filepath.Join(paths...)
}
result, err := filepath.Abs(filepath.Join(localDir, relativepath))
if err != nil {
// I'm allowing this for 1 reason only: It's fatal if the path
// supplied is wrong as it's only used internally in Wails. If we get
// that path wrong, we should know about it immediately. The other reason is
// that it cuts down a ton of unnecassary error handling.
panic(err)
}
return result
}
+4 -7
View File
@@ -4,11 +4,8 @@ type Init struct {
Common
TemplateName string `name:"t" description:"Name of built-in template to use, path to template or template url" default:"vanilla"`
ProjectName string `name:"n" description:"Name of project" default:"."`
//CIMode bool `name:"ci" description:"CI Mode"`
ProjectDir string `name:"d" description:"Project directory" default:"."`
Quiet bool `name:"q" description:"Suppress output to console"`
//InitGit bool `name:"g" description:"Initialise git repository"`
//IDE string `name:"ide" description:"Generate IDE project files"`
List bool `name:"l" description:"List templates"`
ProjectName string `name:"n" description:"Name of project" default:""`
ProjectDir string `name:"d" description:"Project directory" default:"."`
Quiet bool `name:"q" description:"Suppress output to console"`
List bool `name:"l" description:"List templates"`
}
+2 -2
View File
@@ -53,10 +53,10 @@ This package contains the static analyser used for parsing Wails projects so tha
- [x] Recursive
- [x] Anonymous
- [ ] Generation of models
- [x] Scalars
- [ ] Scalars
- [ ] Arrays
- [ ] Maps
- [x] Structs
- [ ] Structs
- [ ] Generation of bindings
## Limitations
+3 -26
View File
@@ -4,8 +4,6 @@ import (
"bytes"
"embed"
"io"
"sort"
"strings"
"text/template"
)
@@ -37,34 +35,13 @@ const modelsHeader = `// @ts-check
// This file is automatically generated. DO NOT EDIT
`
func pkgAlias(fullPkg string) string {
pkgParts := strings.Split(fullPkg, "/")
return pkgParts[len(pkgParts)-1]
}
func GenerateModels(models map[packagePath]map[structName]*StructDef) (string, error) {
if models == nil {
return "", nil
}
var buffer bytes.Buffer
buffer.WriteString(modelsHeader)
// sort pkgs by alias (e.g. services) instead of full pkg name (e.g. github.com/wailsapp/wails/somedir/services)
// and then sort resulting list by the alias
var keys []string
for pkg, _ := range models {
keys = append(keys, pkg)
}
sort.Slice(keys, func(i, j int) bool {
return pkgAlias(keys[i]) < pkgAlias(keys[j])
})
for _, pkg := range keys {
for pkg, pkgModels := range models {
err := GenerateModel(&buffer, &ModelDefinitions{
Package: pkgAlias(pkg),
Models: models[pkg],
Package: pkg,
Models: pkgModels,
})
if err != nil {
return "", err
+118 -62
View File
@@ -2,76 +2,132 @@ package parser
import (
"github.com/google/go-cmp/cmp"
"os"
"path/filepath"
"strings"
"testing"
)
func TestGenerateModels(t *testing.T) {
const expected = `
export namespace main {
export class Person {
name: string;
parent: Person;
details: anon1;
address: package.Address;
static createFrom(source: any = {}) {
return new Person(source);
}
tests := []struct {
dir string
want string
}{
{
"testdata/function_single",
"",
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.name = source["name"]
this.parent = source["parent"]
this.details = source["details"]
this.address = source["address"]
}
}
export class anon1 {
age: int;
address: string;
static createFrom(source: any = {}) {
return new anon1(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.age = source["age"]
this.address = source["address"]
}
}
}
`
func TestGenerateClass(t *testing.T) {
person := StructDef{
Name: "Person",
Fields: []*Field{
{
Name: "Name",
Type: &ParameterType{
Name: "string",
},
},
{
Name: "Parent",
Type: &ParameterType{
Name: "Person",
IsStruct: true,
IsPointer: true,
Package: "main",
},
},
{
Name: "Details",
Type: &ParameterType{
Name: "anon1",
IsStruct: true,
Package: "main",
},
},
{
Name: "Address",
Type: &ParameterType{
Name: "Address",
IsStruct: true,
IsPointer: true,
Package: "github.com/some/other/package",
},
},
},
{
"testdata/function_from_imported_package",
getFile("testdata/function_from_imported_package/models.ts"),
},
{
"testdata/variable_single",
"",
},
{
"testdata/variable_single_from_function",
"",
},
{
"testdata/variable_single_from_other_function",
getFile("testdata/variable_single_from_other_function/models.ts"),
},
{
"testdata/struct_literal_single",
getFile("testdata/struct_literal_single/models.ts"),
},
{
"testdata/struct_literal_multiple",
"",
},
{
"testdata/struct_literal_multiple_other",
getFile("testdata/struct_literal_multiple_other/models.ts"),
},
{
"testdata/struct_literal_multiple_files",
"",
}
anon1 := StructDef{
Name: "anon1",
Fields: []*Field{
{
Name: "Age",
Type: &ParameterType{
Name: "int",
},
},
{
Name: "Address",
Type: &ParameterType{
Name: "string",
},
},
},
}
for _, tt := range tests {
t.Run(tt.dir, func(t *testing.T) {
// Run parser on directory
project, err := ParseProject(tt.dir)
if err != nil {
t.Fatalf("ParseProject() error = %v", err)
}
// Generate Models
got, err := GenerateModels(project.Models)
if err != nil {
t.Fatalf("GenerateModels() error = %v", err)
}
var builder strings.Builder
models := make(map[string]*StructDef)
models["Person"] = &person
models["anon1"] = &anon1
def := ModelDefinitions{
Package: "main",
Models: models,
}
if diff := cmp.Diff(tt.want, got); diff != "" {
err = os.WriteFile(filepath.Join(tt.dir, "models.got.ts"), []byte(got), 0644)
if err != nil {
t.Errorf("os.WriteFile() error = %v", err)
return
}
t.Fatalf("GenerateModels() mismatch (-want +got):\n%s", diff)
}
})
err := GenerateModel(&builder, &def)
if err != nil {
t.Fatal(err)
}
text := builder.String()
println("Built string")
println(text)
if diff := cmp.Diff(expected, text); diff != "" {
t.Errorf("GenerateClass() failed:\n" + diff)
}
}
+4 -27
View File
@@ -90,39 +90,16 @@ func (f *Field) JSName() string {
return strings.ToLower(f.Name[0:1]) + f.Name[1:]
}
// TSBuild contains the typescript to build a field for a JS object
// via assignment for simple types or constructors for structs
func (f *Field) TSBuild(pkg string) string {
if !f.Type.IsStruct {
return fmt.Sprintf("source['%s']", f.JSName())
}
if f.Type.Package == "" || f.Type.Package == pkg {
return fmt.Sprintf("%s.createFrom(source['%s'])", f.Type.Name, f.JSName())
}
return fmt.Sprintf("%s.%s.createFrom(source['%s'])", pkgAlias(f.Type.Package), f.Type.Name, f.JSName())
}
func (f *Field) JSDef(pkg string) string {
var jsType string
switch f.Type.Name {
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64":
jsType = "number"
case "string":
jsType = "string"
case "bool":
jsType = "boolean"
default:
jsType = f.Type.Name
}
name := f.JSName()
var result string
if f.Type.Package == "" || f.Type.Package == pkg {
result += fmt.Sprintf("%s: %s;", f.JSName(), jsType)
result += fmt.Sprintf("%s: %s;", name, f.Type.Name)
} else {
parts := strings.Split(f.Type.Package, "/")
result += fmt.Sprintf("%s: %s.%s;", f.JSName(), parts[len(parts)-1], jsType)
result += fmt.Sprintf("%s: %s.%s;", name, parts[len(parts)-1], f.Type.Name)
}
if !ast.IsExported(f.Name) {
+1 -1
View File
@@ -13,7 +13,7 @@ export namespace {{.Package}} {
source = JSON.parse(source);
}
{{range $def.Fields}}this.{{.JSName}} = {{.TSBuild $pkg}};
{{range $def.Fields}}this.{{.JSName}} = source["{{.JSName}}"]
{{end}}
}
}
@@ -1,51 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
export namespace main {
export class Person {
name: string;
address: services.Address;
static createFrom(source: any = {}) {
return new Person(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.name = source['name'];
this.address = services.Address.createFrom(source['address']);
}
}
}
export namespace services {
export class Address {
street: string;
state: string;
country: string;
static createFrom(source: any = {}) {
return new Address(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.street = source['street'];
this.state = source['state'];
this.country = source['country'];
}
}
}
-5
View File
@@ -1,5 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
// TODO : nothing generated yet
@@ -1,5 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
// TODO : nothing generated yet
@@ -1,5 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
// TODO : nothing generated yet
@@ -1,51 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
export namespace main {
export class Person {
name: string;
address: services.Address;
static createFrom(source: any = {}) {
return new Person(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.name = source['name'];
this.address = services.Address.createFrom(source['address']);
}
}
}
export namespace services {
export class Address {
street: string;
state: string;
country: string;
static createFrom(source: any = {}) {
return new Address(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.street = source['street'];
this.state = source['state'];
this.country = source['country'];
}
}
}
@@ -1,64 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
export namespace main {
export class Person {
name: string;
parent: Person;
details: anon1;
static createFrom(source: any = {}) {
return new Person(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.name = source['name'];
this.parent = Person.createFrom(source['parent']);
this.details = anon1.createFrom(source['details']);
}
}
export class anon1 {
age: number;
address: anon2;
static createFrom(source: any = {}) {
return new anon1(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.age = source['age'];
this.address = anon2.createFrom(source['address']);
}
}
export class anon2 {
street: string;
static createFrom(source: any = {}) {
return new anon2(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.street = source['street'];
}
}
}
-5
View File
@@ -1,5 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
// TODO : nothing generated yet
@@ -1,5 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
// TODO : nothing generated yet
@@ -1,51 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
// This file is automatically generated. DO NOT EDIT
export namespace main {
export class Person {
name: string;
address: services.Address;
static createFrom(source: any = {}) {
return new Person(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.name = source['name'];
this.address = services.Address.createFrom(source['address']);
}
}
}
export namespace services {
export class Address {
street: string;
state: string;
country: string;
static createFrom(source: any = {}) {
return new Address(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) {
source = JSON.parse(source);
}
this.street = source['street'];
this.state = source['state'];
this.country = source['country'];
}
}
}
@@ -11,3 +11,7 @@ require (
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
{{end}}
+4
View File
@@ -11,3 +11,7 @@ require (
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
{{end}}
+4
View File
@@ -11,3 +11,7 @@ require (
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
{{end}}

Some files were not shown because too many files have changed in this diff Show More