From 446c5343bd489bdfa1969c075748339fd6a87f27 Mon Sep 17 00:00:00 2001 From: e Date: Tue, 21 Jan 2025 17:56:15 -0500 Subject: [PATCH] fixed spinner --- main.go | 127 +++++++++++++++++++++++++++---------- pkg_manager/pkg_manager.go | 73 +++++++++++++++------ 2 files changed, 147 insertions(+), 53 deletions(-) diff --git a/main.go b/main.go index f881404..c439d61 100644 --- a/main.go +++ b/main.go @@ -15,11 +15,11 @@ import ( var ( // Color definitions success = color.New(color.FgGreen, color.Bold) + errorColor = color.New(color.FgRed, color.Bold) warning = color.New(color.FgYellow, color.Bold) - error = color.New(color.FgRed, color.Bold) info = color.New(color.FgCyan) header = color.New(color.FgMagenta, color.Bold) - command = color.New(color.FgBlue, color.Bold) + cmdColor = color.New(color.FgBlue, color.Bold) // Command line flags verbose bool ) @@ -34,24 +34,75 @@ func showSpinner(message string) func() { } } +func runWithSpinner(message string, needsSudo bool, fn func() error) error { + var stop func() + cmdStarted := make(chan struct{}) + + if needsSudo { + // Start a goroutine to show spinner after sudo auth + go func() { + <-cmdStarted + fmt.Print("\r\033[K") // Clear the line + stop = showSpinner(message) + }() + } else { + stop = showSpinner(message) + } + + err := fn() + + if needsSudo { + close(cmdStarted) + // Give the spinner a moment to show progress + time.Sleep(500 * time.Millisecond) + } + + if stop != nil { + stop() + } + + return err +} + func main() { // Initialize universal package managers pkg_manager.InitializeUniversalManagers() - if len(os.Args) < 2 { + // Create custom flag set + flags := flag.NewFlagSet("upm", flag.ExitOnError) + flags.Usage = printUsage + + // Define flags + var universalPM string + flags.StringVar(&universalPM, "u", "", "Use universal package manager (snap/flatpak)") + flags.BoolVar(&verbose, "v", false, "Show verbose output") + + // Find where the command starts (after flags) + commandStart := 1 + for commandStart < len(os.Args) { + if !strings.HasPrefix(os.Args[commandStart], "-") { + break + } + commandStart++ + } + + // Parse only the flags before the command + if err := flags.Parse(os.Args[1:commandStart]); err != nil { + fmt.Fprintf(os.Stderr, "Error parsing flags: %v\n", err) + os.Exit(1) + } + + // Get command and its arguments (everything after the flags) + if commandStart >= len(os.Args) { printUsage() os.Exit(1) } - // Check for flags - var universalPM string - flag.StringVar(&universalPM, "u", "", "Use universal package manager (snap/flatpak)") - flag.BoolVar(&verbose, "v", false, "Show verbose output") - flag.Parse() + args := os.Args[commandStart:] if universalPM != "" { if mgr, ok := pkg_manager.GetUniversalManager(universalPM); ok { - handleCommand(&mgr.Manager, os.Args[1:]) + handleCommand(&mgr.Manager, args) return } fmt.Fprintf(os.Stderr, "Universal package manager %s not available\n", universalPM) @@ -64,7 +115,7 @@ func main() { os.Exit(1) } - handleCommand(pm, os.Args[1:]) + handleCommand(pm, args) } func handleCommand(pm *pkg_manager.Manager, args []string) { @@ -81,33 +132,39 @@ func handleCommand(pm *pkg_manager.Manager, args []string) { } command := args[0] - cmdArgs := args[1:] + // Filter out any arguments that look like flags + var cmdArgs []string + for _, arg := range args[1:] { + if !strings.HasPrefix(arg, "-") { + cmdArgs = append(cmdArgs, arg) + } + } switch command { case "install": if len(cmdArgs) == 0 { - error.Fprintln(os.Stderr, "Error: no packages specified") + errorColor.Fprintln(os.Stderr, "Error: no packages specified") os.Exit(1) } - stop := showSpinner("Installing packages...") - err := pm.Install(cmdArgs...) - stop() + err := runWithSpinner("Installing packages...", pm.NeedsSudo(), func() error { + return pm.Install(cmdArgs...) + }) if err != nil { - error.Fprintf(os.Stderr, "Failed to install packages: %v\n", err) + errorColor.Fprintln(os.Stderr, err) os.Exit(1) } success.Printf("✓ Successfully installed: %s\n", strings.Join(cmdArgs, ", ")) case "remove": if len(cmdArgs) == 0 { - error.Fprintln(os.Stderr, "Error: no packages specified") + errorColor.Fprintln(os.Stderr, "Error: no packages specified") os.Exit(1) } - stop := showSpinner("Removing packages...") - err := pm.Remove(cmdArgs...) - stop() + err := runWithSpinner("Removing packages...", pm.NeedsSudo(), func() error { + return pm.Remove(cmdArgs...) + }) if err != nil { - error.Fprintf(os.Stderr, "Failed to remove packages: %v\n", err) + errorColor.Fprintf(os.Stderr, "Failed to remove packages: %v\n", err) os.Exit(1) } success.Printf("✓ Successfully removed: %s\n", strings.Join(cmdArgs, ", ")) @@ -229,39 +286,39 @@ func printUsage() { fmt.Println("\nUsage: upm [-u snap|flatpak] [-v] [arguments]") info.Println("\nCommands:") - command.Print(" install ") + cmdColor.Print(" install ") fmt.Println(" [package2[@version]...] - Install packages") - command.Print(" remove ") + cmdColor.Print(" remove ") fmt.Println(" [package2...] - Remove packages") - command.Print(" search ") + cmdColor.Print(" search ") fmt.Println(" - Search for packages") - command.Print(" update ") + cmdColor.Print(" update ") fmt.Println(" - Update package lists") - command.Print(" upgrade ") + cmdColor.Print(" upgrade ") fmt.Println(" - Upgrade installed packages") - command.Print(" list ") + cmdColor.Print(" list ") fmt.Println(" - List installed packages") - command.Print(" info ") + cmdColor.Print(" info ") fmt.Println(" - Show package information") info.Println("\nRepository Management:") - command.Print(" repo add ") + cmdColor.Print(" repo add ") fmt.Println(" - Add repository") - command.Print(" repo remove ") + cmdColor.Print(" repo remove ") fmt.Println(" - Remove repository") - command.Print(" repo list ") + cmdColor.Print(" repo list ") fmt.Println(" - List repositories") info.Println("\nMaintenance:") - command.Print(" clean ") + cmdColor.Print(" clean ") fmt.Println(" - Clean package cache") - command.Print(" autoremove ") + cmdColor.Print(" autoremove ") fmt.Println(" - Remove unused dependencies") info.Println("\nDependency Management:") - command.Print(" check-deps ") + cmdColor.Print(" check-deps ") fmt.Println("[package] - Check dependencies") - command.Print(" show-deps ") + cmdColor.Print(" show-deps ") fmt.Println(" - Show package dependencies") info.Println("\nFlags:") diff --git a/pkg_manager/pkg_manager.go b/pkg_manager/pkg_manager.go index 5ded3d0..fa4bf17 100644 --- a/pkg_manager/pkg_manager.go +++ b/pkg_manager/pkg_manager.go @@ -80,7 +80,7 @@ var universalManagers = map[string]*UniversalManager{ searchCmd: []string{"search"}, updateCmd: []string{"update", "-y"}, upgradeCmd: []string{"update", "-y"}, - needsSudo: false, + needsSudo: true, }, }, } @@ -232,12 +232,32 @@ func (m *Manager) formatPackageWithVersion(pv PackageVersion) string { return fmt.Sprintf("%s%s%s", pv.Name, m.versionSep, pv.Version) } +// CheckPackageExists verifies if a package exists in the repositories +func (m *Manager) CheckPackageExists(pkg string) bool { + cmd := exec.Command(m.command, append(m.searchCmd, pkg)...) + output, err := cmd.Output() + if err != nil { + return false + } + // Different package managers have different output formats + // This is a simple check that should work for most + return len(output) > 0 +} + // Install packages with version support func (m *Manager) Install(packages ...string) error { if len(packages) == 0 { return fmt.Errorf("no packages specified") } + // Validate packages first + for _, pkg := range packages { + pv := ParsePackageVersion(pkg) + if !m.CheckPackageExists(pv.Name) { + return fmt.Errorf("package not found: %s", pv.Name) + } + } + formattedPkgs := make([]string, len(packages)) for i, pkg := range packages { pv := ParsePackageVersion(pkg) @@ -277,31 +297,43 @@ func SetOutputMode(mode OutputMode) { outputMode = mode } +// execCommandWithSudo executes a command with sudo +func (m *Manager) execCommandWithSudo(baseCmd []string, args ...string) error { + cmdArgs := append(baseCmd, args...) + cmdArgs = append([]string{m.command}, cmdArgs...) + cmd := exec.Command("sudo", cmdArgs...) + + // Always show sudo password prompt + cmd.Stdin = os.Stdin + if outputMode == OutputVerbose { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } else { + cmd.Stdout = io.Discard + cmd.Stderr = io.Discard + } + + return cmd.Run() +} + func (m *Manager) execCommand(baseCmd []string, args ...string) error { cmdArgs := append(baseCmd, args...) var cmd *exec.Cmd if m.needsSudo && os.Geteuid() != 0 { - // Clear the line to prevent spinner interference with sudo prompt - fmt.Print("\r\033[K") - cmdArgs = append([]string{m.command}, cmdArgs...) - cmd = exec.Command("sudo", cmdArgs...) + return m.execCommandWithSudo(baseCmd, args...) } else { cmd = exec.Command(m.command, cmdArgs...) + if outputMode == OutputVerbose { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } else { + cmd.Stdout = io.Discard + cmd.Stderr = io.Discard + } + cmd.Stdin = os.Stdin + return cmd.Run() } - - // Always capture output - if outputMode == OutputVerbose { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - } else { - // Discard output in quiet mode - cmd.Stdout = io.Discard - cmd.Stderr = io.Discard - } - cmd.Stdin = os.Stdin - - return cmd.Run() } // Remove packages @@ -386,4 +418,9 @@ func DetectPackageManager() (*Manager, error) { } } return nil, fmt.Errorf("no supported package manager found") +} + +// NeedsSudo returns whether this package manager needs sudo +func (m *Manager) NeedsSudo() bool { + return m.needsSudo && os.Geteuid() != 0 } \ No newline at end of file