mirror of
https://github.com/navidrome/navidrome.git
synced 2026-03-04 06:35:52 +00:00
185 lines
5.1 KiB
Go
185 lines
5.1 KiB
Go
// hostgen generates Extism host function wrappers from annotated Go interfaces.
|
|
//
|
|
// Usage:
|
|
//
|
|
// hostgen -input=./plugins/host -output=./plugins/host
|
|
//
|
|
// Flags:
|
|
//
|
|
// -input Input directory containing Go source files with annotated interfaces
|
|
// -output Output directory for generated files (default: same as input)
|
|
// -package Output package name (default: inferred from output directory)
|
|
// -host-only Generate only host-side code (default: false)
|
|
// -plugin-only Generate only plugin/client-side code (default: false)
|
|
// -v Verbose output
|
|
// -dry-run Preview generated code without writing files
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"go/format"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/navidrome/navidrome/plugins/cmd/hostgen/internal"
|
|
)
|
|
|
|
func main() {
|
|
var (
|
|
inputDir = flag.String("input", ".", "Input directory containing Go source files")
|
|
outputDir = flag.String("output", "", "Output directory for generated files (default: same as input)")
|
|
pkgName = flag.String("package", "", "Output package name (default: inferred from output directory)")
|
|
hostOnly = flag.Bool("host-only", false, "Generate only host-side code")
|
|
pluginOnly = flag.Bool("plugin-only", false, "Generate only plugin/client-side code")
|
|
verbose = flag.Bool("v", false, "Verbose output")
|
|
dryRun = flag.Bool("dry-run", false, "Preview generated code without writing files")
|
|
)
|
|
flag.Parse()
|
|
|
|
// Validate conflicting flags
|
|
if *hostOnly && *pluginOnly {
|
|
fmt.Fprintf(os.Stderr, "Error: -host-only and -plugin-only cannot be used together\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
if *outputDir == "" {
|
|
*outputDir = *inputDir
|
|
}
|
|
|
|
// Resolve absolute paths
|
|
absInput, err := filepath.Abs(*inputDir)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error resolving input path: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
absOutput, err := filepath.Abs(*outputDir)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error resolving output path: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Infer package name if not provided
|
|
if *pkgName == "" {
|
|
*pkgName = filepath.Base(absOutput)
|
|
}
|
|
|
|
// Determine what to generate
|
|
generateHost := !*pluginOnly
|
|
generateClient := !*hostOnly
|
|
|
|
if *verbose {
|
|
fmt.Printf("Input directory: %s\n", absInput)
|
|
fmt.Printf("Output directory: %s\n", absOutput)
|
|
fmt.Printf("Package name: %s\n", *pkgName)
|
|
fmt.Printf("Generate host code: %v\n", generateHost)
|
|
fmt.Printf("Generate client code: %v\n", generateClient)
|
|
}
|
|
|
|
// Parse source files
|
|
services, err := internal.ParseDirectory(absInput)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing source files: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(services) == 0 {
|
|
if *verbose {
|
|
fmt.Println("No host services found")
|
|
}
|
|
return
|
|
}
|
|
|
|
if *verbose {
|
|
fmt.Printf("Found %d host service(s)\n", len(services))
|
|
for _, svc := range services {
|
|
fmt.Printf(" - %s (%d methods)\n", svc.Name, len(svc.Methods))
|
|
}
|
|
}
|
|
|
|
// Generate code for each service
|
|
for _, svc := range services {
|
|
// Generate host-side code
|
|
if generateHost {
|
|
if err := generateHostCode(svc, *pkgName, absOutput, *dryRun, *verbose); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error generating host code for %s: %v\n", svc.Name, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// Generate client-side code
|
|
if generateClient {
|
|
if err := generateClientCode(svc, absOutput, *dryRun, *verbose); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error generating client code for %s: %v\n", svc.Name, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// generateHostCode generates host-side code for a service.
|
|
func generateHostCode(svc internal.Service, pkgName, outputDir string, dryRun, verbose bool) error {
|
|
code, err := internal.GenerateHost(svc, pkgName)
|
|
if err != nil {
|
|
return fmt.Errorf("generating code: %w", err)
|
|
}
|
|
|
|
formatted, err := format.Source(code)
|
|
if err != nil {
|
|
return fmt.Errorf("formatting code: %w\nRaw code:\n%s", err, code)
|
|
}
|
|
|
|
outputFile := filepath.Join(outputDir, svc.OutputFileName())
|
|
|
|
if dryRun {
|
|
fmt.Printf("=== %s ===\n%s\n", outputFile, formatted)
|
|
return nil
|
|
}
|
|
|
|
if err := os.WriteFile(outputFile, formatted, 0600); err != nil {
|
|
return fmt.Errorf("writing file: %w", err)
|
|
}
|
|
|
|
if verbose {
|
|
fmt.Printf("Generated host code: %s\n", outputFile)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// generateClientCode generates client-side code for a service.
|
|
func generateClientCode(svc internal.Service, outputDir string, dryRun, verbose bool) error {
|
|
code, err := internal.GenerateClientGo(svc)
|
|
if err != nil {
|
|
return fmt.Errorf("generating code: %w", err)
|
|
}
|
|
|
|
formatted, err := format.Source(code)
|
|
if err != nil {
|
|
return fmt.Errorf("formatting code: %w\nRaw code:\n%s", err, code)
|
|
}
|
|
|
|
// Client code goes in go/ subdirectory
|
|
clientDir := filepath.Join(outputDir, "go")
|
|
clientFile := filepath.Join(clientDir, "nd_host_"+strings.ToLower(svc.Name)+".go")
|
|
|
|
if dryRun {
|
|
fmt.Printf("=== %s ===\n%s\n", clientFile, formatted)
|
|
return nil
|
|
}
|
|
|
|
// Create go/ subdirectory if needed
|
|
if err := os.MkdirAll(clientDir, 0755); err != nil {
|
|
return fmt.Errorf("creating client directory: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(clientFile, formatted, 0600); err != nil {
|
|
return fmt.Errorf("writing file: %w", err)
|
|
}
|
|
|
|
if verbose {
|
|
fmt.Printf("Generated client code: %s\n", clientFile)
|
|
}
|
|
return nil
|
|
}
|