From ec33e5ed70dcb4555eb0fcf04b0f6435c9ea6d21 Mon Sep 17 00:00:00 2001 From: Zikani Nyirenda Mwase Date: Fri, 1 Mar 2024 23:37:16 +0200 Subject: [PATCH 1/2] feat: support generating Dart classes via 11wizards/go-to-dart --- 11wizards/README.md | 1 + 11wizards/go-to-dart/.gitignore | 2 + 11wizards/go-to-dart/LICENSE | 21 ++ 11wizards/go-to-dart/README.md | 92 +++++++++ 11wizards/go-to-dart/generator/class.go | 62 ++++++ .../generator/dart/timestamp_converter.dart | 9 + 11wizards/go-to-dart/generator/field.go | 67 +++++++ .../go-to-dart/generator/format/alias.go | 40 ++++ .../go-to-dart/generator/format/array.go | 39 ++++ .../go-to-dart/generator/format/format.go | 55 ++++++ .../go-to-dart/generator/format/helpers.go | 38 ++++ 11wizards/go-to-dart/generator/format/map.go | 40 ++++ .../go-to-dart/generator/format/mo/option.go | 45 +++++ .../go-to-dart/generator/format/pointer.go | 38 ++++ .../go-to-dart/generator/format/primitive.go | 69 +++++++ .../go-to-dart/generator/format/struct.go | 39 ++++ 11wizards/go-to-dart/generator/format/time.go | 36 ++++ 11wizards/go-to-dart/generator/generator.go | 162 ++++++++++++++++ .../go-to-dart/generator/options/options.go | 15 ++ go.mod | 16 ++ go.sum | 180 ++++++++++++++++++ main.go | 23 +++ 22 files changed, 1089 insertions(+) create mode 100644 11wizards/README.md create mode 100644 11wizards/go-to-dart/.gitignore create mode 100644 11wizards/go-to-dart/LICENSE create mode 100644 11wizards/go-to-dart/README.md create mode 100644 11wizards/go-to-dart/generator/class.go create mode 100644 11wizards/go-to-dart/generator/dart/timestamp_converter.dart create mode 100644 11wizards/go-to-dart/generator/field.go create mode 100644 11wizards/go-to-dart/generator/format/alias.go create mode 100644 11wizards/go-to-dart/generator/format/array.go create mode 100644 11wizards/go-to-dart/generator/format/format.go create mode 100644 11wizards/go-to-dart/generator/format/helpers.go create mode 100644 11wizards/go-to-dart/generator/format/map.go create mode 100644 11wizards/go-to-dart/generator/format/mo/option.go create mode 100644 11wizards/go-to-dart/generator/format/pointer.go create mode 100644 11wizards/go-to-dart/generator/format/primitive.go create mode 100644 11wizards/go-to-dart/generator/format/struct.go create mode 100644 11wizards/go-to-dart/generator/format/time.go create mode 100644 11wizards/go-to-dart/generator/generator.go create mode 100644 11wizards/go-to-dart/generator/options/options.go diff --git a/11wizards/README.md b/11wizards/README.md new file mode 100644 index 0000000..c40f739 --- /dev/null +++ b/11wizards/README.md @@ -0,0 +1 @@ +This directory contains a slightly modified version of [https://github.com/11wizards/go-to-dart](https://github.com/11wizards/go-to-dart) intended to be embedded in geneveev. diff --git a/11wizards/go-to-dart/.gitignore b/11wizards/go-to-dart/.gitignore new file mode 100644 index 0000000..b717f86 --- /dev/null +++ b/11wizards/go-to-dart/.gitignore @@ -0,0 +1,2 @@ +.idea +dist/ diff --git a/11wizards/go-to-dart/LICENSE b/11wizards/go-to-dart/LICENSE new file mode 100644 index 0000000..c91721d --- /dev/null +++ b/11wizards/go-to-dart/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 11Wizards (U.K.) Limited. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/11wizards/go-to-dart/README.md b/11wizards/go-to-dart/README.md new file mode 100644 index 0000000..1184b41 --- /dev/null +++ b/11wizards/go-to-dart/README.md @@ -0,0 +1,92 @@ +# go-to-dart + +Go-to-dart helps you convert Go structs to Dart classes that can be used with [json_serializable](https://pub.dev/packages/json_serializable). + +## Features + +- Supports only structs in the same package (no generics or embedded structs yet) +- Supports primitives, slices, maps, and pointers +- Support some other arbitrary types such as `time.Time` and `mo.Option` (easy to extend!) +- Support for `json` tags + +Need something more? Please open an issue or even better, a PR! + +## Installation + +```bash +go install github.com/11wizards/go-to-dart@latest +``` + +The above command will install go-to-dart in your `$GOPATH/bin` directory. Make sure that directory is in your `$PATH`. + +## Usage + +For plain JSON serialization, you can use the `json` mode. +```bash +go-to-dart -i ./examples/user -o ./examples/user -m json +``` + +For serialization that works with Firestore, you can use the `firestore` mode. +```bash +go-to-dart -i ./examples/user -o ./examples/user -m firestore +``` + +## Example + +Running the command above would take the package `./examples/user` below and generate a file `./examples/user/user.dart`. + +```go +package user + +import ( + "time" +) + +type User struct { + ID int + Name string + Email string + Password string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt *time.Time + Options map[string]string + Tags []string +} +``` + +Contents of `./examples/user/user.dart`: +```dart +import 'package:json_annotation/json_annotation.dart'; + +part 'user.go.g.dart'; + +@JsonSerializable(explicitToJson: true) +class User { + @JsonKey(name: "ID")final int id; + @JsonKey(name: "Name")final String name; + @JsonKey(name: "Email")final String email; + @JsonKey(name: "Password")final String password; + @JsonKey(name: "CreatedAt")final DateTime createdAt; + @JsonKey(name: "UpdatedAt")final DateTime updatedAt; + @JsonKey(name: "DeletedAt")final DateTime? deletedAt; + @JsonKey(name: "Options", defaultValue: {})final Map options; + @JsonKey(name: "Tags", defaultValue: >[])final List tags; + + User({ + required this.id, + required this.name, + required this.email, + required this.password, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.options, + required this.tags, + }); + + Map toJson() => _$UserToJson(this); + + factory User.fromJson(Map json) => _$UserFromJson(json); +} +``` diff --git a/11wizards/go-to-dart/generator/class.go b/11wizards/go-to-dart/generator/class.go new file mode 100644 index 0000000..6ae9158 --- /dev/null +++ b/11wizards/go-to-dart/generator/class.go @@ -0,0 +1,62 @@ +package generator + +import ( + "fmt" + "go/types" + "io" + + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/format" + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/options" + "github.com/openconfig/goyang/pkg/indent" +) + +func generateFields(wr io.Writer, st *types.Struct, registry *format.TypeFormatterRegistry, mode options.Mode) { + for i := 0; i < st.NumFields(); i++ { + field := st.Field(i) + tag := st.Tag(i) + generateFieldDeclaration(wr, field, tag, registry, mode) + fmt.Fprintln(wr, ";") + } + fmt.Fprintln(wr) +} + +func generateConstructor(wr io.Writer, ts *types.TypeName, st *types.Struct, registry *format.TypeFormatterRegistry) { + fmt.Fprintf(wr, "%s({\n", ts.Name()) + + for i := 0; i < st.NumFields(); i++ { + f := st.Field(i) + generateFieldConstrutor(indent.NewWriter(wr, "\t"), f, registry) + fmt.Fprintln(wr, ",") + } + + fmt.Fprintf(wr, "});") + fmt.Fprintln(wr) + fmt.Fprintln(wr) +} + +func generateSerialization(wr io.Writer, ts *types.TypeName) { + fmt.Fprintf(wr, "Map toJson() => _$%sToJson(this);\n\n", ts.Name()) +} + +func generateDeserialization(wr io.Writer, ts *types.TypeName) { + fmt.Fprintf(wr, "factory %s.fromJson(Map json) => _$%sFromJson(json);\n", ts.Name(), ts.Name()) +} + +func generateDartClass(outputFile io.Writer, ts *types.TypeName, st *types.Struct, registry *format.TypeFormatterRegistry, mode options.Mode) { + fmt.Fprintln(outputFile, "@JsonSerializable(explicitToJson: true)") + if mode == options.Firestore { + fmt.Fprintln(outputFile, "@_TimestampConverter()") + } + + fmt.Fprintf(outputFile, "class %s {\n", ts.Name()) + + wr := indent.NewWriter(outputFile, "\t") + + generateFields(wr, st, registry, mode) + generateConstructor(wr, ts, st, registry) + generateSerialization(wr, ts) + generateDeserialization(wr, ts) + + fmt.Fprintln(outputFile, "}") + fmt.Fprintln(outputFile, "") +} diff --git a/11wizards/go-to-dart/generator/dart/timestamp_converter.dart b/11wizards/go-to-dart/generator/dart/timestamp_converter.dart new file mode 100644 index 0000000..e283eff --- /dev/null +++ b/11wizards/go-to-dart/generator/dart/timestamp_converter.dart @@ -0,0 +1,9 @@ +class _TimestampConverter implements JsonConverter { + const _TimestampConverter(); + + @override + DateTime fromJson(Timestamp json) => json.toDate(); + + @override + Timestamp toJson(DateTime object) => Timestamp.fromDate(object); +} \ No newline at end of file diff --git a/11wizards/go-to-dart/generator/field.go b/11wizards/go-to-dart/generator/field.go new file mode 100644 index 0000000..2ce85b3 --- /dev/null +++ b/11wizards/go-to-dart/generator/field.go @@ -0,0 +1,67 @@ +package generator + +import ( + "fmt" + "go/types" + "io" + "slices" + + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/format" + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/options" +) + +func generateFieldJSONKey(writer io.Writer, f *types.Var, tag string, registry *format.TypeFormatterRegistry, mode options.Mode) format.TypeFormatter { + formatter := registry.GetTypeFormatter(f.Type()) + fieldName := format.GetFieldName(f) + jsonFieldName := format.GetJSONFieldName(tag, mode) + + keyProperties := map[string]string{} + + if jsonFieldName != "" && jsonFieldName != fieldName { + keyProperties["name"] = fmt.Sprintf("\"%s\"", jsonFieldName) + } else if jsonFieldName == "" { + keyProperties["name"] = fmt.Sprintf("\"%s\"", f.Name()) + } + + if formatter.DefaultValue(f.Type()) != "" { + keyProperties["defaultValue"] = formatter.DefaultValue(f.Type()) + } + + if len(keyProperties) > 0 { + fmt.Fprint(writer, "@JsonKey(") + first := true + + // keys := maps.Keys(keyProperties) + keys := make([]string, 0) + for key, _ := range keyProperties { + keys = append(keys, key) + } + slices.Sort(keys) + + for _, key := range keys { + value := keyProperties[key] + + if !first { + fmt.Fprint(writer, ", ") + } else { + first = false + } + + fmt.Fprintf(writer, "%s: %s", key, value) + } + + fmt.Fprintf(writer, ")") + + } + return formatter +} + +func generateFieldDeclaration(writer io.Writer, f *types.Var, tag string, registry *format.TypeFormatterRegistry, mode options.Mode) { + formatter := generateFieldJSONKey(writer, f, tag, registry, mode) + fmt.Fprintf(writer, "final %s", formatter.Declaration(format.GetFieldName(f), f.Type())) +} + +func generateFieldConstrutor(writer io.Writer, f *types.Var, registry *format.TypeFormatterRegistry) { + formatter := registry.GetTypeFormatter(f.Type()) + fmt.Fprint(writer, formatter.Constructor(format.GetFieldName(f), f.Type())) +} diff --git a/11wizards/go-to-dart/generator/format/alias.go b/11wizards/go-to-dart/generator/format/alias.go new file mode 100644 index 0000000..58e5cb0 --- /dev/null +++ b/11wizards/go-to-dart/generator/format/alias.go @@ -0,0 +1,40 @@ +package format + +import ( + "fmt" + "go/types" +) + +type AliasFormatter struct { + TypeFormatterBase +} + +func (f *AliasFormatter) under(expr types.Type) types.Type { + if namedType, ok := expr.(*types.Named); ok { + return namedType.Underlying() + } + + return expr +} + +func (f *AliasFormatter) CanFormat(expr types.Type) bool { + return f.under(expr) != nil +} + +func (f *AliasFormatter) Signature(expr types.Type) string { + u := f.under(expr) + return f.Registry.GetTypeFormatter(u).Signature(u) +} + +func (f *AliasFormatter) DefaultValue(_ types.Type) string { + return "" +} + +func (f *AliasFormatter) Declaration(fieldName string, expr types.Type) string { + return fmt.Sprintf("%s %s", f.Signature(expr), fieldName) +} + +func (f *AliasFormatter) Constructor(fieldName string, expr types.Type) string { + u := f.under(expr) + return f.Registry.GetTypeFormatter(u).Constructor(fieldName, u) +} diff --git a/11wizards/go-to-dart/generator/format/array.go b/11wizards/go-to-dart/generator/format/array.go new file mode 100644 index 0000000..02fd0b8 --- /dev/null +++ b/11wizards/go-to-dart/generator/format/array.go @@ -0,0 +1,39 @@ +package format + +import ( + "fmt" + "go/types" +) + +type ArrayFormatter struct { + TypeFormatterBase +} + +func (f *ArrayFormatter) under(expr types.Type) (TypeFormatter, types.Type) { + sliceExpr := expr.(*types.Slice) + elem := sliceExpr.Elem() + formatter := f.Registry.GetTypeFormatter(elem) + return formatter, elem +} + +func (f *ArrayFormatter) CanFormat(expr types.Type) bool { + _, ok := expr.(*types.Slice) + return ok +} + +func (f *ArrayFormatter) Signature(expr types.Type) string { + formatter, expr := f.under(expr) + return fmt.Sprintf("List<%s>", formatter.Signature(expr)) +} + +func (f *ArrayFormatter) DefaultValue(expr types.Type) string { + return fmt.Sprintf("<%s>[]", f.Signature(expr)) +} + +func (f *ArrayFormatter) Declaration(fieldName string, expr types.Type) string { + return fmt.Sprintf("%s %s", f.Signature(expr), fieldName) +} + +func (f *ArrayFormatter) Constructor(fieldName string, _ types.Type) string { + return "required this." + fieldName +} diff --git a/11wizards/go-to-dart/generator/format/format.go b/11wizards/go-to-dart/generator/format/format.go new file mode 100644 index 0000000..86ba171 --- /dev/null +++ b/11wizards/go-to-dart/generator/format/format.go @@ -0,0 +1,55 @@ +package format + +import ( + "fmt" + "go/types" + + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/options" +) + +type TypeFormatter interface { + SetRegistry(registry *TypeFormatterRegistry) + CanFormat(expr types.Type) bool + Signature(expr types.Type) string + DefaultValue(expr types.Type) string + Declaration(fieldName string, expr types.Type) string + Constructor(fieldName string, expr types.Type) string +} + +type TypeFormatterBase struct { + Registry *TypeFormatterRegistry + Mode options.Mode +} + +func (t *TypeFormatterBase) SetRegistry(registry *TypeFormatterRegistry) { + t.Registry = registry +} + +type TypeFormatterRegistry struct { + KnownTypes map[types.Type]struct{} + Formatters []TypeFormatter +} + +func NewTypeFormatterRegistry() *TypeFormatterRegistry { + return &TypeFormatterRegistry{ + KnownTypes: make(map[types.Type]struct{}), + Formatters: make([]TypeFormatter, 0), + } +} +func (t *TypeFormatterRegistry) RegisterTypeFormatter(formatter TypeFormatter) { + t.Formatters = append(t.Formatters, formatter) + formatter.SetRegistry(t) +} + +func (t *TypeFormatterRegistry) GetTypeFormatter(expr types.Type) TypeFormatter { + // walks the t.Formatters in reverse order + // so that the last registered formatter is the first to be checked + for i := len(t.Formatters) - 1; i >= 0; i-- { + f := t.Formatters[i] + if f.CanFormat(expr) { + return f + } + } + + panic(fmt.Sprintf("no formatter found for %v", expr)) +} diff --git a/11wizards/go-to-dart/generator/format/helpers.go b/11wizards/go-to-dart/generator/format/helpers.go new file mode 100644 index 0000000..fab7b80 --- /dev/null +++ b/11wizards/go-to-dart/generator/format/helpers.go @@ -0,0 +1,38 @@ +package format + +import ( + "fmt" + "go/types" + "reflect" + "strings" + + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/options" + "github.com/iancoleman/strcase" +) + +func GetFieldName(f *types.Var) string { + if f.Anonymous() { + panic(fmt.Sprintf("no name for field: %#v", f)) + } + + return strcase.ToLowerCamel(f.Name()) +} + +func GetJSONFieldName(tag string, mode options.Mode) string { + var tagName string + if mode == options.Firestore { + tagName = "firestore" + } else { + tagName = "json" + } + + if tag != "" { + val := reflect.StructTag(strings.Trim(tag, "`")) + value, ok := val.Lookup(tagName) + if ok { + return strings.Split(value, ",")[0] + } + } + + return "" +} diff --git a/11wizards/go-to-dart/generator/format/map.go b/11wizards/go-to-dart/generator/format/map.go new file mode 100644 index 0000000..da2de42 --- /dev/null +++ b/11wizards/go-to-dart/generator/format/map.go @@ -0,0 +1,40 @@ +package format + +import ( + "fmt" + "go/types" +) + +type MapFormatter struct { + TypeFormatterBase +} + +func (f *MapFormatter) under(expr types.Type) (TypeFormatter, TypeFormatter, types.Type, types.Type) { + mapExpr := expr.(*types.Map) + keyFormatter := f.Registry.GetTypeFormatter(mapExpr.Key()) + valueFormatter := f.Registry.GetTypeFormatter(mapExpr.Elem()) + return keyFormatter, valueFormatter, mapExpr.Key(), mapExpr.Elem() +} + +func (f *MapFormatter) CanFormat(expr types.Type) bool { + _, ok := expr.(*types.Map) + return ok +} + +func (f *MapFormatter) Signature(expr types.Type) string { + keyFormatter, valueFormatter, keyExpr, valueExpr := f.under(expr) + return fmt.Sprintf("Map<%s, %s>", keyFormatter.Signature(keyExpr), valueFormatter.Signature(valueExpr)) +} + +func (f *MapFormatter) DefaultValue(expr types.Type) string { + keyFormatter, valueFormatter, keyExpr, valueExpr := f.under(expr) + return fmt.Sprintf("<%s, %s>{}", keyFormatter.Signature(keyExpr), valueFormatter.Signature(valueExpr)) +} + +func (f *MapFormatter) Declaration(fieldName string, expr types.Type) string { + return fmt.Sprintf("%s %s", f.Signature(expr), fieldName) +} + +func (f *MapFormatter) Constructor(fieldName string, _ types.Type) string { + return "required this." + fieldName +} diff --git a/11wizards/go-to-dart/generator/format/mo/option.go b/11wizards/go-to-dart/generator/format/mo/option.go new file mode 100644 index 0000000..d6d8627 --- /dev/null +++ b/11wizards/go-to-dart/generator/format/mo/option.go @@ -0,0 +1,45 @@ +package mo + +import ( + "fmt" + "go/types" + + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/format" +) + +type OptionFormatter struct { + format.TypeFormatterBase +} + +func (f *OptionFormatter) under(expr types.Type) (format.TypeFormatter, types.Type) { + e := expr.(*types.Named).TypeArgs().At(0) + formatter := f.Registry.GetTypeFormatter(e) + return formatter, e +} + +func (f *OptionFormatter) CanFormat(expr types.Type) bool { + if namedType, ok := expr.(*types.Named); ok { + if namedType.Obj().Type().String() == "github.com/samber/mo.Option[T any]" { + return true + } + } + + return false +} + +func (f *OptionFormatter) Signature(expr types.Type) string { + formatter, expr := f.under(expr) + return fmt.Sprintf("%s?", formatter.Signature(expr)) +} + +func (f *OptionFormatter) DefaultValue(_ types.Type) string { + return "" +} + +func (f *OptionFormatter) Declaration(fieldName string, expr types.Type) string { + return fmt.Sprintf("%s %s", f.Signature(expr), fieldName) +} + +func (f *OptionFormatter) Constructor(fieldName string, _ types.Type) string { + return "this." + fieldName +} diff --git a/11wizards/go-to-dart/generator/format/pointer.go b/11wizards/go-to-dart/generator/format/pointer.go new file mode 100644 index 0000000..05c97a7 --- /dev/null +++ b/11wizards/go-to-dart/generator/format/pointer.go @@ -0,0 +1,38 @@ +package format + +import ( + "fmt" + "go/types" +) + +type PointerFormatter struct { + TypeFormatterBase +} + +func (f *PointerFormatter) under(expr types.Type) (TypeFormatter, types.Type) { + starExpr := expr.(*types.Pointer) + formatter := f.Registry.GetTypeFormatter(starExpr.Elem()) + return formatter, starExpr.Elem() +} + +func (f *PointerFormatter) CanFormat(expr types.Type) bool { + _, ok := expr.(*types.Pointer) + return ok +} + +func (f *PointerFormatter) Signature(expr types.Type) string { + formatter, expr := f.under(expr) + return fmt.Sprintf("%s?", formatter.Signature(expr)) +} + +func (f *PointerFormatter) DefaultValue(_ types.Type) string { + return "" +} + +func (f *PointerFormatter) Declaration(fieldName string, expr types.Type) string { + return fmt.Sprintf("%s %s", f.Signature(expr), fieldName) +} + +func (f *PointerFormatter) Constructor(fieldName string, _ types.Type) string { + return "this." + fieldName +} diff --git a/11wizards/go-to-dart/generator/format/primitive.go b/11wizards/go-to-dart/generator/format/primitive.go new file mode 100644 index 0000000..210ac4a --- /dev/null +++ b/11wizards/go-to-dart/generator/format/primitive.go @@ -0,0 +1,69 @@ +package format + +import ( + "fmt" + "go/types" +) + +type PrimitiveFormatter struct { + TypeFormatterBase +} + +func (f *PrimitiveFormatter) toDartPrimitive(expr types.Type) string { + if e, ok := expr.(*types.Basic); ok { + switch e.Kind() { + case types.Bool: + return "bool" + case types.Float32: + return "double" + case types.Float64: + return "double" + case types.Int: + return "int" + case types.Int16: + return "int" + case types.Int32: + return "int" + case types.Int64: + return "int" + case types.Int8: + return "int" + case types.String: + return "String" + case types.Uint: + return "int" + case types.Uint16: + return "int" + case types.Uint32: + return "int" + case types.Uint64: + return "int" + case types.Uint8: + return "int" + case types.Uintptr: + return "int" + } + } + + return "" +} + +func (f *PrimitiveFormatter) CanFormat(expr types.Type) bool { + return f.toDartPrimitive(expr) != "" +} + +func (f *PrimitiveFormatter) Signature(expr types.Type) string { + return f.toDartPrimitive(expr) +} + +func (f *PrimitiveFormatter) DefaultValue(_ types.Type) string { + return "" +} + +func (f *PrimitiveFormatter) Declaration(fieldName string, expr types.Type) string { + return fmt.Sprintf("%s %s", f.Signature(expr), fieldName) +} + +func (f *PrimitiveFormatter) Constructor(fieldName string, _ types.Type) string { + return "required this." + fieldName +} diff --git a/11wizards/go-to-dart/generator/format/struct.go b/11wizards/go-to-dart/generator/format/struct.go new file mode 100644 index 0000000..6445b1a --- /dev/null +++ b/11wizards/go-to-dart/generator/format/struct.go @@ -0,0 +1,39 @@ +package format + +import ( + "fmt" + "go/types" +) + +type StructFormatter struct { + TypeFormatterBase +} + +func (f *StructFormatter) under(expr types.Type) *types.Struct { + if namedType, ok := expr.(*types.Named); ok { + if structType, ok := namedType.Underlying().(*types.Struct); ok { + return structType + } + } + return nil +} + +func (f *StructFormatter) CanFormat(expr types.Type) bool { + return f.under(expr) != nil +} + +func (f *StructFormatter) Signature(expr types.Type) string { + return expr.(*types.Named).Obj().Name() +} + +func (f *StructFormatter) DefaultValue(_ types.Type) string { + return "" +} + +func (f *StructFormatter) Declaration(fieldName string, expr types.Type) string { + return fmt.Sprintf("%s %s", f.Signature(expr), fieldName) +} + +func (f *StructFormatter) Constructor(fieldName string, _ types.Type) string { + return "required this." + fieldName +} diff --git a/11wizards/go-to-dart/generator/format/time.go b/11wizards/go-to-dart/generator/format/time.go new file mode 100644 index 0000000..0d71109 --- /dev/null +++ b/11wizards/go-to-dart/generator/format/time.go @@ -0,0 +1,36 @@ +package format + +import ( + "fmt" + "go/types" +) + +type TimeFormatter struct { + TypeFormatterBase +} + +func (f *TimeFormatter) CanFormat(expr types.Type) bool { + if namedType, ok := expr.(*types.Named); ok { + if namedType.Obj().Type().String() == "time.Time" { + return true + } + } + + return false +} + +func (f *TimeFormatter) Signature(_ types.Type) string { + return "DateTime" +} + +func (f *TimeFormatter) DefaultValue(_ types.Type) string { + return "" +} + +func (f *TimeFormatter) Declaration(fieldName string, expr types.Type) string { + return fmt.Sprintf("%s %s", f.Signature(expr), fieldName) +} + +func (f *TimeFormatter) Constructor(fieldName string, _ types.Type) string { + return fmt.Sprintf("required this.%s", fieldName) +} diff --git a/11wizards/go-to-dart/generator/generator.go b/11wizards/go-to-dart/generator/generator.go new file mode 100644 index 0000000..50bf498 --- /dev/null +++ b/11wizards/go-to-dart/generator/generator.go @@ -0,0 +1,162 @@ +package generator + +import ( + "bytes" + _ "embed" + "fmt" + "go/types" + "io" + "os" + "path/filepath" + "sort" + + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/format" + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/format/mo" + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/options" + + "golang.org/x/tools/go/packages" +) + +//go:embed dart/timestamp_converter.dart +var timestampConverterSrc string + +func generateHeader(pkg *packages.Package, wr io.Writer, mode options.Mode, imports []string) { + if mode == options.Firestore { + fmt.Fprint(wr, "import 'package:cloud_firestore/cloud_firestore.dart';\n") + } + + fmt.Fprint(wr, "import 'package:json_annotation/json_annotation.dart';\n") + for _, imp := range imports { + fmt.Fprintf(wr, "import '%s';\n", imp) + } + + fmt.Fprintf(wr, "\npart '%s.go.g.dart';\n\n", pkg.Name) + + if mode == options.Firestore { + fmt.Fprint(wr, timestampConverterSrc) + fmt.Fprint(wr, "\n\n") + } +} + +func createRegistry(mode options.Mode) *format.TypeFormatterRegistry { + registry := format.NewTypeFormatterRegistry() + + base := format.TypeFormatterBase{Mode: mode} + + registry.RegisterTypeFormatter(&format.AliasFormatter{TypeFormatterBase: base}) + registry.RegisterTypeFormatter(&format.StructFormatter{TypeFormatterBase: base}) + registry.RegisterTypeFormatter(&format.PrimitiveFormatter{TypeFormatterBase: base}) + registry.RegisterTypeFormatter(&format.TimeFormatter{TypeFormatterBase: base}) + registry.RegisterTypeFormatter(&format.PointerFormatter{TypeFormatterBase: base}) + registry.RegisterTypeFormatter(&format.ArrayFormatter{TypeFormatterBase: base}) + registry.RegisterTypeFormatter(&format.MapFormatter{TypeFormatterBase: base}) + registry.RegisterTypeFormatter(&mo.OptionFormatter{TypeFormatterBase: base}) + + return registry +} + +func generateClasses(pkg *packages.Package, wr io.Writer, mode options.Mode) { + registry := createRegistry(mode) + + for _, value := range pkg.TypesInfo.Defs { + if value == nil { + continue + } + + if typeName, ok := value.(*types.TypeName); ok { + registry.KnownTypes[typeName.Type()] = struct{}{} + } + } + + var list []struct { + TypeName *types.TypeName + StructType *types.Struct + } + + for _, value := range pkg.TypesInfo.Defs { + if value == nil { + continue + } + + if typeName, ok := value.(*types.TypeName); ok { + if structType, ok := typeName.Type().Underlying().(*types.Struct); ok { + list = append(list, struct { + TypeName *types.TypeName + StructType *types.Struct + }{ + TypeName: typeName, + StructType: structType, + }) + } + } + } + + sort.Slice(list, func(i, j int) bool { + return list[i].TypeName.Name() < list[j].TypeName.Name() + }) + + for _, item := range list { + generateDartClass(wr, item.TypeName, item.StructType, registry, mode) + } +} + +func writeOut(output, outputDartFile string, wr *bytes.Buffer) { + if _, err := os.Stat(output); os.IsNotExist(err) { + err = os.MkdirAll(output, os.ModePerm) + if err != nil { + panic(err) + } + } + + outputFilePath := filepath.Join(output, outputDartFile) + outputFile, err := os.Create(outputFilePath) + if err != nil { + panic(err) + } + + defer func() { _ = outputFile.Close() }() + + _, err = outputFile.Write(wr.Bytes()) + + if err != nil { + panic(err) + } + + fmt.Printf("Processed: %s -> %s\n", outputDartFile, outputFilePath) +} + +func Run(options options.Options) { + if abs, err := filepath.Abs(options.Input); err == nil { + options.Input = abs + } else { + panic(err) + } + + pkgs, err := packages.Load(&packages.Config{ + Dir: options.Input, + Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo, + }, options.Input) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + for _, pkg := range pkgs { + if len(pkg.Errors) > 0 { + for _, err := range pkg.Errors { + fmt.Println(err) + } + + os.Exit(1) + } + } + + for _, pkg := range pkgs { + var buf []byte + wr := bytes.NewBuffer(buf) + generateHeader(pkg, wr, options.Mode, options.Imports) + generateClasses(pkg, wr, options.Mode) + writeOut(options.Output, fmt.Sprintf("%s.go.dart", pkg.Name), wr) + } +} diff --git a/11wizards/go-to-dart/generator/options/options.go b/11wizards/go-to-dart/generator/options/options.go new file mode 100644 index 0000000..999586f --- /dev/null +++ b/11wizards/go-to-dart/generator/options/options.go @@ -0,0 +1,15 @@ +package options + +type Mode string + +const ( + JSON Mode = "json" + Firestore Mode = "firestore" +) + +type Options struct { + Input string + Output string + Imports []string + Mode Mode +} diff --git a/go.mod b/go.mod index 03b0844..6053ecf 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,19 @@ module github.com/golang-malawi/geneveev go 1.21.5 + +require ( + github.com/gtramontina/ooze v0.2.0 + github.com/iancoleman/strcase v0.3.0 + github.com/openconfig/goyang v1.4.5 + golang.org/x/tools v0.18.0 +) + +require ( + github.com/fatih/color v1.14.1 // indirect + github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/sys v0.17.0 // indirect +) diff --git a/go.sum b/go.sum index e69de29..fbca308 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,180 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/gtramontina/ooze v0.2.0 h1:QDW1zeq1TQgTLbIWuk76GCgNV3adkamYxY1aJNYp/Bc= +github.com/gtramontina/ooze v0.2.0/go.mod h1:e0dltGb+Ws7SQKfoj4XkKf9C/UaIAK2YGWbLKLPwL6k= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/openconfig/gnmi v0.10.0/go.mod h1:Y9os75GmSkhHw2wX8sMsxfI7qRGAEcDh8NTa5a8vj6E= +github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea/go.mod h1:dhXaV0JgHJzdrHi2l+w0fZrwArtXL7jEFoiqLEdmkvU= +github.com/openconfig/goyang v1.4.5 h1:+s3p3MeiPQ/QNsC5DL3MXhCp5cv4dag3vlGKCtszsRU= +github.com/openconfig/goyang v1.4.5/go.mod h1:sdNZi/wdTZyLNBNfgLzmmbi7kISm7FskMDKKzMY+x1M= +github.com/openconfig/grpctunnel v0.0.0-20220819142823-6f5422b8ca70/go.mod h1:OmTWe7RyZj2CIzIgy4ovEBzCLBJzRvWSZmn7u02U9gU= +github.com/openconfig/ygot v0.6.0/go.mod h1:o30svNf7O0xK+R35tlx95odkDmZWS9JyWWQSmIhqwAs= +github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/protocolbuffers/txtpbfmt v0.0.0-20220608084003-fc78c767cd6a/go.mod h1:KjY0wibdYKc4DYkerHSbguaf3JeIPGhNJBp2BNiFH78= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +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= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20210811021853-ddbe55d93216/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index cc5b845..a414ee0 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,14 @@ package main import ( "flag" + "fmt" "go/ast" "go/parser" "go/token" + "os" + + dartGenerator "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator" + "github.com/golang-malawi/geneveev/11wizards/go-to-dart/generator/options" ) var ( @@ -12,6 +17,7 @@ var ( outputDir string snakeCase bool zodMode bool + dartMode bool ) func init() { @@ -19,6 +25,7 @@ func init() { flag.StringVar(&outputDir, "output-dir", "", "directory to place generated code") flag.BoolVar(&snakeCase, "snakecase", false, "whether to name files as snake_case") flag.BoolVar(&zodMode, "zod", false, "whether to generate zod schemas or not") + flag.BoolVar(&dartMode, "dart", false, "whether to generate Dart classes or not") } func main() { @@ -31,6 +38,22 @@ func main() { } for _, file := range packages { + if dartMode { + + o := options.Options{ + Input: packageDir, + Output: outputDir, + Imports: []string{}, + Mode: options.Mode(options.JSON), + } + + if o.Mode != options.JSON && o.Mode != options.Firestore { + fmt.Println("Mode must be either json or firestore") + os.Exit(1) + } + dartGenerator.Run(o) + } + if zodMode { ast.Inspect(file, generator(Zod())) } else { From 9f9c117fde34a2730864be8cec31bcbed96244a3 Mon Sep 17 00:00:00 2001 From: Zikani Nyirenda Mwase Date: Sat, 2 Mar 2024 11:04:40 +0200 Subject: [PATCH 2/2] docs: update README, remove go-to-dart readme --- 11wizards/go-to-dart/README.md | 92 ------------------------- 11wizards/go-to-dart/generator/field.go | 1 - README.md | 43 ++++++++++-- 3 files changed, 37 insertions(+), 99 deletions(-) delete mode 100644 11wizards/go-to-dart/README.md diff --git a/11wizards/go-to-dart/README.md b/11wizards/go-to-dart/README.md deleted file mode 100644 index 1184b41..0000000 --- a/11wizards/go-to-dart/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# go-to-dart - -Go-to-dart helps you convert Go structs to Dart classes that can be used with [json_serializable](https://pub.dev/packages/json_serializable). - -## Features - -- Supports only structs in the same package (no generics or embedded structs yet) -- Supports primitives, slices, maps, and pointers -- Support some other arbitrary types such as `time.Time` and `mo.Option` (easy to extend!) -- Support for `json` tags - -Need something more? Please open an issue or even better, a PR! - -## Installation - -```bash -go install github.com/11wizards/go-to-dart@latest -``` - -The above command will install go-to-dart in your `$GOPATH/bin` directory. Make sure that directory is in your `$PATH`. - -## Usage - -For plain JSON serialization, you can use the `json` mode. -```bash -go-to-dart -i ./examples/user -o ./examples/user -m json -``` - -For serialization that works with Firestore, you can use the `firestore` mode. -```bash -go-to-dart -i ./examples/user -o ./examples/user -m firestore -``` - -## Example - -Running the command above would take the package `./examples/user` below and generate a file `./examples/user/user.dart`. - -```go -package user - -import ( - "time" -) - -type User struct { - ID int - Name string - Email string - Password string - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt *time.Time - Options map[string]string - Tags []string -} -``` - -Contents of `./examples/user/user.dart`: -```dart -import 'package:json_annotation/json_annotation.dart'; - -part 'user.go.g.dart'; - -@JsonSerializable(explicitToJson: true) -class User { - @JsonKey(name: "ID")final int id; - @JsonKey(name: "Name")final String name; - @JsonKey(name: "Email")final String email; - @JsonKey(name: "Password")final String password; - @JsonKey(name: "CreatedAt")final DateTime createdAt; - @JsonKey(name: "UpdatedAt")final DateTime updatedAt; - @JsonKey(name: "DeletedAt")final DateTime? deletedAt; - @JsonKey(name: "Options", defaultValue: {})final Map options; - @JsonKey(name: "Tags", defaultValue: >[])final List tags; - - User({ - required this.id, - required this.name, - required this.email, - required this.password, - required this.createdAt, - required this.updatedAt, - this.deletedAt, - required this.options, - required this.tags, - }); - - Map toJson() => _$UserToJson(this); - - factory User.fromJson(Map json) => _$UserFromJson(json); -} -``` diff --git a/11wizards/go-to-dart/generator/field.go b/11wizards/go-to-dart/generator/field.go index 2ce85b3..0858dd3 100644 --- a/11wizards/go-to-dart/generator/field.go +++ b/11wizards/go-to-dart/generator/field.go @@ -31,7 +31,6 @@ func generateFieldJSONKey(writer io.Writer, f *types.Var, tag string, registry * fmt.Fprint(writer, "@JsonKey(") first := true - // keys := maps.Keys(keyProperties) keys := make([]string, 0) for key, _ := range keyProperties { keys = append(keys, key) diff --git a/README.md b/README.md index 2b7628f..3bd197e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ geneveev ======== -A command-line tool to generate [Yup](https://yup-docs.vercel.app/docs/Api/yup) object schemas from Go structs that use [go-playground/validator](https://github.com/go-playground/validator) tags. +A command-line tool to generate object schemas from Go structs. -Useful for setting up validation stuff for front-end development. +Currently it supports generating + + - [Yup](https://yup-docs.vercel.app/docs/Api/yup) and [Zod](https://zod.dev) schemas that use [go-playground/validator](https://github.com/go-playground/validator) tags. + - Dart classes via the embedded go-to-dart functionality + +Useful for setting up validation for front-end development and for creating Data Transfer objects for Go APIs that have flutter clients > NOTE: still in very early development @@ -15,8 +20,23 @@ go install github.com/golang-malawi/geneveev@latest ## Usage + +**Generating Yup Schemas** + ```sh -$ geneveev -d /path/to/package/with/validated/structs/ +$ geneveev -d /path/to/package/with/validated/structs/ -output-dir ./yup-schemas +``` + +**Generating Zod Schemas** + +```sh +$ geneveev -zod -d /path/to/package/with/validated/structs/ -output-dir ./zod-schemas +``` + +**Generating Dart classes** + +```sh +$ geneveev -dart -d /path/to/package/with/validated/structs/ -output-dir ./dart-classes ``` ## Example @@ -31,12 +51,23 @@ To this: ## Features +### Yup Schema + - [x] Generates basic yup object schemas from basic Go structs -## TODO +### Zod Schema + +- [x] Generates basic zod object schemas from basic Go structs + +### go-to-dart + +The Go-to-dart implementation helps you convert Go structs to Dart classes that can be used with [json_serializable](https://pub.dev/packages/json_serializable). + +- Supports only structs in the same package (no generics or embedded structs yet) +- Supports primitives, slices, maps, and pointers +- Support some other arbitrary types such as `time.Time` and `mo.Option` (easy to extend!) +- Support for `json` tags -- Support nested structs (map to mixed, objects) -- Support ref fields ## Contributors