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  }