diff --git a/README.md b/README.md index 3fd14d05..1abee1c6 100644 --- a/README.md +++ b/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) diff --git a/actions.go b/actions.go index 4245afe3..d808d41c 100644 --- a/actions.go +++ b/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") - } + err = aur.Install(aurs, flags) - 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) - } - - 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 -} diff --git a/aur/aur.go b/aur/aur.go index bcb41620..969d31fa 100644 --- a/aur/aur.go +++ b/aur/aur.go @@ -2,35 +2,99 @@ 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 } -// 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 { +// CreateDevelDB forces yay to create a DB of the existing development packages +func CreateDevelDB() error { + foreign, err := pacman.ForeignPackages() + if err != nil { return err } @@ -41,36 +105,101 @@ func Upgrade(flags []string) error { i++ } - q, _, err := MultiInfo(keys) + 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, err := pacman.ForeignPackages() + if err != nil { + return err + } + keys := make([]string, len(foreign)) + i := 0 + for k := range foreign { + keys[i] = k + i++ + } + + 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 { diff --git a/aur/aur_test.go b/aur/aur_test.go deleted file mode 100644 index edae5abb..00000000 --- a/aur/aur_test.go +++ /dev/null @@ -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 -} diff --git a/aur/query.go b/aur/query.go index f6fa08d1..80b00088 100644 --- a/aur/query.go +++ b/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 { diff --git a/aur/result.go b/aur/result.go index 88ee7696..03382b3d 100644 --- a/aur/result.go +++ b/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) { - 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 + "/" +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 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 - } + if _, err = os.Stat(dir); !os.IsNotExist(err) { + if !config.ContinueTask("Directory exists. Clean Build?", "yY") { + _ = os.RemoveAll(config.YayConf.BuildDir + a.PackageBase) } } - if !util.ContinueTask("Edit PKGBUILD?", "yY") { - editcmd := exec.Command(util.Editor(), dir+"PKGBUILD") + 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() } - runDeps, makeDeps, err := a.Dependencies() + 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") + } + + _, err = setupPackageSpace(a) + if err != nil { + return + } + + if specialDBsauce { + return + } + + 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) diff --git a/aur/utils.go b/aur/utils.go deleted file mode 100644 index 4b77206d..00000000 --- a/aur/utils.go +++ /dev/null @@ -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) -} diff --git a/aur/vcs/github.go b/aur/vcs/github.go new file mode 100644 index 00000000..e4d58751 --- /dev/null +++ b/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 +} diff --git a/aur/vcs/github_test.go b/aur/vcs/github_test.go new file mode 100644 index 00000000..6f6fefec --- /dev/null +++ b/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) + } +} diff --git a/cmd/yay/yay.go b/cmd/yay/yay.go deleted file mode 100644 index 294e0e49..00000000 --- a/cmd/yay/yay.go +++ /dev/null @@ -1,139 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/jguer/yay" - "github.com/jguer/yay/util" -) - -func usage() { - fmt.Println(`usage: yay [...] - operations: - yay {-h --help} - yay {-V --version} - yay {-D --database} - yay {-F --files} [options] [package(s)] - yay {-Q --query} [options] [package(s)] - yay {-R --remove} [options] - yay {-S --sync} [options] [package(s)] - yay {-T --deptest} [options] [package(s)] - yay {-U --upgrade} [options] - - 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) - } -} diff --git a/config/config.go b/config/config.go new file mode 100644 index 00000000..42c35bec --- /dev/null +++ b/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 +} diff --git a/pacman/pacman.go b/pacman/pacman.go index 894204bf..b60502cd 100644 --- a/pacman/pacman.go +++ b/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) + res.DB().Name(), res.Name(), res.Version()) - if len(res.Group) != 0 { - toprint += fmt.Sprintf("(%s) ", res.Group) + if len(res.Groups().Slice()) != 0 { + toprint += fmt.Sprint(res.Groups().Slice(), " ") } - if res.Installed { - toprint += fmt.Sprintf("\x1b[32;40mInstalled\x1b[0m") - } - - 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() + localDb, err := config.AlpmHandle.LocalDb() if err != nil { return } - - localDb, err := h.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() +func ForeignPackages() (foreign map[string]alpm.Package, err error) { + localDb, err := config.AlpmHandle.LocalDb() + if err != nil { + return + } + dbList, err := config.AlpmHandle.SyncDbs() if err != nil { return } - localDb, err := h.LocalDb() - if err != nil { - return - } - dbList, err := h.SyncDbs() - if err != nil { - return - } + foreign = make(map[string]alpm.Package) - 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 + 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") - } - + _ = 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 - } - - f := func(db alpm.Db) error { - db.PkgCache().ForEach(p) - return nil - } - - dbList.ForEach(f) + }) return nil } diff --git a/pacman/pacman_test.go b/pacman/pacman_test.go index 7d4f3a98..9a7ff12b 100644 --- a/pacman/pacman_test.go +++ b/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) } diff --git a/query.go b/query.go new file mode 100644 index 00000000..3a8e528a --- /dev/null +++ b/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 +} diff --git a/util/util.go b/util/util.go deleted file mode 100644 index 29b815e2..00000000 --- a/util/util.go +++ /dev/null @@ -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 - } -} diff --git a/utils.go b/utils.go new file mode 100644 index 00000000..29ea4b30 --- /dev/null +++ b/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)]) +} diff --git a/yay.go b/yay.go new file mode 100644 index 00000000..c1e5e136 --- /dev/null +++ b/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 [...] + operations: + yay {-h --help} + yay {-V --version} + yay {-D --database} + yay {-F --files} [options] [package(s)] + yay {-Q --query} [options] [package(s)] + yay {-R --remove} [options] + yay {-S --sync} [options] [package(s)] + yay {-T --deptest} [options] [package(s)] + yay {-U --upgrade} [options] + + 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 +}