nginx-ui/cmd/notification/generate.go
2025-02-25 19:06:39 +08:00

245 lines
6.5 KiB
Go

package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
)
// Structure for notification function calls
type NotificationCall struct {
Type string
Title string
Content string
Path string
}
// Directories to exclude
var excludeDirs = []string{
".devcontainer", ".github", ".idea", ".pnpm-store",
".vscode", "app", "query", "tmp",
}
// Main function
func main() {
// Start scanning from the current directory
root := "."
calls := []NotificationCall{}
// Scan all Go files
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip excluded directories
for _, dir := range excludeDirs {
if strings.HasPrefix(path, "./"+dir) || strings.HasPrefix(path, dir+"/") {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
}
// Only process Go files
if !info.IsDir() && strings.HasSuffix(path, ".go") {
findNotificationCalls(path, &calls)
}
return nil
})
if err != nil {
fmt.Printf("Error walking the path: %v\n", err)
return
}
// Generate a single TS file
generateSingleTSFile(calls)
fmt.Printf("Found %d notification calls\n", len(calls))
}
// Find notification function calls in Go files
func findNotificationCalls(filePath string, calls *[]NotificationCall) {
// Parse Go code
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", filePath, err)
return
}
// Traverse the AST to find function calls
ast.Inspect(node, func(n ast.Node) bool {
callExpr, ok := n.(*ast.CallExpr)
if !ok {
return true
}
// Check if it's a call to the notification package
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
xident, ok := selExpr.X.(*ast.Ident)
if !ok {
return true
}
// Check if it's one of the functions we're interested in: notification.Info/Error/Warning/Success
if xident.Name == "notification" {
funcName := selExpr.Sel.Name
if funcName == "Info" || funcName == "Error" || funcName == "Warning" || funcName == "Success" {
// Function must have at least two parameters (title, content)
if len(callExpr.Args) >= 2 {
titleArg := callExpr.Args[0]
contentArg := callExpr.Args[1]
// Get parameter values
title := getStringValue(titleArg)
content := getStringValue(contentArg)
// Ignore cases where content is a variable name or function call
if content != "" && !isVariableOrFunctionCall(content) {
*calls = append(*calls, NotificationCall{
Type: funcName,
Title: title,
Content: content,
Path: filePath,
})
}
}
}
}
return true
})
}
// Check if the string is a variable name or function call
func isVariableOrFunctionCall(s string) bool {
// Simple check: if the string doesn't contain spaces or quotes, it might be a variable name
if !strings.Contains(s, " ") && !strings.Contains(s, "\"") && !strings.Contains(s, "'") {
return true
}
// If it looks like a function call, e.g., err.Error()
if strings.Contains(s, "(") && strings.Contains(s, ")") {
return true
}
return false
}
// Get string value from AST node
func getStringValue(expr ast.Expr) string {
// Direct string
if lit, ok := expr.(*ast.BasicLit); ok && lit.Kind == token.STRING {
// Return string without quotes
return strings.Trim(lit.Value, "\"")
}
// Recover string value from source code expression
var str strings.Builder
if bin, ok := expr.(*ast.BinaryExpr); ok {
// Handle string concatenation expression
leftStr := getStringValue(bin.X)
rightStr := getStringValue(bin.Y)
str.WriteString(leftStr)
str.WriteString(rightStr)
}
if str.Len() > 0 {
return str.String()
}
// Return empty string if unable to parse as string
return ""
}
// Generate a single TypeScript file
func generateSingleTSFile(calls []NotificationCall) {
// Create target directory
targetDir := "app/src/components/Notification"
err := os.MkdirAll(targetDir, 0755)
if err != nil {
fmt.Printf("Error creating directory %s: %v\n", targetDir, err)
return
}
// Create file name
tsFilePath := filepath.Join(targetDir, "notifications.ts")
// Prepare file content
var content strings.Builder
content.WriteString("// Auto-generated notification texts\n")
content.WriteString("// Extracted from Go source code notification function calls\n")
content.WriteString("/* eslint-disable ts/no-explicit-any */\n\n")
content.WriteString("const notifications: Record<string, { title: () => string, content: (args: any) => string }> = {\n")
// Track used keys to avoid duplicates
usedKeys := make(map[string]bool)
// Organize notifications by directory
messagesByDir := make(map[string][]NotificationCall)
for _, call := range calls {
dir := filepath.Dir(call.Path)
// Extract module name from directory path
dirParts := strings.Split(dir, "/")
moduleName := dirParts[len(dirParts)-1]
if strings.HasPrefix(dir, "internal/") || strings.HasPrefix(dir, "api/") {
messagesByDir[moduleName] = append(messagesByDir[moduleName], call)
} else {
messagesByDir["general"] = append(messagesByDir["general"], call)
}
}
// Add comments for each module and write notifications
for module, moduleCalls := range messagesByDir {
content.WriteString(fmt.Sprintf("\n // %s module notifications\n", module))
for _, call := range moduleCalls {
// Escape quotes in title and content
escapedTitle := strings.ReplaceAll(call.Title, "'", "\\'")
escapedContent := strings.ReplaceAll(call.Content, "'", "\\'")
// Use just the title as the key
key := call.Title
// Check if key is already used, generate unique key if necessary
uniqueKey := key
counter := 1
for usedKeys[uniqueKey] {
uniqueKey = fmt.Sprintf("%s_%d", key, counter)
counter++
}
usedKeys[uniqueKey] = true
// Write record with both title and content as functions
content.WriteString(fmt.Sprintf(" '%s': {\n", uniqueKey))
content.WriteString(fmt.Sprintf(" title: () => $gettext('%s'),\n", escapedTitle))
content.WriteString(fmt.Sprintf(" content: (args: any) => $gettext('%s', args),\n", escapedContent))
content.WriteString(" },\n")
}
}
content.WriteString("}\n\n")
content.WriteString("export default notifications\n")
// Write file
err = os.WriteFile(tsFilePath, []byte(content.String()), 0644)
if err != nil {
fmt.Printf("Error writing TS file %s: %v\n", tsFilePath, err)
return
}
fmt.Printf("Generated single TS file: %s with %d notifications\n", tsFilePath, len(calls))
}