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 }