mirror of
https://github.com/Jguer/yay.git
synced 2024-11-06 09:07:21 +01:00
5f2933271a
The order of targets does somewhat matter. For example doing something like 'pacman -S db1/foo db2/foo' should cause the second package to be skipped. The order of targets also effects in which order they are resolved. This should make errors more reproducable if any ever occur.
635 lines
15 KiB
Go
635 lines
15 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
rpc "github.com/mikkeloscar/aur"
|
|
)
|
|
|
|
const arrow = "==>"
|
|
const smallArrow = " ->"
|
|
|
|
func (warnings *aurWarnings) print() {
|
|
if len(warnings.Missing) > 0 {
|
|
fmt.Print(bold(yellow(smallArrow)) + " Missing AUR Packages:")
|
|
for _, name := range warnings.Missing {
|
|
fmt.Print(" " + cyan(name))
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
if len(warnings.Orphans) > 0 {
|
|
fmt.Print(bold(yellow(smallArrow)) + " Orphaned AUR Packages:")
|
|
for _, name := range warnings.Orphans {
|
|
fmt.Print(" " + cyan(name))
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
if len(warnings.OutOfDate) > 0 {
|
|
fmt.Print(bold(yellow(smallArrow)) + " Out Of Date AUR Packages:")
|
|
for _, name := range warnings.OutOfDate {
|
|
fmt.Print(" " + cyan(name))
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
}
|
|
|
|
// human method returns results in human readable format.
|
|
func human(size int64) string {
|
|
floatsize := float32(size)
|
|
units := [...]string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"}
|
|
for _, unit := range units {
|
|
if floatsize < 1024 {
|
|
return fmt.Sprintf("%.1f %sB", floatsize, unit)
|
|
}
|
|
floatsize /= 1024
|
|
}
|
|
return fmt.Sprintf("%d%s", size, "B")
|
|
}
|
|
|
|
// PrintSearch handles printing search results in a given format
|
|
func (q aurQuery) printSearch(start int) {
|
|
localDb, _ := alpmHandle.LocalDb()
|
|
|
|
for i, res := range q {
|
|
var toprint string
|
|
if config.SearchMode == NumberMenu {
|
|
if config.SortMode == BottomUp {
|
|
toprint += magenta(strconv.Itoa(len(q)+start-i-1) + " ")
|
|
} else {
|
|
toprint += magenta(strconv.Itoa(start+i) + " ")
|
|
}
|
|
} else if config.SearchMode == Minimal {
|
|
fmt.Println(res.Name)
|
|
continue
|
|
}
|
|
|
|
toprint += bold(colourHash("aur")) + "/" + bold(res.Name) +
|
|
" " + cyan(res.Version) +
|
|
bold(" (+"+strconv.Itoa(res.NumVotes)) +
|
|
" " + bold(strconv.FormatFloat(res.Popularity, 'f', 2, 64)+"%) ")
|
|
|
|
if res.Maintainer == "" {
|
|
toprint += bold(red("(Orphaned)")) + " "
|
|
}
|
|
|
|
if res.OutOfDate != 0 {
|
|
toprint += bold(red("(Out-of-date "+formatTime(res.OutOfDate)+")")) + " "
|
|
}
|
|
|
|
if pkg, err := localDb.PkgByName(res.Name); err == nil {
|
|
if pkg.Version() != res.Version {
|
|
toprint += bold(green("(Installed: " + pkg.Version() + ")"))
|
|
} else {
|
|
toprint += bold(green("(Installed)"))
|
|
}
|
|
}
|
|
toprint += "\n " + res.Description
|
|
fmt.Println(toprint)
|
|
}
|
|
}
|
|
|
|
// PrintSearch receives a RepoSearch type and outputs pretty text.
|
|
func (s repoQuery) printSearch() {
|
|
for i, res := range s {
|
|
var toprint string
|
|
if config.SearchMode == NumberMenu {
|
|
if config.SortMode == BottomUp {
|
|
toprint += magenta(strconv.Itoa(len(s)-i) + " ")
|
|
} else {
|
|
toprint += magenta(strconv.Itoa(i+1) + " ")
|
|
}
|
|
} else if config.SearchMode == Minimal {
|
|
fmt.Println(res.Name())
|
|
continue
|
|
}
|
|
|
|
toprint += bold(colourHash(res.DB().Name())) + "/" + bold(res.Name()) +
|
|
" " + cyan(res.Version()) +
|
|
bold(" ("+human(res.Size())+
|
|
" "+human(res.ISize())+") ")
|
|
|
|
if len(res.Groups().Slice()) != 0 {
|
|
toprint += fmt.Sprint(res.Groups().Slice(), " ")
|
|
}
|
|
|
|
localDb, err := alpmHandle.LocalDb()
|
|
if err == nil {
|
|
if pkg, err := localDb.PkgByName(res.Name()); err == nil {
|
|
if pkg.Version() != res.Version() {
|
|
toprint += bold(green("(Installed: " + pkg.Version() + ")"))
|
|
} else {
|
|
toprint += bold(green("(Installed)"))
|
|
}
|
|
}
|
|
}
|
|
|
|
toprint += "\n " + res.Description()
|
|
fmt.Println(toprint)
|
|
}
|
|
}
|
|
|
|
// Pretty print a set of packages from the same package base.
|
|
// Packages foo and bar from a pkgbase named base would print like so:
|
|
// base (foo bar)
|
|
func formatPkgbase(pkg *rpc.Pkg, bases map[string][]*rpc.Pkg) string {
|
|
str := pkg.PackageBase
|
|
if len(bases[pkg.PackageBase]) > 1 || pkg.PackageBase != pkg.Name {
|
|
str2 := " ("
|
|
for _, split := range bases[pkg.PackageBase] {
|
|
str2 += split.Name + " "
|
|
}
|
|
str2 = str2[:len(str2)-1] + ")"
|
|
|
|
str += str2
|
|
}
|
|
|
|
return str
|
|
}
|
|
|
|
func (u upgrade) StylizedNameWithRepository() string {
|
|
return bold(colourHash(u.Repository)) + "/" + bold(u.Name)
|
|
}
|
|
|
|
// Print prints the details of the packages to upgrade.
|
|
func (u upSlice) print() {
|
|
longestName, longestVersion := 0, 0
|
|
for _, pack := range u {
|
|
packNameLen := len(pack.StylizedNameWithRepository())
|
|
version, _ := getVersionDiff(pack.LocalVersion, pack.RemoteVersion)
|
|
packVersionLen := len(version)
|
|
longestName = max(packNameLen, longestName)
|
|
longestVersion = max(packVersionLen, longestVersion)
|
|
}
|
|
|
|
namePadding := fmt.Sprintf("%%-%ds ", longestName)
|
|
versionPadding := fmt.Sprintf("%%-%ds", longestVersion)
|
|
numberPadding := fmt.Sprintf("%%%dd ", len(fmt.Sprintf("%v", len(u))))
|
|
|
|
for k, i := range u {
|
|
left, right := getVersionDiff(i.LocalVersion, i.RemoteVersion)
|
|
|
|
fmt.Print(magenta(fmt.Sprintf(numberPadding, len(u)-k)))
|
|
|
|
fmt.Printf(namePadding, i.StylizedNameWithRepository())
|
|
|
|
fmt.Printf("%s -> %s\n", fmt.Sprintf(versionPadding, left), right)
|
|
}
|
|
}
|
|
|
|
// printDownloadsFromRepo prints repository packages to be downloaded
|
|
func (do *depOrder) Print() {
|
|
repo := ""
|
|
repoMake := ""
|
|
aur := ""
|
|
aurMake := ""
|
|
|
|
repoLen := 0
|
|
repoMakeLen := 0
|
|
aurLen := 0
|
|
aurMakeLen := 0
|
|
|
|
for _, pkg := range do.Repo {
|
|
if do.Runtime.get(pkg.Name()) {
|
|
repo += " " + pkg.Name() + "-" + pkg.Version()
|
|
repoLen++
|
|
} else {
|
|
repoMake += " " + pkg.Name() + "-" + pkg.Version()
|
|
repoMakeLen++
|
|
}
|
|
}
|
|
|
|
for _, pkg := range do.Aur {
|
|
pkgStr := " " + pkg.PackageBase + "-" + pkg.Version
|
|
pkgStrMake := pkgStr
|
|
|
|
push := false
|
|
pushMake := false
|
|
|
|
if len(do.Bases[pkg.PackageBase]) > 1 || pkg.PackageBase != pkg.Name {
|
|
pkgStr += " ("
|
|
pkgStrMake += " ("
|
|
|
|
for _, split := range do.Bases[pkg.PackageBase] {
|
|
if do.Runtime.get(split.Name) {
|
|
pkgStr += split.Name + " "
|
|
aurLen++
|
|
push = true
|
|
} else {
|
|
pkgStrMake += split.Name + " "
|
|
aurMakeLen++
|
|
pushMake = true
|
|
}
|
|
}
|
|
|
|
pkgStr = pkgStr[:len(pkgStr)-1] + ")"
|
|
pkgStrMake = pkgStrMake[:len(pkgStrMake)-1] + ")"
|
|
} else if do.Runtime.get(pkg.Name) {
|
|
aurLen++
|
|
push = true
|
|
} else {
|
|
aurMakeLen++
|
|
pushMake = true
|
|
}
|
|
|
|
if push {
|
|
aur += pkgStr
|
|
}
|
|
if pushMake {
|
|
aurMake += pkgStrMake
|
|
}
|
|
}
|
|
|
|
printDownloads("Repo", repoLen, repo)
|
|
printDownloads("Repo Make", repoMakeLen, repoMake)
|
|
printDownloads("Aur", aurLen, aur)
|
|
printDownloads("Aur Make", aurMakeLen, aurMake)
|
|
}
|
|
|
|
func printDownloads(repoName string, length int, packages string) {
|
|
if length < 1 {
|
|
return
|
|
}
|
|
|
|
repoInfo := bold(blue(
|
|
"[" + repoName + ": " + strconv.Itoa(length) + "]"))
|
|
fmt.Println(repoInfo + cyan(packages))
|
|
}
|
|
|
|
func printInfoValue(str, value string) {
|
|
fmt.Printf(bold("%-16s%s")+" %s\n", str, ":", value)
|
|
}
|
|
|
|
// PrintInfo prints package info like pacman -Si.
|
|
func PrintInfo(a *rpc.Pkg) {
|
|
printInfoValue("Repository", "aur")
|
|
printInfoValue("Name", a.Name)
|
|
printInfoValue("Version", a.Version)
|
|
printInfoValue("Description", a.Description)
|
|
printInfoValue("URL", a.URL)
|
|
printInfoValue("Licenses", strings.Join(a.License, " "))
|
|
printInfoValue("Provides", strings.Join(a.Provides, " "))
|
|
printInfoValue("Depends On", strings.Join(a.Depends, " "))
|
|
printInfoValue("Make Deps", strings.Join(a.MakeDepends, " "))
|
|
printInfoValue("Check Deps", strings.Join(a.CheckDepends, " "))
|
|
printInfoValue("Optional Deps", strings.Join(a.OptDepends, " "))
|
|
printInfoValue("Conflicts With", strings.Join(a.Conflicts, " "))
|
|
printInfoValue("Maintainer", a.Maintainer)
|
|
printInfoValue("Votes", fmt.Sprintf("%d", a.NumVotes))
|
|
printInfoValue("Popularity", fmt.Sprintf("%f", a.Popularity))
|
|
if a.OutOfDate != 0 {
|
|
printInfoValue("Out-of-date", "Yes ["+formatTime(a.OutOfDate)+"]")
|
|
} else {
|
|
printInfoValue("Out-of-date", "No")
|
|
}
|
|
|
|
fmt.Println()
|
|
}
|
|
|
|
// BiggestPackages prints the name of the ten biggest packages in the system.
|
|
func biggestPackages() {
|
|
localDb, err := alpmHandle.LocalDb()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
pkgCache := localDb.PkgCache()
|
|
pkgS := pkgCache.SortBySize().Slice()
|
|
|
|
if len(pkgS) < 10 {
|
|
return
|
|
}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
fmt.Println(bold(pkgS[i].Name()) + ": " + cyan(human(pkgS[i].ISize())))
|
|
}
|
|
// Could implement size here as well, but we just want the general idea
|
|
}
|
|
|
|
// localStatistics prints installed packages statistics.
|
|
func localStatistics() error {
|
|
info, err := statistics()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, _, _, remoteNames, err := filterPackages()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf(bold("Yay version v%s\n"), version)
|
|
fmt.Println(bold(cyan("===========================================")))
|
|
fmt.Println(bold(green("Total installed packages: ")) + cyan(strconv.Itoa(info.Totaln)))
|
|
fmt.Println(bold(green("Total foreign installed packages: ")) + cyan(strconv.Itoa(len(remoteNames))))
|
|
fmt.Println(bold(green("Explicitly installed packages: ")) + cyan(strconv.Itoa(info.Expln)))
|
|
fmt.Println(bold(green("Total Size occupied by packages: ")) + cyan(human(info.TotalSize)))
|
|
fmt.Println(bold(cyan("===========================================")))
|
|
fmt.Println(bold(green("Ten biggest packages:")))
|
|
biggestPackages()
|
|
fmt.Println(bold(cyan("===========================================")))
|
|
|
|
aurInfoPrint(remoteNames)
|
|
|
|
return nil
|
|
}
|
|
|
|
//TODO: Make it less hacky
|
|
func printNumberOfUpdates() error {
|
|
//todo
|
|
warnings := &aurWarnings{}
|
|
old := os.Stdout // keep backup of the real stdout
|
|
os.Stdout = nil
|
|
aurUp, repoUp, err := upList(warnings)
|
|
os.Stdout = old // restoring the real stdout
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(len(aurUp) + len(repoUp))
|
|
|
|
return nil
|
|
}
|
|
|
|
//TODO: Make it less hacky
|
|
func printUpdateList(parser *arguments) error {
|
|
targets := sliceToStringSet(parser.targets)
|
|
warnings := &aurWarnings{}
|
|
old := os.Stdout // keep backup of the real stdout
|
|
os.Stdout = nil
|
|
_, _, localNames, remoteNames, err := filterPackages()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
aurUp, repoUp, err := upList(warnings)
|
|
os.Stdout = old // restoring the real stdout
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
noTargets := len(targets) == 0
|
|
|
|
if !parser.existsArg("m", "foreign") {
|
|
for _, pkg := range repoUp {
|
|
if noTargets || targets.get(pkg.Name) {
|
|
fmt.Printf("%s %s -> %s\n", bold(pkg.Name), green(pkg.LocalVersion), green(pkg.RemoteVersion))
|
|
delete(targets, pkg.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
if !parser.existsArg("n", "native") {
|
|
for _, pkg := range aurUp {
|
|
if noTargets || targets.get(pkg.Name) {
|
|
fmt.Printf("%s %s -> %s\n", bold(pkg.Name), green(pkg.LocalVersion), green(pkg.RemoteVersion))
|
|
delete(targets, pkg.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
missing := false
|
|
|
|
outer:
|
|
for pkg := range targets {
|
|
for _, name := range localNames {
|
|
if name == pkg {
|
|
continue outer
|
|
}
|
|
}
|
|
|
|
for _, name := range remoteNames {
|
|
if name == pkg {
|
|
continue outer
|
|
}
|
|
}
|
|
|
|
fmt.Println(red(bold("error:")), "package '"+pkg+"' was not found")
|
|
missing = true
|
|
}
|
|
|
|
if missing {
|
|
return fmt.Errorf("")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type item struct {
|
|
Title string `xml:"title"`
|
|
Link string `xml:"link"`
|
|
Description string `xml:"description"`
|
|
PubDate string `xml:"pubDate"`
|
|
Creator string `xml:"dc:creator"`
|
|
}
|
|
|
|
func (item item) print(buildTime time.Time) {
|
|
var fd string
|
|
date, err := time.Parse(time.RFC1123Z, item.PubDate)
|
|
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
} else {
|
|
fd = formatTime(int(date.Unix()))
|
|
if _, double, _ := cmdArgs.getArg("news", "w"); !double && !buildTime.IsZero() {
|
|
if buildTime.After(date) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Println(bold(magenta(fd)), bold(strings.TrimSpace(item.Title)))
|
|
//fmt.Println(strings.TrimSpace(item.Link))
|
|
|
|
if !cmdArgs.existsArg("q", "quiet") {
|
|
desc := strings.TrimSpace(parseNews(item.Description))
|
|
fmt.Println(desc)
|
|
}
|
|
}
|
|
|
|
type channel struct {
|
|
Title string `xml:"title"`
|
|
Link string `xml:"link"`
|
|
Description string `xml:"description"`
|
|
Language string `xml:"language"`
|
|
Lastbuilddate string `xml:"lastbuilddate"`
|
|
Items []item `xml:"item"`
|
|
}
|
|
|
|
type rss struct {
|
|
Channel channel `xml:"channel"`
|
|
}
|
|
|
|
func printNewsFeed() error {
|
|
resp, err := http.Get("https://archlinux.org/feeds/news")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rss := rss{}
|
|
|
|
d := xml.NewDecoder(bytes.NewReader(body))
|
|
err = d.Decode(&rss)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
buildTime, err := lastBuildTime()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if config.SortMode == BottomUp {
|
|
for i := len(rss.Channel.Items) - 1; i >= 0; i-- {
|
|
rss.Channel.Items[i].print(buildTime)
|
|
}
|
|
} else {
|
|
for i := 0; i < len(rss.Channel.Items); i++ {
|
|
rss.Channel.Items[i].print(buildTime)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Formats a unix timestamp to ISO 8601 date (yyyy-mm-dd)
|
|
func formatTime(i int) string {
|
|
t := time.Unix(int64(i), 0)
|
|
return t.Format("2006-01-02")
|
|
}
|
|
|
|
const (
|
|
redCode = "\x1b[31m"
|
|
greenCode = "\x1b[32m"
|
|
yellowCode = "\x1b[33m"
|
|
blueCode = "\x1b[34m"
|
|
magentaCode = "\x1b[35m"
|
|
cyanCode = "\x1b[36m"
|
|
boldCode = "\x1b[1m"
|
|
|
|
resetCode = "\x1b[0m"
|
|
)
|
|
|
|
func stylize(startCode, in string) string {
|
|
if useColor {
|
|
return startCode + in + resetCode
|
|
}
|
|
|
|
return in
|
|
}
|
|
|
|
func red(in string) string {
|
|
return stylize(redCode, in)
|
|
}
|
|
|
|
func green(in string) string {
|
|
return stylize(greenCode, in)
|
|
}
|
|
|
|
func yellow(in string) string {
|
|
return stylize(yellowCode, in)
|
|
}
|
|
|
|
func blue(in string) string {
|
|
return stylize(blueCode, in)
|
|
}
|
|
|
|
func cyan(in string) string {
|
|
return stylize(cyanCode, in)
|
|
}
|
|
|
|
func magenta(in string) string {
|
|
return stylize(magentaCode, in)
|
|
}
|
|
|
|
func bold(in string) string {
|
|
return stylize(boldCode, in)
|
|
}
|
|
|
|
// Colours text using a hashing algorithm. The same text will always produce the
|
|
// same colour while different text will produce a different colour.
|
|
func colourHash(name string) (output string) {
|
|
if !useColor {
|
|
return name
|
|
}
|
|
var hash = 5381
|
|
for i := 0; i < len(name); i++ {
|
|
hash = int(name[i]) + ((hash << 5) + (hash))
|
|
}
|
|
return fmt.Sprintf("\x1b[%dm%s\x1b[0m", hash%6+31, name)
|
|
}
|
|
|
|
func providerMenu(dep string, providers providers) *rpc.Pkg {
|
|
size := providers.Len()
|
|
|
|
fmt.Print(bold(cyan(":: ")))
|
|
str := bold(fmt.Sprintf(bold("There are %d providers available for %s:"), size, dep))
|
|
|
|
size = 1
|
|
str += bold(cyan("\n:: ")) + bold("Repository AUR\n ")
|
|
|
|
for _, pkg := range providers.Pkgs {
|
|
str += fmt.Sprintf("%d) %s ", size, pkg.Name)
|
|
size++
|
|
}
|
|
|
|
fmt.Println(str)
|
|
|
|
for {
|
|
fmt.Print("\nEnter a number (default=1): ")
|
|
|
|
if config.NoConfirm {
|
|
fmt.Println()
|
|
break
|
|
}
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
numberBuf, overflow, err := reader.ReadLine()
|
|
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
break
|
|
}
|
|
|
|
if overflow {
|
|
fmt.Println("Input too long")
|
|
continue
|
|
}
|
|
|
|
if string(numberBuf) == "" {
|
|
return providers.Pkgs[0]
|
|
}
|
|
|
|
num, err := strconv.Atoi(string(numberBuf))
|
|
if err != nil {
|
|
fmt.Printf("%s invalid number: %s\n", red("error:"), string(numberBuf))
|
|
continue
|
|
}
|
|
|
|
if num < 1 || num > size {
|
|
fmt.Printf("%s invalid value: %d is not between %d and %d\n", red("error:"), num, 1, size)
|
|
continue
|
|
}
|
|
|
|
return providers.Pkgs[num-1]
|
|
}
|
|
|
|
return nil
|
|
}
|