Browse Source

Merge branch 'Morganamilo-fix#62'

Jguer 7 years ago
parent
commit
9cbef8d643
7 changed files with 964 additions and 239 deletions
  1. 8 4
      clean.go
  2. 381 184
      cmd.go
  3. 1 28
      config.go
  4. 20 20
      install.go
  5. 542 0
      parser.go
  6. 5 1
      query.go
  7. 7 2
      upgrade.go

+ 8 - 4
clean.go

@@ -32,7 +32,7 @@ func removeVCSPackage(pkgs []string) {
 }
 
 // CleanDependencies removes all dangling dependencies in system
-func cleanDependencies(pkgs []string) error {
+func cleanDependencies() error {
 	hanging, err := hangingPackages()
 	if err != nil {
 		return err
@@ -49,11 +49,15 @@ func cleanDependencies(pkgs []string) error {
 }
 
 // CleanRemove sends a full removal command to pacman with the pkgName slice
-func cleanRemove(pkgName []string) (err error) {
-	if len(pkgName) == 0 {
+func cleanRemove(pkgNames []string) (err error) {
+	if len(pkgNames) == 0 {
 		return nil
 	}
+	
+	arguments := makeArguments()
+	arguments.addArg("R", "noconfirm")
+	arguments.addTarget(pkgNames...)
 
-	err = passToPacman("-Rsnc", pkgName, []string{"--noconfirm"})
+	err = passToPacman(arguments)
 	return err
 }

+ 381 - 184
cmd.go

@@ -6,51 +6,58 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
 )
 
+var cmdArgs *arguments = makeArguments()
+
 func usage() {
-	fmt.Println(`usage:  yay <operation> [...]
-	operations:
-	yay {-h --help}
-	yay {-V --version}
-	yay {-D --database} <options> <package(s)>
-	yay {-F --files}    [options] [package(s)]
-	yay {-Q --query}    [options] [package(s)]
-	yay {-R --remove}   [options] <package(s)>
-	yay {-S --sync}     [options] [package(s)]
-	yay {-T --deptest}  [options] [package(s)]
-	yay {-U --upgrade}  [options] <file(s)>
-
-	New operations:
-	yay -Qstats          displays system information
-	yay -Cd              remove unneeded dependencies
-	yay -G [package(s)]  get pkgbuild from ABS or AUR
-	yay --gendb          generates development package DB used for updating.
-
-	Permanent configuration options:
-	--topdown            shows repository's packages first and then aur's
-	--bottomup           shows aur's packages first and then repository's
-	--devel              Check -git/-svn/-hg development version
-	--nodevel            Disable development version checking
-	--afterclean         Clean package sources after successful build
-	--noafterclean       Disable package sources cleaning after successful build
-	--timeupdate         Check package's modification date and version
-	--notimeupdate       Check only package version change
-
-	New options:
-	--noconfirm          skip user input on package install
-	--printconfig        Prints current yay configuration
-	`)
+	fmt.Println(`Usage:
+    yay <operation> [...]
+    yay <package(s)>
+
+operations:
+    yay {-h --help}
+    yay {-V --version}
+    yay {-D --database}    <options> <package(s)>
+    yay {-F --files}       [options] [package(s)]
+    yay {-Q --query}       [options] [package(s)]
+    yay {-R --remove}      [options] <package(s)>
+    yay {-S --sync}        [options] [package(s)]
+    yay {-T --deptest}     [options] [package(s)]
+    yay {-U --upgrade}     [options] <file(s)>
+
+New operations:
+    yay {-Y --yay}         [options] [package(s)]
+    yay {-G --getpkgbuild} [package(s)]
+
+Permanent configuration options:
+    --topdown            Shows repository's packages first and then aur's
+    --bottomup           Shows aur's packages first and then repository's
+    --devel              Check -git/-svn/-hg development version
+    --nodevel            Disable development version checking
+    --afterclean         Clean package sources after successful build
+    --noafterclean       Disable package sources cleaning after successful build
+    --timeupdate         Check package's modification date and version
+    --notimeupdate       Check only package version change
+
+Yay specific options:
+    --printconfig        Prints current yay configuration
+    --stats              Displays system information
+    --cleandeps          Remove unneeded dependencies
+    --gendb              Generates development package DB used for updating.
+
+If no operation is provided -Y will be assumed
+`)
 }
 
-func init() {
+func initYay() (err error) {
 	var configHome string // configHome handles config directory home
 	var cacheHome string  // cacheHome handles cache home
-	var err error
 
 	if 0 == os.Geteuid() {
 		fmt.Println("Please avoid running yay as root/sudo.")
@@ -88,15 +95,15 @@ func init() {
 	if _, err = os.Stat(configFile); os.IsNotExist(err) {
 		err = os.MkdirAll(filepath.Dir(configFile), 0755)
 		if err != nil {
-			fmt.Println("Unable to create config directory:", filepath.Dir(configFile), err)
-			os.Exit(2)
+			err = fmt.Errorf("Unable to create config directory:", filepath.Dir(configFile), err)
+			return
 		}
 		// Save the default config if nothing is found
 		config.saveConfig()
 	} else {
 		cfile, errf := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE, 0644)
 		if errf != nil {
-			fmt.Println("Error reading config:", err)
+			fmt.Println("Error reading config: %s", err)
 		} else {
 			defer cfile.Close()
 			decoder := json.NewDecoder(cfile)
@@ -120,194 +127,343 @@ func init() {
 		_ = decoder.Decode(&savedInfo)
 	}
 
+	return
+}
+
+func initAlpm() (err error) {
 	/////////////////
 	// alpm config //
 	/////////////////
+
+	var value string
+	var exists bool
+	//var double bool
+
+	value, _, exists = cmdArgs.getArg("config")
+	if exists {
+		config.PacmanConf = value
+	}
+
 	alpmConf, err = readAlpmConfig(config.PacmanConf)
 	if err != nil {
-		fmt.Println("Unable to read Pacman conf", err)
-		os.Exit(1)
+		err = fmt.Errorf("Unable to read Pacman conf: %s", err)
+		return
 	}
 
-	alpmHandle, err = alpmConf.CreateHandle()
-	if err != nil {
-		fmt.Println("Unable to CreateHandle", err)
-		os.Exit(1)
+	value, _, exists = cmdArgs.getArg("dbpath", "b")
+	if exists {
+		alpmConf.DBPath = value
 	}
-}
 
-func parser() (op string, options []string, packages []string, changedConfig bool, err error) {
-	if len(os.Args) < 2 {
-		err = fmt.Errorf("no operation specified")
-		return
+	value, _, exists = cmdArgs.getArg("root", "r")
+	if exists {
+		alpmConf.RootDir = value
 	}
-	changedConfig = false
-	op = "yogurt"
 
-	for _, arg := range os.Args[1:] {
-		if len(arg) < 2 {
-			continue
-		}
-		if arg[0] == '-' && arg[1] != '-' {
-			switch arg {
-			case "-V":
-				arg = "--version"
-			case "-h":
-				arg = "--help"
-			default:
-				op = arg
-				continue
-			}
-		}
+	value, _, exists = cmdArgs.getArg("arch")
+	if exists {
+		alpmConf.Architecture = value
+	}
 
-		if strings.HasPrefix(arg, "--") {
-			changedConfig = true
-			switch arg {
-			case "--afterclean":
-				config.CleanAfter = true
-			case "--noafterclean":
-				config.CleanAfter = false
-			case "--printconfig":
-				fmt.Printf("%#v", config)
-				os.Exit(0)
-			case "--gendb":
-				err = createDevelDB()
-				if err != nil {
-					fmt.Println(err)
-				}
-				err = saveVCSInfo()
-				if err != nil {
-					fmt.Println(err)
-				}
-				os.Exit(0)
-			case "--devel":
-				config.Devel = true
-			case "--nodevel":
-				config.Devel = false
-			case "--timeupdate":
-				config.TimeUpdate = true
-			case "--notimeupdate":
-				config.TimeUpdate = false
-			case "--topdown":
-				config.SortMode = TopDown
-			case "--bottomup":
-				config.SortMode = BottomUp
-			case "--complete":
-				config.Shell = "sh"
-				_ = complete()
-				os.Exit(0)
-			case "--fcomplete":
-				config.Shell = fishShell
-				_ = complete()
-				os.Exit(0)
-			case "--help":
-				usage()
-				os.Exit(0)
-			case "--version":
-				fmt.Printf("yay v%s\n", version)
-				os.Exit(0)
-			case "--noconfirm":
-				config.NoConfirm = true
-				fallthrough
-			default:
-				options = append(options, arg)
-			}
-			continue
-		}
-		packages = append(packages, arg)
+	//TODO
+	//current system does not allow duplicate arguments
+	//but pacman allows multiple cachdirs to be passed
+	//for now only hanle one cache dir
+	value, _, exists = cmdArgs.getArg("cachdir")
+	if exists {
+		alpmConf.CacheDir = []string{value}
+	}
+
+	value, _, exists = cmdArgs.getArg("gpgdir")
+	if exists {
+		alpmConf.GPGDir = value
+	}
+
+	alpmHandle, err = alpmConf.CreateHandle()
+	if err != nil {
+		err = fmt.Errorf("Unable to CreateHandle", err)
+		return
 	}
+
 	return
 }
 
 func main() {
-	op, options, pkgs, changedConfig, err := parser()
+	var status int
+	var err error
+	var changedConfig bool
+
+	err = cmdArgs.parseCommandLine()
 	if err != nil {
 		fmt.Println(err)
-		os.Exit(1)
+		status = 1
+		goto cleanup
 	}
 
-	switch op {
-	case "-Cd":
-		err = cleanDependencies(pkgs)
-	case "-G":
-		for _, pkg := range pkgs {
-			err = getPkgbuild(pkg)
-			if err != nil {
-				fmt.Println(pkg+":", err)
-			}
-		}
-	case "-Qstats":
-		err = localStatistics()
-	case "-Ss", "-Ssq", "-Sqs":
-		if op == "-Ss" {
-			config.SearchMode = Detailed
-		} else {
-			config.SearchMode = Minimal
+	err = initYay()
+	if err != nil {
+		fmt.Println(err)
+		status = 1
+		goto cleanup
+	}
+
+	err = initAlpm()
+	if err != nil {
+		fmt.Println(err)
+		status = 1
+		goto cleanup
+	}
+
+	changedConfig, err = handleCmd()
+	if err != nil {
+		fmt.Println(err)
+		status = 1
+		goto cleanup
+	}
+
+	//ive used a goto here
+	//i think its the best way to do this sort of thing
+cleanup:
+	//cleanup
+	//from here on out dont exit if an error occurs
+	//if we fail to save the configuration
+	//atleast continue on and try clean up other parts
+
+	if updated {
+		err = saveVCSInfo()
+
+		if err != nil {
+			fmt.Println(err)
+			status = 1
 		}
+	}
 
-		if pkgs != nil {
-			err = syncSearch(pkgs)
+	if changedConfig {
+		err = config.saveConfig()
+
+		if err != nil {
+			fmt.Println(err)
+			status = 1
 		}
-	case "-S":
-		err = install(pkgs, options)
-	case "-Sy":
-		err = passToPacman("-Sy", nil, nil)
+	}
+
+	if alpmHandle != nil {
+		err = alpmHandle.Release()
 		if err != nil {
-			break
+			fmt.Println(err)
+			status = 1
 		}
-		err = install(pkgs, options)
-	case "-Syu", "-Suy", "-Su":
-		if strings.Contains(op, "y") {
-			err = passToPacman("-Sy", nil, nil)
-			if err != nil {
-				break
-			}
+	}
+
+	os.Exit(status)
+}
+
+func handleCmd() (changedConfig bool, err error) {
+	changedConfig = false
+
+	for option, _ := range cmdArgs.options {
+		changedConfig = changedConfig || handleConfig(option)
+	}
+
+	for option, _ := range cmdArgs.globals {
+		changedConfig = changedConfig || handleConfig(option)
+	}
+
+	switch cmdArgs.op {
+	case "V", "version":
+		handleVersion()
+	case "D", "database":
+		passToPacman(cmdArgs)
+	case "F", "files":
+		passToPacman(cmdArgs)
+	case "Q", "query":
+		passToPacman(cmdArgs)
+	case "R", "remove":
+		handleRemove()
+	case "S", "sync":
+		err = handleSync()
+	case "T", "deptest":
+		passToPacman(cmdArgs)
+	case "U", "upgrade":
+		passToPacman(cmdArgs)
+	case "G", "getpkgbuild":
+		err = handleGetpkgbuild()
+	case "Y", "--yay":
+		err = handleYay()
+	default:
+		//this means we allowed an op but not implement it
+		//if this happens it an error in the code and not the usage
+		err = fmt.Errorf("unhandled operation")
+	}
+
+	return
+}
+
+//this function should only set config options
+//but currently still uses the switch left over from old code
+//eventuall this should be refactored out futher
+//my current plan is to have yay specific operations in its own operator
+//e.g. yay -Y --gendb
+//e.g yay -Yg
+func handleConfig(option string) (changedConfig bool) {
+	switch option {
+	case "afterclean":
+		config.CleanAfter = true
+	case "noafterclean":
+		config.CleanAfter = false
+		//		case "printconfig":
+		//			fmt.Printf("%#v", config)
+		//			os.Exit(0)
+		//		case "gendb":
+		//			err = createDevelDB()
+		//			if err != nil {
+		//				fmt.Println(err)
+		//			}
+		//			err = saveVCSInfo()
+		//			if err != nil {
+		//				fmt.Println(err)
+		//			}
+		//			os.Exit(0)
+	case "devel":
+		config.Devel = true
+	case "nodevel":
+		config.Devel = false
+	case "timeupdate":
+		config.TimeUpdate = true
+	case "notimeupdate":
+		config.TimeUpdate = false
+	case "topdown":
+		config.SortMode = TopDown
+	case "--bottomup":
+		config.SortMode = BottomUp
+		//		case "complete":
+		//			config.Shell = "sh"
+		//			complete()
+		//			os.Exit(0)
+		//		case "fcomplete":
+		//			config.Shell = fishShell
+		//			complete()
+		//			os.Exit(0)
+		//		case "help":
+		//			usage()
+		//			os.Exit(0)
+		//		case "version":
+		//			fmt.Printf("yay v%s\n", version)
+		//			os.Exit(0)
+	case "noconfirm":
+		config.NoConfirm = true
+	default:
+		return
+	}
+
+	changedConfig = true
+	return
+}
+
+func handleVersion() {
+	fmt.Printf("yay v%s\n", version)
+}
+
+func handleYay() (err error) {
+	//_, options, targets := cmdArgs.formatArgs()
+	if cmdArgs.existsArg("h", "help") {
+		usage()
+	} else if cmdArgs.existsArg("printconfig") {
+		fmt.Printf("%#v", config)
+	} else if cmdArgs.existsArg("gendb") {
+		err = createDevelDB()
+		if err != nil {
+			return
 		}
-		err = upgradePkgs(options)
-	case "-Si":
-		err = syncInfo(pkgs, options)
-	case "yogurt":
-		config.SearchMode = NumberMenu
-
-		if pkgs != nil {
-			err = numberMenu(pkgs, options)
+		err = saveVCSInfo()
+		if err != nil {
+			return
 		}
-	default:
-		if op[0] == 'R' {
-			removeVCSPackage(pkgs)
+	} else if cmdArgs.existsArg("complete") {
+		config.Shell = "sh"
+		complete()
+	} else if cmdArgs.existsArg("fcomplete") {
+		config.Shell = "fish"
+		complete()
+	} else if cmdArgs.existsArg("stats") {
+		err = localStatistics()
+	} else if cmdArgs.existsArg("cleandeps") {
+		err = cleanDependencies()
+	} else if len(cmdArgs.targets) > 0 {
+		err = handleYogurt()
+	}
+
+	return
+}
+
+func handleGetpkgbuild() (err error) {
+	for pkg := range cmdArgs.targets {
+		err = getPkgbuild(pkg)
+		if err != nil {
+			//we print the error instead of returning it
+			//seems as we can handle multiple errors without stoping
+			//theres no easy way arround this right now
+			fmt.Println(pkg+":", err)
 		}
-		err = passToPacman(op, pkgs, options)
 	}
 
-	var erra error
-	if updated {
-		erra = saveVCSInfo()
-		if erra != nil {
-			fmt.Println(err)
+	return
+}
+
+func handleYogurt() (err error) {
+	options := cmdArgs.formatArgs()
+	targets := cmdArgs.formatTargets()
+
+	config.SearchMode = NumberMenu
+	err = numberMenu(targets, options)
+
+	return
+}
+
+func handleSync() (err error) {
+	targets := cmdArgs.formatTargets()
+	options := cmdArgs.formatArgs()
+
+	if cmdArgs.existsArg("y", "refresh") {
+		arguments := cmdArgs.copy()
+		arguments.delArg("u", "sysupgrade")
+		arguments.targets = make(stringSet)
+		err = passToPacman(arguments)
+		if err != nil {
+			return
 		}
 	}
 
-	if changedConfig {
-		erra = config.saveConfig()
-		if erra != nil {
-			fmt.Println(err)
+	if cmdArgs.existsArg("s", "search") {
+		if cmdArgs.existsArg("q", "quiet") {
+			config.SearchMode = Minimal
+		} else {
+			config.SearchMode = Detailed
 		}
 
+		err = syncSearch(targets)
+	} else if cmdArgs.existsArg("c", "clean") {
+		err = passToPacman(cmdArgs)
+	} else if cmdArgs.existsArg("u", "sysupgrade") {
+		err = upgradePkgs(make([]string, 0))
+	} else if cmdArgs.existsArg("i", "info") {
+		err = syncInfo(targets, options)
+	} else if len(cmdArgs.targets) > 0 {
+		err = install(cmdArgs)
 	}
 
-	erra = alpmHandle.Release()
-	if erra != nil {
-		fmt.Println(err)
-	}
+	return
+}
 
-	if err != nil {
-		fmt.Println(err)
-		os.Exit(1)
-	}
+func handleRemove() (err error) {
+	removeVCSPackage(cmdArgs.formatTargets())
+	err = passToPacman(cmdArgs)
+	return
 }
 
 // NumberMenu presents a CLI for selecting packages to install.
 func numberMenu(pkgS []string, flags []string) (err error) {
+	//func numberMenu(cmdArgs *arguments) (err error) {
 	var num int
 
 	aq, err := narrowSearch(pkgS, true)
@@ -315,6 +471,7 @@ func numberMenu(pkgS []string, flags []string) (err error) {
 		fmt.Println("Error during AUR search:", err)
 	}
 	numaq := len(aq)
+
 	pq, numpq, err := queryRepo(pkgS)
 	if err != nil {
 		return
@@ -369,11 +526,14 @@ func numberMenu(pkgS []string, flags []string) (err error) {
 	}
 
 	if len(repoI) != 0 {
-		err = passToPacman("-S", repoI, flags)
+		arguments := makeArguments()
+		arguments.addArg("S")
+		arguments.addTarget(repoI...)
+		err = passToPacman(arguments)
 	}
 
 	if len(aurI) != 0 {
-		err = aurInstall(aurI, flags)
+		err = aurInstall(aurI, nil)
 	}
 
 	return err
@@ -408,3 +568,40 @@ func complete() error {
 	_, err = io.Copy(os.Stdout, in)
 	return err
 }
+
+// passToPacman outsorces execution to pacman binary without modifications.
+func passToPacman(args *arguments) error {
+	var cmd *exec.Cmd
+	argArr := make([]string, 0)
+
+	if args.needRoot() {
+		argArr = append(argArr, "sudo")
+	}
+
+	argArr = append(argArr, "pacman")
+	argArr = append(argArr, cmdArgs.formatGlobals()...)
+	argArr = append(argArr, args.formatArgs()...)
+	argArr = append(argArr, args.formatTargets()...)
+
+	cmd = exec.Command(argArr[0], argArr[1:]...)
+
+	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
+	err := cmd.Run()
+	return err
+}
+
+// passToMakepkg outsorces execution to makepkg binary without modifications.
+func passToMakepkg(dir string, args ...string) (err error) {
+	cmd := exec.Command(config.MakepkgBin, args...)
+	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
+	cmd.Dir = dir
+	err = cmd.Run()
+	if err == nil {
+		_ = saveVCSInfo()
+		if config.CleanAfter {
+			fmt.Println("\x1b[1;32m==> CleanAfter enabled. Deleting source folder.\x1b[0m")
+			os.RemoveAll(dir)
+		}
+	}
+	return
+}

+ 1 - 28
config.go

@@ -6,7 +6,6 @@ import (
 	"os"
 	"os/exec"
 	"os/user"
-	"strings"
 
 	alpm "github.com/jguer/go-alpm"
 )
@@ -195,30 +194,4 @@ func continueTask(s string, def string) (cont bool) {
 	}
 
 	return true
-}
-
-// PassToPacman outsorces execution to pacman binary without modifications.
-func passToPacman(op string, pkgs []string, flags []string) error {
-	var cmd *exec.Cmd
-	var args []string
-
-	args = append(args, op)
-	if len(pkgs) != 0 {
-		args = append(args, pkgs...)
-	}
-
-	if len(flags) != 0 {
-		args = append(args, flags...)
-	}
-
-	if strings.Contains(op, "-Q") || op == "Si" {
-		cmd = exec.Command(config.PacmanBin, args...)
-	} else {
-		args = append([]string{config.PacmanBin}, args...)
-		cmd = exec.Command("sudo", args...)
-	}
-
-	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
-	err := cmd.Run()
-	return err
-}
+}

+ 20 - 20
install.go

@@ -10,18 +10,24 @@ import (
 )
 
 // Install handles package installs
-func install(pkgs []string, flags []string) error {
-	aurs, repos, _ := packageSlices(pkgs)
+func install(parser *arguments) error {
+	aurs, repos, _ := packageSlices(parser.targets.toSlice())
+
+	arguments := parser.copy()
+	arguments.delArg("u", "sysupgrade")
+	arguments.delArg("y", "refresh")
+	arguments.targets = make(stringSet)
+	arguments.addTarget(repos...)
 
 	if len(repos) != 0 {
-		err := passToPacman("-S", repos, flags)
+		err := passToPacman(arguments)
 		if err != nil {
 			fmt.Println("Error installing repo packages.")
 		}
 	}
 
 	if len(aurs) != 0 {
-		err := aurInstall(aurs, flags)
+		err := aurInstall(aurs, []string{"-S"})
 		if err != nil {
 			fmt.Println("Error installing aur packages.")
 		}
@@ -130,15 +136,20 @@ func PkgInstall(a *rpc.Pkg, flags []string) (finalmdeps []string, err error) {
 		}
 	}
 
+	arguments := makeArguments()
+	arguments.addArg("S", "asdeps", "noconfirm")
+	arguments.addTarget(repoDeps...)
+
 	var depArgs []string
 	if config.NoConfirm {
-		depArgs = []string{"--asdeps", "--noconfirm"}
+		depArgs = []string{"asdeps", "noconfirm"}
 	} else {
-		depArgs = []string{"--asdeps"}
+		depArgs = []string{"asdeps"}
 	}
+	
 	// Repo dependencies
 	if len(repoDeps) != 0 {
-		errR := passToPacman("-S", repoDeps, depArgs)
+		errR := passToPacman(arguments)
 		if errR != nil {
 			return finalmdeps, errR
 		}
@@ -156,18 +167,7 @@ func PkgInstall(a *rpc.Pkg, flags []string) (finalmdeps []string, err error) {
 		}
 	}
 
-	args := []string{"-sri"}
-	args = append(args, flags...)
-	makepkgcmd := exec.Command(config.MakepkgBin, args...)
-	makepkgcmd.Stdin, makepkgcmd.Stdout, makepkgcmd.Stderr = os.Stdin, os.Stdout, os.Stderr
-	makepkgcmd.Dir = dir
-	err = makepkgcmd.Run()
-	if err == nil {
-		_ = saveVCSInfo()
-		if config.CleanAfter {
-			fmt.Println("\x1b[1;32m==> CleanAfter enabled. Deleting source folder.\x1b[0m")
-			os.RemoveAll(dir)
-		}
-	}
+	flags = append(flags, "-sri")
+	err = passToMakepkg(dir, flags...)
 	return
 }

+ 542 - 0
parser.go

@@ -0,0 +1,542 @@
+package main
+
+import (
+	"os"
+	"fmt"
+	"strings"
+	"io"
+)
+
+type stringSet  map[string]struct{}
+
+func (set stringSet) getAny() string {
+	for v := range set {
+		return v
+	}
+    
+	//maybe should return error instrad
+	return ""
+}
+
+func (set stringSet) toSlice() []string {
+	slice := make([]string, 0, len(set))
+	
+	for v := range set {
+		slice = append(slice, v)
+	}
+	
+	return slice
+}
+
+func (set stringSet) removeAny() string {
+	v := set.getAny()
+	delete(set, v)
+	return v
+}
+
+
+type arguments struct {
+	op string
+	options map[string]string
+	globals map[string]string
+	doubles stringSet //tracks args passed twice such as -yy and -dd
+	targets stringSet
+}
+
+func makeArguments() *arguments {
+	return &arguments {
+		"",
+		make(map[string]string),
+		make(map[string]string),
+		make(stringSet),
+		make(stringSet),
+	}
+}
+
+func (parser *arguments) copy() (cp *arguments) {
+	cp = makeArguments()
+	
+	cp.op = parser.op
+	
+	for k,v := range parser.options {
+		cp.options[k] = v
+	}
+	
+	for k,v := range parser.globals {
+		cp.globals[k] = v
+	}
+	
+	for k,v := range parser.targets {
+		cp.targets[k] = v
+	}
+	
+	for k,v := range parser.doubles {
+		cp.doubles[k] = v
+	}
+	
+	return
+}
+		
+
+func (parser *arguments) delArg(options ...string) {
+	for _, option := range options {
+		delete(parser.options, option)
+		delete(parser.globals, option)
+		delete(parser.doubles, option)
+	}
+}
+
+func (parser *arguments) needRoot() bool {
+	if parser.existsArg("h", "help") {
+		return false
+	}
+	
+	if parser.existsArg("p", "print") {
+			return false
+	}
+	
+	switch parser.op {
+	case "V", "version":
+		return false
+	case "D", "database":
+		return true
+	case "F", "files":
+		if parser.existsArg("y", "refresh") {
+			return true
+		}
+		return false
+	case "Q", "query":
+		return false
+	case "R", "remove":
+		return true
+	case "S", "sync":
+		if parser.existsArg("y", "refresh") {
+			return true
+		}
+		if parser.existsArg("u", "sysupgrade") {
+			return true
+		}
+		if parser.existsArg("s", "search") {
+			return false
+		}
+		if parser.existsArg("l", "list") {
+			return false
+		}
+		if parser.existsArg("i", "info") {
+			return false
+		}
+		return true
+	case "T", "deptest":
+		return false
+	case "U", "upgrade":
+		return true
+        
+	//yay specific
+	case "Y", "yay":
+		return false
+	case "G", "getpkgbuild":
+		return false
+	default:
+		return false
+	}
+}
+
+func (parser *arguments) addOP(op string) (err error) {
+	if parser.op != "" {
+		err = fmt.Errorf("only one operation may be used at a time")
+		return
+	}
+	
+	parser.op = op
+	return
+}
+
+func (parser *arguments) addParam(option string, arg string) (err error) {
+	if isOp(option) {
+		err = parser.addOP(option)
+		return
+	}
+	
+	if parser.existsArg(option) {
+		parser.doubles[option] = struct{}{}
+	} else if isGlobal(option) {
+		parser.globals[option] = arg
+	} else {
+		parser.options[option] = arg
+	}
+	
+	return
+}
+
+func (parser *arguments) addArg(options ...string) (err error) {
+	for _, option := range options {
+		err = parser.addParam(option, "")
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+//multiple args acts as an OR operator
+func (parser *arguments) existsArg(options ...string) bool {
+	for _, option := range options {
+		_, exists := parser.options[option]
+		if exists {
+			return true
+		}
+		
+		_, exists = parser.globals[option]
+		if exists {
+			return true
+		}
+	}
+	return false
+}
+
+func (parser *arguments) getArg(options ...string) (arg string, double bool, exists bool) {
+	for _, option := range options {
+		arg, exists = parser.options[option]
+
+		if exists {
+			_, double = parser.doubles[option]
+			return
+		}
+
+		arg, exists = parser.globals[option]
+		
+		if exists {
+			_, double = parser.doubles[option]
+			return
+		}
+	}
+	
+	return
+}
+
+func (parser *arguments) addTarget(targets ...string) {
+	for _, target := range targets {
+		parser.targets[target] = struct{}{}
+	}
+}
+
+func (parser *arguments) delTarget(targets ...string) {
+	for _, target := range targets {
+		delete(parser.targets, target)
+	}
+}
+
+//multiple args acts as an OR operator
+func (parser *arguments) existsDouble(options ...string) bool {
+	for _, option := range options {
+		_, exists := parser.doubles[option]
+		if exists {
+			return true
+		}
+	}
+	
+	return false
+}
+
+func (parser *arguments) formatTargets() (args []string) {
+	for target := range parser.targets {
+		args = append(args, target)
+	}
+	
+	return
+}
+
+func (parser *arguments) formatArgs() (args []string) {
+	op := formatArg(parser.op)
+	args = append(args, op)
+	
+	for option, arg := range parser.options {
+		formatedOption := formatArg(option)
+		args = append(args, formatedOption)
+		
+		if hasParam(option) {
+			args = append(args, arg)
+		}
+		
+		if parser.existsDouble(option) {
+			args = append(args, formatedOption)
+		}
+	}
+	
+	return
+}
+
+func (parser *arguments) formatGlobals() (args []string) {
+	for option, arg := range parser.globals {
+		formatedOption := formatArg(option)
+		args = append(args, formatedOption)
+		
+		if hasParam(option) {
+			args = append(args, arg)
+		}
+		
+		if parser.existsDouble(option) {
+			args = append(args, formatedOption)
+		}
+	}
+	
+	return
+	
+}
+
+func formatArg(arg string) string {
+	if len(arg) > 1 {
+		arg = "--" + arg
+	} else {
+		arg = "-" + arg
+	}
+	
+	return arg
+}
+
+func isOp(op string) bool {
+	switch op {
+	case "V", "version":
+		return true
+	case "D", "database":
+		return true
+	case "F", "files":
+		return true
+	case "Q", "query":
+		return true
+	case "R", "remove":
+		return true
+	case "S", "sync":
+		return true
+	case "T", "deptest":
+		return true
+	case "U", "upgrade":
+		return true
+        
+    //yay specific
+	case "Y", "yay":
+		return true
+	case "G", "getpkgbuild":
+		return true
+	default:
+		return false
+	}
+}
+
+func isGlobal(op string) bool {
+	switch op {
+	case "b", "dbpath":
+		return true
+	case "r", "root":
+		return true
+	case "v", "verbose":
+		return true
+	case "arch":
+		return true
+	case "cachedir":
+		return true
+	case "color":
+		return true
+	case "config":
+		return true
+	case "debug":
+		return true
+	case "gpgdir":
+		return true
+	case "hookdir":
+		return true
+	case "logfile":
+		return true
+	case "noconfirm":
+		return true
+	case "confirm":
+		return true
+	default:
+		return false
+	}
+}
+
+func isYayParam(arg string) bool {
+	switch arg {
+	case "afterclean":
+		return true
+	case "noafterclean":
+		return true
+	case "devel":
+		return true
+	case "nodevel":
+		return true
+	case "timeupdate":
+		return true
+	case "notimeupdate":
+		return true
+	case "topdown":
+		return true
+	default:
+		return false
+	}
+}
+
+func hasParam(arg string) bool {
+	switch arg {
+	case "dbpath", "b":
+		return true
+	case "root", "r":
+		return true
+	case "sysroot":
+		return true
+	case "config":
+		return true
+	case "ignore":
+		return true
+	case "assume-installed":
+		return true
+	case "overwrite":
+		return true
+	case "ask":
+		return true
+	case "cachedir":
+		return true
+	case "hookdir":
+		return true
+	case "logfile":
+		return true
+	case "ignoregroup":
+		return true
+	case "arch":
+		return true
+	case "print-format":
+		return true
+	case "gpgdir":
+		return true
+	case "color":
+		return true
+	default:
+		return false
+	}
+}
+
+//parses short hand options such as:
+//-Syu -b/some/path -
+func (parser *arguments) parseShortOption(arg string, param string) (usedNext bool, err error) {
+	if arg == "-" {
+		err = parser.addArg("-")
+		return
+	}
+	
+	arg = arg[1:]
+	
+	for k, _char := range arg {
+		char := string(_char)
+		
+		if hasParam(char) {
+			if k < len(arg) - 2 {
+				err = parser.addParam(char, arg[k+2:])
+			} else {
+				usedNext = true
+				err = parser.addParam(char, param)
+			}
+			
+			break
+		} else {
+			err = parser.addArg(char)
+			
+			if err != nil {
+				return
+			}
+		}
+	}
+
+	return
+}
+
+//parses full length options such as:
+//--sync --refresh --sysupgrade --dbpath /some/path --
+func (parser *arguments) parseLongOption(arg string, param string) (usedNext bool, err error){
+	if arg == "--" {
+		err = parser.addArg(arg)
+		return
+	}
+	
+	arg = arg[2:]
+	
+	if hasParam(arg) {
+		err = parser.addParam(arg, param)
+		usedNext = true
+	} else {
+		err = parser.addArg(arg)
+	}
+	
+	return
+}
+
+func (parser *arguments) parseStdin() (err error) {
+	for true {
+		var target string
+		_, err = fmt.Scan(&target)
+		
+		if err != nil {
+			if err == io.EOF {
+				err = nil
+			}
+			
+			return
+		}
+		
+		parser.addTarget(target)
+	}
+	
+	return
+}
+
+func (parser *arguments)parseCommandLine() (err error) {
+	args := os.Args[1:]
+	usedNext := false
+	
+	if len(args) < 1 {
+		err = fmt.Errorf("no operation specified (use -h for help)")
+		return
+	}
+
+	for k, arg := range args {
+		var nextArg string
+		
+		if usedNext {
+			usedNext = false
+			continue
+		}
+		
+		if k + 1 < len(args) {
+			nextArg = args[k + 1]
+		}
+		
+		if parser.existsArg("--") {
+			parser.addTarget(arg)
+		} else if strings.HasPrefix(arg, "--") {
+			usedNext, err = parser.parseLongOption(arg, nextArg)
+		} else if strings.HasPrefix(arg, "-") {
+			usedNext, err = parser.parseShortOption(arg, nextArg)
+		} else {
+			parser.addTarget(arg)
+		}
+		
+		if err != nil {
+			return
+		}
+	}
+	
+	if parser.op == "" {
+		parser.op = "Y"
+	}
+	
+	if cmdArgs.existsArg("-") {
+		err = cmdArgs.parseStdin();
+
+		if err != nil {
+			return
+		}
+	}
+	
+	return
+}

+ 5 - 1
query.go

@@ -169,7 +169,11 @@ func syncInfo(pkgS []string, flags []string) (err error) {
 	}
 
 	if len(repoS) != 0 {
-		err = passToPacman("-Si", repoS, flags)
+		arguments := makeArguments()
+		arguments.addArg("S", "i")
+		//arguments.addArg(flags...)
+		arguments.addTarget(repoS...)
+		err = passToPacman(arguments)
 	}
 
 	return

+ 7 - 2
upgrade.go

@@ -329,8 +329,13 @@ func upgradePkgs(flags []string) error {
 			}
 			repoNames = append(repoNames, k.Name)
 		}
-
-		err := passToPacman("-S", repoNames, append(flags, "--noconfirm"))
+		
+		arguments := makeArguments()
+		arguments.addArg("S", "noconfirm")
+		arguments.addArg(flags...)
+		arguments.addTarget(repoNames...)
+		
+		err := passToPacman(arguments)
 		if err != nil {
 			fmt.Println("Error upgrading repo packages.")
 		}