cmd.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. package main
  2. import (
  3. "bufio"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "os"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "time"
  13. )
  14. func usage() {
  15. fmt.Println(`usage: yay <operation> [...]
  16. operations:
  17. yay {-h --help}
  18. yay {-V --version}
  19. yay {-D --database} <options> <package(s)>
  20. yay {-F --files} [options] [package(s)]
  21. yay {-Q --query} [options] [package(s)]
  22. yay {-R --remove} [options] <package(s)>
  23. yay {-S --sync} [options] [package(s)]
  24. yay {-T --deptest} [options] [package(s)]
  25. yay {-U --upgrade} [options] <file(s)>
  26. New operations:
  27. yay -Qstats displays system information
  28. yay -Cd remove unneeded dependencies
  29. yay -G [package(s)] get pkgbuild from ABS or AUR
  30. yay --gendb generates development package DB used for updating.
  31. Permanent configuration options:
  32. --topdown shows repository's packages first and then aur's
  33. --bottomup shows aur's packages first and then repository's
  34. --devel Check -git/-svn/-hg development version
  35. --nodevel Disable development version checking
  36. --afterclean Clean package sources after successful build
  37. --noafterclean Disable package sources cleaning after successful build
  38. --timeupdate Check package's modification date and version
  39. --notimeupdate Check only package version change
  40. New options:
  41. --noconfirm skip user input on package install
  42. --printconfig Prints current yay configuration
  43. `)
  44. }
  45. func init() {
  46. var configHome string // configHome handles config directory home
  47. var cacheHome string // cacheHome handles cache home
  48. var err error
  49. if 0 == os.Geteuid() {
  50. fmt.Println("Please avoid running yay as root/sudo.")
  51. }
  52. if configHome = os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
  53. if info, err := os.Stat(configHome); err == nil && info.IsDir() {
  54. configHome = configHome + "/yay"
  55. } else {
  56. configHome = os.Getenv("HOME") + "/.config/yay"
  57. }
  58. } else {
  59. configHome = os.Getenv("HOME") + "/.config/yay"
  60. }
  61. if cacheHome = os.Getenv("XDG_CACHE_HOME"); cacheHome != "" {
  62. if info, err := os.Stat(cacheHome); err == nil && info.IsDir() {
  63. cacheHome = cacheHome + "/yay"
  64. } else {
  65. cacheHome = os.Getenv("HOME") + "/.cache/yay"
  66. }
  67. } else {
  68. cacheHome = os.Getenv("HOME") + "/.cache/yay"
  69. }
  70. configFile = configHome + "/config.json"
  71. vcsFile = configHome + "/yay_vcs.json"
  72. completionFile = cacheHome + "/aur_"
  73. ////////////////
  74. // yay config //
  75. ////////////////
  76. defaultSettings(&config)
  77. if _, err = os.Stat(configFile); os.IsNotExist(err) {
  78. err = os.MkdirAll(filepath.Dir(configFile), 0755)
  79. if err != nil {
  80. fmt.Println("Unable to create config directory:",
  81. filepath.Dir(configFile), err)
  82. os.Exit(2)
  83. }
  84. // Save the default config if nothing is found
  85. config.saveConfig()
  86. } else {
  87. cfile, errf := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE, 0644)
  88. if errf != nil {
  89. fmt.Println("Error reading config:", err)
  90. } else {
  91. defer cfile.Close()
  92. decoder := json.NewDecoder(cfile)
  93. err = decoder.Decode(&config)
  94. if err != nil {
  95. fmt.Println("Loading default Settings.\nError reading config:",
  96. err)
  97. defaultSettings(&config)
  98. }
  99. }
  100. }
  101. /////////////////
  102. // vcs config //
  103. ////////////////
  104. updated = false
  105. vfile, err := os.Open(vcsFile)
  106. if err == nil {
  107. defer vfile.Close()
  108. decoder := json.NewDecoder(vfile)
  109. _ = decoder.Decode(&savedInfo)
  110. }
  111. /////////////////
  112. // alpm config //
  113. /////////////////
  114. alpmConf, err = readAlpmConfig(config.PacmanConf)
  115. if err != nil {
  116. fmt.Println("Unable to read Pacman conf", err)
  117. os.Exit(1)
  118. }
  119. alpmHandle, err = alpmConf.CreateHandle()
  120. if err != nil {
  121. fmt.Println("Unable to CreateHandle", err)
  122. os.Exit(1)
  123. }
  124. }
  125. func parser() (op string, options, packages []string, changedConfig bool, err error) {
  126. if len(os.Args) < 2 {
  127. err = fmt.Errorf("no operation specified")
  128. return
  129. }
  130. changedConfig = false
  131. op = "yogurt"
  132. for _, arg := range os.Args[1:] {
  133. if len(arg) < 2 {
  134. continue
  135. }
  136. if arg[0] == '-' && arg[1] != '-' {
  137. switch arg {
  138. case "-V":
  139. arg = "--version"
  140. case "-h":
  141. arg = "--help"
  142. default:
  143. op = arg
  144. continue
  145. }
  146. }
  147. if strings.HasPrefix(arg, "--") {
  148. changedConfig = true
  149. switch arg {
  150. case "--afterclean":
  151. config.CleanAfter = true
  152. case "--noafterclean":
  153. config.CleanAfter = false
  154. case "--printconfig":
  155. fmt.Printf("%#v", config)
  156. os.Exit(0)
  157. case "--gendb":
  158. err = createDevelDB()
  159. if err != nil {
  160. fmt.Println(err)
  161. }
  162. err = saveVCSInfo()
  163. if err != nil {
  164. fmt.Println(err)
  165. }
  166. os.Exit(0)
  167. case "--devel":
  168. config.Devel = true
  169. case "--nodevel":
  170. config.Devel = false
  171. case "--timeupdate":
  172. config.TimeUpdate = true
  173. case "--notimeupdate":
  174. config.TimeUpdate = false
  175. case "--topdown":
  176. config.SortMode = TopDown
  177. case "--bottomup":
  178. config.SortMode = BottomUp
  179. case "--complete":
  180. config.Shell = "sh"
  181. _ = complete()
  182. os.Exit(0)
  183. case "--fcomplete":
  184. config.Shell = fishShell
  185. _ = complete()
  186. os.Exit(0)
  187. case "--help":
  188. usage()
  189. os.Exit(0)
  190. case "--version":
  191. fmt.Printf("yay v%s\n", version)
  192. os.Exit(0)
  193. case "--noconfirm":
  194. config.NoConfirm = true
  195. fallthrough
  196. default:
  197. options = append(options, arg)
  198. }
  199. continue
  200. }
  201. packages = append(packages, arg)
  202. }
  203. return
  204. }
  205. func main() {
  206. op, options, pkgs, changedConfig, err := parser()
  207. if err != nil {
  208. fmt.Println(err)
  209. os.Exit(1)
  210. }
  211. switch op {
  212. case "-Cd":
  213. err = cleanDependencies(pkgs)
  214. case "-G":
  215. for _, pkg := range pkgs {
  216. err = getPkgbuild(pkg)
  217. if err != nil {
  218. fmt.Println(pkg+":", err)
  219. }
  220. }
  221. case "-Qstats":
  222. err = localStatistics()
  223. case "-Ss", "-Ssq", "-Sqs":
  224. if op == "-Ss" {
  225. config.SearchMode = Detailed
  226. } else {
  227. config.SearchMode = Minimal
  228. }
  229. if pkgs != nil {
  230. err = syncSearch(pkgs)
  231. }
  232. case "-S":
  233. err = install(pkgs, options)
  234. case "-Sy":
  235. err = passToPacman("-Sy", nil, nil)
  236. if err != nil {
  237. break
  238. }
  239. err = install(pkgs, options)
  240. case "-Syu", "-Suy", "-Su":
  241. if strings.Contains(op, "y") {
  242. err = passToPacman("-Sy", nil, nil)
  243. if err != nil {
  244. break
  245. }
  246. }
  247. err = upgradePkgs(options)
  248. case "-Si":
  249. err = syncInfo(pkgs, options)
  250. case "yogurt":
  251. config.SearchMode = NumberMenu
  252. if pkgs != nil {
  253. err = numberMenu(pkgs, options)
  254. }
  255. default:
  256. if op[0] == 'R' {
  257. removeVCSPackage(pkgs)
  258. }
  259. err = passToPacman(op, pkgs, options)
  260. }
  261. var erra error
  262. if updated {
  263. erra = saveVCSInfo()
  264. if erra != nil {
  265. fmt.Println(err)
  266. }
  267. }
  268. if changedConfig {
  269. erra = config.saveConfig()
  270. if erra != nil {
  271. fmt.Println(err)
  272. }
  273. }
  274. erra = alpmHandle.Release()
  275. if erra != nil {
  276. fmt.Println(err)
  277. }
  278. if err != nil {
  279. fmt.Println(err)
  280. os.Exit(1)
  281. }
  282. }
  283. // BuildIntRange build the range from start to end
  284. func BuildIntRange(rangeStart, rangeEnd int) []int {
  285. if rangeEnd-rangeStart == 0 {
  286. // rangeEnd == rangeStart, which means no range
  287. return []int{rangeStart}
  288. }
  289. if rangeEnd < rangeStart {
  290. swap := rangeEnd
  291. rangeEnd = rangeStart
  292. rangeStart = swap
  293. }
  294. final := make([]int, 0)
  295. for i := rangeStart; i <= rangeEnd; i++ {
  296. final = append(final, i)
  297. }
  298. return final
  299. }
  300. // BuildRange construct a range of ints from the format 1-10
  301. func BuildRange(input string) ([]int, error) {
  302. multipleNums := strings.Split(input, "-")
  303. if len(multipleNums) != 2 {
  304. return nil, errors.New("Invalid range")
  305. }
  306. rangeStart, err := strconv.Atoi(multipleNums[0])
  307. if err != nil {
  308. return nil, err
  309. }
  310. rangeEnd, err := strconv.Atoi(multipleNums[1])
  311. if err != nil {
  312. return nil, err
  313. }
  314. return BuildIntRange(rangeStart, rangeEnd), err
  315. }
  316. // NumberMenu presents a CLI for selecting packages to install.
  317. func numberMenu(pkgS []string, flags []string) (err error) {
  318. var num int
  319. aq, err := narrowSearch(pkgS, true)
  320. if err != nil {
  321. fmt.Println("Error during AUR search:", err)
  322. }
  323. numaq := len(aq)
  324. pq, numpq, err := queryRepo(pkgS)
  325. if err != nil {
  326. return
  327. }
  328. if numpq == 0 && numaq == 0 {
  329. return fmt.Errorf("no packages match search")
  330. }
  331. if config.SortMode == BottomUp {
  332. aq.printSearch(numpq + 1)
  333. pq.printSearch()
  334. } else {
  335. pq.printSearch()
  336. aq.printSearch(numpq + 1)
  337. }
  338. fmt.Printf("\x1b[32m%s %s\x1b[0m\nNumbers: ",
  339. "Type the numbers or ranges (e.g. 1-10) you want to install.",
  340. "Separate each one of them with a space.")
  341. reader := bufio.NewReader(os.Stdin)
  342. numberBuf, overflow, err := reader.ReadLine()
  343. if err != nil || overflow {
  344. fmt.Println(err)
  345. return
  346. }
  347. numberString := string(numberBuf)
  348. var aurI []string
  349. var repoI []string
  350. result := strings.Fields(numberString)
  351. for _, numS := range result {
  352. var numbers []int
  353. num, err = strconv.Atoi(numS)
  354. if err != nil {
  355. numbers, err = BuildRange(numS)
  356. if err != nil {
  357. continue
  358. }
  359. } else {
  360. numbers = []int{num}
  361. }
  362. // Install package
  363. for _, x := range numbers {
  364. if x > numaq+numpq || x <= 0 {
  365. continue
  366. } else if x > numpq {
  367. if config.SortMode == BottomUp {
  368. aurI = append(aurI, aq[numaq+numpq-x].Name)
  369. } else {
  370. aurI = append(aurI, aq[x-numpq-1].Name)
  371. }
  372. } else {
  373. if config.SortMode == BottomUp {
  374. repoI = append(repoI, pq[numpq-x].Name())
  375. } else {
  376. repoI = append(repoI, pq[x-1].Name())
  377. }
  378. }
  379. }
  380. }
  381. if len(repoI) != 0 {
  382. err = passToPacman("-S", repoI, flags)
  383. }
  384. if len(aurI) != 0 {
  385. err = aurInstall(aurI, flags)
  386. }
  387. return err
  388. }
  389. // Complete provides completion info for shells
  390. func complete() error {
  391. path := completionFile + config.Shell + ".cache"
  392. info, err := os.Stat(path)
  393. if os.IsNotExist(err) || time.Since(info.ModTime()).Hours() > 48 {
  394. os.MkdirAll(filepath.Dir(completionFile), 0755)
  395. out, errf := os.Create(path)
  396. if errf != nil {
  397. return errf
  398. }
  399. if createAURList(out) != nil {
  400. defer os.Remove(path)
  401. }
  402. erra := createRepoList(out)
  403. out.Close()
  404. return erra
  405. }
  406. in, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
  407. if err != nil {
  408. return err
  409. }
  410. defer in.Close()
  411. _, err = io.Copy(os.Stdout, in)
  412. return err
  413. }