feat(cli): add Go module, subcommand dispatch, frontend registry
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <[email protected]>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/zr-out
|
||||
*.part
|
||||
@@ -0,0 +1,109 @@
|
||||
// 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"
|
||||
|
||||
"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 <command> [flags] <input>
|
||||
|
||||
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 <dir> output directory (default ".")
|
||||
--repo <url> repository base URL (default "` + defaultRepo + `")
|
||||
|
||||
arch-src flags:
|
||||
--output <dir> output directory (default ".")
|
||||
--arch <arch> target architecture (default "x86_64")
|
||||
`
|
||||
|
||||
func main() {
|
||||
os.Exit(run(os.Args[1:]))
|
||||
}
|
||||
|
||||
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: "."}
|
||||
switch frontend.Name() {
|
||||
case "arch-binary":
|
||||
opts.Repo = defaultRepo
|
||||
fs.StringVar(&opts.Repo, "repo", opts.Repo, "repository base URL")
|
||||
case "arch-src":
|
||||
opts.Arch = "x86_64"
|
||||
fs.StringVar(&opts.Arch, "arch", opts.Arch, "target architecture")
|
||||
}
|
||||
fs.StringVar(&opts.Output, "output", opts.Output, "output directory")
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "usage: zeta-reconstruct %s [flags] <input>\n", frontend.Name())
|
||||
fmt.Fprintln(os.Stderr, "Run 'zeta-reconstruct --help' for usage.")
|
||||
}
|
||||
|
||||
if err := fs.Parse(args[1:]); 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 err := frontend.Convert(rest[0], opts); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module git.spectoria.dev/huntedbytheirs/zeta-reconstruct
|
||||
|
||||
go 1.26.5
|
||||
@@ -0,0 +1,23 @@
|
||||
// Package binary converts Arch Linux binary packages (.pkg.tar.zst) into
|
||||
// Zeta packages.
|
||||
package binary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.spectoria.dev/huntedbytheirs/zeta-reconstruct/internal/registry"
|
||||
)
|
||||
|
||||
// frontend implements registry.Frontend for Arch binary packages.
|
||||
type frontend struct{}
|
||||
|
||||
func (frontend) Name() string { return "arch-binary" }
|
||||
|
||||
func (frontend) Convert(input string, opts registry.Options) error {
|
||||
// Implemented in a later todo (.pkg.tar.zst -> tarball + package.lua).
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Register(frontend{})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Package src converts Arch Linux source packages (PKGBUILD/.SRCINFO) into
|
||||
// Zeta recipes.
|
||||
package src
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.spectoria.dev/huntedbytheirs/zeta-reconstruct/internal/registry"
|
||||
)
|
||||
|
||||
// frontend implements registry.Frontend for Arch source packages.
|
||||
type frontend struct{}
|
||||
|
||||
func (frontend) Name() string { return "arch-src" }
|
||||
|
||||
func (frontend) Convert(input string, opts registry.Options) error {
|
||||
// Implemented in a later todo (PKGBUILD/.SRCINFO -> .recipe + build.sh).
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Register(frontend{})
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Package registry provides a global registry of package-format frontends.
|
||||
//
|
||||
// Frontends self-register via init() from their own packages, so a future
|
||||
// format (e.g. Gentoo ebuild, void-src) only needs a new package that imports
|
||||
// this one — nothing else changes.
|
||||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Options carries per-conversion settings shared by all frontends.
|
||||
type Options struct {
|
||||
Output string // directory to write converted output into
|
||||
Repo string // repository base URL (binary frontend)
|
||||
Arch string // target architecture (source frontend)
|
||||
}
|
||||
|
||||
// Frontend converts one input of a specific package format to Zeta.
|
||||
type Frontend interface {
|
||||
// Name returns the frontend's CLI subcommand name.
|
||||
Name() string
|
||||
// Convert converts input into Zeta format under opts.Output.
|
||||
Convert(input string, opts Options) error
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
frontends = make(map[string]Frontend)
|
||||
)
|
||||
|
||||
// Register adds f to the global registry. It panics if a frontend with the
|
||||
// same Name is already registered.
|
||||
func Register(f Frontend) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
name := f.Name()
|
||||
if name == "" {
|
||||
panic("registry: frontend with empty name")
|
||||
}
|
||||
if _, dup := frontends[name]; dup {
|
||||
panic(fmt.Sprintf("registry: duplicate frontend %q", name))
|
||||
}
|
||||
frontends[name] = f
|
||||
}
|
||||
|
||||
// List returns the names of all registered frontends, sorted.
|
||||
func List() []string {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
names := make([]string, 0, len(frontends))
|
||||
for name := range frontends {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
// Get returns the frontend registered under name.
|
||||
func Get(name string) (Frontend, bool) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
f, ok := frontends[name]
|
||||
return f, ok
|
||||
}
|
||||
Reference in New Issue
Block a user