github.com/tada-team/tdproto@v1.51.57/codegen/dart/main.go (about)

     1  package main
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"sort"
     9  	"strings"
    10  	"text/template"
    11  	"unicode"
    12  
    13  	"github.com/tada-team/tdproto/codegen"
    14  )
    15  
    16  const libPathPrefix = "./lib/"
    17  const enumsPathPrefix = "./src/enums"
    18  const modelsPathPrefix = "./src/models"
    19  
    20  var dartTypeMap = map[string]string{
    21  	"string":            "String",
    22  	"int":               "int",
    23  	"int32":             "int",
    24  	"int64":             "int",
    25  	"uint":              "int",
    26  	"uint16":            "int",
    27  	"uint32":            "int",
    28  	"uint64":            "int",
    29  	"float":             "double",
    30  	"float32":           "double",
    31  	"float64":           "double",
    32  	"ISODateTimeString": "DateTime",
    33  	"interface{}":       "dynamic",
    34  	"time.Time":         "String",
    35  	"bool":              "bool",
    36  	"UiSettingsData":    "Map<String, dynamic>",
    37  }
    38  
    39  var dartFieldNameSubstitutions = map[string]string{
    40  	"Default": "isDefault",
    41  	"New":     "isNew",
    42  	"Public":  "isPublic",
    43  	"Static":  "isStatic",
    44  }
    45  
    46  var dartClassTemplate = template.Must(template.New("dartClass").Parse(`import 'package:freezed_annotation/freezed_annotation.dart';
    47  import 'package:tdproto_dart/tdproto_dart.dart';
    48  
    49  part '{{.SnakeCase}}.freezed.dart';
    50  part '{{.SnakeCase}}.g.dart';
    51  
    52  {{if eq .Parent.Help "MISSING CLASS DOCUMENTATION"}}// {{else}}/// {{end}}{{.Parent.Help}}.
    53  @freezed
    54  class {{.Name}} with _${{.Name}} {
    55    const factory {{.Name}}({
    56      {{range $field := .Fields -}}
    57      {{if eq $field.Parent.Help "DOCUMENTATION MISSING"}}// {{else}}/// {{end}}{{$field.Parent.Help}}.
    58      {{$field.DefaultStr}}{{if $field.IsDeprecated}}@Deprecated('{{$field.Parent.Help}}.') {{end}}@JsonKey(name: '{{$field.Parent.JsonName}}') 
    59  	{{- if eq $field.DartType "DateTime"}} @DateTimeConverter(){{end -}}
    60      {{- if $field.IsNotRequired}} {{else}} required {{end -}}
    61      {{if $field.Parent.IsList}}List<{{$field.DartType}}>{{else if eq $field.DartType "MessageLink"}}List<{{$field.DartType}}>{{else}}{{$field.DartType}}{{end -}}
    62  	{{- if $field.IsNotRequired}}? {{else}} {{end -}}
    63  	{{$field.Name}},
    64      
    65      {{end}}
    66    }) = _{{.Name}};
    67  
    68    factory {{.Name}}.fromJson(Map<String, dynamic> json) => _${{.Name}}FromJson(json);
    69  }
    70  `))
    71  
    72  var dartEnumTemplate = template.Must(template.New("dartEnum").Parse(`import 'package:freezed_annotation/freezed_annotation.dart';
    73  
    74  enum {{.Name}} { {{range $value :=  .Values}}
    75    @JsonValue('{{$value}}')
    76    {{$value}},
    77    {{end}}
    78  }
    79  `))
    80  
    81  var dartLibTemplate = template.Must(template.New("dartLib").Parse(`library tdproto_dart;
    82  
    83  // Converters:
    84  export 'src/converters/date_time_converter.dart';
    85  
    86  // Interfaces:
    87  export 'src/interfaces/i_response.dart';
    88  export 'src/interfaces/i_websocket_event.dart';
    89  
    90  // Generated enums:
    91  {{range $value := .GeneratedEnums}}export '{{$value}}';
    92  {{end}}
    93  // Generated models:
    94  {{range $value := .GeneratedModels}}export '{{$value}}';
    95  {{end}}
    96  `))
    97  
    98  type DartLibInfo struct {
    99  	GeneratedEnums  []string
   100  	GeneratedModels []string
   101  }
   102  
   103  type DartClassField struct {
   104  	Name         string
   105  	DartType     string
   106  	IsList       bool
   107  	IsDeprecated bool
   108  	Parent       codegen.TdStructField
   109  }
   110  
   111  func (s *DartClassField) IsNotRequired() bool {
   112  	return s.Parent.IsOmitEmpty || s.Parent.IsPointer
   113  }
   114  
   115  func (s *DartClassField) DefaultStr() string {
   116  	if !s.IsNotRequired() {
   117  		return ""
   118  	}
   119  
   120  	// Remove DKelin
   121  	// if s.DartType == "bool" {
   122  	// 	return "@Default(false) "
   123  	// }
   124  
   125  	return ""
   126  }
   127  
   128  type DartClass struct {
   129  	Fields    []DartClassField
   130  	Parent    codegen.TdStruct
   131  	Name      string
   132  	SnakeCase string
   133  }
   134  
   135  func snakeCaseOrLower(input string) string {
   136  	for _, char := range input {
   137  		if unicode.IsLower(char) {
   138  			return codegen.ToSnakeCase(input)
   139  		}
   140  	}
   141  
   142  	return strings.ToLower(input)
   143  }
   144  
   145  func lowercaseFirstOrAll(input string) string {
   146  	for _, char := range input {
   147  		if unicode.IsLower(char) {
   148  			return codegen.LowercaseFirstLetter(input)
   149  		}
   150  	}
   151  
   152  	return strings.ToLower(input)
   153  }
   154  
   155  func writeFileFromTemplate(fileName string, template *template.Template, data interface{}, useExclusive bool) error {
   156  
   157  	fileFlags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
   158  	if useExclusive {
   159  		fileFlags |= os.O_EXCL
   160  	}
   161  
   162  	file, err := os.OpenFile(fileName, fileFlags, 0o640)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	err = template.Execute(file, data)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	err = file.Close()
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  func generateDart(tdprotoInfo *codegen.TdPackage, dartPackagePath string) error {
   181  	var libInfo DartLibInfo
   182  
   183  	baseLibPath := path.Join(dartPackagePath, libPathPrefix)
   184  
   185  	for _, tdEnum := range tdprotoInfo.GetEnums() {
   186  		enumFileName := codegen.ToSnakeCase(tdEnum.Name)
   187  		enumFilePath := path.Join(enumsPathPrefix, fmt.Sprintf("%s.dart", enumFileName))
   188  		libInfo.GeneratedEnums = append(libInfo.GeneratedEnums, enumFilePath)
   189  
   190  		err := writeFileFromTemplate(path.Join(baseLibPath, enumFilePath), dartEnumTemplate, tdEnum, true)
   191  		if err != nil {
   192  			return err
   193  		}
   194  	}
   195  
   196  	dartClasses := generateDartClasses(tdprotoInfo)
   197  
   198  	for _, dartClass := range dartClasses {
   199  		dartClassFilename := dartClass.SnakeCase
   200  		dartClassFolderPath := path.Join(modelsPathPrefix, dartClassFilename)
   201  		dartClassFilePath := path.Join(dartClassFolderPath, fmt.Sprintf("%s.dart", dartClassFilename))
   202  		libInfo.GeneratedModels = append(libInfo.GeneratedModels, dartClassFilePath)
   203  
   204  		err := os.Mkdir(path.Join(baseLibPath, dartClassFolderPath), 0o750)
   205  		if err != nil {
   206  			return nil
   207  		}
   208  
   209  		err = writeFileFromTemplate(path.Join(baseLibPath, dartClassFilePath), dartClassTemplate, dartClass, true)
   210  		if err != nil {
   211  			return err
   212  		}
   213  	}
   214  
   215  	sort.Slice(libInfo.GeneratedEnums, func(i, j int) bool {
   216  		return libInfo.GeneratedEnums[i] < libInfo.GeneratedEnums[j]
   217  	})
   218  
   219  	sort.Slice(libInfo.GeneratedModels, func(i, j int) bool {
   220  		return libInfo.GeneratedModels[i] < libInfo.GeneratedModels[j]
   221  	})
   222  
   223  	err := writeFileFromTemplate(path.Join(baseLibPath, "./tdproto_dart.dart"), dartLibTemplate, libInfo, true)
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	return nil
   229  }
   230  
   231  func getDartTypeFromGoType(goType string, tdprotoInfo *codegen.TdPackage) (string, bool) {
   232  	primitiveType, ok := dartTypeMap[goType]
   233  	if ok {
   234  		return primitiveType, false
   235  	}
   236  
   237  	for _, tdType := range tdprotoInfo.TdTypes {
   238  		if tdType.Name == goType {
   239  			unwrappedTypeName, isUnwrappedArray := getDartTypeFromGoType(tdType.BaseType, tdprotoInfo)
   240  			return unwrappedTypeName, isUnwrappedArray || tdType.IsArray
   241  		}
   242  	}
   243  
   244  	return codegen.UppercaseFirstLetter(goType), false
   245  }
   246  
   247  func generateDartClasses(tdprotoInfo *codegen.TdPackage) (dartClasses []DartClass) {
   248  	for _, structInfo := range tdprotoInfo.TdStructs {
   249  
   250  		newDartClass := DartClass{
   251  			Parent:    structInfo,
   252  			Name:      codegen.UppercaseFirstLetter(structInfo.Name),
   253  			SnakeCase: snakeCaseOrLower(structInfo.Name),
   254  		}
   255  
   256  		var allFields []codegen.TdStructField
   257  		allFields = append(allFields, structInfo.Fields...)
   258  		for _, anonStruct := range structInfo.GetStructAnonymousStructs(tdprotoInfo) {
   259  			allFields = append(allFields, anonStruct.Fields...)
   260  		}
   261  
   262  		for _, tdField := range allFields {
   263  			dartTypeStr, isList := getDartTypeFromGoType(tdField.TypeStr, tdprotoInfo)
   264  
   265  			dartFieldName, isSubstituted := dartFieldNameSubstitutions[tdField.Name]
   266  			if !isSubstituted {
   267  				dartFieldName = lowercaseFirstOrAll(tdField.Name)
   268  			}
   269  
   270  			newDartClass.Fields = append(newDartClass.Fields, DartClassField{
   271  				Name:         dartFieldName,
   272  				DartType:     dartTypeStr,
   273  				IsList:       isList || tdField.IsList,
   274  				Parent:       tdField,
   275  				IsDeprecated: strings.Contains(tdField.Help, "Deprecated"),
   276  			})
   277  		}
   278  
   279  		dartClasses = append(dartClasses, newDartClass)
   280  	}
   281  
   282  	// Manually add TeamUnread
   283  	dartClasses = append(dartClasses, DartClass{
   284  		Name:      "TeamUnread",
   285  		SnakeCase: "team_unread",
   286  		Parent: codegen.TdStruct{
   287  			Help: "Manually added",
   288  		},
   289  		Fields: []DartClassField{
   290  			{Name: "Direct", DartType: "Unread", Parent: codegen.TdStructField{
   291  				Help:     "Manually added",
   292  				JsonName: "direct",
   293  			}},
   294  			{Name: "Group", DartType: "Unread", Parent: codegen.TdStructField{
   295  				Help:     "Manually added",
   296  				JsonName: "group",
   297  			}},
   298  			{Name: "Task", DartType: "Unread", Parent: codegen.TdStructField{
   299  				Help:     "Manually added",
   300  				JsonName: "task",
   301  			}},
   302  		},
   303  	})
   304  
   305  	return
   306  }
   307  
   308  func createDirectoryStructure(basePath string) error {
   309  	enumsPath := path.Join(basePath, libPathPrefix, enumsPathPrefix)
   310  	modelsPath := path.Join(basePath, libPathPrefix, modelsPathPrefix)
   311  
   312  	err := os.MkdirAll(enumsPath, 0o750)
   313  	if err != nil {
   314  		return err
   315  	}
   316  
   317  	err = os.MkdirAll(modelsPath, 0o750)
   318  	if err != nil {
   319  		return err
   320  	}
   321  
   322  	return nil
   323  }
   324  
   325  func main() {
   326  
   327  	tdprotoInfo, err := codegen.ParseTdproto()
   328  
   329  	if err != nil {
   330  		panic(err)
   331  	}
   332  
   333  	flag.Parse()
   334  
   335  	var dartLibPath string
   336  	switch len_args := len(flag.Args()); len_args {
   337  	case 0:
   338  		newTempDir, err := os.MkdirTemp("", "tdproto_dart")
   339  		if err != nil {
   340  			panic(err)
   341  		}
   342  		dartLibPath = newTempDir
   343  		println("Temp directory: ", newTempDir)
   344  	case 1:
   345  		dartLibPath = flag.Arg(0)
   346  	default:
   347  		panic(fmt.Errorf("expected zero or one argument got %d", len_args))
   348  	}
   349  
   350  	err = createDirectoryStructure(dartLibPath)
   351  	if err != nil {
   352  		panic(err)
   353  	}
   354  
   355  	err = generateDart(tdprotoInfo.TdModels, dartLibPath)
   356  	if err != nil {
   357  		panic(err)
   358  	}
   359  
   360  }