aurel.el 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896
  1. ;;; aurel.el --- Search, get info, vote for and download AUR packages -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2014-2017 Alex Kost
  3. ;; Author: Alex Kost <alezost@gmail.com>
  4. ;; Created: 6 Feb 2014
  5. ;; Version: 0.9
  6. ;; URL: https://github.com/alezost/aurel
  7. ;; Keywords: tools
  8. ;; Package-Requires: ((emacs "24.3") (bui "1.1.0") (dash "2.11.0"))
  9. ;; This program is free software; you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation, either version 3 of the License, or
  12. ;; (at your option) any later version.
  13. ;;
  14. ;; This program is distributed in the hope that it will be useful,
  15. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;; GNU General Public License for more details.
  18. ;;
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. ;;; Commentary:
  22. ;; This package provides an interface for searching, getting information,
  23. ;; voting for, subscribing and downloading packages from the Arch User
  24. ;; Repository (AUR) <https://aur.archlinux.org/>.
  25. ;; To manually install the package, add the following to your init-file:
  26. ;;
  27. ;; (add-to-list 'load-path "/path/to/aurel-dir")
  28. ;; (autoload 'aurel-package-info "aurel" nil t)
  29. ;; (autoload 'aurel-package-search "aurel" nil t)
  30. ;; (autoload 'aurel-package-search-by-name "aurel" nil t)
  31. ;; (autoload 'aurel-maintainer-search "aurel" nil t)
  32. ;; (autoload 'aurel-installed-packages "aurel" nil t)
  33. ;; Also set a directory where downloaded packages will be put:
  34. ;;
  35. ;; (setq aurel-download-directory "~/aur")
  36. ;; To search for packages, use `aurel-package-search' or
  37. ;; `aurel-maintainer-search' commands. If you know the name of a
  38. ;; package, use `aurel-package-info' command. Also you can display a
  39. ;; list of installed AUR packages with `aurel-installed-packages'.
  40. ;; Information about the packages is represented in a list-like buffer
  41. ;; similar to a buffer containing emacs packages. Press "h" to see a
  42. ;; hint (a summary of the available key bindings). To get more info
  43. ;; about a package (or marked packages), press "RET". To download a
  44. ;; package, press "d" (don't forget to set `aurel-download-directory'
  45. ;; before). In a list buffer, you can mark several packages for
  46. ;; downloading with "m"/"M" (and unmark with "u"/"U" and "DEL"); also
  47. ;; you can perform filtering (press "f f" to enable a filter and "f d"
  48. ;; to disable all filters) of a current list to hide particular
  49. ;; packages.
  50. ;; It is possible to move to the previous/next displayed results with
  51. ;; "l"/"r" (each aurel buffer has its own history) and to refresh
  52. ;; information with "g".
  53. ;; After receiving information about the packages, pacman is called to
  54. ;; find what packages are installed. To disable that, set
  55. ;; `aurel-installed-packages-check' to nil.
  56. ;; To vote/subscribe for a package, press "v"/"s" (with prefix,
  57. ;; unvote/unsubscribe) in a package info buffer (you should have an AUR
  58. ;; account for that). To add information about "Voted"/"Subscribed"
  59. ;; status, use the following:
  60. ;;
  61. ;; (setq aurel-aur-user-package-info-check t)
  62. ;; For full description and screenshots, see
  63. ;; <https://github.com/alezost/aurel>.
  64. ;;; Code:
  65. (require 'url)
  66. (require 'url-handlers)
  67. (require 'json)
  68. (require 'cl-lib)
  69. (require 'dash)
  70. (require 'bui)
  71. (defgroup aurel nil
  72. "Search for and download AUR (Arch User Repository) packages."
  73. :group 'applications)
  74. (defgroup aurel-faces nil
  75. "Faces for 'aurel' buffers."
  76. :group 'aurel
  77. :group 'faces)
  78. (defcustom aurel-aur-user-package-info-check nil
  79. "If non-nil, check additional info before displaying a package info.
  80. Additional info is an AUR user specific information (whether the user
  81. voted for the package or subscribed to receive comments)."
  82. :type 'boolean
  83. :group 'aurel)
  84. (defvar aurel-unknown-string "Unknown"
  85. "String used if a value of the parameter is unknown.")
  86. (defvar aurel-none-string "None"
  87. "String saying that a parameter has no value.
  88. This string can be displayed by pacman.")
  89. (defvar aurel-package-name-re
  90. "[-+_[:alnum:]]+"
  91. "Regexp matching a valid package name.")
  92. ;;; Debugging
  93. (defvar aurel-debug-level 0
  94. "If > 0, display debug messages in `aurel-debug-buffer'.
  95. The greater the number, the more messages is printed.
  96. Max level is 9.")
  97. (defvar aurel-debug-buffer "*aurel debug*"
  98. "Name of a buffer containing debug messages.")
  99. (defvar aurel-debug-time-format "%T.%3N"
  100. "Time format used for debug mesages.")
  101. (defun aurel-debug (level msg &rest args)
  102. "Print debug message if needed.
  103. If `aurel-debug-level' >= LEVEL, print debug message MSG with
  104. arguments ARGS into `aurel-debug-buffer'.
  105. Return nil."
  106. (when (>= aurel-debug-level level)
  107. (with-current-buffer (get-buffer-create aurel-debug-buffer)
  108. (goto-char (point-max))
  109. (insert (format-time-string aurel-debug-time-format (current-time)))
  110. (insert " " (apply 'format msg args) "\n")))
  111. nil)
  112. ;;; Interacting with AUR server
  113. (defcustom aurel-aur-user-name ""
  114. "User name for AUR."
  115. :type 'string
  116. :group 'aurel)
  117. (defvar aurel-aur-host "aur.archlinux.org"
  118. "AUR domain.")
  119. (defvar aurel-aur-base-url (concat "https://" aurel-aur-host)
  120. "Root URL of the AUR service.")
  121. (defvar aurel-aur-login-url
  122. (url-expand-file-name "login" aurel-aur-base-url)
  123. "Login URL.")
  124. (defconst aurel-aur-cookie-name "AURSID"
  125. "Cookie name used for AUR login.")
  126. ;; Avoid compilation warning about `url-http-response-status'
  127. (defvar url-http-response-status)
  128. (defun aurel-check-response-status (buffer &optional noerror)
  129. "Return t, if URL response status in BUFFER is 2XX or 3XX.
  130. Otherwise, throw an error or return nil, if NOERROR is nil."
  131. (with-current-buffer buffer
  132. (aurel-debug 3 "Response status: %s" url-http-response-status)
  133. (if (or (null (numberp url-http-response-status))
  134. (> url-http-response-status 399))
  135. (unless noerror (error "Error during request: %s"
  136. url-http-response-status))
  137. t)))
  138. (defun aurel-receive-parse-info (url)
  139. "Return received output from URL processed with `json-read'."
  140. (aurel-debug 3 "Retrieving %s" url)
  141. (with-temp-buffer
  142. (url-insert-file-contents url)
  143. (goto-char (point-min))
  144. (let ((json-key-type 'string)
  145. (json-array-type 'list)
  146. (json-object-type 'alist))
  147. (json-read))))
  148. (defun aurel-get-aur-packages-info (url)
  149. "Return information about the packages from URL.
  150. Output from URL should be a json data. It is parsed with
  151. `json-read'.
  152. Returning value is alist of AUR package parameters (strings from
  153. `aurel-aur-param-alist') and their values."
  154. (let* ((full-info (aurel-receive-parse-info url))
  155. (type (cdr (assoc "type" full-info)))
  156. (count (cdr (assoc "resultcount" full-info)))
  157. (results (cdr (assoc "results" full-info))))
  158. (cond
  159. ((string= type "error")
  160. (error "%s" results))
  161. ((= count 0)
  162. nil)
  163. (t
  164. (when (string= type "info")
  165. (setq results (list results)))
  166. results))))
  167. ;; Because of the bug <http://bugs.gnu.org/16960>, we can't use
  168. ;; `url-retrieve-synchronously' (or any other simple call of
  169. ;; `url-retrieve', as the callback is never called) to login to
  170. ;; <https://aur.archlinux.org>. So we use
  171. ;; `aurel-url-retrieve-synchronously' - it is almost the same, except it
  172. ;; can exit from the waiting loop when a buffer with received data
  173. ;; appears in `url-dead-buffer-list'. This hack is currently possible,
  174. ;; because `url-http-parse-headers' marks the buffer as dead when it
  175. ;; returns nil.
  176. (defun aurel-url-retrieve-synchronously (url &optional silent inhibit-cookies)
  177. "Retrieve URL synchronously.
  178. Return the buffer containing the data, or nil if there are no data
  179. associated with it (the case for dired, info, or mailto URLs that need
  180. no further processing). URL is either a string or a parsed URL.
  181. See `url-retrieve' for SILENT and INHIBIT-COOKIES."
  182. (url-do-setup)
  183. (let (asynch-buffer retrieval-done)
  184. (setq asynch-buffer
  185. (url-retrieve url
  186. (lambda (&rest ignored)
  187. (url-debug 'retrieval
  188. "Synchronous fetching done (%S)"
  189. (current-buffer))
  190. (setq retrieval-done t
  191. asynch-buffer (current-buffer)))
  192. nil silent inhibit-cookies))
  193. (when asynch-buffer
  194. (let ((proc (get-buffer-process asynch-buffer)))
  195. (while (not (or retrieval-done
  196. ;; retrieval can be done even if
  197. ;; `retrieval-done' is nil (see the comment
  198. ;; above)
  199. (memq asynch-buffer url-dead-buffer-list)))
  200. (url-debug 'retrieval
  201. "Spinning in url-retrieve-synchronously: %S (%S)"
  202. retrieval-done asynch-buffer)
  203. (if (buffer-local-value 'url-redirect-buffer asynch-buffer)
  204. (setq proc (get-buffer-process
  205. (setq asynch-buffer
  206. (buffer-local-value 'url-redirect-buffer
  207. asynch-buffer))))
  208. (if (and proc (memq (process-status proc)
  209. '(closed exit signal failed))
  210. ;; Make sure another process hasn't been started.
  211. (eq proc (or (get-buffer-process asynch-buffer) proc)))
  212. (progn ;; Call delete-process so we run any sentinel now.
  213. (delete-process proc)
  214. (setq retrieval-done t)))
  215. (unless (or (with-local-quit
  216. (accept-process-output proc))
  217. (null proc))
  218. (when quit-flag
  219. (delete-process proc))
  220. (setq proc (and (not quit-flag)
  221. (get-buffer-process asynch-buffer)))))))
  222. asynch-buffer)))
  223. (defun aurel-url-post (url args &optional inhibit-cookies)
  224. "Send ARGS to URL as a POST request.
  225. ARGS is alist of field names and values to send.
  226. Return the buffer with the received data.
  227. If INHIBIT-COOKIES is non-nil, do not use saved cookies."
  228. (let ((url-request-method "POST")
  229. (url-request-extra-headers
  230. '(("Content-Type" . "application/x-www-form-urlencoded")))
  231. (url-request-data (aurel-get-fields-string args)))
  232. (aurel-debug 2 "POSTing to %s" url)
  233. (aurel-url-retrieve-synchronously url inhibit-cookies)))
  234. (defun aurel-get-aur-cookie ()
  235. "Return cookie for AUR login.
  236. Return nil, if there is no such cookie or it is expired."
  237. (url-do-setup) ; initialize cookies
  238. (let* ((cookies (url-cookie-retrieve aurel-aur-host "/" t))
  239. (cookie (car (cl-member-if
  240. (lambda (cookie)
  241. (equal (url-cookie-name cookie)
  242. aurel-aur-cookie-name))
  243. cookies))))
  244. (if (null cookie)
  245. (aurel-debug 4 "AUR login cookie not found")
  246. (if (url-cookie-expired-p cookie)
  247. (aurel-debug 4 "AUR login cookie is expired")
  248. (aurel-debug 4 "AUR login cookie is valid")
  249. cookie))))
  250. (declare-function auth-source-search "auth-source" t)
  251. (defun aurel-aur-login-maybe (&optional force noerror)
  252. "Login to AUR, use cookie if possible.
  253. If FORCE is non-nil (interactively, with prefix), prompt for
  254. credentials and login without trying the cookie.
  255. See `aurel-aur-login' for the meaning of NOERROR and returning value."
  256. (interactive "P")
  257. (if (aurel-get-aur-cookie)
  258. (progn
  259. (aurel-debug 2 "Using cookie instead of a real login")
  260. t)
  261. (let (user password)
  262. (let ((auth (car (auth-source-search :host aurel-aur-host))))
  263. (when auth
  264. (let ((secret (plist-get auth :secret)))
  265. (setq user (plist-get auth :user)
  266. password (if (functionp secret)
  267. (funcall secret)
  268. secret)))))
  269. (when (or force (null user))
  270. (setq user (read-string "AUR user name: " aurel-aur-user-name)))
  271. (when (or force (null password))
  272. (setq password (read-passwd "Password: ")))
  273. (aurel-aur-login user password t noerror))))
  274. (defun aurel-aur-login (user password &optional remember noerror)
  275. "Login to AUR with USER and PASSWORD.
  276. If REMEMBER is non-nil, remember a cookie.
  277. Return t, if login was successful, otherwise throw an error or
  278. return nil, if NOERROR is non-nil."
  279. (let ((buf (aurel-url-post
  280. aurel-aur-login-url
  281. (list (cons "user" user)
  282. (cons "passwd" password)
  283. (cons "remember_me" (if remember "on" "off")))
  284. 'inhibit-cookie)))
  285. (when (aurel-check-response-status buf noerror)
  286. (with-current-buffer buf
  287. (if (re-search-forward "errorlist.+<li>\\(.+\\)</li>" nil t)
  288. (let ((err (match-string 1)))
  289. (aurel-debug 1 "Error during login: %s" )
  290. (or noerror (error "%s" err))
  291. nil)
  292. (url-cookie-write-file)
  293. (aurel-debug 1 "Login for %s is successful" user)
  294. t)))))
  295. (defun aurel-add-aur-user-package-info (info)
  296. "Return a new info by adding AUR user info to package INFO.
  297. See `aurel-aur-user-package-info-check' for the meaning of
  298. additional info."
  299. (let ((add (aurel-get-aur-user-package-info
  300. (aurel-get-aur-package-url
  301. (bui-entry-value info 'name)))))
  302. (if add
  303. (cons (cons 'user-info add)
  304. info)
  305. info)))
  306. (defun aurel-get-aur-user-package-info (url)
  307. "Return AUR user specific information about a package from URL.
  308. Returning value is alist of package parameters specific for AUR
  309. user (`voted' and `subscribed') and their values.
  310. Return nil, if information is not found."
  311. (when (aurel-aur-login-maybe nil t)
  312. (aurel-debug 3 "Retrieving %s" url)
  313. (let ((buf (url-retrieve-synchronously url)))
  314. (aurel-debug 4 "Searching in %S for voted/subscribed params" buf)
  315. (list (cons 'voted
  316. (aurel-aur-package-voted buf))
  317. (cons 'subscribed
  318. (aurel-aur-package-subscribed buf))))))
  319. (defun aurel-aur-package-voted (buffer)
  320. "Return `voted' parameter value from BUFFER with fetched data.
  321. Return non-nil if a package is voted by the user; nil if it is not;
  322. `aurel-unknown-string' if the information is not found.
  323. BUFFER should contain html data about the package."
  324. (cond
  325. ((aurel-search-in-buffer
  326. (aurel-get-aur-user-action-name 'vote) buffer)
  327. nil)
  328. ((aurel-search-in-buffer
  329. (aurel-get-aur-user-action-name 'unvote) buffer)
  330. t)
  331. (t aurel-unknown-string)))
  332. (defun aurel-aur-package-subscribed (buffer)
  333. "Return `subscribed' parameter value from BUFFER with fetched data.
  334. Return non-nil if a package is subscribed by the user; nil if it is not;
  335. `aurel-unknown-string' if the information is not found.
  336. BUFFER should contain html data about the package."
  337. (cond
  338. ((aurel-search-in-buffer
  339. (aurel-get-aur-user-action-name 'subscribe) buffer)
  340. nil)
  341. ((aurel-search-in-buffer
  342. (aurel-get-aur-user-action-name 'unsubscribe) buffer)
  343. t)
  344. (t aurel-unknown-string)))
  345. (defun aurel-search-in-buffer (regexp buffer)
  346. "Return non-nil if BUFFER contains REGEXP; return nil otherwise."
  347. (with-current-buffer buffer
  348. (goto-char (point-min))
  349. (let ((res (re-search-forward regexp nil t)))
  350. (aurel-debug 7 "Searching for %s in %S: %S" regexp buffer res)
  351. res)))
  352. (defvar aurel-aur-user-actions
  353. '((vote "do_Vote" "vote" "Vote for '%s' package?")
  354. (unvote "do_UnVote" "unvote" "Remove vote from '%s' package?")
  355. (subscribe "do_Notify" "notify" "Enable notifications for '%s' package?")
  356. (unsubscribe "do_UnNotify" "unnotify" "Disable notifications for '%s' package?"))
  357. "Alist of the available actions.
  358. Each association has the following form:
  359. (SYMBOL NAME URL-END CONFIRM)
  360. SYMBOL is a name of the action used internally in code of this package.
  361. NAME is a name (string) used in the html-code of AUR package page.
  362. URL-END is appended to the package URL; used for posting the action.
  363. CONFIRM is a prompt to confirm the action or nil if it is not required.")
  364. (defun aurel-get-aur-user-action-name (action)
  365. "Return the name of an ACTION."
  366. (cadr (assoc action aurel-aur-user-actions)))
  367. (defun aurel-aur-user-action (action package-base)
  368. "Perform AUR user ACTION on the PACKAGE-BASE.
  369. ACTION is a symbol from `aurel-aur-user-actions'.
  370. PACKAGE-BASE is a name of the package base (string).
  371. Return non-nil, if ACTION was performed; return nil otherwise."
  372. (let ((assoc (assoc action aurel-aur-user-actions)))
  373. (let ((action-name (nth 1 assoc))
  374. (url-end (nth 2 assoc))
  375. (confirm (nth 3 assoc)))
  376. (when (or (null confirm)
  377. (y-or-n-p (format confirm package-base)))
  378. (aurel-aur-login-maybe)
  379. (aurel-url-post
  380. (aurel-get-package-action-url package-base url-end)
  381. (list (cons "token" (url-cookie-value (aurel-get-aur-cookie)))
  382. (cons action-name "")))
  383. t))))
  384. ;;; Interacting with pacman
  385. (defcustom aurel-pacman-program (executable-find "pacman")
  386. "Absolute or relative name of `pacman' program."
  387. :type 'string
  388. :group 'aurel)
  389. (defvar aurel-pacman-locale "C"
  390. "Default locale used to start pacman.")
  391. (defcustom aurel-installed-packages-check
  392. (and aurel-pacman-program t)
  393. "If non-nil, check if the found packages are installed.
  394. If nil, searching works faster, because `aurel-pacman-program' is not
  395. called, but it stays unknown if a package is installed or not."
  396. :type 'boolean
  397. :group 'aurel)
  398. (defvar aurel-pacman-buffer-name " *aurel-pacman*"
  399. "Name of the buffer used internally for pacman output.")
  400. (defvar aurel-pacman-info-line-re
  401. (rx line-start
  402. (group (+? (any word " ")))
  403. (+ " ") ":" (+ " ")
  404. (group (+ any) (* (and "\n " (+ any))))
  405. line-end)
  406. "Regexp matching a line of pacman query info output.
  407. Contain 2 parenthesized groups: parameter name and its value.")
  408. (defun aurel-call-pacman (&optional buffer &rest args)
  409. "Call `aurel-pacman-program' with arguments ARGS.
  410. Insert output in BUFFER. If it is nil, use `aurel-pacman-buffer-name'.
  411. Return numeric exit status."
  412. (or aurel-pacman-program
  413. (error (concat "Couldn't find pacman.\n"
  414. "Set aurel-pacman-program to a proper value")))
  415. (with-current-buffer
  416. (or buffer (get-buffer-create aurel-pacman-buffer-name))
  417. (erase-buffer)
  418. (let ((process-environment
  419. (cons (concat "LC_ALL=" aurel-pacman-locale)
  420. process-environment)))
  421. (apply #'call-process aurel-pacman-program nil t nil args))))
  422. (defun aurel-get-foreign-packages ()
  423. "Return list of names of installed foreign packages."
  424. (let ((buf (get-buffer-create aurel-pacman-buffer-name)))
  425. (aurel-call-pacman buf "--query" "--foreign")
  426. (aurel-pacman-query-names-buffer-parse buf)))
  427. (defun aurel-pacman-query-names-buffer-parse (&optional buffer)
  428. "Parse BUFFER with packages names.
  429. BUFFER should contain an output returned by 'pacman -Q' command.
  430. If BUFFER is nil, use `aurel-pacman-buffer-name'.
  431. Return list of names of packages."
  432. (with-current-buffer
  433. (or buffer (get-buffer-create aurel-pacman-buffer-name))
  434. (goto-char (point-min))
  435. (let (names)
  436. (while (re-search-forward
  437. (concat "^\\(" aurel-package-name-re "\\) ") nil t)
  438. (setq names (cons (match-string 1) names)))
  439. names)))
  440. (defun aurel-get-installed-packages-info (&rest names)
  441. "Return information about installed packages NAMES.
  442. Each name from NAMES should be a string (a name of a package).
  443. Returning value is a list of alists with installed package
  444. parameters (strings from `aurel-installed-param-alist') and their
  445. values."
  446. (let ((buf (get-buffer-create aurel-pacman-buffer-name)))
  447. (apply 'aurel-call-pacman buf "--query" "--info" names)
  448. (aurel-pacman-query-buffer-parse buf)))
  449. (defun aurel-pacman-query-buffer-parse (&optional buffer)
  450. "Parse BUFFER with packages info.
  451. BUFFER should contain an output returned by 'pacman -Qi' command.
  452. If BUFFER is nil, use `aurel-pacman-buffer-name'.
  453. Return list of alists with parameter names and values."
  454. (with-current-buffer
  455. (or buffer (get-buffer-create aurel-pacman-buffer-name))
  456. (let ((beg (point-min))
  457. end info)
  458. ;; Packages info are separated with empty lines, search for those
  459. ;; till the end of buffer
  460. (cl-loop
  461. do (progn
  462. (goto-char beg)
  463. (setq end (re-search-forward "^\n" nil t))
  464. (and end
  465. (setq info (aurel-pacman-query-region-parse beg end)
  466. beg end)))
  467. while end
  468. if info collect info))))
  469. (defun aurel-pacman-query-region-parse (beg end)
  470. "Parse text (package info) in current buffer from BEG to END.
  471. Parsing region should be an output for one package returned by
  472. 'pacman -Qi' command.
  473. Return alist with parameter names and values."
  474. (goto-char beg)
  475. (let (point)
  476. (cl-loop
  477. do (setq point (re-search-forward
  478. aurel-pacman-info-line-re end t))
  479. while point
  480. collect (cons (match-string 1) (match-string 2)))))
  481. ;;; Package parameters
  482. (defvar aurel-aur-param-alist
  483. '((pkg-url . "URLPath")
  484. (home-url . "URL")
  485. (last-date . "LastModified")
  486. (first-date . "FirstSubmitted")
  487. (outdated . "OutOfDate")
  488. (votes . "NumVotes")
  489. (popularity . "Popularity")
  490. (license . "License")
  491. (description . "Description")
  492. (keywords . "Keywords")
  493. (version . "Version")
  494. (name . "Name")
  495. (id . "ID")
  496. (base-name . "PackageBase")
  497. (base-id . "PackageBaseID")
  498. (maintainer . "Maintainer")
  499. (replaces . "Replaces")
  500. (provides . "Provides")
  501. (conflicts . "Conflicts")
  502. (depends . "Depends")
  503. (depends-make . "MakeDepends"))
  504. "Association list of symbols and names of package info parameters.
  505. Car of each assoc is a symbol used in code of this package.
  506. Cdr - is a parameter name (string) returned by the AUR server.")
  507. (defvar aurel-pacman-param-alist
  508. '((installed-name . "Name")
  509. (installed-version . "Version")
  510. (architecture . "Architecture")
  511. (installed-provides . "Provides")
  512. (installed-depends . "Depends On")
  513. (depends-opt . "Optional Deps")
  514. (script . "Install Script")
  515. (reason . "Install Reason")
  516. (validated . "Validated By")
  517. (required . "Required By")
  518. (optional-for . "Optional For")
  519. (installed-conflicts . "Conflicts With")
  520. (installed-replaces . "Replaces")
  521. (installed-size . "Installed Size")
  522. (packager . "Packager")
  523. (build-date . "Build Date")
  524. (install-date . "Install Date"))
  525. "Association list of symbols and names of package info parameters.
  526. Car of each assoc is a symbol used in code of this package.
  527. Cdr - is a parameter name (string) returned by pacman.")
  528. (defun aurel-get-aur-param-name (param-symbol)
  529. "Return a name (string) of a parameter.
  530. PARAM-SYMBOL is a symbol from `aurel-aur-param-alist'."
  531. (cdr (assoc param-symbol aurel-aur-param-alist)))
  532. (defun aurel-get-aur-param-symbol (param-name)
  533. "Return a symbol name of a parameter.
  534. PARAM-NAME is a string from `aurel-aur-param-alist'."
  535. (car (rassoc param-name aurel-aur-param-alist)))
  536. (defun aurel-get-pacman-param-name (param-symbol)
  537. "Return a name (string) of a parameter.
  538. PARAM-SYMBOL is a symbol from `aurel-pacman-param-alist'."
  539. (cdr (assoc param-symbol aurel-pacman-param-alist)))
  540. (defun aurel-get-pacman-param-symbol (param-name)
  541. "Return a symbol name of a parameter.
  542. PARAM-NAME is a string from `aurel-pacman-param-alist'."
  543. (car (rassoc param-name aurel-pacman-param-alist)))
  544. ;;; Filters for processing package info
  545. (defvar aurel-filter-params nil
  546. "List of parameters (symbols), that should match specified strings.
  547. Used in `aurel-filter-contains-every-string'.")
  548. (defvar aurel-filter-strings nil
  549. "List of strings, a package info should match.
  550. Used in `aurel-filter-contains-every-string'.")
  551. (defvar aurel-aur-filters
  552. '(aurel-aur-filter-intern
  553. aurel-filter-contains-every-string
  554. aurel-filter-pkg-url)
  555. "List of filter functions applied to a package info got from AUR.
  556. Each filter function should accept a single argument - info alist
  557. with package parameters and should return info alist or
  558. nil (which means: ignore this package info). Functions may
  559. modify associations or add the new ones to the alist. In the
  560. latter case you might want to add descriptions of the added
  561. symbols into `aurel-titles'.
  562. `aurel-aur-filter-intern' should be the first symbol in the list as
  563. other filters use symbols for working with info parameters (see
  564. `aurel-aur-param-alist').
  565. For more information, see `aurel-receive-packages-info'.")
  566. (defvar aurel-pacman-filters
  567. '(aurel-pacman-filter-intern
  568. aurel-pacman-filter-none)
  569. "List of filter functions applied to a package info got from pacman.
  570. `aurel-pacman-filter-intern' should be the first symbol in the list as
  571. other filters use symbols for working with info parameters (see
  572. `aurel-pacman-param-alist').
  573. For more information, see `aurel-aur-filters' and
  574. `aurel-receive-packages-info'.")
  575. (defvar aurel-final-filters
  576. '()
  577. "List of filter functions applied to a package info.
  578. For more information, see `aurel-receive-packages-info'.")
  579. (defun aurel-apply-filters (info filters)
  580. "Apply functions from FILTERS list to a package INFO.
  581. INFO is alist with package parameters. It is passed as an
  582. argument to the first function from FILTERS, the returned result
  583. is passed to the second function from that list and so on.
  584. Return filtered info (result of the last filter). Return nil, if
  585. one of the FILTERS returns nil (do not call the rest filters)."
  586. (cl-loop for fun in filters
  587. do (setq info (funcall fun info))
  588. while info
  589. finally return info))
  590. (defun aurel-filter-intern (info param-fun &optional warning)
  591. "Replace names of parameters with symbols in a package INFO.
  592. INFO is alist of parameter names (strings) and values.
  593. PARAM-FUN is a function for getting parameter internal symbol by
  594. its name (string).
  595. If WARNING is non-nil, show a message if unknown parameter is found.
  596. Return modified info."
  597. (delq nil
  598. (mapcar
  599. (-lambda ((param-name . param-val))
  600. (let ((param-symbol (funcall param-fun param-name)))
  601. (if param-symbol
  602. (cons param-symbol param-val)
  603. (when warning
  604. (message "\
  605. Warning: unknown parameter '%s'. It will be omitted."
  606. param-name))
  607. nil)))
  608. info)))
  609. (defun aurel-aur-filter-intern (info)
  610. "Replace names of parameters with symbols in a package INFO.
  611. INFO is alist of parameter names (strings) from
  612. `aurel-aur-param-alist' and their values.
  613. Return modified info."
  614. (aurel-filter-intern info 'aurel-get-aur-param-symbol t))
  615. (defun aurel-pacman-filter-intern (info)
  616. "Replace names of parameters with symbols in a package INFO.
  617. INFO is alist of parameter names (strings) from
  618. `aurel-pacman-param-alist' and their values.
  619. Return modified info."
  620. (aurel-filter-intern info 'aurel-get-pacman-param-symbol))
  621. (defun aurel-pacman-filter-none (info)
  622. "Replace `aurel-none-string' values in pacman INFO with nil."
  623. (mapcar (-lambda ((name . val))
  624. (cons name
  625. (unless (string= val aurel-none-string) val)))
  626. info))
  627. (defun aurel-filter-contains-every-string (info)
  628. "Check if a package INFO contains all necessary strings.
  629. Return INFO, if values of parameters from `aurel-filter-params'
  630. contain all strings from `aurel-filter-strings', otherwise return nil.
  631. Pass the check (return INFO), if `aurel-filter-strings' or
  632. `aurel-filter-params' is nil."
  633. (when (or (null aurel-filter-params)
  634. (null aurel-filter-strings)
  635. (let ((str (mapconcat (lambda (param)
  636. (bui-entry-value info param))
  637. aurel-filter-params
  638. "\n")))
  639. (cl-every (lambda (substr)
  640. (string-match-p (regexp-quote substr) str))
  641. aurel-filter-strings)))
  642. info))
  643. (defun aurel-filter-pkg-url (info)
  644. "Update `pkg-url' parameter in a package INFO.
  645. INFO is alist of parameter symbols and values.
  646. Return modified info."
  647. (let ((param (assoc 'pkg-url info)))
  648. (setcdr param (url-expand-file-name (cdr param) aurel-aur-base-url)))
  649. info)
  650. ;;; Searching/showing packages
  651. (defun aurel-receive-packages-info (url)
  652. "Return information about the packages from URL.
  653. Information is received with `aurel-get-aur-packages-info', then
  654. it is passed through `aurel-aur-filters' with
  655. `aurel-apply-filters'. If `aurel-installed-packages-check' is
  656. non-nil, additional information about installed packages is
  657. received with `aurel-get-installed-packages-info' and is passed
  658. through `aurel-installed-filters'. Finally packages info is passed
  659. through `aurel-final-filters'.
  660. Returning value is alist of (ID . PACKAGE-ALIST) entries."
  661. ;; To speed-up the process, pacman should be called once with the
  662. ;; names of found packages (instead of calling it for each name). So
  663. ;; we need to know the names at first, that's why we don't use a
  664. ;; single filters variable: at first we filter info received from AUR,
  665. ;; then we add information about installed packages from pacman and
  666. ;; finally filter the whole info.
  667. (let (aur-info-list aur-info-alist
  668. pac-info-list pac-info-alist
  669. info-list)
  670. ;; Receive and process information from AUR server
  671. (setq aur-info-list (aurel-get-aur-packages-info url)
  672. aur-info-alist (aurel-get-filtered-alist
  673. aur-info-list aurel-aur-filters 'name))
  674. ;; Receive and process information from pacman
  675. (when aurel-installed-packages-check
  676. (setq pac-info-list (apply 'aurel-get-installed-packages-info
  677. (mapcar #'car aur-info-alist))
  678. pac-info-alist (aurel-get-filtered-alist
  679. pac-info-list
  680. aurel-pacman-filters
  681. 'installed-name)))
  682. ;; Join info and do final processing
  683. (setq info-list
  684. (mapcar (lambda (aur-info-assoc)
  685. (let* ((name (car aur-info-assoc))
  686. (pac-info-assoc (assoc name pac-info-alist)))
  687. (append (cdr aur-info-assoc)
  688. (cdr pac-info-assoc))))
  689. aur-info-alist))
  690. (aurel-get-filtered-alist info-list aurel-final-filters 'id)))
  691. (defun aurel-get-filtered-alist (info-list filters param)
  692. "Return alist with filtered packages info.
  693. INFO-LIST is a list of packages info. Each info is passed through
  694. FILTERS with `aurel-apply-filters'.
  695. Each association of a returned value has a form:
  696. (PARAM-VAL . INFO)
  697. PARAM-VAL is a value of a parameter PARAM.
  698. INFO is a filtered package info."
  699. (delq nil ; ignore filtered (empty) info
  700. (mapcar (lambda (info)
  701. (let ((info (aurel-apply-filters info filters)))
  702. (and info
  703. (cons (bui-entry-value info param) info))))
  704. info-list)))
  705. (defun aurel-get-packages-by-name (&rest names)
  706. "Return packages by package NAMES (list of strings)."
  707. (aurel-receive-packages-info
  708. (apply #'aurel-get-package-info-url names)))
  709. (defun aurel-get-packages-by-string (&rest strings)
  710. "Return packages matching STRINGS."
  711. ;; A hack for searching by multiple strings: the actual server search
  712. ;; is done by the biggest string and the rest strings are searched in
  713. ;; the results returned by the server
  714. (let* ((str-list
  715. ;; sort to search by the biggest (first) string
  716. (sort strings
  717. (lambda (a b)
  718. (> (length a) (length b)))))
  719. (aurel-filter-params '(name description))
  720. (aurel-filter-strings (cdr str-list)))
  721. (aurel-receive-packages-info
  722. (aurel-get-package-search-url (car str-list)))))
  723. (defun aurel-get-packages-by-name-string (string)
  724. "Return packages with name containing STRING."
  725. (aurel-receive-packages-info
  726. (aurel-get-package-name-search-url string)))
  727. (defun aurel-get-packages-by-maintainer (name)
  728. "Return packages by maintainer NAME."
  729. (aurel-receive-packages-info
  730. (aurel-get-maintainer-search-url name)))
  731. (defvar aurel-search-type-alist
  732. '((name . aurel-get-packages-by-name)
  733. (string . aurel-get-packages-by-string)
  734. (name-string . aurel-get-packages-by-name-string)
  735. (maintainer . aurel-get-packages-by-maintainer))
  736. "Alist of available search types and search functions.")
  737. (defun aurel-search-packages (type &rest vals)
  738. "Search for AUR packages and return results.
  739. TYPE is a type of search - symbol from `aurel-search-type-alist'.
  740. It defines a search function which is called with VALS as
  741. arguments."
  742. (let ((fun (cdr (assoc type aurel-search-type-alist))))
  743. (or fun
  744. (error "Wrong search type '%s'" type))
  745. (apply fun vals)))
  746. (defun aurel-search-packages-with-user-info (type &rest vals)
  747. "Search for AUR packages and return results.
  748. This is like `aurel-search-packages' but also add AUR user info
  749. depending on `aurel-aur-user-package-info-check'."
  750. (let ((entries (apply #'aurel-search-packages type vals)))
  751. (if aurel-aur-user-package-info-check
  752. (mapcar #'aurel-add-aur-user-package-info entries)
  753. entries)))
  754. (defun aurel-search-show-packages (search-type &rest search-vals)
  755. "Search for packages and show results.
  756. See `aurel-search-packages' for the meaning of SEARCH-TYPE and
  757. SEARCH-VALS."
  758. (apply #'bui-list-get-display-entries
  759. 'aurel search-type search-vals))
  760. (defvar aurel-found-messages
  761. '((name (0 "The package \"%s\" not found." "Packages not found.")
  762. (1 "The package \"%s\"."))
  763. (string (0 "No packages matching %s.")
  764. (1 "A single package matching %s.")
  765. (many "%d packages matching %s."))
  766. (maintainer (0 "No packages by maintainer %s.")
  767. (1 "A single package by maintainer %s.")
  768. (many "%d packages by maintainer %s.")))
  769. "Alist used by `aurel-found-message'.")
  770. (defun aurel-found-message (packages search-type &rest search-vals)
  771. "Display a proper message about found PACKAGES.
  772. SEARCH-TYPE and SEARCH-VALS are arguments for
  773. `aurel-search-packages', by which the PACKAGES were found."
  774. (let* ((count (length packages))
  775. (found-key (if (> count 1) 'many count))
  776. (type-alist (cdr (assoc search-type aurel-found-messages)))
  777. (found-list (cdr (assoc found-key type-alist)))
  778. (msg (if (or (= 1 (length search-vals))
  779. (null (cdr found-list)))
  780. (car found-list)
  781. (cadr found-list)))
  782. (args (delq nil
  783. (list
  784. (and (eq found-key 'many) count)
  785. (cond
  786. ((eq search-type 'string)
  787. (mapconcat (lambda (str) (concat "\"" str "\""))
  788. search-vals " "))
  789. ((and (= count 1) (eq search-type 'name))
  790. (bui-entry-value (cdar packages) 'name))
  791. (t (car search-vals)))))))
  792. (and msg (apply 'message msg args))))
  793. ;;; Downloading
  794. (defcustom aurel-download-directory temporary-file-directory
  795. "Default directory for downloading AUR packages."
  796. :type 'directory
  797. :group 'aurel)
  798. (defcustom aurel-directory-prompt "Download to: "
  799. "Default directory prompt for downloading AUR packages."
  800. :type 'string
  801. :group 'aurel)
  802. (defvar aurel-download-functions
  803. '(aurel-download aurel-download-unpack aurel-download-unpack-dired
  804. aurel-download-unpack-pkgbuild aurel-download-unpack-eshell)
  805. "List of available download functions.")
  806. (defun aurel-read-download-directory ()
  807. "Return `aurel-download-directory' or prompt for it.
  808. This function is intended for using in `interactive' forms."
  809. (if current-prefix-arg
  810. (read-directory-name aurel-directory-prompt
  811. aurel-download-directory)
  812. aurel-download-directory))
  813. (defun aurel-download-get-defcustom-type ()
  814. "Return `defcustom' type for selecting a download function."
  815. `(radio ,@(mapcar (lambda (fun) (list 'function-item fun))
  816. aurel-download-functions)
  817. (function :tag "Other function")))
  818. (defun aurel-download (url dir)
  819. "Download AUR package from URL to a directory DIR.
  820. Return a path to the downloaded file."
  821. ;; Is there a simpler way to download a file?
  822. (let ((file-name-handler-alist
  823. (cons (cons url-handler-regexp 'url-file-handler)
  824. file-name-handler-alist)))
  825. (with-temp-buffer
  826. (insert-file-contents-literally url)
  827. (let ((file (expand-file-name (url-file-nondirectory url) dir)))
  828. (write-file file)
  829. file))))
  830. ;; Code for working with `tar-mode' came from `package-untar-buffer'
  831. ;; Avoid compilation warnings about tar functions and variables
  832. (defvar tar-parse-info)
  833. (defvar tar-data-buffer)
  834. (declare-function tar-untar-buffer "tar-mode" ())
  835. (declare-function tar-header-name "tar-mode" (tar-header) t)
  836. (declare-function tar-header-link-type "tar-mode" (tar-header) t)
  837. (defun aurel-tar-subdir (tar-info)
  838. "Return directory name where files from TAR-INFO will be extracted."
  839. (let* ((first-header (car tar-info))
  840. (first-header-type (tar-header-link-type first-header)))
  841. (cl-case first-header-type
  842. (55 ; pax_global_header
  843. ;; There are other special headers (see `tar--check-descriptor', for
  844. ;; example). Should they also be ignored?
  845. (aurel-tar-subdir (cdr tar-info)))
  846. (5 ; directory
  847. (let* ((dir-name (tar-header-name first-header))
  848. (dir-re (regexp-quote dir-name)))
  849. (dolist (tar-data (cdr tar-info))
  850. (or (string-match dir-re (tar-header-name tar-data))
  851. (error (concat "Not all files are going to be extracted"
  852. " into directory '%s'")
  853. dir-name)))
  854. dir-name))
  855. (t
  856. (error "The first entry '%s' in tar file is not a directory"
  857. (tar-header-name first-header))))))
  858. (defun aurel-download-unpack (url dir)
  859. "Download AUR package from URL and unpack it into a directory DIR.
  860. Use `tar-untar-buffer' from Tar mode. All files should be placed
  861. in one directory; otherwise, signal an error.
  862. Return a path to the unpacked directory."
  863. (let ((file-name-handler-alist
  864. (cons (cons url-handler-regexp 'url-file-handler)
  865. file-name-handler-alist)))
  866. (with-temp-buffer
  867. (insert-file-contents url)
  868. (setq default-directory dir)
  869. (let ((file (expand-file-name (url-file-nondirectory url) dir)))
  870. (write-file file))
  871. (tar-mode)
  872. (let ((tar-dir (aurel-tar-subdir tar-parse-info)))
  873. (tar-untar-buffer)
  874. (expand-file-name tar-dir dir)))))
  875. (defun aurel-download-unpack-dired (url dir)
  876. "Download and unpack AUR package, and open the unpacked directory.
  877. For the meaning of URL and DIR, see `aurel-download-unpack'."
  878. (dired (aurel-download-unpack url dir)))
  879. (defun aurel-download-unpack-pkgbuild (url dir)
  880. "Download and unpack AUR package, and open PKGBUILD file.
  881. For the meaning of URL and DIR, see `aurel-download-unpack'."
  882. (let* ((pkg-dir (aurel-download-unpack url dir))
  883. (file (expand-file-name "PKGBUILD" pkg-dir)))
  884. (if (file-exists-p file)
  885. (find-file file)
  886. (error "File '%s' doesn't exist" file))))
  887. ;; Avoid compilation warning about `eshell/cd'
  888. (declare-function eshell/cd "em-dirs" (&rest args))
  889. (defun aurel-download-unpack-eshell (url dir)
  890. "Download and unpack AUR package, switch to eshell.
  891. For the meaning of URL and DIR, see `aurel-download-unpack'."
  892. (let ((pkg-dir (aurel-download-unpack url dir)))
  893. (eshell)
  894. (eshell/cd pkg-dir)))
  895. ;;; Defining URL
  896. (defun aurel-get-fields-string (args)
  897. "Return string of names and values from ARGS alist.
  898. Each association of ARGS has a form: (NAME . VALUE).
  899. If NAME and VALUE are not strings, they are converted to strings
  900. with `prin1-to-string'.
  901. Returning string has a form: \"NAME=VALUE&...\"."
  902. (cl-flet ((hexify (arg)
  903. (url-hexify-string
  904. (if (stringp arg) arg (prin1-to-string arg)))))
  905. (mapconcat (lambda (arg)
  906. (concat (car arg)
  907. "="
  908. (hexify (cdr arg))))
  909. args
  910. "&")))
  911. (defun aurel-get-rpc-url (type args)
  912. "Return URL for getting info about AUR packages.
  913. TYPE is the name of an allowed method.
  914. ARGS should have a form taken by `aurel-get-fields-string'."
  915. (url-expand-file-name
  916. (concat "rpc/?"
  917. (aurel-get-fields-string
  918. (append `(("v" . "5") ; v5 of the RPC API.
  919. ("type" . ,type))
  920. args)))
  921. aurel-aur-base-url))
  922. (defun aurel-get-package-info-url (&rest names)
  923. "Return URL for getting info about packages with NAMES."
  924. (let ((args (mapcar (lambda (name)
  925. (cons "arg[]" name))
  926. names)))
  927. (aurel-get-rpc-url "info" args)))
  928. (defun aurel-get-package-search-url (str &optional field)
  929. "Return URL for searching a package by string STR.
  930. FIELD is a field (string) for searching. May be: 'name',
  931. 'name-desc' (default) or 'maintainer'."
  932. (or field (setq field "name-desc"))
  933. (aurel-get-rpc-url
  934. "search"
  935. `(("by" . ,field)
  936. ("arg" . ,str))))
  937. (defun aurel-get-package-name-search-url (str)
  938. "Return URL for searching a package name by string STR."
  939. (aurel-get-package-search-url str "name"))
  940. (defun aurel-get-maintainer-search-url (str)
  941. "Return URL for searching a maintainer by string STR."
  942. (aurel-get-package-search-url str "maintainer"))
  943. (defun aurel-get-maintainer-account-url (maintainer)
  944. "Return URL for MAINTAINER's AUR account."
  945. (url-expand-file-name (concat "account/" maintainer)
  946. aurel-aur-base-url))
  947. (defun aurel-get-aur-package-url (package)
  948. "Return AUR URL of a PACKAGE."
  949. (url-expand-file-name (concat "packages/" package)
  950. aurel-aur-base-url))
  951. (defun aurel-get-package-base-url (package-base)
  952. "Return AUR URL of a PACKAGE-BASE."
  953. (url-expand-file-name (concat "pkgbase/" package-base)
  954. aurel-aur-base-url))
  955. (defun aurel-get-package-action-url (package-base action)
  956. "Return URL for the PACKAGE-BASE ACTION."
  957. (concat (aurel-get-package-base-url package-base)
  958. "/" action))
  959. ;;; UI
  960. (defvar aurel-package-info-history nil
  961. "A history list for `aurel-package-info'.")
  962. (defvar aurel-package-search-history nil
  963. "A history list for `aurel-package-search'.")
  964. (defvar aurel-maintainer-search-history nil
  965. "A history list for `aurel-maintainer-search'.")
  966. ;;;###autoload
  967. (defun aurel-package-info (name)
  968. "Display information about AUR package with NAME."
  969. (interactive
  970. (list (read-string "Name: "
  971. nil 'aurel-package-info-history)))
  972. (aurel-search-show-packages 'name name))
  973. ;;;###autoload
  974. (defun aurel-package-search (string)
  975. "Search for AUR packages matching STRING.
  976. STRING can be a string of multiple words separated by spaces. To
  977. search for a string containing spaces, quote it with double
  978. quotes. For example, the following search is allowed:
  979. \"python library\" plot"
  980. (interactive
  981. (list (read-string "Search by name/description: "
  982. nil 'aurel-package-search-history)))
  983. (apply #'aurel-search-show-packages
  984. 'string (split-string-and-unquote string)))
  985. ;;;###autoload
  986. (defun aurel-package-search-by-name (string)
  987. "Search for AUR packages with name containing STRING."
  988. (interactive
  989. (list (read-string "Search by name: "
  990. nil 'aurel-package-search-history)))
  991. (aurel-search-show-packages 'name-string string))
  992. ;;;###autoload
  993. (defun aurel-maintainer-search (name)
  994. "Search for AUR packages by maintainer NAME."
  995. (interactive
  996. (list (read-string "Search by maintainer: "
  997. nil 'aurel-maintainer-search-history)))
  998. (aurel-search-show-packages 'maintainer name))
  999. ;;;###autoload
  1000. (defun aurel-installed-packages ()
  1001. "Display information about AUR packages installed in the system."
  1002. (interactive)
  1003. (apply #'aurel-search-show-packages
  1004. 'name (aurel-get-foreign-packages)))
  1005. ;;; Filtering packages
  1006. (defvar aurel-available-filters
  1007. '(aurel-filter-maintained
  1008. aurel-filter-unmaintained
  1009. aurel-filter-outdated
  1010. aurel-filter-not-outdated
  1011. aurel-filter-match-regexp
  1012. aurel-filter-not-match-regexp
  1013. aurel-filter-different-versions
  1014. aurel-filter-same-versions)
  1015. "List of commands that can be called for filtering a package list.
  1016. Used by `aurel-enable-filter'.")
  1017. (defvar aurel-filter-map
  1018. (let ((map (make-sparse-keymap)))
  1019. (set-keymap-parent map bui-filter-map)
  1020. (define-key map (kbd "f") 'aurel-enable-filter)
  1021. (define-key map (kbd "v") 'aurel-filter-same-versions)
  1022. (define-key map (kbd "V") 'aurel-filter-different-versions)
  1023. (define-key map (kbd "m") 'aurel-filter-unmaintained)
  1024. (define-key map (kbd "M") 'aurel-filter-maintained)
  1025. (define-key map (kbd "o") 'aurel-filter-outdated)
  1026. (define-key map (kbd "O") 'aurel-filter-not-outdated)
  1027. (define-key map (kbd "r") 'aurel-filter-not-match-regexp)
  1028. (define-key map (kbd "R") 'aurel-filter-match-regexp)
  1029. map)
  1030. "Keymap with filter commands for `aurel-list-mode'.")
  1031. (fset 'aurel-filter-map aurel-filter-map)
  1032. (defun aurel-package-maintained? (entry)
  1033. "Return non-nil, if package ENTRY has a maintainer."
  1034. (bui-entry-non-void-value entry 'maintainer))
  1035. (defun aurel-package-unmaintained? (entry)
  1036. "Return non-nil, if package ENTRY does not have a maintainer."
  1037. (not (aurel-package-maintained? entry)))
  1038. (defun aurel-package-outdated? (entry)
  1039. "Return non-nil, if package ENTRY is outdated."
  1040. (bui-entry-non-void-value entry 'outdated))
  1041. (defun aurel-package-not-outdated? (entry)
  1042. "Return non-nil, if package ENTRY is not outdated."
  1043. (not (aurel-package-outdated? entry)))
  1044. (defun aurel-package-same-versions? (entry)
  1045. "Return non-nil, if package ENTRY has the same installed and
  1046. available AUR versions."
  1047. (equal (bui-entry-non-void-value entry 'version)
  1048. (bui-entry-non-void-value entry 'installed-version)))
  1049. (defun aurel-package-different-versions? (entry)
  1050. "Return non-nil, if package ENTRY has different installed and
  1051. available AUR versions."
  1052. (not (aurel-package-same-versions? entry)))
  1053. (defun aurel-package-matching-regexp? (entry regexp)
  1054. "Return non-nil, if package ENTRY's name or description match REGEXP."
  1055. (or (string-match-p regexp (bui-entry-non-void-value entry 'name))
  1056. (string-match-p regexp (bui-entry-non-void-value entry 'description))))
  1057. (defun aurel-package-not-matching-regexp? (entry regexp)
  1058. "Return non-nil, if package ENTRY's name or description do not match REGEXP."
  1059. (not (aurel-package-matching-regexp? entry regexp)))
  1060. (defun aurel-enable-filter (arg)
  1061. "Prompt for a function for filtering package list and call it.
  1062. Choose candidates from `aurel-available-filters'.
  1063. If ARG is non-nil (with prefix), make selected filter the only
  1064. active one (remove other filters)."
  1065. (interactive "P")
  1066. (let ((fun (intern (completing-read
  1067. (if current-prefix-arg
  1068. "Add filter: "
  1069. "Enable filter: ")
  1070. aurel-available-filters))))
  1071. (or (fboundp fun)
  1072. (error "Wrong function %s" fun))
  1073. (funcall fun arg)))
  1074. (defun aurel-filter-maintained (arg)
  1075. "Filter current list by hiding maintained packages.
  1076. See `aurel-enable-filter' for the meaning of ARG."
  1077. (interactive "P")
  1078. (bui-enable-filter 'aurel-package-unmaintained? arg))
  1079. (defun aurel-filter-unmaintained (arg)
  1080. "Filter current list by hiding unmaintained packages.
  1081. See `aurel-enable-filter' for the meaning of ARG."
  1082. (interactive "P")
  1083. (bui-enable-filter 'aurel-package-maintained? arg))
  1084. (defun aurel-filter-outdated (arg)
  1085. "Filter current list by hiding outdated packages.
  1086. See `aurel-enable-filter' for the meaning of ARG."
  1087. (interactive "P")
  1088. (bui-enable-filter 'aurel-package-not-outdated? arg))
  1089. (defun aurel-filter-not-outdated (arg)
  1090. "Filter current list by hiding not outdated packages.
  1091. See `aurel-enable-filter' for the meaning of ARG."
  1092. (interactive "P")
  1093. (bui-enable-filter 'aurel-package-outdated? arg))
  1094. (defun aurel-filter-same-versions (arg)
  1095. "Hide packages with the same installed and available AUR versions.
  1096. See `aurel-enable-filter' for the meaning of ARG."
  1097. (interactive "P")
  1098. (bui-enable-filter 'aurel-package-different-versions? arg))
  1099. (defun aurel-filter-different-versions (arg)
  1100. "Hide packages with different installed and available AUR versions.
  1101. See `aurel-enable-filter' for the meaning of ARG."
  1102. (interactive "P")
  1103. (bui-enable-filter 'aurel-package-same-versions? arg))
  1104. (defun aurel-filter-match-regexp (arg)
  1105. "Hide packages with names or descriptions matching prompted regexp.
  1106. See `aurel-enable-filter' for the meaning of ARG."
  1107. (interactive "P")
  1108. (let ((re (read-regexp "Hide packages matching regexp: ")))
  1109. (bui-enable-filter
  1110. (lambda (entry)
  1111. (aurel-package-not-matching-regexp? entry re))
  1112. arg)))
  1113. (defun aurel-filter-not-match-regexp (arg)
  1114. "Hide packages with names or descriptions not matching prompted regexp.
  1115. See `aurel-enable-filter' for the meaning of ARG."
  1116. (interactive "P")
  1117. (let ((re (read-regexp "Hide packages not matching regexp: ")))
  1118. (bui-enable-filter
  1119. (lambda (entry)
  1120. (aurel-package-matching-regexp? entry re))
  1121. arg)))
  1122. ;;; Minibuffer readers
  1123. (defun aurel-read-package-name (&optional entries)
  1124. "Prompt for a package name and return it.
  1125. Names are completed from package ENTRIES."
  1126. (completing-read "Package: "
  1127. (--map (bui-entry-value it 'name) entries)))
  1128. (defun aurel-read-entry-by-name (entries)
  1129. "Prompt for a package name and return an entry with this name from ENTRIES."
  1130. (pcase entries
  1131. (`(,entry) entry)
  1132. (_ (bui-entry-by-param entries 'name
  1133. (aurel-read-package-name entries)))))
  1134. ;;; Common for 'list' and 'info'
  1135. (bui-define-entry-type aurel
  1136. :message-function 'aurel-found-message
  1137. :mode-init-function 'aurel-initialize
  1138. :titles
  1139. '((pkg-url . "Package URL")
  1140. (home-url . "Home page")
  1141. (aur-url . "AUR page")
  1142. (base-url . "Package base")
  1143. (last-date . "Last modified")
  1144. (first-date . "Submitted")
  1145. (outdated . "Out of date")
  1146. (base-name . "Package base")
  1147. (base-id . "Package base ID")
  1148. (depends . "Depends on")
  1149. (depends-make . "Make deps")
  1150. (conflicts . "Conflicts with"))
  1151. :filter-predicates
  1152. '(aurel-package-maintained?
  1153. aurel-package-unmaintained?
  1154. aurel-package-outdated?
  1155. aurel-package-not-outdated?
  1156. aurel-package-different-versions?
  1157. aurel-package-same-versions?)
  1158. :boolean-params '(outdated))
  1159. (defun aurel-initialize ()
  1160. "Set local variables common for aurel modes."
  1161. (setq default-directory aurel-download-directory))
  1162. ;;; Package list
  1163. (defcustom aurel-list-download-function 'aurel-download-unpack
  1164. "Function used for downloading a single AUR package from list buffer.
  1165. It should accept 2 arguments: URL of a downloading file and a
  1166. destination directory."
  1167. :type (aurel-download-get-defcustom-type)
  1168. :group 'aurel-list)
  1169. (defcustom aurel-list-multi-download-function 'aurel-download-unpack
  1170. "Function used for downloading multiple AUR packages from list buffer.
  1171. It should accept 2 arguments: URL of a downloading file and a
  1172. destination directory."
  1173. :type (aurel-download-get-defcustom-type)
  1174. :group 'aurel-list)
  1175. (defcustom aurel-list-multi-download-no-confirm nil
  1176. "If non-nil, do not ask to confirm if multiple packages are downloaded."
  1177. :type 'boolean
  1178. :group 'aurel-list)
  1179. (bui-define-interface aurel list
  1180. :buffer-name "*AUR Packages*"
  1181. :mode-name "AURel-List"
  1182. :get-entries-function 'aurel-search-packages
  1183. :describe-function 'aurel-list-describe
  1184. :titles '((installed-version . "Installed"))
  1185. :format '((name aurel-list-get-name 30 t)
  1186. (version nil 12 t)
  1187. (installed-version nil 12 t)
  1188. (maintainer aurel-list-get-maintainer 13 t)
  1189. (votes nil 8 bui-list-sort-numerically-4 :right-align t)
  1190. (popularity aurel-list-get-popularity 12 t)
  1191. (description nil 30 nil))
  1192. :hint 'aurel-list-hint
  1193. :sort-key '(name))
  1194. (let ((map aurel-list-mode-map))
  1195. (define-key map (kbd "d") 'aurel-list-download-package)
  1196. (define-key map (kbd "f") 'aurel-filter-map))
  1197. (defvar aurel-list-default-hint
  1198. '(("\\[aurel-list-download-package]") " download package(s);\n"))
  1199. (defun aurel-list-hint ()
  1200. (bui-format-hints
  1201. aurel-list-default-hint
  1202. (bui-default-hint)))
  1203. (defun aurel-list-get-name (name entry)
  1204. "Return package NAME.
  1205. Colorize the name with `aurel-info-outdated' if the package is
  1206. out of date."
  1207. (bui-get-string name
  1208. (when (bui-entry-value entry 'outdated)
  1209. 'aurel-info-outdated)))
  1210. (defun aurel-list-get-popularity (popularity &optional _)
  1211. "Return formatted POPULARITY."
  1212. ;; Display popularity in a decimal-point notation to avoid things like
  1213. ;; "9.6e-05".
  1214. (format "%10.4f" popularity))
  1215. (defun aurel-list-get-maintainer (name &optional _)
  1216. "Return maintainer NAME specification for `tabulated-list-entries'."
  1217. (bui-get-non-nil name
  1218. (list name
  1219. 'face 'aurel-info-maintainer
  1220. 'action (lambda (btn)
  1221. (aurel-maintainer-search (button-label btn)))
  1222. 'follow-link t
  1223. 'help-echo "Find packages by this maintainer")))
  1224. (defun aurel-list-describe (&rest ids)
  1225. "Describe packages with IDS."
  1226. ;; A list of packages is received using 'search' type. However, in
  1227. ;; AUR RPC API, 'info' type returns several additional parameters
  1228. ;; ("Depends", "Replaces", ...) comparing to the 'search' type. So
  1229. ;; re-receiving a package info (using 'info' type this time) is
  1230. ;; needed. Moreover, this API does not (!) provide a way to get info
  1231. ;; by package IDs, so we have to search by names.
  1232. (let* ((entries (bui-entries-by-ids (bui-current-entries) ids))
  1233. (names (--map (bui-entry-value it 'name)
  1234. entries)))
  1235. (bui-get-display-entries 'aurel 'info (cons 'name names))))
  1236. (defun aurel-list-download-package ()
  1237. "Download marked packages or the current package if nothing is marked.
  1238. With prefix, prompt for a directory with `aurel-directory-prompt'
  1239. to save the package; without prefix, save to
  1240. `aurel-download-directory' without prompting.
  1241. Use `aurel-list-download-function' if a single package is
  1242. downloaded or `aurel-list-multi-download-function' otherwise."
  1243. (interactive)
  1244. (let* ((dir (aurel-read-download-directory))
  1245. (ids (or (bui-list-get-marked-id-list)
  1246. (list (bui-list-current-id))))
  1247. (count (length ids)))
  1248. (if (> count 1)
  1249. (when (or aurel-list-multi-download-no-confirm
  1250. (y-or-n-p (format "Download %d marked packages? "
  1251. count)))
  1252. (mapcar (lambda (entry)
  1253. (funcall aurel-list-multi-download-function
  1254. (bui-entry-value entry 'pkg-url)
  1255. dir))
  1256. (bui-entries-by-ids (bui-current-entries) ids)))
  1257. (funcall aurel-list-download-function
  1258. (bui-entry-value (bui-entry-by-id (bui-current-entries)
  1259. (car ids))
  1260. 'pkg-url)
  1261. dir))))
  1262. ;;; Package info
  1263. (defface aurel-info-id
  1264. '((t))
  1265. "Face used for ID of a package."
  1266. :group 'aurel-info-faces)
  1267. (defface aurel-info-name
  1268. '((t :inherit font-lock-keyword-face))
  1269. "Face used for a name of a package."
  1270. :group 'aurel-info-faces)
  1271. (defface aurel-info-maintainer
  1272. '((t :inherit button))
  1273. "Face used for a maintainer of a package."
  1274. :group 'aurel-info-faces)
  1275. (defface aurel-info-version
  1276. '((t :inherit font-lock-builtin-face))
  1277. "Face used for a version of a package."
  1278. :group 'aurel-info-faces)
  1279. (defface aurel-info-keywords
  1280. '((t :inherit font-lock-comment-face))
  1281. "Face used for keywords of a package."
  1282. :group 'aurel-info-faces)
  1283. (defface aurel-info-description
  1284. '((t))
  1285. "Face used for a description of a package."
  1286. :group 'aurel-info-faces)
  1287. (defface aurel-info-license
  1288. '((t))
  1289. "Face used for a license of a package."
  1290. :group 'aurel-info-faces)
  1291. (defface aurel-info-votes
  1292. '((t :weight bold))
  1293. "Face used for a number of votes of a package."
  1294. :group 'aurel-info-faces)
  1295. (defface aurel-info-popularity
  1296. '((t))
  1297. "Face used for popularity of a package."
  1298. :group 'aurel-info-faces)
  1299. (defface aurel-info-voted-mark
  1300. '((t :inherit aurel-info-voted))
  1301. "Face used for `aurel-info-voted-mark' string."
  1302. :group 'aurel-info-faces)
  1303. (defface aurel-info-outdated
  1304. '((t :inherit font-lock-warning-face))
  1305. "Face used if a package is out of date."
  1306. :group 'aurel-info-faces)
  1307. (defface aurel-info-voted
  1308. '((default :weight bold)
  1309. (((class color) (min-colors 88) (background light))
  1310. :foreground "ForestGreen")
  1311. (((class color) (min-colors 88) (background dark))
  1312. :foreground "PaleGreen")
  1313. (((class color) (min-colors 8))
  1314. :foreground "green")
  1315. (t :underline t))
  1316. "Face used if a package is voted."
  1317. :group 'aurel-info-faces)
  1318. (defface aurel-info-not-voted
  1319. '((t))
  1320. "Face used if a package is not voted."
  1321. :group 'aurel-info-faces)
  1322. (defface aurel-info-subscribed
  1323. '((t :inherit aurel-info-voted))
  1324. "Face used if a package is subscribed."
  1325. :group 'aurel-info-faces)
  1326. (defface aurel-info-not-subscribed
  1327. '((t :inherit aurel-info-not-voted))
  1328. "Face used if a package is not subscribed."
  1329. :group 'aurel-info-faces)
  1330. (defface aurel-info-date
  1331. '((t :inherit font-lock-constant-face))
  1332. "Face used for dates."
  1333. :group 'aurel-info-faces)
  1334. (defface aurel-info-size
  1335. '((t :inherit font-lock-variable-name-face))
  1336. "Face used for size of installed package."
  1337. :group 'aurel-info-faces)
  1338. (defface aurel-info-architecture
  1339. '((t))
  1340. "Face used for 'Architecture' parameter."
  1341. :group 'aurel-info-faces)
  1342. (defface aurel-info-provides
  1343. '((t :inherit font-lock-function-name-face))
  1344. "Face used for 'Provides' parameter."
  1345. :group 'aurel-info-faces)
  1346. (defface aurel-info-replaces
  1347. '((t :inherit aurel-info-provides))
  1348. "Face used for 'Replaces' parameter."
  1349. :group 'aurel-info-faces)
  1350. (defface aurel-info-conflicts
  1351. '((t :inherit aurel-info-provides))
  1352. "Face used for 'Conflicts With' parameter."
  1353. :group 'aurel-info-faces)
  1354. (defface aurel-info-depends
  1355. '((t))
  1356. "Face used for 'Depends On' parameter."
  1357. :group 'aurel-info-faces)
  1358. (defface aurel-info-depends-make
  1359. '((t))
  1360. "Face used for 'Make Deps' parameter."
  1361. :group 'aurel-info-faces)
  1362. (defface aurel-info-depends-opt
  1363. '((t :inherit aurel-info-depends))
  1364. "Face used for 'Optional Deps' parameter."
  1365. :group 'aurel-info-faces)
  1366. (defface aurel-info-required
  1367. '((t))
  1368. "Face used for 'Required By' parameter."
  1369. :group 'aurel-info-faces)
  1370. (defface aurel-info-optional-for
  1371. '((t :inherit aurel-info-required))
  1372. "Face used for 'Optional For' parameter."
  1373. :group 'aurel-info-faces)
  1374. (defface aurel-info-packager
  1375. '((t))
  1376. "Face used for 'Packager' parameter."
  1377. :group 'aurel-info-faces)
  1378. (defface aurel-info-validated
  1379. '((t))
  1380. "Face used for 'Validated By' parameter."
  1381. :group 'aurel-info-faces)
  1382. (defface aurel-info-script
  1383. '((t))
  1384. "Face used for 'Install script' parameter."
  1385. :group 'aurel-info-faces)
  1386. (defcustom aurel-info-download-function 'aurel-download-unpack-dired
  1387. "Function used for downloading AUR package from package info buffer.
  1388. It should accept 2 arguments: URL of a downloading file and a
  1389. destination directory."
  1390. :type (aurel-download-get-defcustom-type)
  1391. :group 'aurel-info)
  1392. (defcustom aurel-info-voted-mark "*"
  1393. "String inserted after the number of votes in info buffer.
  1394. See `aurel-info-display-voted-mark' for details."
  1395. :type 'string
  1396. :group 'aurel-info)
  1397. (defcustom aurel-info-display-voted-mark t
  1398. "If non-nil, display `aurel-info-voted-mark' in info buffer.
  1399. It is displayed only if a package is voted by you (this
  1400. information is available if `aurel-aur-user-package-info-check'
  1401. is non-nil)."
  1402. :type 'boolean
  1403. :group 'aurel-info)
  1404. (defcustom aurel-info-installed-package-string
  1405. "\nThis package is installed:\n\n"
  1406. "String inserted in info buffer if a package is installed.
  1407. It is inserted after printing info from AUR and before info from pacman."
  1408. :type 'string
  1409. :group 'aurel-info)
  1410. (defcustom aurel-info-aur-user-string
  1411. "\nAUR user account info:\n\n"
  1412. "String inserted before printing info specific for AUR user."
  1413. :type 'string
  1414. :group 'aurel-info)
  1415. (bui-define-interface aurel info
  1416. :buffer-name "*AUR Package Info*"
  1417. :mode-name "AURel-Info"
  1418. :get-entries-function 'aurel-search-packages-with-user-info
  1419. :format '((name nil (simple aurel-info-name))
  1420. nil
  1421. (description nil (simple aurel-info-description))
  1422. nil
  1423. (pkg-url simple aurel-info-insert-package-url)
  1424. (version format (simple aurel-info-version))
  1425. (maintainer format aurel-info-insert-maintainer)
  1426. (home-url format (format bui-url))
  1427. aurel-info-insert-aur-url
  1428. aurel-info-insert-base-url
  1429. (provides format (format aurel-info-provides))
  1430. (depends-make format (format aurel-info-depends-make))
  1431. (depends format (format aurel-info-depends))
  1432. (conflicts format (format aurel-info-conflicts))
  1433. (replaces format (format aurel-info-replaces))
  1434. (license format (format aurel-info-license))
  1435. (keywords format (format aurel-info-keywords))
  1436. (votes format aurel-info-insert-votes)
  1437. (popularity format (simple aurel-info-popularity))
  1438. (outdated format (time aurel-info-outdated))
  1439. (first-date format (time aurel-info-date))
  1440. (last-date format (time aurel-info-date))
  1441. aurel-info-insert-pacman-info
  1442. aurel-info-insert-aur-user-info)
  1443. :hint 'aurel-info-hint)
  1444. (bui-define-interface aurel-pacman info
  1445. :reduced? t
  1446. :format '((installed-version format (simple aurel-info-version))
  1447. (architecture format (simple aurel-info-architecture))
  1448. (installed-size format (simple aurel-info-size))
  1449. (installed-provides format (format aurel-info-provides))
  1450. (installed-depends format (format aurel-info-depends))
  1451. (depends-opt format (format aurel-info-depends-opt))
  1452. (required format (format aurel-info-required))
  1453. (optional-for format (format aurel-info-optional-for))
  1454. (installed-conflicts format (format aurel-info-conflicts))
  1455. (installed-replaces format (format aurel-info-replaces))
  1456. (packager format (simple aurel-info-packager))
  1457. (build-date format (time aurel-info-date))
  1458. (install-date format (time aurel-info-date))
  1459. (script format (format aurel-info-script))
  1460. (validated format (format aurel-info-validated)))
  1461. :titles
  1462. '((installed-name . "Name")
  1463. (installed-version . "Version")
  1464. (installed-provides . "Provides")
  1465. (installed-depends . "Depends on")
  1466. (installed-conflicts . "Conflicts with")
  1467. (installed-replaces . "Replaces")
  1468. (installed-size . "Size")
  1469. (depends-opt . "Optional deps")
  1470. (script . "Install script")
  1471. (reason . "Install reason")
  1472. (validated . "Validated by")
  1473. (required . "Required by")))
  1474. (bui-define-interface aurel-user info
  1475. :reduced? t
  1476. :format '((voted format aurel-info-insert-voted)
  1477. (subscribed format aurel-info-insert-subscribed)))
  1478. (let ((map aurel-info-mode-map))
  1479. (define-key map (kbd "f") 'aurel-filter-map)
  1480. (define-key map (kbd "d") 'aurel-info-download-package)
  1481. (define-key map (kbd "v") 'aurel-info-vote-unvote)
  1482. (define-key map (kbd "s") 'aurel-info-subscribe-unsubscribe))
  1483. (defvar aurel-info-default-hint
  1484. '(("\\[aurel-info-download-package]") " download package;\n"
  1485. ("\\[aurel-info-vote-unvote]") " vote/unvote;\n"
  1486. ("\\[aurel-info-subscribe-unsubscribe]") " subscribe/unsubscribe;\n"))
  1487. (defun aurel-info-hint ()
  1488. (bui-format-hints
  1489. aurel-info-default-hint
  1490. (bui-default-hint)))
  1491. (defun aurel-info-insert-votes (votes entry)
  1492. "Insert the number of VOTES at point.
  1493. If `aurel-info-display-voted-mark' is non-nil, insert
  1494. `aurel-info-voted-mark' after."
  1495. (bui-format-insert votes 'aurel-info-votes)
  1496. (and aurel-info-display-voted-mark
  1497. (--when-let (bui-entry-non-void-value entry 'user-info)
  1498. (bui-entry-value it 'voted))
  1499. (bui-format-insert aurel-info-voted-mark
  1500. 'aurel-info-voted-mark)))
  1501. (define-button-type 'aurel-maintainer
  1502. :supertype 'bui
  1503. 'face 'aurel-info-maintainer
  1504. 'follow-link t
  1505. 'help-echo "Browse maintainer's account"
  1506. 'action (lambda (btn)
  1507. (browse-url (aurel-get-maintainer-account-url
  1508. (button-label btn)))))
  1509. (defun aurel-info-insert-maintainer (name &optional _)
  1510. "Make button from maintainer NAME and insert it at point."
  1511. (bui-insert-non-nil name
  1512. (bui-insert-button name 'aurel-maintainer)
  1513. (bui-insert-indent)
  1514. (bui-insert-action-button
  1515. "Packages"
  1516. (lambda (btn)
  1517. (aurel-maintainer-search (button-get btn 'maintainer)))
  1518. "Find packages by this maintainer"
  1519. 'maintainer name)))
  1520. (defun aurel-info-insert-package-url (url &optional _)
  1521. "Insert package URL and 'Download' button at point."
  1522. (bui-insert-action-button
  1523. "Download"
  1524. (lambda (btn)
  1525. (aurel-info-download-package (button-get btn 'url)
  1526. (aurel-read-download-directory)))
  1527. "Download this package"
  1528. 'url url)
  1529. (bui-info-insert-value-indent url 'bui-url))
  1530. (defun aurel-info-insert-aur-url (entry)
  1531. "Insert URL of the AUR package."
  1532. (bui-info-insert-title-format (bui-info-param-title 'aurel 'aur-url))
  1533. (bui-info-insert-value-simple
  1534. (aurel-get-aur-package-url (bui-entry-value entry 'name))
  1535. 'bui-url)
  1536. (bui-newline))
  1537. (defun aurel-info-insert-base-url (entry)
  1538. "Insert URL of the AUR package base."
  1539. (bui-info-insert-title-format (bui-info-param-title 'aurel 'base-url))
  1540. (bui-info-insert-value-simple
  1541. (aurel-get-package-base-url (bui-entry-value entry 'base-name))
  1542. 'bui-url)
  1543. (bui-newline))
  1544. (defun aurel-info-insert-pacman-info (entry)
  1545. "Insert installed (pacman) info from package ENTRY."
  1546. (when (bui-entry-non-void-value entry 'installed-name)
  1547. (insert aurel-info-installed-package-string)
  1548. (bui-info-insert-entry entry 'aurel-pacman)))
  1549. (defun aurel-info-insert-aur-user-info (entry)
  1550. "Insert AUR user info from package ENTRY."
  1551. (--when-let (bui-entry-non-void-value entry 'user-info)
  1552. (insert aurel-info-aur-user-string)
  1553. (bui-info-insert-entry
  1554. ;; Add 'base-name' as it is needed for Vote/Subscribe buttons.
  1555. `((base-name . ,(bui-entry-value entry 'base-name))
  1556. ,@it)
  1557. 'aurel-user)))
  1558. (defun aurel-info-insert-boolean (val &optional t-face nil-face)
  1559. "Insert boolean value VAL at point.
  1560. If VAL is nil, use NIL-FACE, otherwise use T-FACE."
  1561. (let ((face (if val t-face nil-face)))
  1562. (insert (bui-get-string (or val bui-false-string) face))))
  1563. (defun aurel-info-aur-user-action-button (button)
  1564. (aurel-info-aur-user-action (button-get button 'aur-action)
  1565. (button-get button 'base-name)))
  1566. (defun aurel-info-insert-voted (voted entry)
  1567. "Insert VOTED parameter at point."
  1568. (aurel-info-insert-boolean voted
  1569. 'aurel-info-voted
  1570. 'aurel-info-not-voted)
  1571. (bui-insert-indent)
  1572. (bui-insert-action-button
  1573. (if voted "Unvote" "Vote")
  1574. 'aurel-info-aur-user-action-button
  1575. (if voted
  1576. "Remove your vote for this package"
  1577. "Vote for this package")
  1578. 'base-name (bui-entry-value entry 'base-name)
  1579. 'aur-action (if voted 'unvote 'vote)))
  1580. (defun aurel-info-insert-subscribed (subscribed entry)
  1581. "Insert SUBSCRIBED parameter at point."
  1582. (aurel-info-insert-boolean subscribed
  1583. 'aurel-info-subscribed
  1584. 'aurel-info-not-subscribed)
  1585. (bui-insert-indent)
  1586. (bui-insert-action-button
  1587. (if subscribed "Unsubscribe" "Subscribe")
  1588. 'aurel-info-aur-user-action-button
  1589. (if subscribed
  1590. "Unsubscribe from this package"
  1591. "Subscribe to this package")
  1592. 'base-name (bui-entry-value entry 'base-name)
  1593. 'aur-action (if subscribed 'unsubscribe 'subscribe)))
  1594. (defun aurel-info-download-package (url dir)
  1595. "Download package URL to DIR using `aurel-info-download-function'.
  1596. Interactively, download the current package.
  1597. With prefix, prompt for a directory with `aurel-directory-prompt'
  1598. to save the package; without prefix, save to
  1599. `aurel-download-directory' without prompting."
  1600. (interactive
  1601. (list (bui-entry-value (aurel-read-entry-by-name (bui-current-entries))
  1602. 'pkg-url)
  1603. (aurel-read-download-directory)))
  1604. (funcall aurel-info-download-function url dir))
  1605. (defun aurel-info-aur-user-action (action package-base &optional norevert)
  1606. "Perform AUR user ACTION on the current package.
  1607. See `aurel-aur-user-action' for the meaning of ACTION and PACKAGE-BASE.
  1608. If NOREVERT is non-nil, do not revert the buffer (i.e. do not
  1609. refresh package information) after ACTION."
  1610. (and (aurel-aur-user-action action package-base)
  1611. (null norevert)
  1612. (revert-buffer nil t)))
  1613. (defun aurel-info-vote-unvote (arg)
  1614. "Vote for the current package.
  1615. With prefix (if ARG is non-nil), unvote."
  1616. (interactive "P")
  1617. (aurel-info-aur-user-action
  1618. (if arg 'unvote 'vote)
  1619. (bui-entry-value (aurel-read-entry-by-name (bui-current-entries))
  1620. 'base-name)))
  1621. (defun aurel-info-subscribe-unsubscribe (arg)
  1622. "Subscribe to the new comments of the current package.
  1623. With prefix (if ARG is non-nil), unsubscribe."
  1624. (interactive "P")
  1625. (aurel-info-aur-user-action
  1626. (if arg 'unsubscribe 'subscribe)
  1627. (bui-entry-value (aurel-read-entry-by-name (bui-current-entries))
  1628. 'base-name)))
  1629. (provide 'aurel)
  1630. ;;; aurel.el ends here