fixed spinner

This commit is contained in:
e 2025-01-21 17:56:15 -05:00
parent 326a2b79d2
commit 446c5343bd
2 changed files with 147 additions and 53 deletions

127
main.go
View File

@ -15,11 +15,11 @@ import (
var ( var (
// Color definitions // Color definitions
success = color.New(color.FgGreen, color.Bold) success = color.New(color.FgGreen, color.Bold)
errorColor = color.New(color.FgRed, color.Bold)
warning = color.New(color.FgYellow, color.Bold) warning = color.New(color.FgYellow, color.Bold)
error = color.New(color.FgRed, color.Bold)
info = color.New(color.FgCyan) info = color.New(color.FgCyan)
header = color.New(color.FgMagenta, color.Bold) header = color.New(color.FgMagenta, color.Bold)
command = color.New(color.FgBlue, color.Bold) cmdColor = color.New(color.FgBlue, color.Bold)
// Command line flags // Command line flags
verbose bool 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() { func main() {
// Initialize universal package managers // Initialize universal package managers
pkg_manager.InitializeUniversalManagers() 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() printUsage()
os.Exit(1) os.Exit(1)
} }
// Check for flags args := os.Args[commandStart:]
var universalPM string
flag.StringVar(&universalPM, "u", "", "Use universal package manager (snap/flatpak)")
flag.BoolVar(&verbose, "v", false, "Show verbose output")
flag.Parse()
if universalPM != "" { if universalPM != "" {
if mgr, ok := pkg_manager.GetUniversalManager(universalPM); ok { if mgr, ok := pkg_manager.GetUniversalManager(universalPM); ok {
handleCommand(&mgr.Manager, os.Args[1:]) handleCommand(&mgr.Manager, args)
return return
} }
fmt.Fprintf(os.Stderr, "Universal package manager %s not available\n", universalPM) fmt.Fprintf(os.Stderr, "Universal package manager %s not available\n", universalPM)
@ -64,7 +115,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
handleCommand(pm, os.Args[1:]) handleCommand(pm, args)
} }
func handleCommand(pm *pkg_manager.Manager, args []string) { func handleCommand(pm *pkg_manager.Manager, args []string) {
@ -81,33 +132,39 @@ func handleCommand(pm *pkg_manager.Manager, args []string) {
} }
command := args[0] 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 { switch command {
case "install": case "install":
if len(cmdArgs) == 0 { if len(cmdArgs) == 0 {
error.Fprintln(os.Stderr, "Error: no packages specified") errorColor.Fprintln(os.Stderr, "Error: no packages specified")
os.Exit(1) os.Exit(1)
} }
stop := showSpinner("Installing packages...") err := runWithSpinner("Installing packages...", pm.NeedsSudo(), func() error {
err := pm.Install(cmdArgs...) return pm.Install(cmdArgs...)
stop() })
if err != nil { if err != nil {
error.Fprintf(os.Stderr, "Failed to install packages: %v\n", err) errorColor.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
} }
success.Printf("✓ Successfully installed: %s\n", strings.Join(cmdArgs, ", ")) success.Printf("✓ Successfully installed: %s\n", strings.Join(cmdArgs, ", "))
case "remove": case "remove":
if len(cmdArgs) == 0 { if len(cmdArgs) == 0 {
error.Fprintln(os.Stderr, "Error: no packages specified") errorColor.Fprintln(os.Stderr, "Error: no packages specified")
os.Exit(1) os.Exit(1)
} }
stop := showSpinner("Removing packages...") err := runWithSpinner("Removing packages...", pm.NeedsSudo(), func() error {
err := pm.Remove(cmdArgs...) return pm.Remove(cmdArgs...)
stop() })
if err != nil { 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) os.Exit(1)
} }
success.Printf("✓ Successfully removed: %s\n", strings.Join(cmdArgs, ", ")) success.Printf("✓ Successfully removed: %s\n", strings.Join(cmdArgs, ", "))
@ -229,39 +286,39 @@ func printUsage() {
fmt.Println("\nUsage: upm [-u snap|flatpak] [-v] <command> [arguments]") fmt.Println("\nUsage: upm [-u snap|flatpak] [-v] <command> [arguments]")
info.Println("\nCommands:") info.Println("\nCommands:")
command.Print(" install ") cmdColor.Print(" install ")
fmt.Println("<package1[@version]> [package2[@version]...] - Install packages") fmt.Println("<package1[@version]> [package2[@version]...] - Install packages")
command.Print(" remove ") cmdColor.Print(" remove ")
fmt.Println("<package1> [package2...] - Remove packages") fmt.Println("<package1> [package2...] - Remove packages")
command.Print(" search ") cmdColor.Print(" search ")
fmt.Println("<term> - Search for packages") fmt.Println("<term> - Search for packages")
command.Print(" update ") cmdColor.Print(" update ")
fmt.Println(" - Update package lists") fmt.Println(" - Update package lists")
command.Print(" upgrade ") cmdColor.Print(" upgrade ")
fmt.Println(" - Upgrade installed packages") fmt.Println(" - Upgrade installed packages")
command.Print(" list ") cmdColor.Print(" list ")
fmt.Println(" - List installed packages") fmt.Println(" - List installed packages")
command.Print(" info ") cmdColor.Print(" info ")
fmt.Println("<package> - Show package information") fmt.Println("<package> - Show package information")
info.Println("\nRepository Management:") info.Println("\nRepository Management:")
command.Print(" repo add ") cmdColor.Print(" repo add ")
fmt.Println("<url> - Add repository") fmt.Println("<url> - Add repository")
command.Print(" repo remove ") cmdColor.Print(" repo remove ")
fmt.Println("<name> - Remove repository") fmt.Println("<name> - Remove repository")
command.Print(" repo list ") cmdColor.Print(" repo list ")
fmt.Println(" - List repositories") fmt.Println(" - List repositories")
info.Println("\nMaintenance:") info.Println("\nMaintenance:")
command.Print(" clean ") cmdColor.Print(" clean ")
fmt.Println(" - Clean package cache") fmt.Println(" - Clean package cache")
command.Print(" autoremove ") cmdColor.Print(" autoremove ")
fmt.Println(" - Remove unused dependencies") fmt.Println(" - Remove unused dependencies")
info.Println("\nDependency Management:") info.Println("\nDependency Management:")
command.Print(" check-deps ") cmdColor.Print(" check-deps ")
fmt.Println("[package] - Check dependencies") fmt.Println("[package] - Check dependencies")
command.Print(" show-deps ") cmdColor.Print(" show-deps ")
fmt.Println("<package> - Show package dependencies") fmt.Println("<package> - Show package dependencies")
info.Println("\nFlags:") info.Println("\nFlags:")

View File

@ -80,7 +80,7 @@ var universalManagers = map[string]*UniversalManager{
searchCmd: []string{"search"}, searchCmd: []string{"search"},
updateCmd: []string{"update", "-y"}, updateCmd: []string{"update", "-y"},
upgradeCmd: []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) 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 // Install packages with version support
func (m *Manager) Install(packages ...string) error { func (m *Manager) Install(packages ...string) error {
if len(packages) == 0 { if len(packages) == 0 {
return fmt.Errorf("no packages specified") 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)) formattedPkgs := make([]string, len(packages))
for i, pkg := range packages { for i, pkg := range packages {
pv := ParsePackageVersion(pkg) pv := ParsePackageVersion(pkg)
@ -277,32 +297,44 @@ func SetOutputMode(mode OutputMode) {
outputMode = mode 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 { func (m *Manager) execCommand(baseCmd []string, args ...string) error {
cmdArgs := append(baseCmd, args...) cmdArgs := append(baseCmd, args...)
var cmd *exec.Cmd var cmd *exec.Cmd
if m.needsSudo && os.Geteuid() != 0 { if m.needsSudo && os.Geteuid() != 0 {
// Clear the line to prevent spinner interference with sudo prompt return m.execCommandWithSudo(baseCmd, args...)
fmt.Print("\r\033[K")
cmdArgs = append([]string{m.command}, cmdArgs...)
cmd = exec.Command("sudo", cmdArgs...)
} else { } else {
cmd = exec.Command(m.command, cmdArgs...) cmd = exec.Command(m.command, cmdArgs...)
}
// Always capture output
if outputMode == OutputVerbose { if outputMode == OutputVerbose {
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
} else { } else {
// Discard output in quiet mode
cmd.Stdout = io.Discard cmd.Stdout = io.Discard
cmd.Stderr = io.Discard cmd.Stderr = io.Discard
} }
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
return cmd.Run() return cmd.Run()
} }
}
// Remove packages // Remove packages
func (m *Manager) Remove(packages ...string) error { func (m *Manager) Remove(packages ...string) error {
@ -387,3 +419,8 @@ func DetectPackageManager() (*Manager, error) {
} }
return nil, fmt.Errorf("no supported package manager found") 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
}