diff --git a/cmd/zeta-reconstruct/main.go b/cmd/zeta-reconstruct/main.go index 5c02c24..e6c2fc4 100644 --- a/cmd/zeta-reconstruct/main.go +++ b/cmd/zeta-reconstruct/main.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "os" + "strings" "git.spectoria.dev/huntedbytheirs/zeta-reconstruct/internal/registry" @@ -39,6 +40,29 @@ 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") @@ -67,21 +91,25 @@ func run(args []string) int { 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(args[1:]); err != nil { + if err := fs.Parse(normalizeArgs(args[1:], valueFlags)); err != nil { if err == flag.ErrHelp { fs.Usage() return 0 @@ -100,6 +128,11 @@ func run(args []string) int { 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) diff --git a/cmd/zeta-reconstruct/main_test.go b/cmd/zeta-reconstruct/main_test.go new file mode 100644 index 0000000..1934ba7 --- /dev/null +++ b/cmd/zeta-reconstruct/main_test.go @@ -0,0 +1,60 @@ +package main + +import ( + "reflect" + "testing" +) + +func TestNormalizeArgs(t *testing.T) { + valueFlags := map[string]bool{ + "--output": true, + "--repo": true, + "--arch": true, + } + + tests := []struct { + name string + in []string + want []string + }{ + { + name: "flags after positional", + in: []string{"input.pkg.tar.zst", "--output", "/tmp/out"}, + want: []string{"--output", "/tmp/out", "input.pkg.tar.zst"}, + }, + { + name: "flags before positional", + in: []string{"--output", "/tmp/out", "input.pkg.tar.zst"}, + want: []string{"--output", "/tmp/out", "input.pkg.tar.zst"}, + }, + { + name: "flag=value form", + in: []string{"input.pkg.tar.zst", "--output=/tmp/out"}, + want: []string{"--output=/tmp/out", "input.pkg.tar.zst"}, + }, + { + name: "unknown flag stays a flag", + in: []string{"input.pkg.tar.zst", "--bogus"}, + want: []string{"--bogus", "input.pkg.tar.zst"}, + }, + { + name: "value-taking flag at end without value", + in: []string{"input.pkg.tar.zst", "--output"}, + want: []string{"--output", "input.pkg.tar.zst"}, + }, + { + name: "mixed flags and positionals keep order", + in: []string{"--repo", "https://x", "a.zst", "--output", "/tmp/out", "b.zst"}, + want: []string{"--repo", "https://x", "--output", "/tmp/out", "a.zst", "b.zst"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := normalizeArgs(tt.in, valueFlags) + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("normalizeArgs(%v) = %v, want %v", tt.in, got, tt.want) + } + }) + } +}