mirror of
https://github.com/Jguer/yay
synced 2024-10-14 20:12:22 +00:00
43feb12c85
depOrder.Aur contains the order in which AUR packages are to be installed. While depOrder.bases contains the actual package data organized by pkgbase. deoOrder.AUR is kind of counterintuitive, as it only contains one package from each package base. For example if you were to install libc++{,abi,experimental}, depOrder.Aur would only contain one of those packages, which one actually being quite random. depOrder.Bases[pkg.Pkgbase] will then be looked up and everything under that slice would be installed. This means that the only real use depOrder.Aur has, is to give the pkgbase. So to cut out the middleman, lets merge .Aur and .Bases into a single field. Doing this has also heped to spot som subtle bugs: Fix subtle split package errors. The number menus now correctly list and respect (installed) for bases that have atleast one of their packages installed. Entering package names in number menus now always expects the pkgbase name instead of the random package which happened to make it into .Aur. --rebuild and --redownload correctly handles split packages. formatPkgbase is also used more.
654 lines
15 KiB
Go
654 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(base []*rpc.Pkg) string {
|
|
pkg := base[0]
|
|
str := pkg.PackageBase
|
|
if len(base) > 1 || pkg.PackageBase != pkg.Name {
|
|
str2 := " ("
|
|
for _, split := range base {
|
|
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 _, base := range do.Aur {
|
|
pkg := base.Pkgbase()
|
|
pkgStr := " " + pkg + "-" + base[0].Version
|
|
pkgStrMake := pkgStr
|
|
|
|
push := false
|
|
pushMake := false
|
|
|
|
if len(base) > 1 || pkg != base[0].Name {
|
|
pkgStr += " ("
|
|
pkgStrMake += " ("
|
|
|
|
for _, split := range base {
|
|
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(base[0].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) {
|
|
if value == "" {
|
|
value = "None"
|
|
}
|
|
|
|
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("Keywords", strings.Join(a.Keywords, " "))
|
|
printInfoValue("Version", a.Version)
|
|
printInfoValue("Description", a.Description)
|
|
printInfoValue("URL", a.URL)
|
|
printInfoValue("AUR URL", baseURL+"/packages/"+a.Name)
|
|
printInfoValue("Groups", strings.Join(a.Groups, " "))
|
|
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))
|
|
printInfoValue("First Submitted", formatTime(a.FirstSubmitted))
|
|
printInfoValue("Last Modified", formatTime(a.LastModified))
|
|
|
|
if a.OutOfDate != 0 {
|
|
printInfoValue("Out-of-date", "Yes ["+formatTime(a.OutOfDate)+"]")
|
|
} else {
|
|
printInfoValue("Out-of-date", "No")
|
|
}
|
|
|
|
if cmdArgs.existsDouble("i") {
|
|
printInfoValue("ID", fmt.Sprintf("%d", a.ID))
|
|
printInfoValue("Package Base ID", fmt.Sprintf("%d", a.PackageBaseID))
|
|
printInfoValue("Package Base", a.PackageBase)
|
|
printInfoValue("Snapshot URL", baseURL+a.URLPath)
|
|
}
|
|
|
|
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("1")
|
|
return providers.Pkgs[0]
|
|
}
|
|
|
|
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
|
|
}
|