1// Command wlturbo-scanner generates Go bindings from Wayland protocol XML files.
2//
3// Usage:
4//
5// wlturbo-scanner [flags] protocol.xml
6//
7// Flags:
8//
9// -o, --output Output file path (default: <protocol>_generated.go)
10// -p, --package Go package name (default: derived from protocol name)
11// -h, --help Show help
12//
13// Examples:
14//
15// # Generate bindings for virtual pointer protocol
16// wlturbo-scanner -o virtual_pointer.go ../wlr-protocols/unstable/wlr-virtual-pointer-unstable-v1.xml
17//
18// # Generate with custom package name
19// wlturbo-scanner -p vpointer -o vpointer.go protocol.xml
20package main
21
22import (
23 "flag"
24 "fmt"
25 "log"
26 "os"
27 "path/filepath"
28 "strings"
29
30 "github.com/bnema/wlturbo/internal/scanner"
31)
32
33func main() {
34 var (
35 outputPath string
36 packageName string
37 showHelp bool
38 crossImports crossFlags
39 )
40
41 flag.StringVar(&outputPath, "o", "", "Output file path")
42 flag.StringVar(&outputPath, "output", "", "Output file path")
43 flag.StringVar(&packageName, "p", "", "Go package name")
44 flag.StringVar(&packageName, "package", "", "Go package name")
45 flag.Var(&crossImports, "import", "Interface=Go import path (repeatable)")
46 flag.BoolVar(&showHelp, "h", false, "Show help")
47 flag.BoolVar(&showHelp, "help", false, "Show help")
48
49 flag.Usage = func() {
50 fmt.Fprintf(os.Stderr, "Usage: %s [flags] protocol.xml\n\n", os.Args[0])
51 fmt.Fprintf(os.Stderr, "Generates Go bindings from Wayland protocol XML files.\n\n")
52 fmt.Fprintf(os.Stderr, "Flags:\n")
53 flag.PrintDefaults()
54 fmt.Fprintf(os.Stderr, "\nExamples:\n")
55 fmt.Fprintf(os.Stderr, " # Generate bindings for virtual pointer protocol\n")
56 fmt.Fprintf(os.Stderr, " %s -o virtual_pointer.go wlr-virtual-pointer-unstable-v1.xml\n\n", os.Args[0])
57 fmt.Fprintf(os.Stderr, " # Generate with custom package name\n")
58 fmt.Fprintf(os.Stderr, " %s -p vpointer -o vpointer.go protocol.xml\n", os.Args[0])
59 }
60
61 flag.Parse()
62
63 if showHelp || flag.NArg() == 0 {
64 flag.Usage()
65 if showHelp {
66 os.Exit(0)
67 }
68 os.Exit(1)
69 }
70
71 xmlPath := flag.Arg(0)
72
73 // Derive output path if not specified
74 if outputPath == "" {
75 base := filepath.Base(xmlPath)
76 base = strings.TrimSuffix(base, filepath.Ext(base))
77 outputPath = base + "_generated.go"
78 }
79
80 // Derive package name if not specified
81 if packageName == "" {
82 base := filepath.Base(xmlPath)
83 base = strings.TrimSuffix(base, filepath.Ext(base))
84
85 // Clean up common prefixes/suffixes
86 base = strings.TrimPrefix(base, "wlr-")
87 base = strings.TrimPrefix(base, "zwlr-")
88 base = strings.TrimSuffix(base, "-unstable-v1")
89 base = strings.TrimSuffix(base, "-unstable-v2")
90 base = strings.TrimSuffix(base, "-stable-v1")
91
92 // Convert to valid Go package name
93 packageName = strings.ReplaceAll(base, "-", "_")
94 }
95
96 // Create scanner
97 s := scanner.NewScanner()
98 s.CrossPackage = crossImports
99
100 // Parse XML
101 if err := s.ParseXML(xmlPath); err != nil {
102 log.Fatalf("Failed to parse XML: %v", err)
103 }
104
105 // Generate code
106 code, err := s.Generate(packageName)
107 if err != nil {
108 log.Fatalf("Failed to generate code: %v", err)
109 }
110
111 // Write output with secure permissions
112 if err := os.WriteFile(outputPath, code, 0600); err != nil {
113 log.Fatalf("Failed to write output: %v", err)
114 }
115
116 fmt.Printf("Generated %s (package %s)\n", outputPath, packageName)
117}
118
119type crossFlags map[string]string
120
121func (f *crossFlags) String() string { return fmt.Sprint(map[string]string(*f)) }
122func (f *crossFlags) Set(v string) error {
123 parts := strings.SplitN(v, "=", 2)
124 if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
125 return fmt.Errorf("-import must be interface=GoImportPath")
126 }
127 if *f == nil {
128 *f = make(map[string]string)
129 }
130 (*f)[parts[0]] = parts[1]
131 return nil
132}