package main import ( "fmt" "go/ast" "go/parser" "go/token" "os" "path/filepath" "regexp" "sort" "strings" "text/template" ) // Structure to hold extracted notifier information type NotifierInfo struct { Name string Fields []FieldInfo FileName string ConfigKey string } // Structure to hold field information for notifier type FieldInfo struct { Name string Key string Title string } // Template for the TypeScript config file const tsConfigTemplate = `// This file is auto-generated by notification generator. DO NOT EDIT. import type { ExternalNotifyConfig } from './types' const {{.Name}}Config: ExternalNotifyConfig = { name: () => $gettext('{{.Name}}'), config: [ {{- range .Fields}} { key: '{{.Key}}', label: () => $gettext('{{.Title}}'), }, {{- end}} ], } export default {{.Name}}Config ` // Regular expression to extract @external_notifier annotation var externalNotifierRegex = regexp.MustCompile(`@external_notifier\((\w+)\)`) func main() { if err := GenerateExternalNotifiers(); err != nil { fmt.Printf("error generating external notifier configs: %v\n", err) } } // GenerateExternalNotifiers generates TypeScript config files for external notifiers func GenerateExternalNotifiers() error { fmt.Println("Generating external notifier configs...") // Notification package path notificationPkgPath := "internal/notification" outputDir := "app/src/views/preference/components/ExternalNotify" // Create output directory if it doesn't exist if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("error creating output directory: %w", err) } // Get all Go files in the notification package files, err := filepath.Glob(filepath.Join(notificationPkgPath, "*.go")) if err != nil { return fmt.Errorf("error scanning notification package: %w", err) } // Collect all notifier info notifiers := []NotifierInfo{} for _, file := range files { notifier, found := extractNotifierInfo(file) if found { notifiers = append(notifiers, notifier) fmt.Printf("Found notifier: %s in %s\n", notifier.Name, file) } } // Generate TypeScript config files for _, notifier := range notifiers { if err := generateTSConfig(notifier, outputDir); err != nil { return fmt.Errorf("error generating config for %s: %w", notifier.Name, err) } } // Update index.ts if err := updateIndexFile(notifiers, outputDir); err != nil { return fmt.Errorf("error updating index.ts: %w", err) } fmt.Println("Generation completed successfully!") return nil } // Extract notifier information from a Go file func extractNotifierInfo(filePath string) (NotifierInfo, bool) { // Create the FileSet fset := token.NewFileSet() // Parse the file file, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) if err != nil { fmt.Printf("Error parsing file %s: %v\n", filePath, err) return NotifierInfo{}, false } var notifierInfo NotifierInfo found := false // Look for the type declaration with the @external_notifier annotation for _, decl := range file.Decls { genDecl, ok := decl.(*ast.GenDecl) if !ok || genDecl.Tok != token.TYPE { continue } for _, spec := range genDecl.Specs { typeSpec, ok := spec.(*ast.TypeSpec) if !ok { continue } structType, ok := typeSpec.Type.(*ast.StructType) if !ok { continue } // Check if we have a comment with @external_notifier if genDecl.Doc != nil { for _, comment := range genDecl.Doc.List { matches := externalNotifierRegex.FindStringSubmatch(comment.Text) if len(matches) > 1 { notifierInfo.Name = matches[1] notifierInfo.ConfigKey = strings.ToLower(typeSpec.Name.Name) notifierInfo.FileName = strings.ToLower(matches[1]) found = true // Extract fields for _, field := range structType.Fields.List { if len(field.Names) > 0 { fieldName := field.Names[0].Name // Get json tag and title from field tags var jsonKey, title string if field.Tag != nil { tagValue := strings.Trim(field.Tag.Value, "`") // Extract json key jsonRegex := regexp.MustCompile(`json:"([^"]+)"`) jsonMatches := jsonRegex.FindStringSubmatch(tagValue) if len(jsonMatches) > 1 { jsonKey = jsonMatches[1] } // Extract title titleRegex := regexp.MustCompile(`title:"([^"]+)"`) titleMatches := titleRegex.FindStringSubmatch(tagValue) if len(titleMatches) > 1 { title = titleMatches[1] } } if jsonKey == "" { jsonKey = strings.ToLower(fieldName) } if title == "" { title = fieldName } notifierInfo.Fields = append(notifierInfo.Fields, FieldInfo{ Name: fieldName, Key: jsonKey, Title: title, }) } } break } } } if found { break } } if found { break } } return notifierInfo, found } // Generate TypeScript config file for a notifier func generateTSConfig(notifier NotifierInfo, outputDir string) error { // Create template tmpl, err := template.New("tsConfig").Parse(tsConfigTemplate) if err != nil { return fmt.Errorf("error creating template: %w", err) } // Create output file outputFile := filepath.Join(outputDir, notifier.FileName+".ts") file, err := os.Create(outputFile) if err != nil { return fmt.Errorf("error creating output file %s: %w", outputFile, err) } defer file.Close() // Execute template err = tmpl.Execute(file, notifier) if err != nil { return fmt.Errorf("error executing template: %w", err) } fmt.Printf("Generated TypeScript config for %s at %s\n", notifier.Name, outputFile) return nil } // Update index.ts file func updateIndexFile(notifiers []NotifierInfo, outputDir string) error { // Create content for index.ts var imports strings.Builder var configMap strings.Builder // Sort notifiers alphabetically by name for stable output sort.Slice(notifiers, func(i, j int) bool { return notifiers[i].Name < notifiers[j].Name }) for _, notifier := range notifiers { fileName := notifier.FileName configName := notifier.Name + "Config" imports.WriteString(fmt.Sprintf("import %s from './%s'\n", configName, fileName)) } // Generate the map configMap.WriteString("const configMap = {\n") for _, notifier := range notifiers { configMap.WriteString(fmt.Sprintf(" %s: %sConfig", strings.ToLower(notifier.Name), notifier.Name)) configMap.WriteString(",\n") } configMap.WriteString("}\n") 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()) // Write to index.ts indexPath := filepath.Join(outputDir, "index.ts") err := os.WriteFile(indexPath, []byte(content), 0644) if err != nil { return fmt.Errorf("error writing index.ts: %w", err) } fmt.Printf("Updated index.ts at %s\n", indexPath) return nil }