123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 |
- package main
- import (
- "bufio"
- "encoding/json"
- "errors"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "strconv"
- "strings"
- "time"
- )
- var cmdArgs = makeArguments()
- func usage() {
- fmt.Println(`Usage:
- yay <operation> [...]
- yay <package(s)>
- operations:
- yay {-h --help}
- yay {-V --version}
- yay {-D --database} <options> <package(s)>
- yay {-F --files} [options] [package(s)]
- yay {-Q --query} [options] [package(s)]
- yay {-R --remove} [options] <package(s)>
- yay {-S --sync} [options] [package(s)]
- yay {-T --deptest} [options] [package(s)]
- yay {-U --upgrade} [options] <file(s)>
- New operations:
- yay {-Y --yay} [options] [package(s)]
- yay {-P --print} [options]
- yay {-G --getpkgbuild} [package(s)]
- Permanent configuration options:
- --topdown Shows repository's packages first and then AUR's
- --bottomup Shows AUR's packages first and then repository's
- --devel Check -git/-svn/-hg development version
- --nodevel Disable development version checking
- --afterclean Clean package sources after successful build
- --noafterclean Disable package sources cleaning after successful build
- --timeupdate Check package's modification date and version
- --notimeupdate Check only package version change
- Print specific options:
- -c --complete Used for completions
- -d --defaultconfig Print default yay configuration
- -g --config Print current yay configuration
- -n --numberupgrades Print number of updates
- -s --stats Display system package statistics
- -u --upgrades Print update list
- Yay specific options:
- -g --getpkgbuild Download PKGBUILD from ABS or AUR
- -c --clean Remove unneeded dependencies
- --gendb Generates development package DB used for updating.
- If no operation is provided -Y will be assumed`)
- }
- func initYay() (err error) {
- var configHome string // configHome handles config directory home
- var cacheHome string // cacheHome handles cache home
- if 0 == os.Geteuid() {
- fmt.Println("Please avoid running yay as root/sudo.")
- }
- if configHome = os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
- if info, err := os.Stat(configHome); err == nil && info.IsDir() {
- configHome = configHome + "/yay"
- } else {
- configHome = os.Getenv("HOME") + "/.config/yay"
- }
- } else {
- configHome = os.Getenv("HOME") + "/.config/yay"
- }
- if cacheHome = os.Getenv("XDG_CACHE_HOME"); cacheHome != "" {
- if info, err := os.Stat(cacheHome); err == nil && info.IsDir() {
- cacheHome = cacheHome + "/yay"
- } else {
- cacheHome = os.Getenv("HOME") + "/.cache/yay"
- }
- } else {
- cacheHome = os.Getenv("HOME") + "/.cache/yay"
- }
- configFile = configHome + "/config.json"
- vcsFile = configHome + "/yay_vcs.json"
- completionFile = cacheHome + "/aur_"
- ////////////////
- // yay config //
- ////////////////
- defaultSettings(&config)
- if _, err = os.Stat(configFile); os.IsNotExist(err) {
- err = os.MkdirAll(filepath.Dir(configFile), 0755)
- if err != nil {
- err = fmt.Errorf("Unable to create config directory:\n%s\n"+
- "The error was:\n%s", filepath.Dir(configFile), err)
- return
- }
- // Save the default config if nothing is found
- config.saveConfig()
- } else {
- cfile, errf := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE, 0644)
- if errf != nil {
- fmt.Printf("Error reading config: %s\n", err)
- } else {
- defer cfile.Close()
- decoder := json.NewDecoder(cfile)
- err = decoder.Decode(&config)
- if err != nil {
- fmt.Println("Loading default Settings.\nError reading config:",
- err)
- defaultSettings(&config)
- }
- }
- }
- /////////////////
- // vcs config //
- ////////////////
- vfile, err := os.OpenFile(vcsFile, os.O_RDONLY|os.O_CREATE, 0644)
- if err == nil {
- defer vfile.Close()
- decoder := json.NewDecoder(vfile)
- _ = decoder.Decode(&savedInfo)
- }
- return
- }
- func initAlpm() (err error) {
- /////////////////
- // alpm config //
- /////////////////
- var value string
- var exists bool
- //var double bool
- value, _, exists = cmdArgs.getArg("config")
- if exists {
- config.PacmanConf = value
- }
- alpmConf, err = readAlpmConfig(config.PacmanConf)
- if err != nil {
- err = fmt.Errorf("Unable to read Pacman conf: %s", err)
- return
- }
- value, _, exists = cmdArgs.getArg("dbpath", "b")
- if exists {
- alpmConf.DBPath = value
- }
- value, _, exists = cmdArgs.getArg("root", "r")
- if exists {
- alpmConf.RootDir = value
- }
- value, _, exists = cmdArgs.getArg("arch")
- if exists {
- alpmConf.Architecture = value
- }
- //TODO
- //current system does not allow duplicate arguments
- //but pacman allows multiple cachdirs to be passed
- //for now only handle one cache dir
- value, _, exists = cmdArgs.getArg("cachdir")
- if exists {
- alpmConf.CacheDir = []string{value}
- }
- value, _, exists = cmdArgs.getArg("gpgdir")
- if exists {
- alpmConf.GPGDir = value
- }
- alpmHandle, err = alpmConf.CreateHandle()
- if err != nil {
- err = fmt.Errorf("Unable to CreateHandle: %s", err)
- return
- }
- return
- }
- func main() {
- var status int
- var err error
- err = cmdArgs.parseCommandLine()
- if err != nil {
- fmt.Println(err)
- status = 1
- goto cleanup
- }
- err = initYay()
- if err != nil {
- fmt.Println(err)
- status = 1
- goto cleanup
- }
- err = initAlpm()
- if err != nil {
- fmt.Println(err)
- status = 1
- goto cleanup
- }
- err = handleCmd()
- if err != nil {
- fmt.Println(err)
- status = 1
- goto cleanup
- }
- //ive used a goto here
- //i think its the best way to do this sort of thing
- cleanup:
- //cleanup
- //from here on out dont exit if an error occurs
- //if we fail to save the configuration
- //at least continue on and try clean up other parts
- if changedConfig {
- err = config.saveConfig()
- if err != nil {
- fmt.Println(err)
- status = 1
- }
- }
- if alpmHandle != nil {
- err = alpmHandle.Release()
- if err != nil {
- fmt.Println(err)
- status = 1
- }
- }
- os.Exit(status)
- }
- func sudoLoopBackground() {
- updateSudo()
- go sudoLoop()
- }
- func sudoLoop() {
- for {
- updateSudo()
- time.Sleep(298 * time.Second)
- }
- }
- func updateSudo() {
- for {
- cmd := exec.Command("sudo", "-v")
- cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
- err := cmd.Run()
- if err != nil {
- fmt.Println(err)
- } else {
- break
- }
- }
- }
- func handleCmd() (err error) {
- for option := range cmdArgs.options {
- if handleConfig(option) {
- cmdArgs.delArg(option)
- }
- }
- for option := range cmdArgs.globals {
- if handleConfig(option) {
- cmdArgs.delArg(option)
- }
- }
- if config.SudoLoop == true && cmdArgs.needRoot() {
- sudoLoopBackground()
- }
- switch cmdArgs.op {
- case "V", "version":
- handleVersion()
- case "D", "database":
- passToPacman(cmdArgs)
- case "F", "files":
- passToPacman(cmdArgs)
- case "Q", "query":
- passToPacman(cmdArgs)
- case "R", "remove":
- handleRemove()
- case "S", "sync":
- err = handleSync()
- case "T", "deptest":
- passToPacman(cmdArgs)
- case "U", "upgrade":
- passToPacman(cmdArgs)
- case "G", "getpkgbuild":
- err = handleGetpkgbuild()
- case "P", "print":
- err = handlePrint()
- case "Y", "--yay":
- err = handleYay()
- default:
- //this means we allowed an op but not implement it
- //if this happens it an error in the code and not the usage
- err = fmt.Errorf("unhandled operation")
- }
- return
- }
- //this function should only set config options
- //but currently still uses the switch left over from old code
- //eventually this should be refactored out futher
- //my current plan is to have yay specific operations in its own operator
- //e.g. yay -Y --gendb
- //e.g yay -Yg
- func handleConfig(option string) bool {
- switch option {
- case "afterclean":
- config.CleanAfter = true
- case "noafterclean":
- config.CleanAfter = false
- // case "gendb":
- // err = createDevelDB()
- // if err != nil {
- // fmt.Println(err)
- // }
- // err = saveVCSInfo()
- // if err != nil {
- // fmt.Println(err)
- // }
- // os.Exit(0)
- case "devel":
- config.Devel = true
- case "nodevel":
- config.Devel = false
- case "timeupdate":
- config.TimeUpdate = true
- case "notimeupdate":
- config.TimeUpdate = false
- case "topdown":
- config.SortMode = TopDown
- case "bottomup":
- config.SortMode = BottomUp
- // case "help":
- // usage()
- // os.Exit(0)
- // case "version":
- // fmt.Printf("yay v%s\n", version)
- // os.Exit(0)
- case "noconfirm":
- config.NoConfirm = true
- default:
- return false
- }
- changedConfig = true
- return true
- }
- func handleVersion() {
- fmt.Printf("yay v%s\n", version)
- }
- func handlePrint() (err error) {
- switch {
- case cmdArgs.existsArg("d", "defaultconfig"):
- var tmpConfig Configuration
- defaultSettings(&tmpConfig)
- fmt.Printf("%v", tmpConfig)
- case cmdArgs.existsArg("g", "config"):
- fmt.Printf("%v", config)
- case cmdArgs.existsArg("n", "numberupgrades"):
- err = printNumberOfUpdates()
- case cmdArgs.existsArg("u", "upgrades"):
- err = printUpdateList()
- case cmdArgs.existsArg("c", "complete"):
- switch {
- case cmdArgs.existsArg("f", "fish"):
- complete("fish")
- default:
- complete("sh")
- }
- case cmdArgs.existsArg("s", "stats"):
- err = localStatistics()
- default:
- err = nil
- }
- return err
- }
- func handleYay() (err error) {
- //_, options, targets := cmdArgs.formatArgs()
- if cmdArgs.existsArg("h", "help") {
- usage()
- } else if cmdArgs.existsArg("gendb") {
- err = createDevelDB()
- if err != nil {
- return
- }
- err = saveVCSInfo()
- if err != nil {
- return
- }
- } else if cmdArgs.existsArg("c", "clean") {
- err = cleanDependencies()
- } else if cmdArgs.existsArg("g", "getpkgbuild") {
- err = handleGetpkgbuild()
- } else if len(cmdArgs.targets) > 0 {
- err = handleYogurt()
- }
- return
- }
- func handleGetpkgbuild() (err error) {
- err = getPkgbuilds(cmdArgs.formatTargets())
- return
- }
- func handleYogurt() (err error) {
- options := cmdArgs.formatArgs()
- targets := cmdArgs.formatTargets()
- config.SearchMode = NumberMenu
- err = numberMenu(targets, options)
- return
- }
- func handleSync() (err error) {
- targets := cmdArgs.formatTargets()
- if cmdArgs.existsArg("y", "refresh") {
- arguments := cmdArgs.copy()
- cmdArgs.delArg("y", "refresh")
- arguments.delArg("u", "sysupgrade")
- arguments.delArg("s", "search")
- arguments.delArg("i", "info")
- arguments.targets = make(stringSet)
- err = passToPacman(arguments)
- if err != nil {
- return
- }
- }
- if cmdArgs.existsArg("s", "search") {
- if cmdArgs.existsArg("q", "quiet") {
- config.SearchMode = Minimal
- } else {
- config.SearchMode = Detailed
- }
- err = syncSearch(targets)
- } else if cmdArgs.existsArg("c", "clean") {
- err = passToPacman(cmdArgs)
- } else if cmdArgs.existsArg("i", "info") {
- err = syncInfo(targets)
- } else if cmdArgs.existsArg("u", "sysupgrade") {
- err = install(cmdArgs)
- } else if len(cmdArgs.targets) > 0 {
- err = install(cmdArgs)
- }
- return
- }
- func handleRemove() (err error) {
- removeVCSPackage(cmdArgs.formatTargets())
- err = passToPacman(cmdArgs)
- return
- }
- // BuildIntRange build the range from start to end
- func BuildIntRange(rangeStart, rangeEnd int) []int {
- if rangeEnd-rangeStart == 0 {
- // rangeEnd == rangeStart, which means no range
- return []int{rangeStart}
- }
- if rangeEnd < rangeStart {
- swap := rangeEnd
- rangeEnd = rangeStart
- rangeStart = swap
- }
- final := make([]int, 0)
- for i := rangeStart; i <= rangeEnd; i++ {
- final = append(final, i)
- }
- return final
- }
- // BuildRange construct a range of ints from the format 1-10
- func BuildRange(input string) ([]int, error) {
- multipleNums := strings.Split(input, "-")
- if len(multipleNums) != 2 {
- return nil, errors.New("Invalid range")
- }
- rangeStart, err := strconv.Atoi(multipleNums[0])
- if err != nil {
- return nil, err
- }
- rangeEnd, err := strconv.Atoi(multipleNums[1])
- if err != nil {
- return nil, err
- }
- return BuildIntRange(rangeStart, rangeEnd), err
- }
- // Contains returns whether e is present in s
- func contains(s []string, e string) bool {
- for _, a := range s {
- if a == e {
- return true
- }
- }
- return false
- }
- // RemoveIntListFromList removes all src's elements that are present in target
- func removeListFromList(src, target []string) []string {
- max := len(target)
- for i := 0; i < max; i++ {
- if contains(src, target[i]) {
- target = append(target[:i], target[i+1:]...)
- max--
- i--
- }
- }
- return target
- }
- // NumberMenu presents a CLI for selecting packages to install.
- func numberMenu(pkgS []string, flags []string) (err error) {
- //func numberMenu(cmdArgs *arguments) (err error) {
- var num int
- aurQ, err := narrowSearch(pkgS, true)
- if err != nil {
- fmt.Println("Error during AUR search:", err)
- }
- numaq := len(aurQ)
- repoQ, numpq, err := queryRepo(pkgS)
- if err != nil {
- return
- }
- if numpq == 0 && numaq == 0 {
- return fmt.Errorf("no packages match search")
- }
- if config.SortMode == BottomUp {
- aurQ.printSearch(numpq + 1)
- repoQ.printSearch()
- } else {
- repoQ.printSearch()
- aurQ.printSearch(numpq + 1)
- }
- fmt.Println(boldGreenFg(arrow) + boldGreenFg(" Packages to not upgrade (eg: 1 2 3, 1-3 or ^4)"))
- fmt.Print(boldGreenFg(arrow + " "))
- reader := bufio.NewReader(os.Stdin)
- numberBuf, overflow, err := reader.ReadLine()
- if err != nil || overflow {
- fmt.Println(err)
- return
- }
- numberString := string(numberBuf)
- var aurI, aurNI, repoNI, repoI []string
- result := strings.Fields(numberString)
- for _, numS := range result {
- negate := numS[0] == '^'
- if negate {
- numS = numS[1:]
- }
- var numbers []int
- num, err = strconv.Atoi(numS)
- if err != nil {
- numbers, err = BuildRange(numS)
- if err != nil {
- continue
- }
- } else {
- numbers = []int{num}
- }
- // Install package
- for _, x := range numbers {
- var target string
- if x > numaq+numpq || x <= 0 {
- continue
- } else if x > numpq {
- if config.SortMode == BottomUp {
- target = aurQ[numaq+numpq-x].Name
- } else {
- target = aurQ[x-numpq-1].Name
- }
- if negate {
- aurNI = append(aurNI, target)
- } else {
- aurI = append(aurI, target)
- }
- } else {
- if config.SortMode == BottomUp {
- target = repoQ[numpq-x].Name()
- } else {
- target = repoQ[x-1].Name()
- }
- if negate {
- repoNI = append(repoNI, target)
- } else {
- repoI = append(repoI, target)
- }
- }
- }
- }
- if len(repoI) == 0 && len(aurI) == 0 &&
- (len(aurNI) > 0 || len(repoNI) > 0) {
- // If no package was specified, only exclusions, exclude from all the
- // packages
- for _, pack := range aurQ {
- aurI = append(aurI, pack.Name)
- }
- for _, pack := range repoQ {
- repoI = append(repoI, pack.Name())
- }
- }
- aurI = removeListFromList(aurNI, aurI)
- repoI = removeListFromList(repoNI, repoI)
- if config.SudoLoop == true {
- sudoLoopBackground()
- }
- arguments := makeArguments()
- arguments.addTarget(repoI...)
- arguments.addTarget(aurI...)
- err = install(arguments)
- return err
- }
- // passToPacman outsources execution to pacman binary without modifications.
- func passToPacman(args *arguments) error {
- var cmd *exec.Cmd
- argArr := make([]string, 0)
- if args.needRoot() {
- argArr = append(argArr, "sudo")
- }
- argArr = append(argArr, config.PacmanBin)
- argArr = append(argArr, cmdArgs.formatGlobals()...)
- argArr = append(argArr, args.formatArgs()...)
- if config.NoConfirm {
- argArr = append(argArr, "--noconfirm")
- }
- argArr = append(argArr, "--")
- argArr = append(argArr, args.formatTargets()...)
- cmd = exec.Command(argArr[0], argArr[1:]...)
- cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
- err := cmd.Run()
- return err
- }
- // passToMakepkg outsources execution to makepkg binary without modifications.
- func passToMakepkg(dir string, args ...string) (err error) {
- if config.NoConfirm {
- args = append(args)
- }
- cmd := exec.Command(config.MakepkgBin, args...)
- cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
- cmd.Dir = dir
- err = cmd.Run()
- if err == nil {
- _ = saveVCSInfo()
- }
- return
- }
|