generate.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. //go:generate go run .
  2. package main
  3. import (
  4. "fmt"
  5. "go/ast"
  6. "go/parser"
  7. "go/token"
  8. "os"
  9. "path/filepath"
  10. "regexp"
  11. "runtime"
  12. "sort"
  13. "strings"
  14. "text/template"
  15. "github.com/uozi-tech/cosy/logger"
  16. )
  17. // Structure to hold extracted notifier information
  18. type NotifierInfo struct {
  19. Name string
  20. Fields []FieldInfo
  21. FileName string
  22. ConfigKey string
  23. }
  24. // Structure to hold field information for notifier
  25. type FieldInfo struct {
  26. Name string
  27. Key string
  28. Title string
  29. }
  30. // Template for the TypeScript config file
  31. const tsConfigTemplate = `// This file is auto-generated by notification generator. DO NOT EDIT.
  32. import type { ExternalNotifyConfig } from './types'
  33. const {{.Name | replaceSpaces}}Config: ExternalNotifyConfig = {
  34. name: () => $gettext('{{.Name}}'),
  35. config: [
  36. {{- range .Fields}}
  37. {
  38. key: '{{.Key}}',
  39. label: '{{.Title}}',
  40. },
  41. {{- end}}
  42. ],
  43. }
  44. export default {{.Name | replaceSpaces}}Config
  45. `
  46. // Regular expression to extract @external_notifier annotation
  47. var externalNotifierRegex = regexp.MustCompile(`@external_notifier\(([a-zA-Z0-9 _]+)\)`)
  48. func main() {
  49. logger.Init("release")
  50. _, file, _, ok := runtime.Caller(0)
  51. if !ok {
  52. logger.Error("Unable to get the current file")
  53. return
  54. }
  55. basePath := filepath.Join(filepath.Dir(file), "../../")
  56. if err := GenerateExternalNotifiers(basePath); err != nil {
  57. fmt.Printf("error generating external notifier configs: %v\n", err)
  58. }
  59. }
  60. // GenerateExternalNotifiers generates TypeScript config files for external notifiers
  61. func GenerateExternalNotifiers(root string) error {
  62. fmt.Println("Generating external notifier configs...")
  63. // Notification package path
  64. notificationPkgPath := filepath.Join(root, "internal/notification")
  65. outputDir := filepath.Join(root, "app/src/views/preference/components/ExternalNotify")
  66. // Create output directory if it doesn't exist
  67. if err := os.MkdirAll(outputDir, 0755); err != nil {
  68. return fmt.Errorf("error creating output directory: %w", err)
  69. }
  70. // Get all Go files in the notification package
  71. files, err := filepath.Glob(filepath.Join(notificationPkgPath, "*.go"))
  72. if err != nil {
  73. return fmt.Errorf("error scanning notification package: %w", err)
  74. }
  75. // Collect all notifier info
  76. notifiers := []NotifierInfo{}
  77. for _, file := range files {
  78. notifier, found := extractNotifierInfo(file)
  79. if found {
  80. notifiers = append(notifiers, notifier)
  81. logger.Infof("Found notifier: %s in %s\n", notifier.Name, file)
  82. }
  83. }
  84. // Generate TypeScript config files
  85. for _, notifier := range notifiers {
  86. if err := generateTSConfig(notifier, outputDir); err != nil {
  87. return fmt.Errorf("error generating config for %s: %w", notifier.Name, err)
  88. }
  89. }
  90. // Update index.ts
  91. if err := updateIndexFile(notifiers, outputDir); err != nil {
  92. return fmt.Errorf("error updating index.ts: %w", err)
  93. }
  94. logger.Info("Generation completed successfully!")
  95. return nil
  96. }
  97. // Extract notifier information from a Go file
  98. func extractNotifierInfo(filePath string) (NotifierInfo, bool) {
  99. // Create the FileSet
  100. fset := token.NewFileSet()
  101. // Parse the file
  102. file, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
  103. if err != nil {
  104. logger.Errorf("Error parsing file %s: %v\n", filePath, err)
  105. return NotifierInfo{}, false
  106. }
  107. var notifierInfo NotifierInfo
  108. found := false
  109. // Look for the type declaration with the @external_notifier annotation
  110. for _, decl := range file.Decls {
  111. genDecl, ok := decl.(*ast.GenDecl)
  112. if !ok || genDecl.Tok != token.TYPE {
  113. continue
  114. }
  115. for _, spec := range genDecl.Specs {
  116. typeSpec, ok := spec.(*ast.TypeSpec)
  117. if !ok {
  118. continue
  119. }
  120. structType, ok := typeSpec.Type.(*ast.StructType)
  121. if !ok {
  122. continue
  123. }
  124. // Check if we have a comment with @external_notifier
  125. if genDecl.Doc != nil {
  126. for _, comment := range genDecl.Doc.List {
  127. matches := externalNotifierRegex.FindStringSubmatch(comment.Text)
  128. if len(matches) > 1 {
  129. notifierInfo.Name = matches[1]
  130. notifierInfo.ConfigKey = strings.ToLower(typeSpec.Name.Name)
  131. notifierInfo.FileName = strings.ToLower(strings.ReplaceAll(matches[1], " ", "_"))
  132. found = true
  133. // Extract fields
  134. for _, field := range structType.Fields.List {
  135. if len(field.Names) > 0 {
  136. fieldName := field.Names[0].Name
  137. // Get json tag and title from field tags
  138. var jsonKey, title string
  139. if field.Tag != nil {
  140. tagValue := strings.Trim(field.Tag.Value, "`")
  141. // Extract json key
  142. jsonRegex := regexp.MustCompile(`json:"([^"]+)"`)
  143. jsonMatches := jsonRegex.FindStringSubmatch(tagValue)
  144. if len(jsonMatches) > 1 {
  145. jsonKey = jsonMatches[1]
  146. }
  147. // Extract title
  148. titleRegex := regexp.MustCompile(`title:"([^"]+)"`)
  149. titleMatches := titleRegex.FindStringSubmatch(tagValue)
  150. if len(titleMatches) > 1 {
  151. title = titleMatches[1]
  152. }
  153. }
  154. if jsonKey == "" {
  155. jsonKey = strings.ToLower(fieldName)
  156. }
  157. if title == "" {
  158. title = fieldName
  159. }
  160. notifierInfo.Fields = append(notifierInfo.Fields, FieldInfo{
  161. Name: fieldName,
  162. Key: jsonKey,
  163. Title: title,
  164. })
  165. }
  166. }
  167. break
  168. }
  169. }
  170. }
  171. if found {
  172. break
  173. }
  174. }
  175. if found {
  176. break
  177. }
  178. }
  179. return notifierInfo, found
  180. }
  181. // Generate TypeScript config file for a notifier
  182. func generateTSConfig(notifier NotifierInfo, outputDir string) error {
  183. // Create function map for template
  184. funcMap := template.FuncMap{
  185. "replaceSpaces": func(s string) string {
  186. return strings.ReplaceAll(s, " ", "")
  187. },
  188. }
  189. // Create template with function map
  190. tmpl, err := template.New("tsConfig").Funcs(funcMap).Parse(tsConfigTemplate)
  191. if err != nil {
  192. return fmt.Errorf("error creating template: %w", err)
  193. }
  194. // Create output file
  195. outputFile := filepath.Join(outputDir, notifier.FileName+".ts")
  196. file, err := os.Create(outputFile)
  197. if err != nil {
  198. return fmt.Errorf("error creating output file %s: %w", outputFile, err)
  199. }
  200. defer file.Close()
  201. // Execute template
  202. err = tmpl.Execute(file, notifier)
  203. if err != nil {
  204. return fmt.Errorf("error executing template: %w", err)
  205. }
  206. logger.Infof("Generated TypeScript config for %s at %s\n", notifier.Name, outputFile)
  207. return nil
  208. }
  209. // Update index.ts file
  210. func updateIndexFile(notifiers []NotifierInfo, outputDir string) error {
  211. // Create content for index.ts
  212. var imports strings.Builder
  213. var configMap strings.Builder
  214. // Sort notifiers alphabetically by name for stable output
  215. sort.Slice(notifiers, func(i, j int) bool {
  216. return notifiers[i].Name < notifiers[j].Name
  217. })
  218. for _, notifier := range notifiers {
  219. fileName := notifier.FileName
  220. configName := strings.ReplaceAll(notifier.Name, " ", "") + "Config"
  221. imports.WriteString(fmt.Sprintf("import %s from './%s'\n", configName, fileName))
  222. }
  223. // Generate the map
  224. configMap.WriteString("const configMap = {\n")
  225. for _, notifier := range notifiers {
  226. configKey := strings.ToLower(strings.ReplaceAll(notifier.Name, " ", "_"))
  227. configMap.WriteString(fmt.Sprintf(" %s: %sConfig", configKey, strings.ReplaceAll(notifier.Name, " ", "")))
  228. configMap.WriteString(",\n")
  229. }
  230. configMap.WriteString("}\n")
  231. content := fmt.Sprintf("// This file is auto-generated by notification generator. DO NOT EDIT.\n%s\n%s\nexport default configMap\n", imports.String(), configMap.String())
  232. // Write to index.ts
  233. indexPath := filepath.Join(outputDir, "index.ts")
  234. err := os.WriteFile(indexPath, []byte(content), 0644)
  235. if err != nil {
  236. return fmt.Errorf("error writing index.ts: %w", err)
  237. }
  238. logger.Infof("Updated index.ts at %s\n", indexPath)
  239. return nil
  240. }