github.com/tada-team/tdproto@v1.51.57/codegen/inspect.go (about) 1 package codegen 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/parser" 7 "go/token" 8 "path" 9 "reflect" 10 "strings" 11 "unicode" 12 13 "github.com/tada-team/tdproto" 14 ) 15 16 var GolangPrimitiveTypes = map[string]string{ 17 "string": "", 18 "int": "", 19 "int64": "", 20 "uint16": "", 21 "uint": "", 22 "bool": "", 23 "interface{}": "", 24 "ISODateTimeString": "", 25 "time.Time": "", 26 } 27 28 type TdConstFields struct { 29 Name string 30 Type string 31 Value string 32 Help string 33 } 34 35 type TdQuery struct { 36 Name string 37 Help string 38 ParamsNamesAndHelp map[string]string 39 } 40 41 type TdStructField struct { 42 Name string 43 Help string 44 JsonName string 45 SchemaName string 46 TypeStr string 47 KeyTypeStr string 48 IsPrimitive bool 49 IsReadOnly bool 50 IsPointer bool 51 IsList bool 52 IsOmitEmpty bool 53 IsNotSerialized bool 54 } 55 56 type TdStruct struct { 57 Name string 58 Help string 59 Fields []TdStructField 60 ReadOnly bool 61 AnonnymousFields []string 62 FileName string 63 } 64 65 type TdType struct { 66 Name string 67 Help string 68 IsArray bool 69 BaseType string 70 Filename string 71 } 72 73 type TdMapType struct { 74 Name string 75 Help string 76 KeyTypeStr string 77 ValueTypeStr string 78 Filename string 79 } 80 81 type TdPackage struct { 82 TdStructs map[string]TdStruct 83 TdTypes map[string]TdType 84 TdEvents map[string]string 85 TdMapTypes map[string]TdMapType 86 TdConsts []TdConstFields 87 TdQueries map[string]TdQuery 88 } 89 90 type TdProto struct { 91 TdForms *TdPackage 92 TdModels *TdPackage 93 } 94 95 type TdEnum struct { 96 Name string 97 Values []string 98 } 99 100 func (i TdPackage) GetEnums() []TdEnum { 101 constMap := make(map[string][]string) 102 103 for _, aConst := range i.TdConsts { 104 constType := aConst.Type 105 constValue := aConst.Value 106 107 constValueList := constMap[constType] 108 constMap[constType] = append(constValueList, strings.Trim(constValue, `"`)) 109 } 110 111 var listOfEnums []TdEnum 112 113 for key, value := range constMap { 114 listOfEnums = append(listOfEnums, TdEnum{ 115 Name: key, 116 Values: value, 117 }) 118 } 119 120 return listOfEnums 121 } 122 123 func (tds TdStruct) IsEventParams(tdInfo *TdPackage) bool { 124 125 for eventStructName := range tdInfo.TdEvents { 126 eventStruct := tdInfo.TdStructs[eventStructName] 127 128 for _, field := range eventStruct.Fields { 129 if field.Name != "Params" { 130 continue 131 } 132 133 if field.TypeStr == tds.Name { 134 return true 135 } 136 } 137 } 138 139 return false 140 } 141 142 func (tds TdStruct) GetStructAnonymousStructs(tdInfo *TdPackage) []TdStruct { 143 anonymousStructs := make([]TdStruct, len(tds.AnonnymousFields)) 144 for i, anonymousStructName := range tds.AnonnymousFields { 145 anonymousStructs[i] = tdInfo.TdStructs[anonymousStructName] 146 } 147 148 // TODO: Deep copy Fields and AnonnymousFields 149 return anonymousStructs 150 } 151 152 func (tds TdStruct) GetAllJsonFields(tdInfo *TdPackage) []TdStructField { 153 var allFields []TdStructField 154 155 allFields = append(allFields, tds.Fields...) 156 157 for _, anonStruct := range tds.GetStructAnonymousStructs(tdInfo) { 158 allFields = append(allFields, anonStruct.Fields...) 159 } 160 161 return allFields 162 } 163 164 func ParseTdproto() (infoToFill *TdProto, err error) { 165 infoToFill = new(TdProto) 166 167 tdprotoFileSet := token.NewFileSet() 168 169 tdModelsPackage := new(TdPackage) 170 tdModelsPackage.TdEvents = make(map[string]string) 171 tdModelsPackage.TdStructs = make(map[string]TdStruct) 172 tdModelsPackage.TdTypes = make(map[string]TdType) 173 tdModelsPackage.TdMapTypes = make(map[string]TdMapType) 174 tdModelsPackage.TdQueries = make(map[string]TdQuery) 175 176 infoToFill.TdModels = tdModelsPackage 177 178 tdprotoNameToAstMap, err := extractTdprotoAst(tdprotoFileSet) 179 if err != nil { 180 return nil, err 181 } 182 183 tdprotoAst := tdprotoNameToAstMap["tdproto"] 184 err = parseTdprotoAst(tdprotoAst, tdModelsPackage, nil) 185 if err != nil { 186 return nil, err 187 } 188 189 tdapiFileSet := token.NewFileSet() 190 tdapiNameToAstMap, err := extractTdapiAst(tdapiFileSet) 191 if err != nil { 192 return nil, err 193 } 194 195 tdFormsPackage := new(TdPackage) 196 tdFormsPackage.TdEvents = make(map[string]string) 197 tdFormsPackage.TdStructs = make(map[string]TdStruct) 198 tdFormsPackage.TdTypes = make(map[string]TdType) 199 tdFormsPackage.TdMapTypes = make(map[string]TdMapType) 200 201 infoToFill.TdForms = tdFormsPackage 202 203 err = parseTdprotoAst(tdapiNameToAstMap["tdapi"], tdFormsPackage, 204 &map[string]string{ 205 "task": "", 206 "my_reactions": "", 207 "resp": "", 208 "err": "", 209 "sharplinks": "", 210 "easy_api": "", 211 "botcommands": "", 212 "parser": "", 213 "contact": "", 214 }, 215 ) 216 if err != nil { 217 return nil, err 218 } 219 220 // Cherry picking 221 // Task 222 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "Task") 223 if err != nil { 224 return nil, err 225 } 226 // TaskFilter query 227 err = cherryPickQuery(tdModelsPackage, tdFormsPackage, "TaskFilter") 228 if err != nil { 229 return nil, err 230 } 231 // MyReactions 232 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "MyReactions") 233 if err != nil { 234 return nil, err 235 } 236 237 // Resp 238 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "Resp") 239 if err != nil { 240 return nil, err 241 } 242 243 // Err 244 err = cherryPickTypeAlias(tdModelsPackage, tdFormsPackage, "Err") 245 if err != nil { 246 return nil, err 247 } 248 249 // EasyApiMessage 250 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "EasyApiMessage") 251 if err != nil { 252 return nil, err 253 } 254 255 // SharpLink 256 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "SharpLink") 257 if err != nil { 258 return nil, err 259 } 260 261 // SharpLinks 262 err = cherryPickTypeAlias(tdModelsPackage, tdFormsPackage, "SharpLinks") 263 if err != nil { 264 return nil, err 265 } 266 267 // SharpLinkMeta 268 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "SharpLinkMeta") 269 if err != nil { 270 return nil, err 271 } 272 273 // BotCommand 274 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "BotCommand") 275 if err != nil { 276 return nil, err 277 } 278 279 // BotCommands 280 err = cherryPickTypeAlias(tdModelsPackage, tdFormsPackage, "BotCommands") 281 if err != nil { 282 return nil, err 283 } 284 285 // Parser 286 // ParserUploadArchiveResponse 287 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "ParserUploadArchiveResponse") 288 if err != nil { 289 return nil, err 290 } 291 // ParserGetStateResponse 292 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "ParserGetStateResponse") 293 if err != nil { 294 return nil, err 295 } 296 // ParserGetMappedUsersResponse 297 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "ParserGetMappedUsersResponse") 298 if err != nil { 299 return nil, err 300 } 301 // ParserMapUsersRequest 302 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "ParserMapUsersRequest") 303 if err != nil { 304 return nil, err 305 } 306 // ParserMapUsersResponse 307 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "ParserMapUsersResponse") 308 if err != nil { 309 return nil, err 310 } 311 // ParserGenerateChatsResponse 312 err = cherryPickStruct(tdModelsPackage, tdFormsPackage, "ParserGenerateChatsResponse") 313 if err != nil { 314 return nil, err 315 } 316 317 // ContactFilter query 318 err = cherryPickQuery(tdModelsPackage, tdFormsPackage, "ContactFilter") 319 if err != nil { 320 return nil, err 321 } 322 323 return infoToFill, nil 324 } 325 326 func cherryPickTypeAlias(tdproto *TdPackage, tdapi *TdPackage, name string) error { 327 328 pickObject, ok := tdapi.TdTypes[name] 329 if !ok { 330 return fmt.Errorf("failed to cherry pick query %s", name) 331 } 332 tdproto.TdTypes[name] = pickObject 333 334 return nil 335 } 336 337 func cherryPickQuery(tdproto *TdPackage, tdapi *TdPackage, name string) error { 338 339 pickObject, ok := tdapi.TdStructs[name] 340 if !ok { 341 return fmt.Errorf("failed to cherry pick query %s", name) 342 } 343 344 var newQuery TdQuery 345 346 newQuery.Help = pickObject.Help 347 newQuery.ParamsNamesAndHelp = make(map[string]string) 348 newQuery.Name = name 349 for _, field := range pickObject.Fields { 350 newQuery.ParamsNamesAndHelp[field.SchemaName] = field.Help 351 } 352 353 tdproto.TdQueries[name] = newQuery 354 355 return nil 356 } 357 358 func cherryPickStruct(tdproto *TdPackage, tdapi *TdPackage, name string) error { 359 360 pickObject, ok := tdapi.TdStructs[name] 361 if !ok { 362 return fmt.Errorf("failed to cherry pick struct %s", name) 363 } 364 tdproto.TdStructs[name] = pickObject 365 366 return nil 367 } 368 369 func parseTdprotoAst(packageAst *ast.Package, infoToFill *TdPackage, fileFilter *map[string]string) error { 370 for fileName, fileAst := range packageAst.Files { 371 372 basePath := path.Base(fileName) 373 basePathNoExt := strings.TrimRight(basePath, path.Ext(basePath)) 374 375 if fileFilter != nil { 376 _, ok := (*fileFilter)[basePathNoExt] 377 if !ok { 378 continue 379 } 380 } 381 382 err := ParseTdprotoFile(infoToFill, basePathNoExt, fileAst) 383 if err != nil { 384 return err 385 } 386 } 387 388 return nil 389 } 390 391 func ParseTdprotoFile(infoToFill *TdPackage, fileName string, fileAst *ast.File) error { 392 for _, declaration := range fileAst.Decls { 393 switch declarationType := declaration.(type) { 394 case *ast.GenDecl: 395 err := ParseGenericDeclaration(infoToFill, declarationType, fileName) 396 if err != nil { 397 return err 398 } 399 case *ast.FuncDecl: 400 err := parseFunctionDeclaration(infoToFill, declarationType) 401 if err != nil { 402 return err 403 } 404 } 405 406 } 407 return nil 408 } 409 410 func parseFunctionDeclaration(infoToFill *TdPackage, functionDeclaration *ast.FuncDecl) error { 411 412 if !functionDeclaration.Name.IsExported() { 413 return nil 414 } 415 416 if functionDeclaration.Recv == nil { 417 return nil 418 } 419 420 if len(functionDeclaration.Recv.List) != 1 { 421 return nil 422 } 423 424 // Only parses the GetName functions right now which maps Struct name to an event name 425 if functionDeclaration.Name.Name != "GetName" { 426 return nil 427 } 428 429 returnStatementAst := functionDeclaration.Body.List[0].(*ast.ReturnStmt) 430 returnStatemetExpression, ok := returnStatementAst.Results[0].(*ast.BasicLit) 431 if !ok { 432 return nil 433 } 434 435 eventName := strings.Trim(returnStatemetExpression.Value, "\"") 436 437 typeIdent := functionDeclaration.Recv.List[0].Type.(*ast.Ident) 438 typeEventBelongsTo := typeIdent.Obj.Name 439 440 infoToFill.TdEvents[typeEventBelongsTo] = eventName 441 442 return nil 443 } 444 445 func ParseGenericDeclaration(infoToFill *TdPackage, genDeclaration *ast.GenDecl, fileName string) error { 446 switch genDeclaration.Tok { 447 case token.CONST: 448 return parseConstDeclaration(infoToFill, genDeclaration) 449 case token.TYPE: 450 for _, aSpec := range genDeclaration.Specs { 451 aTypeSpec := aSpec.(*ast.TypeSpec) 452 err := parseTypeDeclaration(infoToFill, genDeclaration, aTypeSpec, fileName) 453 if err != nil { 454 return err 455 } 456 } 457 } 458 return nil 459 } 460 461 // parse type Name struct|type {Field} declarations 462 func parseTypeDeclaration(infoToFill *TdPackage, genDeclaration *ast.GenDecl, declarationSpec *ast.TypeSpec, fileName string) error { 463 464 helpString := cleanHelp(genDeclaration.Doc.Text()) 465 466 switch typeAst := declarationSpec.Type.(type) { 467 case *ast.Ident: 468 err := parseTypeDefinition(infoToFill, declarationSpec, typeAst, helpString, fileName) 469 if err != nil { 470 return err 471 } 472 case *ast.StructType: 473 err := parseStructDefinitionInfo(infoToFill, declarationSpec, typeAst, helpString, fileName) 474 if err != nil { 475 return err 476 } 477 case *ast.ArrayType: 478 err := parseArrayTypeDefinition(infoToFill, declarationSpec, typeAst, helpString, fileName) 479 if err != nil { 480 return err 481 } 482 case *ast.MapType: 483 err := parseMapTypeDeclaration(infoToFill, declarationSpec, typeAst, helpString, fileName) 484 if err != nil { 485 return err 486 } 487 default: 488 errorLogger.Printf("WARN: Not implemented type declaration %#v", typeAst) 489 } 490 491 return nil 492 } 493 494 func parseMapTypeDeclaration(infoToFill *TdPackage, declarationSpec *ast.TypeSpec, mapAst *ast.MapType, helpString string, fileName string) error { 495 typeName := declarationSpec.Name.Name 496 497 keyTypeStr, err := parseExprToString(mapAst.Key) 498 if err != nil { 499 return err 500 } 501 502 valueTypeStr, err := parseExprToString(mapAst.Value) 503 if err != nil { 504 return err 505 } 506 507 infoToFill.TdMapTypes[typeName] = TdMapType{ 508 Name: typeName, 509 KeyTypeStr: keyTypeStr, 510 ValueTypeStr: valueTypeStr, 511 Help: helpString, 512 Filename: fileName, 513 } 514 515 return nil 516 } 517 518 func parseArrayTypeDefinition(infoToFill *TdPackage, declarationSpec *ast.TypeSpec, arrayAst *ast.ArrayType, helpString string, fileName string) error { 519 typeName := declarationSpec.Name.Name 520 arrayExpressionAst := arrayAst.Elt.(*ast.Ident) 521 arrayTypeStr := arrayExpressionAst.Name 522 infoToFill.TdTypes[typeName] = TdType{ 523 Name: typeName, 524 BaseType: arrayTypeStr, 525 IsArray: true, 526 Help: helpString, 527 Filename: fileName, 528 } 529 return nil 530 } 531 532 func parseTypeDefinition(infoToFill *TdPackage, declarationSpec *ast.TypeSpec, typeIndent *ast.Ident, helpString string, fileName string) error { 533 typeName := declarationSpec.Name.Name 534 infoToFill.TdTypes[typeName] = TdType{ 535 Name: typeName, 536 BaseType: typeIndent.Name, 537 Help: helpString, 538 Filename: fileName, 539 } 540 return nil 541 } 542 543 func parseStructDefinitionInfo(infoToFill *TdPackage, declarationSpec *ast.TypeSpec, structInfo *ast.StructType, helpString string, fileName string) error { 544 structName := declarationSpec.Name.Name 545 546 if helpString == "" { 547 errorLogger.Printf("WARN: TdStruct %s missing a doc string in file %s", structName, fileName) 548 } 549 550 if strings.HasPrefix(strings.ToLower(helpString), "deprecated") { 551 return nil 552 } 553 554 isReadOnly := strings.Contains(helpString, "Readonly") 555 556 var fieldsList []TdStructField 557 var anonymousFieldsList []string 558 559 for _, field := range structInfo.Fields.List { 560 switch len(field.Names) { 561 case 0: 562 anonymousIdent := field.Type.(*ast.Ident) 563 anonymousFieldName := anonymousIdent.Name 564 anonymousFieldsList = append(anonymousFieldsList, anonymousFieldName) 565 continue 566 case 1: 567 default: 568 return fmt.Errorf("unexpected struct %s field name amount of %d", structName, len(field.Names)) 569 } 570 571 fieldName := field.Names[0].Name 572 isOmitEmpty := false 573 isReadOnly := false 574 isNotSerialized := false 575 jsonName := fieldName 576 fieldDoc := cleanHelp(field.Doc.Text()) 577 var schemaName string 578 579 if field.Tag != nil { 580 structTags := reflect.StructTag(strings.Trim(field.Tag.Value, "`")) 581 582 var jsonTags []string 583 if jsonTagsStr, ok := structTags.Lookup("json"); ok { 584 jsonTags = strings.Split(jsonTagsStr, ",") 585 } 586 587 for i, aTag := range jsonTags { 588 if i == 0 { 589 if aTag == "-" { 590 isNotSerialized = true 591 } 592 593 jsonName = aTag 594 } else { 595 if aTag == "omitempty" { 596 isOmitEmpty = true 597 } else { 598 return fmt.Errorf("unknown json tag %s", aTag) 599 } 600 } 601 } 602 603 var tdprotoTags []string 604 tdprotoTagsStr, ok := structTags.Lookup("tdproto") 605 if ok { 606 tdprotoTags = strings.Split(tdprotoTagsStr, ",") 607 } 608 609 for _, aTag := range tdprotoTags { 610 if aTag == "readonly" { 611 isReadOnly = true 612 } else { 613 return fmt.Errorf("unknown tdproto tag %s", aTag) 614 } 615 } 616 617 var schemaTags []string 618 schemaTagsStr, ok := structTags.Lookup("schema") 619 if ok { 620 schemaTags = strings.Split(schemaTagsStr, ",") 621 } 622 623 for _, sTag := range schemaTags { 624 schemaName = sTag 625 } 626 } 627 628 isList := false 629 isPointer := false 630 fieldTypeStr := "" 631 keyTypeStr := "" 632 633 switch fieldTypeAst := field.Type.(type) { 634 case *ast.Ident: 635 fieldTypeStr = fieldTypeAst.Name 636 case *ast.ArrayType: 637 isList = true 638 639 switch arrayTypeAst := fieldTypeAst.Elt.(type) { 640 case *ast.Ident: 641 fieldTypeStr = arrayTypeAst.Name 642 case *ast.InterfaceType: 643 fieldTypeStr = "interface{}" 644 case *ast.SelectorExpr: 645 fieldTypeStr = parseSelectorAst(arrayTypeAst) 646 default: 647 return fmt.Errorf("unknown array type %#v", arrayTypeAst) 648 } 649 650 case *ast.StarExpr: 651 isPointer = true 652 653 switch pointedType := fieldTypeAst.X.(type) { 654 case *ast.Ident: 655 fieldTypeStr = pointedType.Name 656 case *ast.ArrayType: 657 isList = true 658 659 arrayExprAst := pointedType.Elt.(*ast.Ident) 660 661 fieldTypeStr = arrayExprAst.Name 662 case *ast.MapType: 663 // TODO: Implement pointers to maps 664 continue 665 case *ast.SelectorExpr: 666 fieldTypeStr = parseSelectorAst(pointedType) 667 default: 668 return fmt.Errorf("unknown pointer field of %s type %#v", structName, pointedType) 669 } 670 671 case *ast.SelectorExpr: 672 fieldTypeStr = parseSelectorAst(fieldTypeAst) 673 case *ast.InterfaceType: 674 fieldTypeStr = "interface{}" 675 case *ast.MapType: 676 var err error 677 keyTypeStr, err = parseExprToString(fieldTypeAst.Key) 678 if err != nil { 679 return err 680 } 681 fieldTypeStr, err = parseExprToString(fieldTypeAst.Value) 682 if err != nil { 683 return err 684 } 685 default: 686 return fmt.Errorf("unknown field of %s type %#v", structName, fieldTypeAst) 687 } 688 689 if fieldTypeStr == "" { 690 return fmt.Errorf("empty field name %s of %s", structName, fieldName) 691 692 } 693 694 _, isPrimitive := GolangPrimitiveTypes[fieldTypeStr] 695 696 fieldsList = append(fieldsList, TdStructField{ 697 Name: fieldName, 698 IsReadOnly: isReadOnly, 699 IsOmitEmpty: isOmitEmpty, 700 JsonName: jsonName, 701 SchemaName: schemaName, 702 TypeStr: fieldTypeStr, 703 KeyTypeStr: keyTypeStr, 704 IsList: isList, 705 IsPointer: isPointer, 706 IsPrimitive: isPrimitive, 707 IsNotSerialized: isNotSerialized, 708 Help: fieldDoc, 709 }) 710 } 711 712 infoToFill.TdStructs[structName] = TdStruct{ 713 Help: helpString, 714 ReadOnly: isReadOnly, 715 Name: structName, 716 Fields: fieldsList, 717 AnonnymousFields: anonymousFieldsList, 718 FileName: fileName, 719 } 720 721 return nil 722 } 723 724 // Parse const ( name Type = value ...) expressions 725 func parseConstDeclaration(infoToFill *TdPackage, genDeclaration *ast.GenDecl) error { 726 for _, spec := range genDeclaration.Specs { 727 valueSpec, ok := spec.(*ast.ValueSpec) 728 if !ok { 729 return fmt.Errorf("expected const spec got %+v", spec) 730 } 731 732 if len(valueSpec.Names) != 1 { 733 return fmt.Errorf("expected one constant name got %+v", valueSpec.Names) 734 } 735 736 constName := valueSpec.Names[0].Name 737 738 constTypeName := fmt.Sprintf("%s", valueSpec.Type) 739 if constTypeName == "" || valueSpec.Type == nil { 740 errorLogger.Printf("WARN: const has no typeName %s", constName) 741 continue 742 } 743 744 if len(valueSpec.Values) != 1 { 745 return fmt.Errorf("expected one constant value got %+v", valueSpec.Values) 746 } 747 748 constValue, ok := valueSpec.Values[0].(*ast.BasicLit) 749 if !ok { 750 return fmt.Errorf("could not extract constant value %+v", valueSpec.Values[0]) 751 } 752 753 infoToFill.TdConsts = append(infoToFill.TdConsts, TdConstFields{ 754 Name: constName, 755 Type: constTypeName, 756 Value: constValue.Value, 757 Help: cleanHelp(valueSpec.Doc.Text()), 758 }) 759 } 760 761 return nil 762 } 763 764 func parseExprToString(expr interface{}) (string, error) { 765 switch exprType := expr.(type) { 766 case *ast.SelectorExpr: 767 return parseSelectorAst(exprType), nil 768 case *ast.Ident: 769 return exprType.Name, nil 770 case *ast.InterfaceType: 771 return "interface{}", nil 772 case *ast.StarExpr: 773 return parseStarAst(exprType) 774 } 775 776 return "", fmt.Errorf("cannot parse expression %#v", expr) 777 } 778 779 func parseStarAst(starAst *ast.StarExpr) (string, error) { 780 pointedType, err := parseExprToString(starAst.X) 781 if err != nil { 782 return "", err 783 } 784 return pointedType, nil 785 } 786 787 func parseSelectorAst(selectorNode *ast.SelectorExpr) string { 788 expresionIdent := selectorNode.X.(*ast.Ident) 789 expressionStr := expresionIdent.Name 790 if expressionStr == "tdproto" { // HACK: when tdapi references tdproto 791 return selectorNode.Sel.Name 792 } 793 return expressionStr + "." + selectorNode.Sel.Name 794 } 795 796 func extractTdprotoAst(fileSet *token.FileSet) (map[string]*ast.Package, error) { 797 tdProtoPath := tdproto.SourceDir() 798 return parser.ParseDir(fileSet, tdProtoPath, nil, parser.ParseComments) 799 } 800 801 func extractTdapiAst(fileSet *token.FileSet) (map[string]*ast.Package, error) { 802 tdProtoPath := tdproto.SourceDir() 803 return parser.ParseDir(fileSet, path.Join(tdProtoPath, "tdapi"), nil, parser.ParseComments) 804 } 805 806 func cleanHelp(s string) string { 807 return strings.TrimSuffix(strings.TrimSpace(strings.Join(strings.Fields(s), " ")), ".") 808 } 809 810 func ToSnakeCase(original string) string { 811 var buildStr strings.Builder 812 813 for i, char := range original { 814 if i != 0 && unicode.IsUpper(char) { 815 buildStr.WriteString("_") 816 } 817 buildStr.WriteString(string(unicode.ToLower(char))) 818 } 819 820 return buildStr.String() 821 } 822 823 func SnakeCaseToLowerCamel(original string) string { 824 var buildStr strings.Builder 825 826 nextCharToUpper := false 827 828 for i, char := range original { 829 if i != 0 && char == '_' { 830 nextCharToUpper = true 831 continue 832 } 833 834 nextChar := char 835 836 if nextCharToUpper { 837 nextChar = unicode.ToUpper(char) 838 nextCharToUpper = false 839 } 840 841 buildStr.WriteString(string(nextChar)) 842 843 } 844 845 return buildStr.String() 846 } 847 848 func LowercaseFirstLetter(original string) string { 849 return strings.ToLower(original[:1]) + original[1:] 850 } 851 852 func UppercaseFirstLetter(original string) string { 853 return strings.ToUpper(original[:1]) + original[1:] 854 }