Files

185 lines
3.5 KiB
Go

package updater
import (
"bufio"
"context"
"os/exec"
"strings"
)
type Package struct {
Source string
Name string
Current string
Available string
Ignored bool
}
type Result struct {
Packages []Package
Warnings []string
}
type Options struct {
CheckAUR bool
IgnoredPackages []string
}
func (r Result) Total() int {
total := 0
for _, pkg := range r.Packages {
if !pkg.Ignored {
total++
}
}
return total
}
func (r Result) IgnoredTotal() int {
total := 0
for _, pkg := range r.Packages {
if pkg.Ignored {
total++
}
}
return total
}
func (r Result) Summary() string {
total := r.Total()
switch total {
case 0:
return "No updates available."
case 1:
return "1 update available."
default:
return strings.TrimSpace(strings.Join([]string{itoa(total), "updates available."}, " "))
}
}
func Check(ctx context.Context) (Result, error) {
return CheckWithOptions(ctx, Options{CheckAUR: true})
}
func CheckWithOptions(ctx context.Context, opts Options) (Result, error) {
var result Result
pacman, err := checkPacman(ctx)
if err != nil {
result.Warnings = append(result.Warnings, err.Error())
}
result.Packages = append(result.Packages, pacman...)
if opts.CheckAUR {
aur, err := checkAUR(ctx)
if err != nil {
result.Warnings = append(result.Warnings, err.Error())
}
result.Packages = append(result.Packages, aur...)
}
markIgnored(&result, opts.IgnoredPackages)
return result, nil
}
func markIgnored(result *Result, names []string) {
ignored := map[string]bool{}
for _, name := range names {
name = strings.TrimSpace(name)
if name != "" {
ignored[name] = true
}
}
if len(ignored) == 0 {
return
}
for i := range result.Packages {
result.Packages[i].Ignored = ignored[result.Packages[i].Name]
}
}
func checkPacman(ctx context.Context) ([]Package, error) {
if _, err := exec.LookPath("checkupdates"); err == nil {
out, err := exec.CommandContext(ctx, "checkupdates").Output()
if err != nil {
return nil, nil
}
return parseUpdates("pacman", string(out)), nil
}
out, err := exec.CommandContext(ctx, "pacman", "-Qu").Output()
if err != nil {
return nil, nil
}
return parseUpdates("pacman", string(out)), nil
}
func checkAUR(ctx context.Context) ([]Package, error) {
helper := AURHelper()
if helper == "" {
return nil, nil
}
out, err := exec.CommandContext(ctx, helper, "-Qua").Output()
if err != nil {
return nil, nil
}
return parseUpdates("aur", string(out)), nil
}
func AURHelper() string {
for _, name := range []string{"paru", "yay"} {
if _, err := exec.LookPath(name); err == nil {
return name
}
}
return ""
}
func parseUpdates(source, output string) []Package {
var packages []Package
scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
packages = append(packages, parseLine(source, line))
}
return packages
}
func parseLine(source, line string) Package {
fields := strings.Fields(line)
pkg := Package{Source: source}
if len(fields) == 0 {
return pkg
}
pkg.Name = fields[0]
if len(fields) >= 4 && fields[2] == "->" {
pkg.Current = fields[1]
pkg.Available = fields[3]
return pkg
}
if len(fields) >= 3 && fields[1] == "->" {
pkg.Available = fields[2]
return pkg
}
return pkg
}
func itoa(n int) string {
if n == 0 {
return "0"
}
var buf [20]byte
i := len(buf)
for n > 0 {
i--
buf[i] = byte('0' + n%10)
n /= 10
}
return string(buf[i:])
}