generate.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. package main
  2. import (
  3. "fmt"
  4. "go/ast"
  5. "go/parser"
  6. "go/token"
  7. "os"
  8. "path/filepath"
  9. "strings"
  10. )
  11. // Structure for notification function calls
  12. type NotificationCall struct {
  13. Type string
  14. Title string
  15. Content string
  16. Path string
  17. }
  18. // Directories to exclude
  19. var excludeDirs = []string{
  20. ".devcontainer", ".github", ".idea", ".pnpm-store",
  21. ".vscode", "app", "query", "tmp",
  22. }
  23. // Main function
  24. func main() {
  25. // Start scanning from the current directory
  26. root := "."
  27. calls := []NotificationCall{}
  28. // Scan all Go files
  29. err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
  30. if err != nil {
  31. return err
  32. }
  33. // Skip excluded directories
  34. for _, dir := range excludeDirs {
  35. if strings.HasPrefix(path, "./"+dir) || strings.HasPrefix(path, dir+"/") {
  36. if info.IsDir() {
  37. return filepath.SkipDir
  38. }
  39. return nil
  40. }
  41. }
  42. // Only process Go files
  43. if !info.IsDir() && strings.HasSuffix(path, ".go") {
  44. findNotificationCalls(path, &calls)
  45. }
  46. return nil
  47. })
  48. if err != nil {
  49. fmt.Printf("Error walking the path: %v\n", err)
  50. return
  51. }
  52. // Generate a single TS file
  53. generateSingleTSFile(calls)
  54. fmt.Printf("Found %d notification calls\n", len(calls))
  55. }
  56. // Find notification function calls in Go files
  57. func findNotificationCalls(filePath string, calls *[]NotificationCall) {
  58. // Parse Go code
  59. fset := token.NewFileSet()
  60. node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
  61. if err != nil {
  62. fmt.Printf("Error parsing %s: %v\n", filePath, err)
  63. return
  64. }
  65. // Traverse the AST to find function calls
  66. ast.Inspect(node, func(n ast.Node) bool {
  67. callExpr, ok := n.(*ast.CallExpr)
  68. if !ok {
  69. return true
  70. }
  71. // Check if it's a call to the notification package
  72. selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
  73. if !ok {
  74. return true
  75. }
  76. xident, ok := selExpr.X.(*ast.Ident)
  77. if !ok {
  78. return true
  79. }
  80. // Check if it's one of the functions we're interested in: notification.Info/Error/Warning/Success
  81. if xident.Name == "notification" {
  82. funcName := selExpr.Sel.Name
  83. if funcName == "Info" || funcName == "Error" || funcName == "Warning" || funcName == "Success" {
  84. // Function must have at least two parameters (title, content)
  85. if len(callExpr.Args) >= 2 {
  86. titleArg := callExpr.Args[0]
  87. contentArg := callExpr.Args[1]
  88. // Get parameter values
  89. title := getStringValue(titleArg)
  90. content := getStringValue(contentArg)
  91. // Ignore cases where content is a variable name or function call
  92. if content != "" && !isVariableOrFunctionCall(content) {
  93. *calls = append(*calls, NotificationCall{
  94. Type: funcName,
  95. Title: title,
  96. Content: content,
  97. Path: filePath,
  98. })
  99. }
  100. }
  101. }
  102. }
  103. return true
  104. })
  105. }
  106. // Check if the string is a variable name or function call
  107. func isVariableOrFunctionCall(s string) bool {
  108. // Simple check: if the string doesn't contain spaces or quotes, it might be a variable name
  109. if !strings.Contains(s, " ") && !strings.Contains(s, "\"") && !strings.Contains(s, "'") {
  110. return true
  111. }
  112. // If it looks like a function call, e.g., err.Error()
  113. if strings.Contains(s, "(") && strings.Contains(s, ")") {
  114. return true
  115. }
  116. return false
  117. }
  118. // Get string value from AST node
  119. func getStringValue(expr ast.Expr) string {
  120. // Direct string
  121. if lit, ok := expr.(*ast.BasicLit); ok && lit.Kind == token.STRING {
  122. // Return string without quotes
  123. return strings.Trim(lit.Value, "\"")
  124. }
  125. // Recover string value from source code expression
  126. var str strings.Builder
  127. if bin, ok := expr.(*ast.BinaryExpr); ok {
  128. // Handle string concatenation expression
  129. leftStr := getStringValue(bin.X)
  130. rightStr := getStringValue(bin.Y)
  131. str.WriteString(leftStr)
  132. str.WriteString(rightStr)
  133. }
  134. if str.Len() > 0 {
  135. return str.String()
  136. }
  137. // Return empty string if unable to parse as string
  138. return ""
  139. }
  140. // Generate a single TypeScript file
  141. func generateSingleTSFile(calls []NotificationCall) {
  142. // Create target directory
  143. targetDir := "app/src/components/Notification"
  144. err := os.MkdirAll(targetDir, 0755)
  145. if err != nil {
  146. fmt.Printf("Error creating directory %s: %v\n", targetDir, err)
  147. return
  148. }
  149. // Create file name
  150. tsFilePath := filepath.Join(targetDir, "notifications.ts")
  151. // Prepare file content
  152. var content strings.Builder
  153. content.WriteString("// Auto-generated notification texts\n")
  154. content.WriteString("// Extracted from Go source code notification function calls\n")
  155. content.WriteString("/* eslint-disable ts/no-explicit-any */\n\n")
  156. content.WriteString("const notifications: Record<string, { title: () => string, content: (args: any) => string }> = {\n")
  157. // Track used keys to avoid duplicates
  158. usedKeys := make(map[string]bool)
  159. // Organize notifications by directory
  160. messagesByDir := make(map[string][]NotificationCall)
  161. for _, call := range calls {
  162. dir := filepath.Dir(call.Path)
  163. // Extract module name from directory path
  164. dirParts := strings.Split(dir, "/")
  165. moduleName := dirParts[len(dirParts)-1]
  166. if strings.HasPrefix(dir, "internal/") || strings.HasPrefix(dir, "api/") {
  167. messagesByDir[moduleName] = append(messagesByDir[moduleName], call)
  168. } else {
  169. messagesByDir["general"] = append(messagesByDir["general"], call)
  170. }
  171. }
  172. // Add comments for each module and write notifications
  173. for module, moduleCalls := range messagesByDir {
  174. content.WriteString(fmt.Sprintf("\n // %s module notifications\n", module))
  175. for _, call := range moduleCalls {
  176. // Escape quotes in title and content
  177. escapedTitle := strings.ReplaceAll(call.Title, "'", "\\'")
  178. escapedContent := strings.ReplaceAll(call.Content, "'", "\\'")
  179. // Use just the title as the key
  180. key := call.Title
  181. // Check if key is already used, generate unique key if necessary
  182. uniqueKey := key
  183. counter := 1
  184. for usedKeys[uniqueKey] {
  185. uniqueKey = fmt.Sprintf("%s_%d", key, counter)
  186. counter++
  187. }
  188. usedKeys[uniqueKey] = true
  189. // Write record with both title and content as functions
  190. content.WriteString(fmt.Sprintf(" '%s': {\n", uniqueKey))
  191. content.WriteString(fmt.Sprintf(" title: () => $gettext('%s'),\n", escapedTitle))
  192. content.WriteString(fmt.Sprintf(" content: (args: any) => $gettext('%s', args),\n", escapedContent))
  193. content.WriteString(" },\n")
  194. }
  195. }
  196. content.WriteString("}\n\n")
  197. content.WriteString("export default notifications\n")
  198. // Write file
  199. err = os.WriteFile(tsFilePath, []byte(content.String()), 0644)
  200. if err != nil {
  201. fmt.Printf("Error writing TS file %s: %v\n", tsFilePath, err)
  202. return
  203. }
  204. fmt.Printf("Generated single TS file: %s with %d notifications\n", tsFilePath, len(calls))
  205. }