J Guerreiro 7 年之前
父节点
当前提交
b7eb2f963f
共有 17 个文件被更改,包括 1311 次插入1186 次删除
  1. 7 0
      README.md
  2. 11 301
      actions.go
  3. 151 22
      aur/aur.go
  4. 0 87
      aur/aur_test.go
  5. 4 124
      aur/query.go
  6. 75 73
      aur/result.go
  7. 0 20
      aur/utils.go
  8. 192 0
      aur/vcs/github.go
  9. 32 0
      aur/vcs/github_test.go
  10. 0 139
      cmd/yay/yay.go
  11. 283 0
      config/config.go
  12. 96 259
      pacman/pacman.go
  13. 14 11
      pacman/pacman_test.go
  14. 136 0
      query.go
  15. 0 150
      util/util.go
  16. 73 0
      utils.go
  17. 237 0
      yay.go

+ 7 - 0
README.md

@@ -36,6 +36,13 @@ Yay was created with a few objectives in mind and based on the design of yaourt
 
 ### Changelog
 
+#### 2.
+- Fetching backend changed to Mikkel Oscar's [Aur](https://github.com/mikkeloscar/aur)
+- Added support for development packages from github.
+- Pacman backend rewritten and simplified
+- Added config framework.
+
+
 #### 1.115
 - Added AUR completions (updates on first completion every 48h)
 

+ 11 - 301
actions.go

@@ -1,145 +1,31 @@
-package yay
+package main
 
 import (
-	"bufio"
 	"fmt"
-	"io"
-	"math"
 	"os"
-	"os/exec"
-	"strconv"
-	"strings"
-	"time"
 
 	aur "github.com/jguer/yay/aur"
+	"github.com/jguer/yay/config"
 	pac "github.com/jguer/yay/pacman"
-	"github.com/jguer/yay/util"
 )
 
-// NumberMenu presents a CLI for selecting packages to install.
-func NumberMenu(pkgS []string, flags []string) (err error) {
-	var num int
-
-	aq, numaq, err := aur.Search(pkgS, true)
-	if err != nil {
-		fmt.Println("Error during AUR search:", err)
-	}
-	pq, numpq, err := pac.Search(pkgS)
-	if err != nil {
-		return
-	}
-
-	if numpq == 0 && numaq == 0 {
-		return fmt.Errorf("no packages match search")
-	}
-
-	if util.SortMode == util.BottomUp {
-		aq.PrintSearch(numpq)
-		pq.PrintSearch()
-	} else {
-		pq.PrintSearch()
-		aq.PrintSearch(numpq)
-	}
-
-	fmt.Printf("\x1b[32m%s\x1b[0m\nNumbers:", "Type numbers to install. Separate each number with a space.")
-	reader := bufio.NewReader(os.Stdin)
-	numberBuf, overflow, err := reader.ReadLine()
-	if err != nil || overflow {
-		fmt.Println(err)
-		return
-	}
-
-	numberString := string(numberBuf)
-	var aurInstall []string
-	var repoInstall []string
-	result := strings.Fields(numberString)
-	for _, numS := range result {
-		num, err = strconv.Atoi(numS)
-		if err != nil {
-			continue
-		}
-
-		// Install package
-		if num > numaq+numpq-1 || num < 0 {
-			continue
-		} else if num > numpq-1 {
-			if util.SortMode == util.BottomUp {
-				aurInstall = append(aurInstall, aq[numaq+numpq-num-1].Name)
-			} else {
-				aurInstall = append(aurInstall, aq[num-numpq].Name)
-			}
-		} else {
-			if util.SortMode == util.BottomUp {
-				repoInstall = append(repoInstall, pq[numpq-num-1].Name)
-			} else {
-				repoInstall = append(repoInstall, pq[num].Name)
-			}
-		}
-	}
-
-	if len(repoInstall) != 0 {
-		pac.Install(repoInstall, flags)
-	}
-
-	if len(aurInstall) != 0 {
-		q, n, err := aur.MultiInfo(aurInstall)
-		if err != nil {
-			return err
-		} else if n != len(aurInstall) {
-			q.MissingPackage(aurInstall)
-		}
-
-		var finalrm []string
-		for _, aurpkg := range q {
-			finalmdeps, err := aurpkg.Install(flags)
-			finalrm = append(finalrm, finalmdeps...)
-			if err != nil {
-				// Do not abandon program, we might still be able to install the rest
-				fmt.Println(err)
-			}
-		}
-
-		if len(finalrm) != 0 {
-			aur.RemoveMakeDeps(finalrm)
-		}
-	}
-
-	return nil
-}
-
 // Install handles package installs
-func Install(pkgs []string, flags []string) error {
+func install(pkgs []string, flags []string) error {
 	aurs, repos, _ := pac.PackageSlices(pkgs)
 
-	err := pac.Install(repos, flags)
+	err := config.PassToPacman("-S", repos, flags)
 	if err != nil {
 		fmt.Println("Error installing repo packages.")
 	}
 
-	q, n, err := aur.MultiInfo(aurs)
-	if len(aurs) != n || err != nil {
-		fmt.Println("Unable to get info on some packages")
-	}
-
-	var finalrm []string
-	for _, aurpkg := range q {
-		finalmdeps, err := aurpkg.Install(flags)
-		finalrm = append(finalrm, finalmdeps...)
-		if err != nil {
-			fmt.Println("Error installing", aurpkg.Name, ":", err)
-		}
-	}
-
-	if len(finalrm) != 0 {
-		aur.RemoveMakeDeps(finalrm)
-	}
+	err = aur.Install(aurs, flags)
 
-	return nil
+	return err
 }
 
 // Upgrade handles updating the cache and installing updates.
-func Upgrade(flags []string) error {
-	errp := pac.UpdatePackages(flags)
+func upgrade(flags []string) error {
+	errp := config.PassToPacman("-Syu", nil, flags)
 	erra := aur.Upgrade(flags)
 
 	if errp != nil {
@@ -149,160 +35,15 @@ func Upgrade(flags []string) error {
 	return erra
 }
 
-// SyncSearch presents a query to the local repos and to the AUR.
-func SyncSearch(pkgS []string) (err error) {
-	aq, _, err := aur.Search(pkgS, true)
-	if err != nil {
-		return err
-	}
-	pq, _, err := pac.Search(pkgS)
-	if err != nil {
-		return err
-	}
-
-	if util.SortMode == util.BottomUp {
-		aq.PrintSearch(0)
-		pq.PrintSearch()
-	} else {
-		pq.PrintSearch()
-		aq.PrintSearch(0)
-	}
-
-	return nil
-}
-
-// SyncInfo serves as a pacman -Si for repo packages and AUR packages.
-func SyncInfo(pkgS []string, flags []string) (err error) {
-	aurS, repoS, err := pac.PackageSlices(pkgS)
-	if err != nil {
-		return
-	}
-
-	q, _, err := aur.MultiInfo(aurS)
-	if err != nil {
-		fmt.Println(err)
-	}
-
-	for _, aurP := range q {
-		aurP.PrintInfo()
-	}
-
-	if len(repoS) != 0 {
-		err = PassToPacman("-Si", repoS, flags)
-	}
-
-	return
-}
-
-// LocalStatistics returns installed packages statistics.
-func LocalStatistics(version string) error {
-	info, err := pac.Statistics()
-	if err != nil {
-		return err
-	}
-
-	foreignS, foreign, _ := pac.ForeignPackages()
-
-	fmt.Printf("\n Yay version r%s\n", version)
-	fmt.Println("\x1B[1;34m===========================================\x1B[0m")
-	fmt.Printf("\x1B[1;32mTotal installed packages: \x1B[0;33m%d\x1B[0m\n", info.Totaln)
-	fmt.Printf("\x1B[1;32mTotal foreign installed packages: \x1B[0;33m%d\x1B[0m\n", foreign)
-	fmt.Printf("\x1B[1;32mExplicitly installed packages: \x1B[0;33m%d\x1B[0m\n", info.Expln)
-	fmt.Printf("\x1B[1;32mTotal Size occupied by packages: \x1B[0;33m%s\x1B[0m\n", size(info.TotalSize))
-	fmt.Println("\x1B[1;34m===========================================\x1B[0m")
-	fmt.Println("\x1B[1;32mTen biggest packages\x1B[0m")
-	pac.BiggestPackages()
-	fmt.Println("\x1B[1;34m===========================================\x1B[0m")
-
-	keys := make([]string, len(foreignS))
-	i := 0
-	for k := range foreignS {
-		keys[i] = k
-		i++
-	}
-	q, _, err := aur.MultiInfo(keys)
-	if err != nil {
-		return err
-	}
-
-	for _, res := range q {
-		if res.Maintainer == "" {
-			fmt.Printf("\x1b[1;31;40mWarning: \x1B[1;33;40m%s\x1b[0;37;40m is orphaned.\x1b[0m\n", res.Name)
-		}
-		if res.OutOfDate != 0 {
-			fmt.Printf("\x1b[1;31;40mWarning: \x1B[1;33;40m%s\x1b[0;37;40m is out-of-date in AUR.\x1b[0m\n", res.Name)
-		}
-	}
-
-	return nil
-}
-
-// Function by pyk https://github.com/pyk/byten
-func index(s int64) float64 {
-	x := math.Log(float64(s)) / math.Log(1024)
-	return math.Floor(x)
-}
-
-// Function by pyk https://github.com/pyk/byten
-func countSize(s int64, i float64) float64 {
-	return float64(s) / math.Pow(1024, math.Floor(i))
-}
-
-// Size return a formated string from file size
-// Function by pyk https://github.com/pyk/byten
-func size(s int64) string {
-
-	symbols := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
-	i := index(s)
-	if s < 10 {
-		return fmt.Sprintf("%dB", s)
-	}
-	size := countSize(s, i)
-	format := "%.0f"
-	if size < 10 {
-		format = "%.1f"
-	}
-
-	return fmt.Sprintf(format+"%s", size, symbols[int(i)])
-}
-
-// 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("pacman", args...)
-	} else {
-		args = append([]string{"pacman"}, args...)
-		cmd = exec.Command("sudo", args...)
-	}
-
-	cmd.Stdout = os.Stdout
-	cmd.Stdin = os.Stdin
-	cmd.Stderr = os.Stderr
-	err := cmd.Run()
-	return err
-}
-
 // CleanDependencies removels all dangling dependencies in system
-func CleanDependencies(pkgs []string) error {
+func cleanDependencies(pkgs []string) error {
 	hanging, err := pac.HangingPackages()
 	if err != nil {
 		return err
 	}
 
 	if len(hanging) != 0 {
-		if !util.ContinueTask("Confirm Removal?", "nN") {
+		if !config.ContinueTask("Confirm Removal?", "nN") {
 			return nil
 		}
 		err = pac.CleanRemove(hanging)
@@ -312,7 +53,7 @@ func CleanDependencies(pkgs []string) error {
 }
 
 // GetPkgbuild gets the pkgbuild of the package 'pkg' trying the ABS first and then the AUR trying the ABS first and then the AUR.
-func GetPkgbuild(pkg string) (err error) {
+func getPkgbuild(pkg string) (err error) {
 	wd, err := os.Getwd()
 	if err != nil {
 		return
@@ -327,34 +68,3 @@ func GetPkgbuild(pkg string) (err error) {
 	err = aur.GetPkgbuild(pkg, wd)
 	return
 }
-
-// Complete provides completion info for shells
-func Complete() (err error) {
-	path := os.Getenv("HOME") + "/.cache/yay/aur_" + util.Shell + ".cache"
-
-	if info, err := os.Stat(path); os.IsNotExist(err) || time.Since(info.ModTime()).Hours() > 48 {
-		os.MkdirAll(os.Getenv("HOME")+"/.cache/yay", 0755)
-
-		out, err := os.Create(path)
-		if err != nil {
-			return err
-		}
-
-		if aur.CreateAURList(out) != nil {
-			defer os.Remove(path)
-		}
-		err = pac.CreatePackageList(out)
-
-		out.Close()
-		return err
-	}
-
-	in, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0755)
-	if err != nil {
-		return err
-	}
-	defer in.Close()
-
-	_, err = io.Copy(os.Stdout, in)
-	return err
-}

+ 151 - 22
aur/aur.go

@@ -2,38 +2,144 @@ package aur
 
 import (
 	"bufio"
+	"bytes"
 	"fmt"
 	"net/http"
 	"os"
+	"sort"
+	"strings"
 
+	alpm "github.com/jguer/go-alpm"
+	vcs "github.com/jguer/yay/aur/vcs"
+	"github.com/jguer/yay/config"
 	"github.com/jguer/yay/pacman"
-	"github.com/jguer/yay/util"
+	rpc "github.com/mikkeloscar/aur"
 )
 
+// BaseURL givers the AUR default address.
+const BaseURL string = "https://aur.archlinux.org"
+
+var specialDBsauce bool = false
+
+// NarrowSearch searches AUR and narrows based on subarguments
+func NarrowSearch(pkgS []string, sortS bool) (Query, error) {
+	if len(pkgS) == 0 {
+		return nil, nil
+	}
+
+	r, err := rpc.Search(pkgS[0])
+	if err != nil {
+		return nil, err
+	}
+
+	if len(pkgS) == 1 {
+		if sortS {
+			sort.Sort(Query(r))
+		}
+		return r, err
+	}
+
+	var aq Query
+	var n int
+
+	for _, res := range r {
+		match := true
+		for _, pkgN := range pkgS[1:] {
+			if !(strings.Contains(res.Name, pkgN) || strings.Contains(strings.ToLower(res.Description), pkgN)) {
+				match = false
+				break
+			}
+		}
+
+		if match {
+			n++
+			aq = append(aq, res)
+		}
+	}
+
+	if sortS {
+		sort.Sort(aq)
+	}
+
+	return aq, err
+}
+
 // Install sends system commands to make and install a package from pkgName
-func Install(pkg string, flags []string) (err error) {
-	q, n, err := Info(pkg)
+func Install(pkgName []string, flags []string) (err error) {
+	q, err := rpc.Info(pkgName)
 	if err != nil {
 		return
 	}
 
-	if n == 0 {
-		return fmt.Errorf("Package %s does not exist", pkg)
+	if len(q) != len(pkgName) {
+		fmt.Printf("Some package from list\n%+v\ndoes not exist", pkgName)
+	}
+
+	var finalrm []string
+	for _, i := range q {
+		mrm, err := PkgInstall(&i, flags)
+		if err != nil {
+			fmt.Println("Error installing", i.Name, ":", err)
+		}
+		finalrm = append(finalrm, mrm...)
+	}
+
+	if len(finalrm) != 0 {
+		err = RemoveMakeDeps(finalrm)
 	}
 
-	q[0].Install(flags)
 	return err
 }
 
+// CreateDevelDB forces yay to create a DB of the existing development packages
+func CreateDevelDB() error {
+	foreign, err := pacman.ForeignPackages()
+	if err != nil {
+		return err
+	}
+
+	keys := make([]string, len(foreign))
+	i := 0
+	for k := range foreign {
+		keys[i] = k
+		i++
+	}
+
+	config.YayConf.NoConfirm = true
+	specialDBsauce = true
+	err = Install(keys, nil)
+	return err
+}
+
+func develUpgrade(foreign map[string]alpm.Package, flags []string) error {
+	fmt.Println(" Checking development packages...")
+	develUpdates := vcs.CheckUpdates(foreign)
+	if len(develUpdates) != 0 {
+		for _, q := range develUpdates {
+			fmt.Printf("\x1b[1m\x1b[32m==>\x1b[33;1m %s\x1b[0m\n", q)
+		}
+		// Install updated packages
+		if !config.ContinueTask("Proceed with upgrade?", "nN") {
+			return nil
+		}
+
+		err := Install(develUpdates, flags)
+		if err != nil {
+			fmt.Println(err)
+		}
+	}
+
+	return nil
+}
+
 // Upgrade tries to update every foreign package installed in the system
 func Upgrade(flags []string) error {
 	fmt.Println("\x1b[1;36;1m::\x1b[0m\x1b[1m Starting AUR upgrade...\x1b[0m")
 
-	foreign, n, err := pacman.ForeignPackages()
-	if err != nil || n == 0 {
+	foreign, err := pacman.ForeignPackages()
+	if err != nil {
 		return err
 	}
-
 	keys := make([]string, len(foreign))
 	i := 0
 	for k := range foreign {
@@ -41,36 +147,59 @@ func Upgrade(flags []string) error {
 		i++
 	}
 
-	q, _, err := MultiInfo(keys)
+	if config.YayConf.Devel {
+		err := develUpgrade(foreign, flags)
+		if err != nil {
+			fmt.Println(err)
+		}
+	}
+
+	q, err := rpc.Info(keys)
 	if err != nil {
 		return err
 	}
 
+	var buffer bytes.Buffer
+	buffer.WriteString("\n")
 	outdated := q[:0]
-	for _, res := range q {
+	for i, res := range q {
+		fmt.Printf("\r Checking %d/%d packages...", i+1, len(q))
+
 		if _, ok := foreign[res.Name]; ok {
 			// Leaving this here for now, warn about downgrades later
-			if res.LastModified > foreign[res.Name].Date {
-				fmt.Printf("\x1b[1m\x1b[32m==>\x1b[33;1m %s: \x1b[0m%s \x1b[33;1m-> \x1b[0m%s\n",
-					res.Name, foreign[res.Name].Version, res.Version)
+			if (config.YayConf.TimeUpdate && (int64(res.LastModified) > foreign[res.Name].BuildDate().Unix())) ||
+				alpm.VerCmp(foreign[res.Name].Version(), res.Version) < 0 {
+				buffer.WriteString(fmt.Sprintf("\x1b[1m\x1b[32m==>\x1b[33;1m %s: \x1b[0m%s \x1b[33;1m-> \x1b[0m%s\n",
+					res.Name, foreign[res.Name].Version(), res.Version))
 				outdated = append(outdated, res)
 			}
 		}
 	}
+	fmt.Println(buffer.String())
 
 	//If there are no outdated packages, don't prompt
 	if len(outdated) == 0 {
-		fmt.Println(" there is nothing to do")
+		fmt.Println("there is nothing to do")
 		return nil
 	}
 
 	// Install updated packages
-	if !util.ContinueTask("Proceed with upgrade?", "nN") {
+	if !config.ContinueTask("Proceed with upgrade?", "nN") {
 		return nil
 	}
 
-	for _, pkg := range outdated {
-		pkg.Install(flags)
+	var finalmdeps []string
+	for _, pkgi := range outdated {
+		mdeps, err := PkgInstall(&pkgi, flags)
+		finalmdeps = append(finalmdeps, mdeps...)
+		if err != nil {
+			fmt.Println(err)
+		}
+	}
+
+	err = pacman.CleanRemove(finalmdeps)
+	if err != nil {
+		fmt.Println(err)
 	}
 
 	return nil
@@ -78,17 +207,17 @@ func Upgrade(flags []string) error {
 
 // GetPkgbuild downloads pkgbuild from the AUR.
 func GetPkgbuild(pkgN string, dir string) (err error) {
-	aq, numaq, err := Info(pkgN)
+	aq, err := rpc.Info([]string{pkgN})
 	if err != nil {
 		return err
 	}
 
-	if numaq == 0 {
+	if len(aq) == 0 {
 		return fmt.Errorf("no results")
 	}
 
 	fmt.Printf("\x1b[1;32m==>\x1b[1;33m %s \x1b[1;32mfound in AUR.\x1b[0m\n", pkgN)
-	util.DownloadAndUnpack(BaseURL+aq[0].URLPath, dir, false)
+	config.DownloadAndUnpack(BaseURL+aq[0].URLPath, dir, false)
 	return
 }
 
@@ -106,7 +235,7 @@ func CreateAURList(out *os.File) (err error) {
 	for scanner.Scan() {
 		fmt.Print(scanner.Text())
 		out.WriteString(scanner.Text())
-		if util.Shell == "fish" {
+		if config.YayConf.Shell == "fish" {
 			fmt.Print("\tAUR\n")
 			out.WriteString("\tAUR\n")
 		} else {

+ 0 - 87
aur/aur_test.go

@@ -1,87 +0,0 @@
-package aur
-
-import (
-	"os"
-	"reflect"
-	"testing"
-)
-
-func TestSearch(t *testing.T) {
-
-	eN := "yay"
-	result, _, err := Search([]string{"yay"}, true)
-	if err != nil {
-		t.Fatalf("Expected err to be nil but it was %s", err)
-	}
-
-	// t.Logf("Got struct: %+v", result)
-	found := false
-	for _, v := range result {
-		if v.Name == eN {
-			found = true
-		}
-	}
-
-	if !found {
-		t.Fatalf("Expected to find yay, found %+v", result)
-	}
-}
-
-func benchmarkSearch(search string, sort bool, b *testing.B) {
-
-	for n := 0; n < b.N; n++ {
-		Search([]string{search}, sort)
-	}
-}
-
-func BenchmarkSearchSimpleNoSort(b *testing.B)  { benchmarkSearch("yay", false, b) }
-func BenchmarkSearchComplexNoSort(b *testing.B) { benchmarkSearch("linux", false, b) }
-func BenchmarkSearchSimpleSorted(b *testing.B)  { benchmarkSearch("yay", true, b) }
-func BenchmarkSearchComplexSorted(b *testing.B) { benchmarkSearch("linux", true, b) }
-
-func TestInfo(t *testing.T) {
-
-	eN := "yay"
-	eM := []string{"go", "git"}
-	result, _, err := Info("yay")
-	if err != nil {
-		t.Fatalf("Expected err to be nil but it was %s", err)
-	}
-
-	// t.Logf("Got struct: %+v", result)
-	found := false
-	for _, v := range result {
-		if v.Name == eN && reflect.DeepEqual(v.MakeDepends, eM) {
-			found = true
-		}
-	}
-
-	if !found {
-		t.Fatalf("Expected to find yay, found %+v", result)
-	}
-}
-
-func TestUpgrade(t *testing.T) {
-	old := os.Stdout
-	_, w, _ := os.Pipe()
-	os.Stdout = w
-
-	err := Upgrade([]string{})
-	if err != nil {
-		t.Fatalf("Expected err to be nil but it was %s", err)
-	}
-
-	os.Stdout = old
-}
-
-func BenchmarkUpgrade(b *testing.B) {
-	old := os.Stdout
-	_, w, _ := os.Pipe()
-	os.Stdout = w
-
-	for n := 0; n < b.N; n++ {
-		Upgrade([]string{})
-	}
-
-	os.Stdout = old
-}

+ 4 - 124
aur/query.go

@@ -2,22 +2,20 @@ package aur
 
 import (
 	"fmt"
-	"sort"
-	"strings"
 
-	"github.com/jguer/yay/pacman"
-	"github.com/jguer/yay/util"
+	"github.com/jguer/yay/config"
+	rpc "github.com/mikkeloscar/aur"
 )
 
 // Query is a collection of Results
-type Query []Result
+type Query []rpc.Pkg
 
 func (q Query) Len() int {
 	return len(q)
 }
 
 func (q Query) Less(i, j int) bool {
-	if util.SortMode == util.BottomUp {
+	if config.YayConf.SortMode == config.BottomUp {
 		return q[i].NumVotes < q[j].NumVotes
 	}
 	return q[i].NumVotes > q[j].NumVotes
@@ -27,124 +25,6 @@ func (q Query) Swap(i, j int) {
 	q[i], q[j] = q[j], q[i]
 }
 
-// PrintSearch handles printing search results in a given format
-func (q Query) PrintSearch(start int) {
-	for i, res := range q {
-		var toprint string
-		if util.SearchVerbosity == util.NumberMenu {
-			if util.SortMode == util.BottomUp {
-				toprint += fmt.Sprintf("%d ", len(q)+start-i-1)
-			} else {
-				toprint += fmt.Sprintf("%d ", start+i)
-			}
-		} else if util.SearchVerbosity == util.Minimal {
-			fmt.Println(res.Name)
-			continue
-		}
-		toprint += fmt.Sprintf("\x1b[1m%s/\x1b[33m%s \x1b[36m%s \x1b[0m(%d) ", "aur", res.Name, res.Version, res.NumVotes)
-		if res.Maintainer == "" {
-			toprint += fmt.Sprintf("\x1b[31;40m(Orphaned)\x1b[0m ")
-		}
-
-		if res.OutOfDate != 0 {
-			toprint += fmt.Sprintf("\x1b[31;40m(Out-of-date)\x1b[0m ")
-		}
-
-		if res.Installed == true {
-			toprint += fmt.Sprintf("\x1b[32;40mInstalled\x1b[0m")
-		}
-		toprint += "\n" + res.Description
-		fmt.Println(toprint)
-	}
-
-	return
-}
-
-// Info returns an AUR search with package details
-func Info(pkg string) (Query, int, error) {
-	type returned struct {
-		Results     Query `json:"results"`
-		ResultCount int   `json:"resultcount"`
-	}
-	r := returned{}
-
-	err := getJSON("https://aur.archlinux.org/rpc/?v=5&type=info&arg[]="+pkg, &r)
-
-	return r.Results, r.ResultCount, err
-}
-
-// MultiInfo takes a slice of strings and returns a slice with the info of each package
-func MultiInfo(pkgS []string) (Query, int, error) {
-	type returned struct {
-		Results     Query `json:"results"`
-		ResultCount int   `json:"resultcount"`
-	}
-	r := returned{}
-
-	var pkg string
-	for _, pkgn := range pkgS {
-		pkg += "&arg[]=" + pkgn
-	}
-
-	err := getJSON("https://aur.archlinux.org/rpc/?v=5&type=info"+pkg, &r)
-
-	return r.Results, r.ResultCount, err
-}
-
-// Search returns an AUR search
-func Search(pkgS []string, sortS bool) (Query, int, error) {
-	type returned struct {
-		Results     Query `json:"results"`
-		ResultCount int   `json:"resultcount"`
-	}
-	r := returned{}
-	err := getJSON("https://aur.archlinux.org/rpc/?v=5&type=search&arg="+pkgS[0], &r)
-
-	var aq Query
-	n := 0
-	setter := pacman.PFactory(pFSetTrue)
-	var fri int
-
-	for _, res := range r.Results {
-		match := true
-		for _, pkgN := range pkgS[1:] {
-			if !(strings.Contains(res.Name, pkgN) || strings.Contains(strings.ToLower(res.Description), pkgN)) {
-				match = false
-				break
-			}
-		}
-
-		if match {
-			n++
-			aq = append(aq, res)
-			fri = len(aq) - 1
-			setter(aq[fri].Name, &aq[fri], false)
-		}
-	}
-
-	if aq != nil {
-		setter(aq[fri].Name, &aq[fri], true)
-	}
-
-	if sortS {
-		sort.Sort(aq)
-	}
-
-	return aq, n, err
-}
-
-// This is very dirty but it works so good.
-func pFSetTrue(res interface{}) {
-	f, ok := res.(*Result)
-	if !ok {
-		fmt.Println("Unable to convert back to Result")
-		return
-	}
-	f.Installed = true
-
-	return
-}
-
 // MissingPackage warns if the Query was unable to find a package
 func (q Query) MissingPackage(pkgS []string) {
 	for _, depName := range pkgS {

+ 75 - 73
aur/result.go

@@ -5,44 +5,20 @@ import (
 	"os"
 	"os/exec"
 
+	vcs "github.com/jguer/yay/aur/vcs"
+	"github.com/jguer/yay/config"
 	"github.com/jguer/yay/pacman"
-	"github.com/jguer/yay/util"
+	rpc "github.com/mikkeloscar/aur"
+	gopkg "github.com/mikkeloscar/gopkgbuild"
 )
 
-// Result describes an AUR package.
-type Result struct {
-	Conflicts      []string `json:"Conflicts"`
-	Depends        []string `json:"Depends"`
-	Description    string   `json:"Description"`
-	FirstSubmitted int      `json:"FirstSubmitted"`
-	ID             int      `json:"ID"`
-	Keywords       []string `json:"Keywords"`
-	LastModified   int64    `json:"LastModified"`
-	License        []string `json:"License"`
-	Maintainer     string   `json:"Maintainer"`
-	MakeDepends    []string `json:"MakeDepends"`
-	Name           string   `json:"Name"`
-	NumVotes       int      `json:"NumVotes"`
-	OptDepends     []string `json:"OptDepends"`
-	OutOfDate      int      `json:"OutOfDate"`
-	PackageBase    string   `json:"PackageBase"`
-	PackageBaseID  int      `json:"PackageBaseID"`
-	Provides       []string `json:"Provides"`
-	URL            string   `json:"URL"`
-	URLPath        string   `json:"URLPath"`
-	Version        string   `json:"Version"`
-	Installed      bool
-	Popularity     float32 `json:"Popularity"`
-}
-
-// Dependencies returns package dependencies not installed belonging to AUR
+// PkgDependencies returns package dependencies not installed belonging to AUR
 // 0 is Repo, 1 is Foreign.
-func (a *Result) Dependencies() (runDeps [2][]string, makeDeps [2][]string, err error) {
+func PkgDependencies(a *rpc.Pkg) (runDeps [2][]string, makeDeps [2][]string, err error) {
 	var q Query
 	if len(a.Depends) == 0 && len(a.MakeDepends) == 0 {
-		var n int
-		q, n, err = Info(a.Name)
-		if n == 0 || err != nil {
+		q, err = rpc.Info([]string{a.Name})
+		if len(q) == 0 || err != nil {
 			err = fmt.Errorf("Unable to search dependencies, %s", err)
 			return
 		}
@@ -90,34 +66,63 @@ func printDeps(repoDeps []string, aurDeps []string) {
 	}
 }
 
-// Install handles install from Info Result.
-func (a *Result) Install(flags []string) (finalmdeps []string, err error) {
+func setupPackageSpace(a *rpc.Pkg) (pkgbuild *gopkg.PKGBUILD, err error) {
+	dir := config.YayConf.BuildDir + a.PackageBase + "/"
+
+	if _, err = os.Stat(dir); !os.IsNotExist(err) {
+		if !config.ContinueTask("Directory exists. Clean Build?", "yY") {
+			_ = os.RemoveAll(config.YayConf.BuildDir + a.PackageBase)
+		}
+	}
+
+	if err = config.DownloadAndUnpack(BaseURL+a.URLPath, config.YayConf.BuildDir, false); err != nil {
+		return
+	}
+
+	if !config.ContinueTask("Edit PKGBUILD?", "yY") {
+		editcmd := exec.Command(config.Editor(), dir+"PKGBUILD")
+		editcmd.Stdin, editcmd.Stdout, editcmd.Stderr = os.Stdin, os.Stdout, os.Stderr
+		editcmd.Run()
+	}
+
+	pkgbuild, err = gopkg.ParseSRCINFO(dir + ".SRCINFO")
+	if err == nil {
+		for _, pkgsource := range pkgbuild.Source {
+			owner, repo := vcs.ParseSource(pkgsource)
+			if owner != "" && repo != "" {
+				err = vcs.BranchInfo(a.Name, owner, repo)
+				if err != nil {
+					fmt.Println(err)
+				}
+			}
+		}
+	}
+
+	err = os.Chdir(dir)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// PkgInstall handles install from Info Result.
+func PkgInstall(a *rpc.Pkg, flags []string) (finalmdeps []string, err error) {
 	fmt.Printf("\x1b[1;32m==> Installing\x1b[33m %s\x1b[0m\n", a.Name)
 	if a.Maintainer == "" {
 		fmt.Println("\x1b[1;31;40m==> Warning:\x1b[0;;40m This package is orphaned.\x1b[0m")
 	}
-	dir := util.BaseDir + a.PackageBase + "/"
 
-	if _, err = os.Stat(dir); os.IsNotExist(err) {
-		if err = util.DownloadAndUnpack(BaseURL+a.URLPath, util.BaseDir, false); err != nil {
-			return
-		}
-	} else {
-		if !util.ContinueTask("Directory exists. Clean Build?", "yY") {
-			os.RemoveAll(util.BaseDir + a.PackageBase)
-			if err = util.DownloadAndUnpack(BaseURL+a.URLPath, util.BaseDir, false); err != nil {
-				return
-			}
-		}
+	_, err = setupPackageSpace(a)
+	if err != nil {
+		return
 	}
 
-	if !util.ContinueTask("Edit PKGBUILD?", "yY") {
-		editcmd := exec.Command(util.Editor(), dir+"PKGBUILD")
-		editcmd.Stdin, editcmd.Stdout, editcmd.Stderr = os.Stdin, os.Stdout, os.Stderr
-		editcmd.Run()
+	if specialDBsauce {
+		return
 	}
 
-	runDeps, makeDeps, err := a.Dependencies()
+	runDeps, makeDeps, err := PkgDependencies(a)
 	if err != nil {
 		return
 	}
@@ -128,28 +133,28 @@ func (a *Result) Install(flags []string) (finalmdeps []string, err error) {
 	finalmdeps = append(finalmdeps, makeDeps[1]...)
 
 	if len(aurDeps) != 0 || len(repoDeps) != 0 {
-		if !util.ContinueTask("Continue?", "nN") {
+		if !config.ContinueTask("Continue?", "nN") {
 			return finalmdeps, fmt.Errorf("user did not like the dependencies")
 		}
 	}
 
-	aurQ, n, _ := MultiInfo(aurDeps)
-	if n != len(aurDeps) {
-		aurQ.MissingPackage(aurDeps)
-		if !util.ContinueTask("Continue?", "nN") {
+	aurQ, _ := rpc.Info(aurDeps)
+	if len(aurQ) != len(aurDeps) {
+		(Query)(aurQ).MissingPackage(aurDeps)
+		if !config.ContinueTask("Continue?", "nN") {
 			return finalmdeps, fmt.Errorf("unable to install dependencies")
 		}
 	}
 
 	var depArgs []string
-	if util.NoConfirm {
+	if config.YayConf.NoConfirm {
 		depArgs = []string{"--asdeps", "--noconfirm"}
 	} else {
 		depArgs = []string{"--asdeps"}
 	}
 	// Repo dependencies
 	if len(repoDeps) != 0 {
-		errR := pacman.Install(repoDeps, depArgs)
+		errR := config.PassToPacman("-S", repoDeps, depArgs)
 		if errR != nil {
 			return finalmdeps, errR
 		}
@@ -157,7 +162,7 @@ func (a *Result) Install(flags []string) (finalmdeps []string, err error) {
 
 	// Handle AUR dependencies
 	for _, dep := range aurQ {
-		finalmdepsR, errA := dep.Install(depArgs)
+		finalmdepsR, errA := PkgInstall(&dep, depArgs)
 		finalmdeps = append(finalmdeps, finalmdepsR...)
 
 		if errA != nil {
@@ -167,21 +172,19 @@ func (a *Result) Install(flags []string) (finalmdeps []string, err error) {
 		}
 	}
 
-	err = os.Chdir(dir)
-	if err != nil {
-		return
-	}
-
 	args := []string{"-sri"}
 	args = append(args, flags...)
-	makepkgcmd := exec.Command(util.MakepkgBin, args...)
+	makepkgcmd := exec.Command(config.YayConf.MakepkgBin, args...)
 	makepkgcmd.Stdin, makepkgcmd.Stdout, makepkgcmd.Stderr = os.Stdin, os.Stdout, os.Stderr
 	err = makepkgcmd.Run()
+	if err == nil {
+		_ = vcs.SaveBranchInfo()
+	}
 	return
 }
 
 // PrintInfo prints package info like pacman -Si.
-func (a *Result) PrintInfo() {
+func PrintInfo(a *rpc.Pkg) {
 	fmt.Println("\x1b[1;37mRepository      :\x1b[0m", "aur")
 	fmt.Println("\x1b[1;37mName            :\x1b[0m", a.Name)
 	fmt.Println("\x1b[1;37mVersion         :\x1b[0m", a.Version)
@@ -193,11 +196,11 @@ func (a *Result) PrintInfo() {
 	}
 	fmt.Println("\x1b[1;37mLicenses        :\x1b[0m", a.License)
 
-	if len(a.Provides) != 0 {
-		fmt.Println("\x1b[1;37mProvides        :\x1b[0m", a.Provides)
-	} else {
-		fmt.Println("\x1b[1;37mProvides        :\x1b[0m", "None")
-	}
+	// if len(a.Provides) != 0 {
+	// 	fmt.Println("\x1b[1;37mProvides        :\x1b[0m", a.Provides)
+	// } else {
+	// 	fmt.Println("\x1b[1;37mProvides        :\x1b[0m", "None")
+	// }
 
 	if len(a.Depends) != 0 {
 		fmt.Println("\x1b[1;37mDepends On      :\x1b[0m", a.Depends)
@@ -234,7 +237,6 @@ func (a *Result) PrintInfo() {
 	if a.OutOfDate != 0 {
 		fmt.Println("\x1b[1;37mOut-of-date     :\x1b[0m", "Yes")
 	}
-
 }
 
 // RemoveMakeDeps receives a make dependency list and removes those
@@ -243,7 +245,7 @@ func RemoveMakeDeps(depS []string) (err error) {
 	hanging := pacman.SliceHangingPackages(depS)
 
 	if len(hanging) != 0 {
-		if !util.ContinueTask("Confirm Removal?", "nN") {
+		if !config.ContinueTask("Confirm Removal?", "nN") {
 			return nil
 		}
 		err = pacman.CleanRemove(hanging)

+ 0 - 20
aur/utils.go

@@ -1,20 +0,0 @@
-package aur
-
-import (
-	"encoding/json"
-	"net/http"
-)
-
-// BaseURL givers the AUR default address.
-const BaseURL string = "https://aur.archlinux.org"
-
-// getJSON handles JSON retrieval and decoding to struct
-func getJSON(url string, target interface{}) error {
-	r, err := http.Get(url)
-	if err != nil {
-		return err
-	}
-	defer r.Body.Close()
-
-	return json.NewDecoder(r.Body).Decode(target)
-}

+ 192 - 0
aur/vcs/github.go

@@ -0,0 +1,192 @@
+package github
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"os"
+	"strings"
+
+	alpm "github.com/jguer/go-alpm"
+)
+
+// branch contains the information of a repository branch
+type branch struct {
+	Name   string `json:"name"`
+	Commit struct {
+		SHA string `json:"sha"`
+		URL string `json:"url"`
+	} `json:"commit"`
+}
+
+type branches []branch
+
+// Info contains the last commit sha of a repo
+type Info struct {
+	Package string `json:"pkgname"`
+	URL     string `json:"url"`
+	SHA     string `json:"sha"`
+}
+
+type infos []Info
+
+var savedInfo infos
+var configfile string
+
+// Updated returns if database has been updated
+var Updated bool
+
+func init() {
+	Updated = false
+	configfile = os.Getenv("HOME") + "/.config/yay/yay_vcs.json"
+
+	if _, err := os.Stat(configfile); os.IsNotExist(err) {
+		_ = os.MkdirAll(os.Getenv("HOME")+"/.config/yay", 0755)
+		return
+	}
+
+	file, err := os.Open(configfile)
+	if err != nil {
+		fmt.Println("error:", err)
+		return
+	}
+	decoder := json.NewDecoder(file)
+	err = decoder.Decode(&savedInfo)
+	if err != nil {
+		fmt.Println("error:", err)
+	}
+}
+
+// ParseSource returns owner and repo from source
+func ParseSource(source string) (owner string, repo string) {
+	if !(strings.Contains(source, "git://") ||
+		strings.Contains(source, ".git") ||
+		strings.Contains(source, "git+https://")) {
+		return
+	}
+	split := strings.Split(source, "github.com/")
+	if len(split) > 1 {
+		secondSplit := strings.Split(split[1], "/")
+		if len(secondSplit) > 1 {
+			owner = secondSplit[0]
+			thirdSplit := strings.Split(secondSplit[1], ".git")
+			if len(thirdSplit) > 0 {
+				repo = thirdSplit[0]
+			}
+		}
+	}
+	return
+}
+
+func (info *Info) needsUpdate() bool {
+	var newRepo branches
+	r, err := http.Get(info.URL)
+	if err != nil {
+		fmt.Println(err)
+		return false
+	}
+	defer r.Body.Close()
+
+	err = json.NewDecoder(r.Body).Decode(&newRepo)
+	if err != nil {
+		fmt.Println(err)
+		return false
+	}
+
+	for _, e := range newRepo {
+		if e.Name == "master" {
+			if e.Commit.SHA != info.SHA {
+				return true
+			} else {
+				return false
+			}
+		}
+	}
+	return false
+}
+
+// CheckUpdates returns list of outdated packages
+func CheckUpdates(foreign map[string]alpm.Package) (toUpdate []string) {
+	for _, e := range savedInfo {
+		if e.needsUpdate() {
+			if _, ok := foreign[e.Package]; ok {
+				toUpdate = append(toUpdate, e.Package)
+			} else {
+				RemovePackage([]string{e.Package})
+			}
+		}
+	}
+	return
+}
+
+func inStore(pkgName string) *Info {
+	for i, e := range savedInfo {
+		if pkgName == e.Package {
+			return &savedInfo[i]
+		}
+	}
+	return nil
+}
+
+// RemovePackage removes package from VCS information
+func RemovePackage(pkgs []string) {
+	for _, pkgName := range pkgs {
+		for i, e := range savedInfo {
+			if e.Package == pkgName {
+				savedInfo[i] = savedInfo[len(savedInfo)-1]
+				savedInfo = savedInfo[:len(savedInfo)-1]
+			}
+		}
+	}
+
+	_ = SaveBranchInfo()
+	return
+}
+
+// BranchInfo updates saved information
+func BranchInfo(pkgName string, owner string, repo string) (err error) {
+	Updated = true
+	var newRepo branches
+	url := "https://api.github.com/repos/" + owner + "/" + repo + "/branches"
+	r, err := http.Get(url)
+	if err != nil {
+		return
+	}
+	defer r.Body.Close()
+
+	_ = json.NewDecoder(r.Body).Decode(&newRepo)
+
+	packinfo := inStore(pkgName)
+
+	for _, e := range newRepo {
+		if e.Name == "master" {
+			if packinfo != nil {
+				packinfo.Package = pkgName
+				packinfo.URL = url
+				packinfo.SHA = e.Commit.SHA
+			} else {
+				savedInfo = append(savedInfo, Info{Package: pkgName, URL: url, SHA: e.Commit.SHA})
+			}
+		}
+	}
+
+	return
+}
+
+func SaveBranchInfo() error {
+	marshalledinfo, err := json.Marshal(savedInfo)
+	if err != nil || string(marshalledinfo) == "null" {
+		return err
+	}
+	in, err := os.OpenFile(configfile, os.O_RDWR|os.O_CREATE, 0755)
+	if err != nil {
+		return err
+	}
+	defer in.Close()
+	_, err = in.Write(marshalledinfo)
+	if err != nil {
+		return err
+	}
+	err = in.Sync()
+	return err
+}

+ 32 - 0
aur/vcs/github_test.go

@@ -0,0 +1,32 @@
+package github
+
+import (
+	"testing"
+)
+
+func TestParsing(t *testing.T) {
+	type source struct {
+		sourceurl string
+		owner     string
+		repo      string
+	}
+
+	neovim := source{sourceurl: "git+https://github.com/neovim/neovim.git"}
+	neovim.owner, neovim.repo = ParseSource(neovim.sourceurl)
+
+	if neovim.owner != "neovim" || neovim.repo != "neovim" {
+		t.Fatalf("Expected to find neovim/neovim, found %+v/%+v", neovim.owner, neovim.repo)
+	}
+
+	yay := source{sourceurl: "git://github.com/jguer/yay.git#branch=master"}
+	yay.owner, yay.repo = ParseSource(yay.sourceurl)
+	if yay.owner != "jguer" || yay.repo != "yay" {
+		t.Fatalf("Expected to find jguer/yay, found %+v/%+v", yay.owner, yay.repo)
+	}
+
+	ack := source{sourceurl: "git://github.com/davidgiven/ack"}
+	ack.owner, ack.repo = ParseSource(ack.sourceurl)
+	if ack.owner != "davidgiven" || ack.repo != "ack" {
+		t.Fatalf("Expected to find davidgiven/ack, found %+v/%+v", ack.owner, ack.repo)
+	}
+}

+ 0 - 139
cmd/yay/yay.go

@@ -1,139 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"os"
-
-	"github.com/jguer/yay"
-	"github.com/jguer/yay/util"
-)
-
-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
-
-    New options:
-    --topdown            shows repository's packages first and then aur's
-    --bottomup           shows aur's packages first and then repository's
-    --noconfirm          skip user input on package install
-`)
-}
-
-var version = "1.100"
-
-func parser() (op string, options []string, packages []string, err error) {
-	if len(os.Args) < 2 {
-		err = fmt.Errorf("no operation specified")
-		return
-	}
-	op = "yogurt"
-
-	for _, arg := range os.Args[1:] {
-		if arg[0] == '-' && arg[1] != '-' {
-			switch arg {
-			case "-b":
-				util.Build = true
-			default:
-				op = arg
-			}
-			continue
-		}
-
-		if arg[0] == '-' && arg[1] == '-' {
-			switch arg {
-			case "--build":
-				util.Build = true
-			case "--bottomup":
-				util.SortMode = util.BottomUp
-			case "--topdown":
-				util.SortMode = util.TopDown
-
-			case "--complete":
-				util.Shell = "sh"
-				yay.Complete()
-				os.Exit(0)
-			case "--fcomplete":
-				util.Shell = "fish"
-				yay.Complete()
-				os.Exit(0)
-			case "--help":
-				usage()
-				os.Exit(0)
-			case "--noconfirm":
-				util.NoConfirm = true
-				fallthrough
-			default:
-				options = append(options, arg)
-			}
-			continue
-		}
-
-		packages = append(packages, arg)
-	}
-	return
-}
-
-func main() {
-	op, options, pkgs, err := parser()
-	if err != nil {
-		fmt.Println(err)
-		os.Exit(1)
-	}
-
-	switch op {
-	case "-Cd":
-		err = yay.CleanDependencies(pkgs)
-	case "-G":
-		for _, pkg := range pkgs {
-			err = yay.GetPkgbuild(pkg)
-			if err != nil {
-				fmt.Println(pkg+":", err)
-			}
-		}
-	case "-Qstats":
-		err = yay.LocalStatistics(version)
-	case "-Ss", "-Ssq", "-Sqs":
-		if op == "-Ss" {
-			util.SearchVerbosity = util.Detailed
-		} else {
-			util.SearchVerbosity = util.Minimal
-		}
-
-		if pkgs != nil {
-			err = yay.SyncSearch(pkgs)
-		}
-	case "-S":
-		err = yay.Install(pkgs, options)
-	case "-Syu", "-Suy":
-		err = yay.Upgrade(options)
-	case "-Si":
-		err = yay.SyncInfo(pkgs, options)
-	case "yogurt":
-		util.SearchVerbosity = util.NumberMenu
-
-		if pkgs != nil {
-			err = yay.NumberMenu(pkgs, options)
-		}
-	default:
-		err = yay.PassToPacman(op, pkgs, options)
-	}
-
-	if err != nil {
-		fmt.Println(err)
-		os.Exit(1)
-	}
-}

+ 283 - 0
config/config.go

@@ -0,0 +1,283 @@
+package config
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"os/exec"
+	"strings"
+
+	alpm "github.com/jguer/go-alpm"
+)
+
+// Verbosity settings for search
+const (
+	NumberMenu = iota
+	Detailed
+	Minimal
+)
+
+// Describes Sorting method for numberdisplay
+const (
+	BottomUp = iota
+	TopDown
+)
+
+// Configuration stores yay's config.
+type Configuration struct {
+	BuildDir   string `json:"buildDir"`
+	Editor     string `json:"editor"`
+	MakepkgBin string `json:"makepkgbin"`
+	Shell      string `json:"-"`
+	NoConfirm  bool   `json:"noconfirm"`
+	Devel      bool   `json:"devel"`
+	PacmanBin  string `json:"pacmanbin"`
+	PacmanConf string `json:"pacmanconf"`
+	SearchMode int    `json:"-"`
+	SortMode   int    `json:"sortmode"`
+	TarBin     string `json:"tarbin"`
+	TimeUpdate bool   `json:"timeupdate"`
+}
+
+// YayConf holds the current config values for yay.
+var YayConf Configuration
+
+// AlpmConf holds the current config values for pacman.
+var AlpmConf alpm.PacmanConfig
+
+// AlpmHandle is the alpm handle used by yay.
+var AlpmHandle *alpm.Handle
+
+func init() {
+	var err error
+	configfile := os.Getenv("HOME") + "/.config/yay/config.json"
+
+	if _, err = os.Stat(configfile); os.IsNotExist(err) {
+		_ = os.MkdirAll(os.Getenv("HOME")+"/.config/yay", 0755)
+		defaultSettings(&YayConf)
+	} else {
+		file, err := os.Open(configfile)
+		if err != nil {
+			fmt.Println("Error reading config:", err)
+		} else {
+			decoder := json.NewDecoder(file)
+			err = decoder.Decode(&YayConf)
+			if err != nil {
+				fmt.Println("Loading default Settings\nError reading config:", err)
+				defaultSettings(&YayConf)
+			}
+		}
+	}
+
+	AlpmConf, err = readAlpmConfig(YayConf.PacmanConf)
+	if err != nil {
+		fmt.Println("Unable to read Pacman conf", err)
+		os.Exit(1)
+	}
+
+	AlpmHandle, err = AlpmConf.CreateHandle()
+	if err != nil {
+		fmt.Println("Unable to CreateHandle", err)
+		os.Exit(1)
+	}
+}
+
+func readAlpmConfig(pacmanconf string) (conf alpm.PacmanConfig, err error) {
+	file, err := os.Open(pacmanconf)
+	if err != nil {
+		return
+	}
+	conf, err = alpm.ParseConfig(file)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// SaveConfig writes yay config to file.
+func SaveConfig() error {
+	YayConf.NoConfirm = false
+	configfile := os.Getenv("HOME") + "/.config/yay/config.json"
+	marshalledinfo, _ := json.Marshal(YayConf)
+	in, err := os.OpenFile(configfile, os.O_RDWR|os.O_CREATE, 0755)
+	if err != nil {
+		return err
+	}
+	defer in.Close()
+	_, err = in.Write(marshalledinfo)
+	if err != nil {
+		return err
+	}
+	err = in.Sync()
+	return err
+}
+
+func defaultSettings(config *Configuration) {
+	config.BuildDir = "/tmp/yaytmp/"
+	config.Editor = ""
+	config.Devel = false
+	config.MakepkgBin = "/usr/bin/makepkg"
+	config.NoConfirm = false
+	config.PacmanBin = "/usr/bin/pacman"
+	config.PacmanConf = "/etc/pacman.conf"
+	config.SortMode = BottomUp
+	config.TarBin = "/usr/bin/bsdtar"
+	config.TimeUpdate = false
+}
+
+// Editor returns the preferred system editor.
+func Editor() string {
+	switch {
+	case YayConf.Editor != "":
+		editor, err := exec.LookPath(YayConf.Editor)
+		if err != nil {
+			fmt.Println(err)
+		} else {
+			return editor
+		}
+		fallthrough
+	case os.Getenv("EDITOR") != "":
+		editor, err := exec.LookPath(os.Getenv("EDITOR"))
+		if err != nil {
+			fmt.Println(err)
+		} else {
+			return editor
+		}
+		fallthrough
+	case os.Getenv("VISUAL") != "":
+		editor, err := exec.LookPath(os.Getenv("VISUAL"))
+		if err != nil {
+			fmt.Println(err)
+		} else {
+			return editor
+		}
+		fallthrough
+	default:
+		fmt.Printf("\x1b[1;31;40mWarning: \x1B[1;33;40m$EDITOR\x1b[0;37;40m is not set.\x1b[0m\nPlease add $EDITOR or to your environment variables.\n")
+
+	editorLoop:
+		fmt.Printf("\x1b[32m%s\x1b[0m ", "Edit PKGBUILD with:")
+		var editorInput string
+		_, err := fmt.Scanln(&editorInput)
+		if err != nil {
+			fmt.Println(err)
+			goto editorLoop
+		}
+
+		editor, err := exec.LookPath(editorInput)
+		if err != nil {
+			fmt.Println(err)
+			goto editorLoop
+		}
+		return editor
+	}
+}
+
+// ContinueTask prompts if user wants to continue task.
+//If NoConfirm is set the action will continue without user input.
+func ContinueTask(s string, def string) (cont bool) {
+	if YayConf.NoConfirm {
+		return true
+	}
+	var postFix string
+
+	if def == "nN" {
+		postFix = "[Y/n] "
+	} else {
+		postFix = "[y/N] "
+	}
+
+	var response string
+	fmt.Printf("\x1b[1;32m==> %s\x1b[1;37m %s\x1b[0m", s, postFix)
+
+	n, err := fmt.Scanln(&response)
+	if err != nil || n == 0 {
+		return true
+	}
+
+	if response == string(def[0]) || response == string(def[1]) {
+		return false
+	}
+
+	return true
+}
+
+func downloadFile(path string, url string) (err error) {
+	// Create the file
+	out, err := os.Create(path)
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+
+	// Get the data
+	resp, err := http.Get(url)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	// Writer the body to file
+	_, err = io.Copy(out, resp.Body)
+	return err
+}
+
+// DownloadAndUnpack downloads url tgz and extracts to path.
+func DownloadAndUnpack(url string, path string, trim bool) (err error) {
+	err = os.MkdirAll(path, 0755)
+	if err != nil {
+		return
+	}
+
+	tokens := strings.Split(url, "/")
+	fileName := tokens[len(tokens)-1]
+
+	tarLocation := path + fileName
+	defer os.Remove(tarLocation)
+
+	err = downloadFile(tarLocation, url)
+	if err != nil {
+		return
+	}
+
+	if trim {
+		err = exec.Command("/bin/sh", "-c",
+			YayConf.TarBin+" --strip-components 2 --include='*/"+fileName[:len(fileName)-7]+"/trunk/' -xf "+tarLocation+" -C "+path).Run()
+		os.Rename(path+"trunk", path+fileName[:len(fileName)-7]) // kurwa
+	} else {
+		err = exec.Command(YayConf.TarBin, "-xf", tarLocation, "-C", path).Run()
+	}
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// 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") {
+		cmd = exec.Command(YayConf.PacmanBin, args...)
+	} else {
+		args = append([]string{YayConf.PacmanBin}, args...)
+		cmd = exec.Command("sudo", args...)
+	}
+
+	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
+	err := cmd.Run()
+	return err
+}

+ 96 - 259
pacman/pacman.go

@@ -3,111 +3,51 @@ package pacman
 import (
 	"fmt"
 	"os"
-	"os/exec"
 	"strings"
 
 	"github.com/jguer/go-alpm"
-	"github.com/jguer/yay/util"
+	"github.com/jguer/yay/config"
 )
 
-// Query describes a Repository search.
-type Query []Result
-
-// Result describes a pkg.
-type Result struct {
-	Name        string
-	Repository  string
-	Version     string
-	Description string
-	Group       string
-	Installed   bool
-}
-
-// PacmanConf describes the default pacman config file
-const PacmanConf string = "/etc/pacman.conf"
-
-var conf alpm.PacmanConfig
-
-func init() {
-	conf, _ = readConfig(PacmanConf)
-}
-
-func readConfig(pacmanconf string) (conf alpm.PacmanConfig, err error) {
-	file, err := os.Open(pacmanconf)
-	if err != nil {
-		return
-	}
-	conf, err = alpm.ParseConfig(file)
-	if err != nil {
-		return
-	}
-	return
-}
-
-// UpdatePackages handles cache update and upgrade
-func UpdatePackages(flags []string) error {
-	args := append([]string{"pacman", "-Syu"}, flags...)
-
-	cmd := exec.Command("sudo", args...)
-	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
-	err := cmd.Run()
-	return err
-}
+// Query holds the results of a repository search.
+type Query []alpm.Package
 
 // Search handles repo searches. Creates a RepoSearch struct.
 func Search(pkgInputN []string) (s Query, n int, err error) {
-	h, err := conf.CreateHandle()
-	defer h.Release()
-	if err != nil {
-	}
-
-	localDb, err := h.LocalDb()
+	dbList, err := config.AlpmHandle.SyncDbs()
 	if err != nil {
 		return
 	}
-	dbList, err := h.SyncDbs()
-	if err != nil {
-		return
-	}
-
-	var installed bool
-	dbS := dbList.Slice()
 
 	// BottomUp functions
 	initL := func(len int) int {
-		return len - 1
-	}
-
-	compL := func(len int, i int) bool {
-		return i > 0
-	}
-
-	finalL := func(i int) int {
-		return i - 1
-	}
-
-	// TopDown functions
-	if util.SortMode == util.TopDown {
-		initL = func(len int) int {
+		if config.YayConf.SortMode == config.TopDown {
 			return 0
+		} else {
+			return len - 1
 		}
-
-		compL = func(len int, i int) bool {
+	}
+	compL := func(len int, i int) bool {
+		if config.YayConf.SortMode == config.TopDown {
 			return i < len
+		} else {
+			return i > -1
 		}
-
-		finalL = func(i int) int {
+	}
+	finalL := func(i int) int {
+		if config.YayConf.SortMode == config.TopDown {
 			return i + 1
+		} else {
+			return i - 1
 		}
 	}
 
+	dbS := dbList.Slice()
 	lenDbs := len(dbS)
 	for f := initL(lenDbs); compL(lenDbs, f); f = finalL(f) {
 		pkgS := dbS[f].PkgCache().Slice()
 		lenPkgs := len(pkgS)
-
 		for i := initL(lenPkgs); compL(lenPkgs, i); i = finalL(i) {
-
 			match := true
 			for _, pkgN := range pkgInputN {
 				if !(strings.Contains(pkgS[i].Name(), pkgN) || strings.Contains(strings.ToLower(pkgS[i].Description()), pkgN)) {
@@ -117,20 +57,8 @@ func Search(pkgInputN []string) (s Query, n int, err error) {
 			}
 
 			if match {
-				installed = false
-				if r, _ := localDb.PkgByName(pkgS[i].Name()); r != nil {
-					installed = true
-				}
 				n++
-
-				s = append(s, Result{
-					Name:        pkgS[i].Name(),
-					Description: pkgS[i].Description(),
-					Version:     pkgS[i].Version(),
-					Repository:  dbS[f].Name(),
-					Group:       strings.Join(pkgS[i].Groups().Slice(), ","),
-					Installed:   installed,
-				})
+				s = append(s, pkgS[i])
 			}
 		}
 	}
@@ -141,74 +69,57 @@ func Search(pkgInputN []string) (s Query, n int, err error) {
 func (s Query) PrintSearch() {
 	for i, res := range s {
 		var toprint string
-		if util.SearchVerbosity == util.NumberMenu {
-			if util.SortMode == util.BottomUp {
-				toprint += fmt.Sprintf("%d ", len(s)-i-1)
+		if config.YayConf.SearchMode == config.NumberMenu {
+			if config.YayConf.SortMode == config.BottomUp {
+				toprint += fmt.Sprintf("\x1b[33m%d\x1b[0m ", len(s)-i-1)
 			} else {
-				toprint += fmt.Sprintf("%d ", i)
+				toprint += fmt.Sprintf("\x1b[33m%d\x1b[0m ", i)
 			}
-		} else if util.SearchVerbosity == util.Minimal {
-			fmt.Println(res.Name)
+		} else if config.YayConf.SearchMode == config.Minimal {
+			fmt.Println(res.Name())
 			continue
 		}
 		toprint += fmt.Sprintf("\x1b[1m%s/\x1b[33m%s \x1b[36m%s \x1b[0m",
-			res.Repository, res.Name, res.Version)
-
-		if len(res.Group) != 0 {
-			toprint += fmt.Sprintf("(%s) ", res.Group)
-		}
+			res.DB().Name(), res.Name(), res.Version())
 
-		if res.Installed {
-			toprint += fmt.Sprintf("\x1b[32;40mInstalled\x1b[0m")
+		if len(res.Groups().Slice()) != 0 {
+			toprint += fmt.Sprint(res.Groups().Slice(), " ")
 		}
 
-		toprint += "\n" + res.Description
-		fmt.Println(toprint)
-	}
-}
-
-// PFactory execute an action over a series of packages without reopening the handle everytime.
-// Everybody told me it wouln't work. It does. It's just not pretty.
-// When it worked: https://youtu.be/a4Z5BdEL0Ag?t=1m11s
-func PFactory(action func(interface{})) func(name string, object interface{}, rel bool) {
-	h, _ := conf.CreateHandle()
-	localDb, _ := h.LocalDb()
-
-	return func(name string, object interface{}, rel bool) {
-		_, err := localDb.PkgByName(name)
+		localDb, err := config.AlpmHandle.LocalDb()
 		if err == nil {
-			action(object)
+			if _, err = localDb.PkgByName(res.Name()); err == nil {
+				toprint += fmt.Sprintf("\x1b[32;40mInstalled\x1b[0m")
+			}
 		}
 
-		if rel {
-			h.Release()
-		}
+		toprint += "\n    " + res.Description()
+		fmt.Println(toprint)
 	}
 }
 
 // PackageSlices separates an input slice into aur and repo slices
 func PackageSlices(toCheck []string) (aur []string, repo []string, err error) {
-	h, err := conf.CreateHandle()
-	defer h.Release()
-	if err != nil {
-		return
-	}
-
-	dbList, err := h.SyncDbs()
+	dbList, err := config.AlpmHandle.SyncDbs()
 	if err != nil {
 		return
 	}
 
 	for _, pkg := range toCheck {
 		found := false
-		for _, db := range dbList.Slice() {
+
+		_ = dbList.ForEach(func(db alpm.Db) error {
+			if found {
+				return nil
+			}
+
 			_, err = db.PkgByName(pkg)
 			if err == nil {
 				found = true
 				repo = append(repo, pkg)
-				break
 			}
-		}
+			return nil
+		})
 
 		if !found {
 			if _, errdb := dbList.PkgCachebyGroup(pkg); errdb == nil {
@@ -226,10 +137,8 @@ func PackageSlices(toCheck []string) (aur []string, repo []string, err error) {
 // BuildDependencies finds packages, on the second run
 // compares with a baselist and avoids searching those
 func BuildDependencies(baselist []string) func(toCheck []string, isBaseList bool, last bool) (repo []string, notFound []string) {
-	h, _ := conf.CreateHandle()
-
-	localDb, _ := h.LocalDb()
-	dbList, _ := h.SyncDbs()
+	localDb, _ := config.AlpmHandle.LocalDb()
+	dbList, _ := config.AlpmHandle.SyncDbs()
 
 	f := func(c rune) bool {
 		return c == '>' || c == '<' || c == '=' || c == ' '
@@ -237,7 +146,6 @@ func BuildDependencies(baselist []string) func(toCheck []string, isBaseList bool
 
 	return func(toCheck []string, isBaseList bool, close bool) (repo []string, notFound []string) {
 		if close {
-			h.Release()
 			return
 		}
 
@@ -266,17 +174,11 @@ func BuildDependencies(baselist []string) func(toCheck []string, isBaseList bool
 // DepSatisfier receives a string slice, returns a slice of packages found in
 // repos and one of packages not found in repos. Leaves out installed packages.
 func DepSatisfier(toCheck []string) (repo []string, notFound []string, err error) {
-	h, err := conf.CreateHandle()
-	defer h.Release()
-	if err != nil {
-		return
-	}
-
-	localDb, err := h.LocalDb()
+	localDb, err := config.AlpmHandle.LocalDb()
 	if err != nil {
 		return
 	}
-	dbList, err := h.SyncDbs()
+	dbList, err := config.AlpmHandle.SyncDbs()
 	if err != nil {
 		return
 	}
@@ -300,21 +202,13 @@ func DepSatisfier(toCheck []string) (repo []string, notFound []string, err error
 	return
 }
 
-// Install sends an install command to pacman with the pkgName slice
-func Install(pkgName []string, flags []string) (err error) {
-	if len(pkgName) == 0 {
-		return nil
-	}
-
-	args := []string{"pacman", "-S"}
-	args = append(args, pkgName...)
-	args = append(args, flags...)
-
-	cmd := exec.Command("sudo", args...)
-	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
-	cmd.Run()
-	return nil
-}
+// PkgNameSlice returns a slice of package names
+// func (s Query) PkgNameSlice() (pkgNames []string) {
+// 	for _, e := range s {
+// 		pkgNames = append(pkgNames, e.Name())
+// 	}
+// 	return
+// }
 
 // CleanRemove sends a full removal command to pacman with the pkgName slice
 func CleanRemove(pkgName []string) (err error) {
@@ -322,61 +216,43 @@ func CleanRemove(pkgName []string) (err error) {
 		return nil
 	}
 
-	args := []string{"pacman", "-Rnsc"}
-	args = append(args, pkgName...)
-	args = append(args, "--noconfirm")
-
-	cmd := exec.Command("sudo", args...)
-	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
-	cmd.Run()
-	return nil
+	err = config.PassToPacman("-Rsnc", pkgName, []string{"--noconfirm"})
+	return err
 }
 
 // ForeignPackages returns a map of foreign packages, with their version and date as values.
-func ForeignPackages() (foreign map[string]*struct {
-	Version string
-	Date    int64
-}, n int, err error) {
-	h, err := conf.CreateHandle()
-	defer h.Release()
-	if err != nil {
-		return
-	}
-
-	localDb, err := h.LocalDb()
+func ForeignPackages() (foreign map[string]alpm.Package, err error) {
+	localDb, err := config.AlpmHandle.LocalDb()
 	if err != nil {
 		return
 	}
-	dbList, err := h.SyncDbs()
+	dbList, err := config.AlpmHandle.SyncDbs()
 	if err != nil {
 		return
 	}
 
-	foreign = make(map[string]*struct {
-		Version string
-		Date    int64
-	})
-	// Find foreign packages in system
-	for _, pkg := range localDb.PkgCache().Slice() {
-		// Change to more effective method
+	foreign = make(map[string]alpm.Package)
+
+	f := func(k alpm.Package) error {
 		found := false
-		for _, db := range dbList.Slice() {
-			_, err = db.PkgByName(pkg.Name())
+		_ = dbList.ForEach(func(d alpm.Db) error {
+			if found {
+				return nil
+			}
+			_, err = d.PkgByName(k.Name())
 			if err == nil {
 				found = true
-				break
 			}
-		}
+			return nil
+		})
 
 		if !found {
-			foreign[pkg.Name()] = &struct {
-				Version string
-				Date    int64
-			}{pkg.Version(), pkg.InstallDate().Unix()}
-			n++
+			foreign[k.Name()] = k
 		}
+		return nil
 	}
 
+	err = localDb.PkgCache().ForEach(f)
 	return
 }
 
@@ -390,13 +266,7 @@ func Statistics() (info struct {
 	var nPkg int
 	var ePkg int
 
-	h, err := conf.CreateHandle()
-	defer h.Release()
-	if err != nil {
-		return
-	}
-
-	localDb, err := h.LocalDb()
+	localDb, err := config.AlpmHandle.LocalDb()
 	if err != nil {
 		return
 	}
@@ -422,13 +292,8 @@ func Statistics() (info struct {
 
 // BiggestPackages prints the name of the ten biggest packages in the system.
 func BiggestPackages() {
-	h, err := conf.CreateHandle()
-	defer h.Release()
-	if err != nil {
-		return
-	}
 
-	localDb, err := h.LocalDb()
+	localDb, err := config.AlpmHandle.LocalDb()
 	if err != nil {
 		return
 	}
@@ -441,7 +306,7 @@ func BiggestPackages() {
 	}
 
 	for i := 0; i < 10; i++ {
-		fmt.Printf("%s: \x1B[0;33m%dMB\x1B[0m\n", pkgS[i].Name(), pkgS[i].ISize()/(1024*1024))
+		fmt.Printf("%s: \x1B[0;33m%.1fMiB\x1B[0m\n", pkgS[i].Name(), float32(pkgS[i].ISize())/(1024.0*1024.0))
 	}
 	// Could implement size here as well, but we just want the general idea
 }
@@ -449,13 +314,7 @@ func BiggestPackages() {
 // HangingPackages returns a list of packages installed as deps
 // and unneeded by the system
 func HangingPackages() (hanging []string, err error) {
-	h, err := conf.CreateHandle()
-	defer h.Release()
-	if err != nil {
-		return
-	}
-
-	localDb, err := h.LocalDb()
+	localDb, err := config.AlpmHandle.LocalDb()
 	if err != nil {
 		return
 	}
@@ -467,7 +326,8 @@ func HangingPackages() (hanging []string, err error) {
 		requiredby := pkg.ComputeRequiredBy()
 		if len(requiredby) == 0 {
 			hanging = append(hanging, pkg.Name())
-			fmt.Printf("%s: \x1B[0;33m%dMB\x1B[0m\n", pkg.Name(), pkg.ISize()/(1024*1024))
+			fmt.Println(pkg.ISize())
+			fmt.Printf("%s: \x1B[0;33m%.2f KiB\x1B[0m\n", pkg.Name(), float32(pkg.ISize())/(1024.0))
 
 		}
 		return nil
@@ -480,13 +340,7 @@ func HangingPackages() (hanging []string, err error) {
 // SliceHangingPackages returns a list of packages installed as deps
 // and unneeded by the system from a provided list of package names.
 func SliceHangingPackages(pkgS []string) (hanging []string) {
-	h, err := conf.CreateHandle()
-	defer h.Release()
-	if err != nil {
-		return
-	}
-
-	localDb, err := h.LocalDb()
+	localDb, err := config.AlpmHandle.LocalDb()
 	if err != nil {
 		return
 	}
@@ -517,13 +371,7 @@ big:
 
 // GetPkgbuild downloads pkgbuild from the ABS.
 func GetPkgbuild(pkgN string, path string) (err error) {
-	h, err := conf.CreateHandle()
-	defer h.Release()
-	if err != nil {
-		return
-	}
-
-	dbList, err := h.SyncDbs()
+	dbList, err := config.AlpmHandle.SyncDbs()
 	if err != nil {
 		return
 	}
@@ -540,7 +388,7 @@ func GetPkgbuild(pkgN string, path string) (err error) {
 				return fmt.Errorf("Not in standard repositories")
 			}
 			fmt.Printf("\x1b[1;32m==>\x1b[1;33m %s \x1b[1;32mfound in ABS.\x1b[0m\n", pkgN)
-			errD := util.DownloadAndUnpack(url, path, true)
+			errD := config.DownloadAndUnpack(url, path, true)
 			return errD
 		}
 	}
@@ -549,36 +397,25 @@ func GetPkgbuild(pkgN string, path string) (err error) {
 
 //CreatePackageList appends Repo packages to completion cache
 func CreatePackageList(out *os.File) (err error) {
-	h, err := conf.CreateHandle()
-	defer h.Release()
+	dbList, err := config.AlpmHandle.SyncDbs()
 	if err != nil {
 		return
 	}
 
-	dbList, err := h.SyncDbs()
-	if err != nil {
-		return
-	}
-
-	p := func(pkg alpm.Package) error {
-		fmt.Print(pkg.Name())
-		out.WriteString(pkg.Name())
-		if util.Shell == "fish" {
-			fmt.Print("\t" + pkg.DB().Name() + "\n")
-			out.WriteString("\t" + pkg.DB().Name() + "\n")
-		} else {
-			fmt.Print("\n")
-			out.WriteString("\n")
-		}
-
-		return nil
-	}
-
-	f := func(db alpm.Db) error {
-		db.PkgCache().ForEach(p)
+	_ = dbList.ForEach(func(db alpm.Db) error {
+		_ = db.PkgCache().ForEach(func(pkg alpm.Package) error {
+			fmt.Print(pkg.Name())
+			out.WriteString(pkg.Name())
+			if config.YayConf.Shell == "fish" {
+				fmt.Print("\t" + pkg.DB().Name() + "\n")
+				out.WriteString("\t" + pkg.DB().Name() + "\n")
+			} else {
+				fmt.Print("\n")
+				out.WriteString("\n")
+			}
+			return nil
+		})
 		return nil
-	}
-
-	dbList.ForEach(f)
+	})
 	return nil
 }

+ 14 - 11
pacman/pacman_test.go

@@ -1,8 +1,11 @@
 package pacman
 
-import "testing"
-import "github.com/jguer/yay/util"
-import "os"
+import (
+	"os"
+	"testing"
+
+	"github.com/jguer/yay/config"
+)
 
 func benchmarkPrintSearch(search string, b *testing.B) {
 	old := os.Stdout
@@ -17,20 +20,20 @@ func benchmarkPrintSearch(search string, b *testing.B) {
 }
 
 func BenchmarkPrintSearchSimpleTopDown(b *testing.B) {
-	util.SortMode = util.TopDown
+	config.YayConf.SortMode = config.TopDown
 	benchmarkPrintSearch("chromium", b)
 }
 func BenchmarkPrintSearchComplexTopDown(b *testing.B) {
-	util.SortMode = util.TopDown
+	config.YayConf.SortMode = config.TopDown
 	benchmarkPrintSearch("linux", b)
 }
 
 func BenchmarkPrintSearchSimpleBottomUp(b *testing.B) {
-	util.SortMode = util.BottomUp
+	config.YayConf.SortMode = config.BottomUp
 	benchmarkPrintSearch("chromium", b)
 }
 func BenchmarkPrintSearchComplexBottomUp(b *testing.B) {
-	util.SortMode = util.BottomUp
+	config.YayConf.SortMode = config.BottomUp
 	benchmarkPrintSearch("linux", b)
 }
 
@@ -40,20 +43,20 @@ func benchmarkSearch(search string, b *testing.B) {
 	}
 }
 func BenchmarkSearchSimpleTopDown(b *testing.B) {
-	util.SortMode = util.TopDown
+	config.YayConf.SortMode = config.TopDown
 	benchmarkSearch("chromium", b)
 }
 
 func BenchmarkSearchSimpleBottomUp(b *testing.B) {
-	util.SortMode = util.BottomUp
+	config.YayConf.SortMode = config.BottomUp
 	benchmarkSearch("chromium", b)
 }
 
 func BenchmarkSearchComplexTopDown(b *testing.B) {
-	util.SortMode = util.TopDown
+	config.YayConf.SortMode = config.TopDown
 	benchmarkSearch("linux", b)
 }
 func BenchmarkSearchComplexBottomUp(b *testing.B) {
-	util.SortMode = util.BottomUp
+	config.YayConf.SortMode = config.BottomUp
 	benchmarkSearch("linux", b)
 }

+ 136 - 0
query.go

@@ -0,0 +1,136 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/jguer/yay/aur"
+	"github.com/jguer/yay/config"
+	pac "github.com/jguer/yay/pacman"
+	rpc "github.com/mikkeloscar/aur"
+)
+
+// PrintSearch handles printing search results in a given format
+func printAURSearch(q aur.Query, start int) {
+	localDb, _ := config.AlpmHandle.LocalDb()
+
+	for i, res := range q {
+		var toprint string
+		if config.YayConf.SearchMode == config.NumberMenu {
+			if config.YayConf.SortMode == config.BottomUp {
+				toprint += fmt.Sprintf("\x1b[33m%d\x1b[0m ", len(q)+start-i-1)
+			} else {
+				toprint += fmt.Sprintf("\x1b[33m%d\x1b[0m ", start+i)
+			}
+		} else if config.YayConf.SearchMode == config.Minimal {
+			fmt.Println(res.Name)
+			continue
+		}
+		toprint += fmt.Sprintf("\x1b[1m%s/\x1b[33m%s \x1b[36m%s \x1b[0m(%d) ", "aur", res.Name, res.Version, res.NumVotes)
+		if res.Maintainer == "" {
+			toprint += fmt.Sprintf("\x1b[31;40m(Orphaned)\x1b[0m ")
+		}
+
+		if res.OutOfDate != 0 {
+			toprint += fmt.Sprintf("\x1b[31;40m(Out-of-date)\x1b[0m ")
+		}
+
+		if _, err := localDb.PkgByName(res.Name); err == nil {
+			toprint += fmt.Sprintf("\x1b[32;40mInstalled\x1b[0m")
+		}
+		toprint += "\n    " + res.Description
+		fmt.Println(toprint)
+	}
+
+	return
+}
+
+// SyncSearch presents a query to the local repos and to the AUR.
+func syncSearch(pkgS []string) (err error) {
+	aq, err := aur.NarrowSearch(pkgS, true)
+	if err != nil {
+		return err
+	}
+	pq, _, err := pac.Search(pkgS)
+	if err != nil {
+		return err
+	}
+
+	if config.YayConf.SortMode == config.BottomUp {
+		printAURSearch(aq, 0)
+		pq.PrintSearch()
+	} else {
+		pq.PrintSearch()
+		printAURSearch(aq, 0)
+	}
+
+	return nil
+}
+
+// SyncInfo serves as a pacman -Si for repo packages and AUR packages.
+func syncInfo(pkgS []string, flags []string) (err error) {
+	aurS, repoS, err := pac.PackageSlices(pkgS)
+	if err != nil {
+		return
+	}
+
+	q, err := rpc.Info(aurS)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	for _, aurP := range q {
+		aur.PrintInfo(&aurP)
+	}
+
+	if len(repoS) != 0 {
+		err = config.PassToPacman("-Si", repoS, flags)
+	}
+
+	return
+}
+
+// LocalStatistics returns installed packages statistics.
+func localStatistics(version string) error {
+	info, err := pac.Statistics()
+	if err != nil {
+		return err
+	}
+
+	foreignS, err := pac.ForeignPackages()
+	if err != nil {
+		return err
+	}
+
+	fmt.Printf("\n Yay version r%s\n", version)
+	fmt.Println("\x1B[1;34m===========================================\x1B[0m")
+	fmt.Printf("\x1B[1;32mTotal installed packages: \x1B[0;33m%d\x1B[0m\n", info.Totaln)
+	fmt.Printf("\x1B[1;32mTotal foreign installed packages: \x1B[0;33m%d\x1B[0m\n", len(foreignS))
+	fmt.Printf("\x1B[1;32mExplicitly installed packages: \x1B[0;33m%d\x1B[0m\n", info.Expln)
+	fmt.Printf("\x1B[1;32mTotal Size occupied by packages: \x1B[0;33m%s\x1B[0m\n", size(info.TotalSize))
+	fmt.Println("\x1B[1;34m===========================================\x1B[0m")
+	fmt.Println("\x1B[1;32mTen biggest packages\x1B[0m")
+	pac.BiggestPackages()
+	fmt.Println("\x1B[1;34m===========================================\x1B[0m")
+
+	keys := make([]string, len(foreignS))
+	i := 0
+	for k := range foreignS {
+		keys[i] = k
+		i++
+	}
+	q, err := rpc.Info(keys)
+	if err != nil {
+		return err
+	}
+
+	for _, res := range q {
+		if res.Maintainer == "" {
+			fmt.Printf("\x1b[1;31;40mWarning: \x1B[1;33;40m%s\x1b[0;37;40m is orphaned.\x1b[0m\n", res.Name)
+		}
+		if res.OutOfDate != 0 {
+			fmt.Printf("\x1b[1;31;40mWarning: \x1B[1;33;40m%s\x1b[0;37;40m is out-of-date in AUR.\x1b[0m\n", res.Name)
+		}
+	}
+
+	return nil
+}

+ 0 - 150
util/util.go

@@ -1,150 +0,0 @@
-package util
-
-import (
-	"fmt"
-	"io"
-	"net/http"
-	"os"
-	"os/exec"
-	"strings"
-)
-
-// TarBin describes the default installation point of tar command.
-const TarBin string = "/usr/bin/bsdtar"
-
-// MakepkgBin describes the default installation point of makepkg command.
-const MakepkgBin string = "/usr/bin/makepkg"
-
-// SearchVerbosity determines print method used in PrintSearch
-var SearchVerbosity = NumberMenu
-
-// Verbosity settings for search
-const (
-	NumberMenu = iota
-	Detailed
-	Minimal
-)
-
-var Shell = "fish"
-
-// Build controls if packages will be built from ABS.
-var Build = false
-
-// NoConfirm ignores prompts.
-var NoConfirm = false
-
-// SortMode determines top down package or down top package display
-var SortMode = BottomUp
-
-// BaseDir is the default building directory for yay
-var BaseDir = "/tmp/yaytmp/"
-
-// Describes Sorting method for numberdisplay
-const (
-	BottomUp = iota
-	TopDown
-)
-
-// ContinueTask prompts if user wants to continue task.
-//If NoConfirm is set the action will continue without user input.
-func ContinueTask(s string, def string) (cont bool) {
-	if NoConfirm {
-		return true
-	}
-	var postFix string
-
-	if def == "nN" {
-		postFix = "(Y/n)"
-	} else {
-		postFix = "(y/N)"
-	}
-
-	var response string
-	fmt.Printf("\x1b[1;32m==> %s\x1b[1;37m %s\x1b[0m\n", s, postFix)
-
-	fmt.Scanln(&response)
-	if response == string(def[0]) || response == string(def[1]) {
-		return false
-	}
-
-	return true
-}
-
-func downloadFile(path string, url string) (err error) {
-	// Create the file
-	out, err := os.Create(path)
-	if err != nil {
-		return err
-	}
-	defer out.Close()
-
-	// Get the data
-	resp, err := http.Get(url)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-
-	// Writer the body to file
-	_, err = io.Copy(out, resp.Body)
-	return err
-}
-
-// DownloadAndUnpack downloads url tgz and extracts to path.
-func DownloadAndUnpack(url string, path string, trim bool) (err error) {
-	err = os.MkdirAll(path, 0755)
-	if err != nil {
-		return
-	}
-
-	tokens := strings.Split(url, "/")
-	fileName := tokens[len(tokens)-1]
-
-	tarLocation := path + fileName
-	defer os.Remove(tarLocation)
-
-	err = downloadFile(tarLocation, url)
-	if err != nil {
-		return
-	}
-
-	if trim {
-		err = exec.Command("/bin/sh", "-c",
-			TarBin+" --strip-components 2 --include='*/"+fileName[:len(fileName)-7]+"/trunk/' -xf "+tarLocation+" -C "+path).Run()
-		os.Rename(path+"trunk", path+fileName[:len(fileName)-7]) // kurwa
-	} else {
-		err = exec.Command(TarBin, "-xf", tarLocation, "-C", path).Run()
-	}
-	if err != nil {
-		return
-	}
-
-	return
-}
-
-// Editor returns the prefered system editor.
-func Editor() string {
-	if os.Getenv("EDITOR") != "" {
-		return os.Getenv("EDITOR")
-	} else if os.Getenv("VISUAL") != "" {
-		return os.Getenv("VISUAL")
-	} else {
-		fmt.Printf("\x1b[1;31;40mWarning: \x1B[1;33;40m$EDITOR\x1b[0;37;40m is not set.\x1b[0m\nPlease add $EDITOR or to your environment variables.\n")
-
-	editorLoop:
-		fmt.Printf("\x1b[32m%s\x1b[0m ", "Edit PKGBUILD with:")
-		var editorInput string
-		_, err := fmt.Scanln(&editorInput)
-		if err != nil {
-			fmt.Println(err)
-			goto editorLoop
-		}
-
-		editor, err := exec.LookPath(editorInput)
-		if err != nil {
-			fmt.Println(err)
-			goto editorLoop
-		}
-		return editor
-	}
-}

+ 73 - 0
utils.go

@@ -0,0 +1,73 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"math"
+	"os"
+	"time"
+
+	"github.com/jguer/yay/aur"
+	"github.com/jguer/yay/config"
+	pac "github.com/jguer/yay/pacman"
+)
+
+// Complete provides completion info for shells
+func complete() (err error) {
+	path := os.Getenv("HOME") + "/.cache/yay/aur_" + config.YayConf.Shell + ".cache"
+
+	if info, err := os.Stat(path); os.IsNotExist(err) || time.Since(info.ModTime()).Hours() > 48 {
+		os.MkdirAll(os.Getenv("HOME")+"/.cache/yay/", 0755)
+
+		out, err := os.Create(path)
+		if err != nil {
+			return err
+		}
+
+		if aur.CreateAURList(out) != nil {
+			defer os.Remove(path)
+		}
+		err = pac.CreatePackageList(out)
+
+		out.Close()
+		return err
+	}
+
+	in, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0755)
+	if err != nil {
+		return err
+	}
+	defer in.Close()
+
+	_, err = io.Copy(os.Stdout, in)
+	return err
+}
+
+// Function by pyk https://github.com/pyk/byten
+func index(s int64) float64 {
+	x := math.Log(float64(s)) / math.Log(1024)
+	return math.Floor(x)
+}
+
+// Function by pyk https://github.com/pyk/byten
+func countSize(s int64, i float64) float64 {
+	return float64(s) / math.Pow(1024, math.Floor(i))
+}
+
+// Size return a formated string from file size
+// Function by pyk https://github.com/pyk/byten
+func size(s int64) string {
+
+	symbols := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
+	i := index(s)
+	if s < 10 {
+		return fmt.Sprintf("%dB", s)
+	}
+	size := countSize(s, i)
+	format := "%.0f"
+	if size < 10 {
+		format = "%.1f"
+	}
+
+	return fmt.Sprintf(format+"%s", size, symbols[int(i)])
+}

+ 237 - 0
yay.go

@@ -0,0 +1,237 @@
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/jguer/yay/aur"
+	vcs "github.com/jguer/yay/aur/vcs"
+	"github.com/jguer/yay/config"
+	pac "github.com/jguer/yay/pacman"
+)
+
+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
+
+    New options:
+    --topdown            shows repository's packages first and then aur's
+    --bottomup           shows aur's packages first and then repository's
+    --noconfirm          skip user input on package install
+	--devel			     Check -git/-svn/-hg development version
+	--nodevel			 Disable development version checking
+`)
+}
+
+var version = "2.116"
+
+func parser() (op string, options []string, packages []string, changedConfig bool, err error) {
+	if len(os.Args) < 2 {
+		err = fmt.Errorf("no operation specified")
+		return
+	}
+	changedConfig = false
+	op = "yogurt"
+
+	for _, arg := range os.Args[1:] {
+		if arg[0] == '-' && arg[1] != '-' {
+			switch arg {
+			default:
+				op = arg
+			}
+			continue
+		}
+
+		if arg[0] == '-' && arg[1] == '-' {
+			changedConfig = true
+			switch arg {
+			case "--gendb":
+				aur.CreateDevelDB()
+				vcs.SaveBranchInfo()
+				os.Exit(0)
+			case "--devel":
+				config.YayConf.Devel = true
+			case "--nodevel":
+				config.YayConf.Devel = false
+			case "--timeupdate":
+				config.YayConf.TimeUpdate = true
+			case "--notimeupdate":
+				config.YayConf.TimeUpdate = false
+			case "--topdown":
+				config.YayConf.SortMode = config.TopDown
+			case "--complete":
+				config.YayConf.Shell = "sh"
+				complete()
+				os.Exit(0)
+			case "--fcomplete":
+				config.YayConf.Shell = "fish"
+				complete()
+				os.Exit(0)
+			case "--help":
+				usage()
+				os.Exit(0)
+			case "--noconfirm":
+				config.YayConf.NoConfirm = true
+				fallthrough
+			default:
+				options = append(options, arg)
+			}
+			continue
+		}
+		packages = append(packages, arg)
+	}
+	return
+}
+
+func main() {
+	op, options, pkgs, changedConfig, err := parser()
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	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(version)
+	case "-Ss", "-Ssq", "-Sqs":
+		if op == "-Ss" {
+			config.YayConf.SearchMode = config.Detailed
+		} else {
+			config.YayConf.SearchMode = config.Minimal
+		}
+
+		if pkgs != nil {
+			err = syncSearch(pkgs)
+		}
+	case "-S":
+		err = install(pkgs, options)
+	case "-Syu", "-Suy":
+		err = upgrade(options)
+	case "-Si":
+		err = syncInfo(pkgs, options)
+	case "yogurt":
+		config.YayConf.SearchMode = config.NumberMenu
+
+		if pkgs != nil {
+			err = numberMenu(pkgs, options)
+		}
+	default:
+		if op[0] == 'R' {
+			vcs.RemovePackage(pkgs)
+		}
+		err = config.PassToPacman(op, pkgs, options)
+	}
+
+	if vcs.Updated {
+		vcs.SaveBranchInfo()
+	}
+
+	if changedConfig {
+		config.SaveConfig()
+	}
+
+	config.AlpmHandle.Release()
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+}
+
+// NumberMenu presents a CLI for selecting packages to install.
+func numberMenu(pkgS []string, flags []string) (err error) {
+	var num int
+
+	aq, err := aur.NarrowSearch(pkgS, true)
+	if err != nil {
+		fmt.Println("Error during AUR search:", err)
+	}
+	numaq := len(aq)
+	pq, numpq, err := pac.Search(pkgS)
+	if err != nil {
+		return
+	}
+
+	if numpq == 0 && numaq == 0 {
+		return fmt.Errorf("no packages match search")
+	}
+
+	if config.YayConf.SortMode == config.BottomUp {
+		printAURSearch(aq, numpq)
+		pq.PrintSearch()
+	} else {
+		pq.PrintSearch()
+		printAURSearch(aq, numpq)
+	}
+
+	fmt.Printf("\x1b[32m%s\x1b[0m\nNumbers: ", "Type numbers to install. Separate each number with a space.")
+	reader := bufio.NewReader(os.Stdin)
+	numberBuf, overflow, err := reader.ReadLine()
+	if err != nil || overflow {
+		fmt.Println(err)
+		return
+	}
+
+	numberString := string(numberBuf)
+	var aurInstall []string
+	var repoInstall []string
+	result := strings.Fields(numberString)
+	for _, numS := range result {
+		num, err = strconv.Atoi(numS)
+		if err != nil {
+			continue
+		}
+
+		// Install package
+		if num > numaq+numpq-1 || num < 0 {
+			continue
+		} else if num > numpq-1 {
+			if config.YayConf.SortMode == config.BottomUp {
+				aurInstall = append(aurInstall, aq[numaq+numpq-num-1].Name)
+			} else {
+				aurInstall = append(aurInstall, aq[num-numpq].Name)
+			}
+		} else {
+			if config.YayConf.SortMode == config.BottomUp {
+				repoInstall = append(repoInstall, pq[numpq-num-1].Name())
+			} else {
+				repoInstall = append(repoInstall, pq[num].Name())
+			}
+		}
+	}
+
+	if len(repoInstall) != 0 {
+		err = config.PassToPacman("-S", repoInstall, flags)
+	}
+
+	if len(aurInstall) != 0 {
+		err = aur.Install(aurInstall, flags)
+	}
+
+	return err
+}