nginx-ui/cmd/external_notifier/generate.go

277 lines
7.2 KiB
Go

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 | 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 annotation
var externalNotifierRegex = regexp.MustCompile(`@external_notifier\(([a-zA-Z0-9 _]+)\)`)
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(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 notifier
func 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)
}
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 := 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)
}
fmt.Printf("Updated index.ts at %s\n", indexPath)
return nil
}