From 96d75d9f1fab87365d7e6b5070eed3a5757c3484 Mon Sep 17 00:00:00 2001 From: James O'Doherty Date: Fri, 22 May 2026 09:18:55 -0400 Subject: Refactor CLI for testability and implement hermetic config path injection --- internal/cli/cli.go | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 internal/cli/cli.go (limited to 'internal/cli/cli.go') diff --git a/internal/cli/cli.go b/internal/cli/cli.go new file mode 100644 index 0000000..cb95202 --- /dev/null +++ b/internal/cli/cli.go @@ -0,0 +1,114 @@ +package cli + +import ( + "flag" + "fmt" + + "git.theodohertyfamily.com/tools/wg-wrap/internal/config" +) + +type App struct { + Args []string + ConfigDir string // Optional override for profile storage location +} + +func NewApp(args []string) *App { + return &App{Args: args} +} + +func (a *App) Run() error { + // Handle subcommands first (profile list, import, configure, delete, stop) + if len(a.Args) > 1 && a.Args[1] == "profile" { + return a.handleProfileCmd() + } + + cfg := &config.Config{} + + fs := flag.NewFlagSet("wg-wrap", flag.ExitOnError) + fs.StringVar(&cfg.Profile, "profile", "", "WireGuard profile to use (filename without extension in ~/.config/wg-wrap/profiles/)") + fs.StringVar(&cfg.DNSServer, "dns-server", "", "Override DNS server to use") + + args := a.Args[1:] + sepIdx := -1 + for i, arg := range args { + if arg == "--" { + sepIdx = i + break + } + } + + var flagsToParse []string + if sepIdx != -1 { + flagsToParse = args[:sepIdx] + cfg.Command = args[sepIdx+1:] + } else { + flagsToParse = args + } + + err := fs.Parse(flagsToParse) + if err != nil { + return fmt.Errorf("error parsing flags: %w", err) + } + + if sepIdx == -1 { + cfg.Command = fs.Args() + } + + if len(cfg.Command) == 0 { + return fmt.Errorf("no command provided. use --help for usage") + } + + if cfg.Profile == "" { + cfg.Profile = "default" + } + + profilesDir := a.ConfigDir + if profilesDir == "" { + profilesDir = config.GetDefaultProfilesDir() + } + + fmt.Printf("Profile: %s\n", cfg.Profile) + fmt.Printf("Profiles Directory: %s\n", profilesDir) + fmt.Printf("DNS Server: %s\n", cfg.DNSServer) + fmt.Printf("Command: %v\n", cfg.Command) + return nil +} + +func (a *App) handleProfileCmd() error { + if len(a.Args) < 3 { + return fmt.Errorf("usage: wg-wrap profile [args]") + } + + subCmd := a.Args[2] + switch subCmd { + case "list": + fmt.Println("Listing profiles...") + return fmt.Errorf("profile list not yet implemented") + case "import": + if len(a.Args) < 4 { + return fmt.Errorf("usage: wg-wrap profile import ") + } + fmt.Printf("Importing profile from %s...\n", a.Args[3]) + return fmt.Errorf("profile import not yet implemented") + case "configure": + if len(a.Args) < 4 { + return fmt.Errorf("usage: wg-wrap profile configure ") + } + fmt.Printf("Configuring profile %s...\n", a.Args[3]) + return fmt.Errorf("profile configure not yet implemented") + case "delete": + if len(a.Args) < 4 { + return fmt.Errorf("usage: wg-wrap profile delete ") + } + fmt.Printf("Deleting profile %s...\n", a.Args[3]) + return fmt.Errorf("profile delete not yet implemented") + case "stop": + if len(a.Args) < 4 { + return fmt.Errorf("usage: wg-wrap profile stop ") + } + fmt.Printf("Stopping profile %s and unpinning namespace...\n", a.Args[3]) + return fmt.Errorf("profile stop not yet implemented") + default: + return fmt.Errorf("unknown profile subcommand: %s", subCmd) + } +} -- cgit v1.2.3