From da90e74268c0209dbcf4589ebb213a643908990f Mon Sep 17 00:00:00 2001 From: Adam Tenderholt Date: Tue, 28 Feb 2023 01:35:18 -0800 Subject: [PATCH] v3 parser: initial work on model generation w/ templates (#2428) * v3 parser: initial work on model generation w/ templates * v3 parser: expand models to namespaces --- v3/internal/parser/models.go | 40 +++++++ v3/internal/parser/models_test.go | 133 +++++++++++++++++++++ v3/internal/parser/parser.go | 16 +++ v3/internal/parser/templates/class.ts.tmpl | 16 +++ v3/internal/parser/templates/model.ts.tmpl | 21 ++++ 5 files changed, 226 insertions(+) create mode 100644 v3/internal/parser/models_test.go create mode 100644 v3/internal/parser/templates/class.ts.tmpl create mode 100644 v3/internal/parser/templates/model.ts.tmpl diff --git a/v3/internal/parser/models.go b/v3/internal/parser/models.go index 4acc67fb3..4d589bc71 100644 --- a/v3/internal/parser/models.go +++ b/v3/internal/parser/models.go @@ -1,5 +1,45 @@ package parser +import ( + "io" + "text/template" +) + +type ModelDefinitions struct { + Package string + Models map[string]*StructDef +} + +func GenerateModel(wr io.Writer, def *ModelDefinitions) error { + tmpl, err := template.New("model.ts.tmpl").ParseFiles("templates/model.ts.tmpl") + if err != nil { + println("Unable to create class template: " + err.Error()) + return err + } + + err = tmpl.ExecuteTemplate(wr, "model.ts.tmpl", def) + if err != nil { + println("Problem executing template: " + err.Error()) + return err + } + return nil +} + +//func GenerateClass(wr io.Writer, def *StructDef) error { +// tmpl, err := template.New("class.ts.tmpl").ParseFiles("templates/class.ts.tmpl") +// if err != nil { +// println("Unable to create class template: " + err.Error()) +// return err +// } +// +// err = tmpl.ExecuteTemplate(wr, "class.ts.tmpl", def) +// if err != nil { +// println("Problem executing template: " + err.Error()) +// return err +// } +// return nil +//} + // //import ( // "bytes" diff --git a/v3/internal/parser/models_test.go b/v3/internal/parser/models_test.go new file mode 100644 index 000000000..8eb74f7d7 --- /dev/null +++ b/v3/internal/parser/models_test.go @@ -0,0 +1,133 @@ +package parser + +import ( + "github.com/google/go-cmp/cmp" + "strings" + "testing" +) + +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); + } + + 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", + }, + }, + }, + } + anon1 := StructDef{ + Name: "anon1", + Fields: []*Field{ + { + Name: "Age", + Type: &ParameterType{ + Name: "int", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + } + + var builder strings.Builder + models := make(map[string]*StructDef) + models["Person"] = &person + models["anon1"] = &anon1 + def := ModelDefinitions{ + Package: "main", + Models: models, + } + + 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) + } +} diff --git a/v3/internal/parser/parser.go b/v3/internal/parser/parser.go index 6c2923953..59b25d1f9 100644 --- a/v3/internal/parser/parser.go +++ b/v3/internal/parser/parser.go @@ -11,6 +11,7 @@ import ( "path/filepath" "reflect" "strconv" + "strings" ) type packagePath = string @@ -49,6 +50,21 @@ type Field struct { Type *ParameterType } +func (f *Field) JSName() string { + return strings.ToLower(f.Name[0:1]) + f.Name[1:] +} + +func (f *Field) JSDef(pkg string) string { + name := f.JSName() + + if f.Type.Package == "" || f.Type.Package == pkg { + return fmt.Sprintf("%s: %s;", name, f.Type.Name) + } + + parts := strings.Split(f.Type.Package, "/") + return fmt.Sprintf("%s: %s.%s;", name, parts[len(parts)-1], f.Type.Name) +} + type ParsedPackage struct { Pkg *ast.Package Name string diff --git a/v3/internal/parser/templates/class.ts.tmpl b/v3/internal/parser/templates/class.ts.tmpl new file mode 100644 index 000000000..886f6b663 --- /dev/null +++ b/v3/internal/parser/templates/class.ts.tmpl @@ -0,0 +1,16 @@ + export class {{.Name}} { + {{range .Fields}}{{.}} + {{end}} + static createFrom(source: any = {}) { + return new {{.Name}}(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + {{range .Fields}}this.{{jsName .}} = source["{{jsName .}}"] + {{end}} + } + } diff --git a/v3/internal/parser/templates/model.ts.tmpl b/v3/internal/parser/templates/model.ts.tmpl new file mode 100644 index 000000000..b2863e37e --- /dev/null +++ b/v3/internal/parser/templates/model.ts.tmpl @@ -0,0 +1,21 @@ +{{$pkg := .Package}} +export namespace {{.Package}} { + {{range $name, $def := .Models}} + export class {{$def.Name}} { + {{range $def.Fields}}{{.JSDef $pkg}} + {{end}} + static createFrom(source: any = {}) { + return new {{$def.Name}}(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + {{range $def.Fields}}this.{{.JSName}} = source["{{.JSName}}"] + {{end}} + } + } + {{end}} +}