cmd.go 9.2 KB

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