// Command zeta-reconstruct converts Arch Linux packages into Zeta package // format. Subcommands are provided by frontends registered in the registry. package main import ( "flag" "fmt" "os" "strings" "git.spectoria.dev/huntedbytheirs/zeta-reconstruct/internal/registry" // Import frontend packages so their init() functions register them. _ "git.spectoria.dev/huntedbytheirs/zeta-reconstruct/internal/arch/binary" _ "git.spectoria.dev/huntedbytheirs/zeta-reconstruct/internal/arch/src" ) const defaultRepo = "https://raw.githubusercontent.com/gretagen/zeta-packages/refs/heads/main" const usageText = `usage: zeta-reconstruct [flags] Commands: arch-binary convert an Arch binary package (.pkg.tar.zst) to Zeta arch-src convert a PKGBUILD/.SRCINFO source recipe to Zeta Flags: --list list registered frontends -h, --help show this help arch-binary flags: --output output directory (default ".") --repo repository base URL (default "` + defaultRepo + `") arch-src flags: --output output directory (default ".") --arch target architecture (default "x86_64") ` func main() { os.Exit(run(os.Args[1:])) } // normalizeArgs reorders args into [flags and their values..., positionals...] // because stdlib flag.Parse stops at the first non-flag token, which would // misparse flags placed after the positional input. func normalizeArgs(args []string, valueFlags map[string]bool) []string { var flags, positionals []string for i := 0; i < len(args); i++ { tok := args[i] if !strings.HasPrefix(tok, "-") || tok == "-" { positionals = append(positionals, tok) continue } flags = append(flags, tok) if !strings.Contains(tok, "=") { name := tok if valueFlags[name] && i+1 < len(args) { i++ flags = append(flags, args[i]) } } } return append(flags, positionals...) } func run(args []string) int { if len(args) == 0 { fmt.Fprintln(os.Stderr, "error: no command specified") fmt.Fprintln(os.Stderr, "Run 'zeta-reconstruct --help' for usage.") return 2 } switch args[0] { case "--list": for _, name := range registry.List() { fmt.Println(name) } return 0 case "--help", "-h": fmt.Print(usageText) return 0 } frontend, ok := registry.Get(args[0]) if !ok { fmt.Fprintf(os.Stderr, "error: unknown command %q\n", args[0]) fmt.Fprintln(os.Stderr, "Run 'zeta-reconstruct --help' for usage.") return 2 } fs := flag.NewFlagSet(frontend.Name(), flag.ContinueOnError) fs.SetOutput(os.Stderr) opts := registry.Options{Output: "."} valueFlags := make(map[string]bool) switch frontend.Name() { case "arch-binary": opts.Repo = defaultRepo fs.StringVar(&opts.Repo, "repo", opts.Repo, "repository base URL") valueFlags["--repo"] = true case "arch-src": opts.Arch = "x86_64" fs.StringVar(&opts.Arch, "arch", opts.Arch, "target architecture") valueFlags["--arch"] = true } fs.StringVar(&opts.Output, "output", opts.Output, "output directory") valueFlags["--output"] = true fs.Usage = func() { fmt.Fprintf(os.Stderr, "usage: zeta-reconstruct %s [flags] \n", frontend.Name()) fmt.Fprintln(os.Stderr, "Run 'zeta-reconstruct --help' for usage.") } if err := fs.Parse(normalizeArgs(args[1:], valueFlags)); err != nil { if err == flag.ErrHelp { fs.Usage() return 0 } return 2 } rest := fs.Args() if len(rest) == 0 { fmt.Fprintf(os.Stderr, "error: %s requires an input argument\n", frontend.Name()) fs.Usage() return 1 } if len(rest) > 1 { fmt.Fprintf(os.Stderr, "error: unexpected argument %q\n", rest[1]) fs.Usage() return 2 } if frontend.Name() == "arch-binary" && !strings.HasSuffix(rest[0], ".pkg.tar.zst") { fmt.Fprintln(os.Stderr, "error: arch-binary input must end with .pkg.tar.zst") fs.Usage() return 2 } if err := frontend.Convert(rest[0], opts); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) return 1 } return 0 }