| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 | //go:generate go run .package mainimport (	"fmt"	"go/ast"	"go/parser"	"go/token"	"os"	"path/filepath"	"regexp"	"runtime"	"sort"	"strings"	"text/template"	"github.com/uozi-tech/cosy/logger")// NotifierInfo Structure to hold extracted notifier informationtype NotifierInfo struct {	Name      string	Fields    []FieldInfo	FileName  string	ConfigKey string}// FieldInfo Structure to hold field information for notifiertype FieldInfo struct {	Name  string	Key   string	Title string}// Template for the TypeScript config fileconst tsConfigTemplate = `// This file is auto-generated by notification generator. DO NOT EDIT.import type { ExternalNotifyConfig } from './types'const {{.Name | replaceSpaces}}Config: ExternalNotifyConfig = {  name: () => $gettext('{{.Name}}'),  config: [    {{- range .Fields}}    {      key: '{{.Key}}',      label: '{{.Title}}',    },    {{- end}}  ],}export default {{.Name | replaceSpaces}}Config`// Regular expression to extract @external_notifier annotationvar externalNotifierRegex = regexp.MustCompile(`@external_notifier\(([a-zA-Z0-9 _]+)\)`)func main() {	logger.Init("release")	_, file, _, ok := runtime.Caller(0)	if !ok {		logger.Error("Unable to get the current file")		return	}	basePath := filepath.Join(filepath.Dir(file), "../../")	if err := GenerateExternalNotifiers(basePath); err != nil {		fmt.Printf("error generating external notifier configs: %v\n", err)	}}// GenerateExternalNotifiers generates TypeScript config files for external notifiersfunc GenerateExternalNotifiers(root string) error {	fmt.Println("Generating external notifier configs...")	// Notification package path	notificationPkgPath := filepath.Join(root, "internal/notification")	outputDir := filepath.Join(root, "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	var notifiers []NotifierInfo	for _, file := range files {		notifier, found := extractNotifierInfo(file)		if found {			notifiers = append(notifiers, notifier)			logger.Infof("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)	}	logger.Info("Generation completed successfully!")	return nil}// Extract notifier information from a Go filefunc 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 {		logger.Errorf("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(strings.ReplaceAll(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 notifierfunc generateTSConfig(notifier NotifierInfo, outputDir string) error {	// Create function map for template	funcMap := template.FuncMap{		"replaceSpaces": func(s string) string {			return strings.ReplaceAll(s, " ", "")		},	}	// Create template with function map	tmpl, err := template.New("tsConfig").Funcs(funcMap).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)	}	logger.Infof("Generated TypeScript config for %s at %s\n", notifier.Name, outputFile)	return nil}// Update index.ts filefunc 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 := strings.ReplaceAll(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 {		configKey := strings.ToLower(strings.ReplaceAll(notifier.Name, " ", "_"))		configMap.WriteString(fmt.Sprintf("  %s: %sConfig", configKey, strings.ReplaceAll(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)	}	logger.Infof("Updated index.ts at %s\n", indexPath)	return nil}
 |