aurel.el 90 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463
  1. ;;; aurel.el --- Search, get info, vote and download AUR packages
  2. ;; Copyright (C) 2014 Alex Kost
  3. ;; Author: Alex Kost <alezost@gmail.com>
  4. ;; Created: 6 Feb 2014
  5. ;; Version: 0.6
  6. ;; URL: https://github.com/alezost/aurel
  7. ;; Keywords: tools
  8. ;; This program is free software; you can redistribute it and/or modify
  9. ;; it under the terms of the GNU General Public License as published by
  10. ;; the Free Software Foundation, either version 3 of the License, or
  11. ;; (at your option) any later version.
  12. ;; This program is distributed in the hope that it will be useful,
  13. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;; GNU General Public License for more details.
  16. ;; You should have received a copy of the GNU General Public License
  17. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. ;;; Commentary:
  19. ;; The package provides an interface for searching, getting information,
  20. ;; voting, subscribing and downloading packages from the Arch User
  21. ;; Repository (AUR) <https://aur.archlinux.org/>.
  22. ;; To manually install the package, add the following to your init-file:
  23. ;;
  24. ;; (add-to-list 'load-path "/path/to/aurel-dir")
  25. ;; (autoload 'aurel-package-info "aurel" nil t)
  26. ;; (autoload 'aurel-package-search "aurel" nil t)
  27. ;; (autoload 'aurel-maintainer-search "aurel" nil t)
  28. ;; (autoload 'aurel-installed-packages "aurel" nil t)
  29. ;; Also set a directory where downloaded packages will be put:
  30. ;;
  31. ;; (setq aurel-download-directory "~/aur")
  32. ;; To search for packages, use `aurel-package-search' or
  33. ;; `aurel-maintainer-search' commands. If you know the name of a
  34. ;; package, use `aurel-package-info' command. Also you can display a
  35. ;; list of installed AUR packages with `aurel-installed-packages'.
  36. ;; Information about the packages is represented in a list-like buffer
  37. ;; similar to a buffer containing emacs packages. To get more info
  38. ;; about a package, press "RET" on a package line. To download a
  39. ;; package, press "d" (don't forget to set `aurel-download-directory'
  40. ;; before). In a list buffer, you can mark several packages for
  41. ;; downloading with "m"/"M" (and unmark with "u"/"U" and "DEL"); also
  42. ;; you can perform filtering (press "f f" to enable a filter and "f d"
  43. ;; to disable all filters) of a current list to hide particular
  44. ;; packages.
  45. ;; It is possible to move to the previous/next displayed results with
  46. ;; "l"/"r" (each aurel buffer has its own history) and to refresh
  47. ;; information with "g".
  48. ;; After receiving information about the packages, pacman is called to
  49. ;; find what packages are installed. To disable that, set
  50. ;; `aurel-installed-packages-check' to nil.
  51. ;; To vote/subscribe for a package, press "v"/"s" (with prefix
  52. ;; unvote/unsubscribe) in a package info buffer (you should have an AUR
  53. ;; account for that). To add information about "Voted"/"Subscribed"
  54. ;; status, use the following:
  55. ;;
  56. ;; (setq aurel-aur-user-package-info-check t)
  57. ;; For full description and screenshots, see
  58. ;; <https://github.com/alezost/aurel>.
  59. ;;; Code:
  60. (require 'url)
  61. (require 'url-handlers)
  62. (require 'json)
  63. (require 'tabulated-list)
  64. (require 'cl-macs)
  65. (defgroup aurel nil
  66. "Search and download AUR (Arch User Repository) packages."
  67. :group 'applications)
  68. (defcustom aurel-empty-string "-"
  69. "String used for empty (or none) values of package parameters."
  70. :type 'string
  71. :group 'aurel)
  72. (defcustom aurel-true-string "Yes"
  73. "String used if a parameter value is t."
  74. :type 'string
  75. :group 'aurel)
  76. (defcustom aurel-false-string "No"
  77. "String used if a parameter value is nil."
  78. :type 'string
  79. :group 'aurel)
  80. (defcustom aurel-date-format "%Y-%m-%d %T"
  81. "Time format used to represent time parameters of a package.
  82. For information about time formats, see `format-time-string'."
  83. :type 'string
  84. :group 'aurel)
  85. (defcustom aurel-list-single-package nil
  86. "If non-nil, list a package even if it is the one matching result.
  87. If nil, show a single matching package in info buffer."
  88. :type 'boolean
  89. :group 'aurel)
  90. (defcustom aurel-aur-user-package-info-check nil
  91. "If non-nil, check additional info before displaying a package info.
  92. Additional info is an AUR user specific information (whether the user
  93. voted for the package or subscribed to receive comments)."
  94. :type 'boolean
  95. :group 'aurel)
  96. (defvar aurel-unknown-string "Unknown"
  97. "String used if a value of the parameter is unknown.")
  98. (defvar aurel-none-string "None"
  99. "String saying that a parameter has no value.")
  100. (defvar aurel-package-name-re
  101. "[-+_[:alnum:]]+"
  102. "Regexp matching a valid package name.")
  103. (defun aurel-get-string (val &optional face)
  104. "Return string from VAL.
  105. If VAL is `aurel-none-string' return `aurel-empty-string'.
  106. If VAL is nil, return `aurel-false-string'.
  107. If VAL is t, return `aurel-true-string'.
  108. If VAL is a number, use `number-to-string'.
  109. If VAL is a time value, format it with `aurel-date-format'.
  110. Otherwise, if VAL is not string, use `prin1-to-string'.
  111. If FACE is non-nil, propertize returned string with this FACE."
  112. (if (equal val aurel-none-string)
  113. aurel-empty-string
  114. (setq val
  115. (cond
  116. ((stringp val) val)
  117. ((null val) aurel-false-string)
  118. ((eq t val) aurel-true-string)
  119. ((numberp val) (number-to-string val))
  120. ((aurel-time-p val)
  121. (format-time-string aurel-date-format val))
  122. (t (prin1-to-string val))))
  123. (if face
  124. (propertize val 'face face)
  125. val)))
  126. (defun aurel-time-p (val)
  127. "Return non-nil, if VAL is a time value; return nil otherwise."
  128. (condition-case nil
  129. (decode-time val)
  130. (error nil)))
  131. ;;; Debugging
  132. (defvar aurel-debug-level 0
  133. "If > 0, display debug messages in `aurel-debug-buffer'.
  134. The greater the number, the more messages is printed.
  135. Max level is 9.")
  136. (defvar aurel-debug-buffer "*aurel debug*"
  137. "Name of a buffer containing debug messages.")
  138. (defvar aurel-debug-time-format "%T.%3N"
  139. "Time format used for debug mesages.")
  140. (defun aurel-debug (level msg &rest args)
  141. "Print debug message if needed.
  142. If `aurel-debug-level' >= LEVEL, print debug message MSG with
  143. arguments ARGS into `aurel-debug-buffer'.
  144. Return nil."
  145. (when (>= aurel-debug-level level)
  146. (with-current-buffer (get-buffer-create aurel-debug-buffer)
  147. (goto-char (point-max))
  148. (insert (format-time-string aurel-debug-time-format (current-time)))
  149. (insert " " (apply 'format msg args) "\n")))
  150. nil)
  151. ;;; Interacting with AUR server
  152. (defcustom aurel-aur-user-name ""
  153. "User name for AUR."
  154. :type 'string
  155. :group 'aurel)
  156. (defvar aurel-aur-host "aur.archlinux.org"
  157. "AUR domain.")
  158. (defvar aurel-aur-base-url (concat "https://" aurel-aur-host)
  159. "Root URL of the AUR service.")
  160. (defvar aurel-aur-login-url
  161. (url-expand-file-name "login" aurel-aur-base-url)
  162. "Login URL.")
  163. (defconst aurel-aur-cookie-name "AURSID"
  164. "Cookie name used for AUR login.")
  165. ;; Avoid compilation warning about `url-http-response-status'
  166. (defvar url-http-response-status)
  167. (defun aurel-check-response-status (buffer &optional noerror)
  168. "Return t, if URL response status in BUFFER is 2XX or 3XX.
  169. Otherwise, throw an error or return nil, if NOERROR is nil."
  170. (with-current-buffer buffer
  171. (aurel-debug 3 "Response status: %s" url-http-response-status)
  172. (if (or (null (numberp url-http-response-status))
  173. (> url-http-response-status 399))
  174. (unless noerror (error "Error during request: %s"
  175. url-http-response-status))
  176. t)))
  177. (defun aurel-receive-parse-info (url)
  178. "Return received output from URL processed with `json-read'."
  179. (aurel-debug 3 "Retrieving %s" url)
  180. (let ((buf (url-retrieve-synchronously url)))
  181. (aurel-check-response-status buf)
  182. (with-current-buffer buf
  183. (goto-char (point-min))
  184. (re-search-forward "^{") ;; is there a better way?
  185. (beginning-of-line)
  186. (let ((json-key-type 'string)
  187. (json-array-type 'list)
  188. (json-object-type 'alist))
  189. (json-read)))))
  190. (defun aurel-get-aur-packages-info (url)
  191. "Return information about the packages from URL.
  192. Output from URL should be a json data. It is parsed with
  193. `json-read'.
  194. Returning value is alist of AUR package parameters (strings from
  195. `aurel-aur-param-alist') and their values."
  196. (let* ((full-info (aurel-receive-parse-info url))
  197. (type (cdr (assoc "type" full-info)))
  198. (count (cdr (assoc "resultcount" full-info)))
  199. (results (cdr (assoc "results" full-info))))
  200. (cond
  201. ((string= type "error")
  202. (error "%s" results))
  203. ((= count 0)
  204. nil)
  205. (t
  206. (when (string= type "info")
  207. (setq results (list results)))
  208. results))))
  209. ;; Because of the bug #16960, we can't use `url-retrieve-synchronously'
  210. ;; (or any other simple call of `url-retrieve', as the callback is never
  211. ;; called) to login to <https://aur.archlinux.org>. So we use
  212. ;; `aurel-url-retrieve-synchronously' - it is almost the same, except it
  213. ;; can exit from the waiting loop when a buffer with received data
  214. ;; appears in `url-dead-buffer-list'. This hack is currently possible,
  215. ;; because `url-http-parse-headers' marks the buffer as dead when it
  216. ;; returns nil.
  217. (defun aurel-url-retrieve-synchronously (url &optional silent inhibit-cookies)
  218. "Retrieve URL synchronously.
  219. Return the buffer containing the data, or nil if there are no data
  220. associated with it (the case for dired, info, or mailto URLs that need
  221. no further processing). URL is either a string or a parsed URL.
  222. See `url-retrieve' for SILENT and INHIBIT-COOKIES."
  223. (url-do-setup)
  224. (let (asynch-buffer retrieval-done)
  225. (setq asynch-buffer
  226. (url-retrieve url
  227. (lambda (&rest ignored)
  228. (url-debug 'retrieval
  229. "Synchronous fetching done (%S)"
  230. (current-buffer))
  231. (setq retrieval-done t
  232. asynch-buffer (current-buffer)))
  233. nil silent inhibit-cookies))
  234. (when asynch-buffer
  235. (let ((proc (get-buffer-process asynch-buffer)))
  236. (while (not (or retrieval-done
  237. ;; retrieval can be done even if
  238. ;; `retrieval-done' is nil (see the comment
  239. ;; above)
  240. (memq asynch-buffer url-dead-buffer-list)))
  241. (url-debug 'retrieval
  242. "Spinning in url-retrieve-synchronously: %S (%S)"
  243. retrieval-done asynch-buffer)
  244. (if (buffer-local-value 'url-redirect-buffer asynch-buffer)
  245. (setq proc (get-buffer-process
  246. (setq asynch-buffer
  247. (buffer-local-value 'url-redirect-buffer
  248. asynch-buffer))))
  249. (if (and proc (memq (process-status proc)
  250. '(closed exit signal failed))
  251. ;; Make sure another process hasn't been started.
  252. (eq proc (or (get-buffer-process asynch-buffer) proc)))
  253. (progn ;; Call delete-process so we run any sentinel now.
  254. (delete-process proc)
  255. (setq retrieval-done t)))
  256. (unless (or (with-local-quit
  257. (accept-process-output proc))
  258. (null proc))
  259. (when quit-flag
  260. (delete-process proc))
  261. (setq proc (and (not quit-flag)
  262. (get-buffer-process asynch-buffer)))))))
  263. asynch-buffer)))
  264. (defun aurel-url-post (url args &optional inhibit-cookies)
  265. "Send ARGS to URL as a POST request.
  266. ARGS is alist of field names and values to send.
  267. Return the buffer with the received data.
  268. If INHIBIT-COOKIES is non-nil, do not use saved cookies."
  269. (let ((url-request-method "POST")
  270. (url-request-extra-headers
  271. '(("Content-Type" . "application/x-www-form-urlencoded")))
  272. (url-request-data (aurel-get-fields-string args)))
  273. (aurel-debug 2 "POSTing to %s" url)
  274. (aurel-url-retrieve-synchronously url inhibit-cookies)))
  275. (defun aurel-get-aur-cookie ()
  276. "Return cookie for AUR login.
  277. Return nil, if there is no such cookie or it is expired."
  278. (url-do-setup) ; initialize cookies
  279. (let* ((cookies (url-cookie-retrieve aurel-aur-host "/" t))
  280. (cookie (car (cl-member-if
  281. (lambda (cookie)
  282. (equal (url-cookie-name cookie)
  283. aurel-aur-cookie-name))
  284. cookies))))
  285. (if (null cookie)
  286. (aurel-debug 4 "AUR login cookie not found")
  287. (if (url-cookie-expired-p cookie)
  288. (aurel-debug 4 "AUR login cookie is expired")
  289. (aurel-debug 4 "AUR login cookie is valid")
  290. cookie))))
  291. (defun aurel-aur-login-maybe (&optional force noerror)
  292. "Login to AUR, use cookie if possible.
  293. If FORCE is non-nil (interactively, with prefix), prompt for
  294. credentials and login without trying the cookie.
  295. See `aurel-aur-login' for the meaning of NOERROR and returning value."
  296. (interactive "P")
  297. (if (aurel-get-aur-cookie)
  298. (progn
  299. (aurel-debug 2 "Using cookie instead of a real login")
  300. t)
  301. ;; TODO add support for authinfo
  302. (let (user password)
  303. (when (or force (null user))
  304. (setq user (read-string "AUR user name: " aurel-aur-user-name)))
  305. (when (or force (null password))
  306. (setq password (read-passwd "Password: ")))
  307. (aurel-aur-login user password t noerror))))
  308. (defun aurel-aur-login (user password &optional remember noerror)
  309. "Login to AUR with USER and PASSWORD.
  310. If REMEMBER is non-nil, remember a cookie.
  311. Return t, if login was successful, otherwise throw an error or
  312. return nil, if NOERROR is non-nil."
  313. (let ((buf (aurel-url-post
  314. aurel-aur-login-url
  315. (list (cons "user" user)
  316. (cons "passwd" password)
  317. (cons "remember_me" (if remember "on" "off")))
  318. 'inhibit-cookie)))
  319. (when (aurel-check-response-status buf noerror)
  320. (with-current-buffer buf
  321. (if (re-search-forward "errorlist.+<li>\\(.+\\)</li>" nil t)
  322. (let ((err (match-string 1)))
  323. (aurel-debug 1 "Error during login: %s" )
  324. (or noerror (error "%s" err))
  325. nil)
  326. (url-cookie-write-file)
  327. (aurel-debug 1 "Login for %s is successful" user)
  328. t)))))
  329. (defun aurel-add-aur-user-package-info (info)
  330. "Append additional info to a package INFO.
  331. INFO should have a form of `aurel-info'.
  332. See `aurel-aur-user-package-info-check' for the meaning of
  333. additional info."
  334. (let ((add (aurel-get-aur-user-package-info
  335. (aurel-get-aur-package-url
  336. (aurel-get-param-val 'name info)))))
  337. (when add
  338. (setcdr (last info) add))))
  339. (defun aurel-get-aur-user-package-info (url)
  340. "Return AUR user specific information about a package from URL.
  341. Returning value is alist of package parameters specific for AUR
  342. user (`voted' and `subscribed') and their values.
  343. Return nil, if information is not found."
  344. (when (aurel-aur-login-maybe nil t)
  345. (aurel-debug 3 "Retrieving %s" url)
  346. (let ((buf (url-retrieve-synchronously url)))
  347. (aurel-debug 4 "Searching in %S for voted/subscribed params" buf)
  348. (list (cons 'voted
  349. (aurel-aur-package-voted buf))
  350. (cons 'subscribed
  351. (aurel-aur-package-subscribed buf))))))
  352. (defun aurel-aur-package-voted (buffer)
  353. "Return `voted' parameter value from BUFFER with fetched data.
  354. Return non-nil if a package is voted by the user; nil if it is not;
  355. `aurel-unknown-string' if the information is not found.
  356. BUFFER should contain html data about the package."
  357. (cond
  358. ((aurel-search-in-buffer
  359. (aurel-get-aur-user-action-name 'vote) buffer)
  360. nil)
  361. ((aurel-search-in-buffer
  362. (aurel-get-aur-user-action-name 'unvote) buffer)
  363. t)
  364. (t aurel-unknown-string)))
  365. (defun aurel-aur-package-subscribed (buffer)
  366. "Return `subscribed' parameter value from BUFFER with fetched data.
  367. Return non-nil if a package is subscribed by the user; nil if it is not;
  368. `aurel-unknown-string' if the information is not found.
  369. BUFFER should contain html data about the package."
  370. (cond
  371. ((aurel-search-in-buffer
  372. (aurel-get-aur-user-action-name 'subscribe) buffer)
  373. nil)
  374. ((aurel-search-in-buffer
  375. (aurel-get-aur-user-action-name 'unsubscribe) buffer)
  376. t)
  377. (t aurel-unknown-string)))
  378. (defun aurel-search-in-buffer (regexp buffer)
  379. "Return non-nil if BUFFER contains REGEXP; return nil otherwise."
  380. (with-current-buffer buffer
  381. (goto-char (point-min))
  382. (let ((res (re-search-forward regexp nil t)))
  383. (aurel-debug 7 "Searching for %s in %S: %S" regexp buffer res)
  384. res)))
  385. (defvar aurel-aur-user-actions
  386. '((vote "do_Vote" "vote" "Vote for the current package?")
  387. (unvote "do_UnVote" "unvote" "Do you really want to unvote?")
  388. (subscribe "do_Notify" "notify" "Subscribe to the new comments?")
  389. (unsubscribe "do_UnNotify" "unnotify" "Unsubscribe from notifying about the new comments?"))
  390. "Alist of the available actions.
  391. Each association has the following form:
  392. (SYMBOL NAME URL-END CONFIRM)
  393. SYMBOL is a name of the action used internally in code of this package.
  394. NAME is a name (string) used in the html-code of AUR package page.
  395. URL-END is appended to the package URL; used for posting the action.
  396. CONFIRM is a prompt to confirm the action or nil if it is not required.")
  397. (defun aurel-get-aur-user-action-name (action)
  398. "Return the name of an ACTION."
  399. (cadr (assoc action aurel-aur-user-actions)))
  400. (defun aurel-aur-user-action (action package)
  401. "Perform AUR user ACTION on the PACKAGE.
  402. ACTION is a symbol from `aurel-aur-user-actions'.
  403. PACKAGE is a name of the package (string).
  404. Return non-nil, if ACTION was performed; return nil otherwise."
  405. (let ((assoc (assoc action aurel-aur-user-actions)))
  406. (let ((action-name (nth 1 assoc))
  407. (url-end (nth 2 assoc))
  408. (confirm (nth 3 assoc)))
  409. (when (or (null confirm)
  410. (y-or-n-p confirm))
  411. (aurel-aur-login-maybe)
  412. (aurel-url-post
  413. (aurel-get-package-action-url package url-end)
  414. (list (cons "token" (url-cookie-value (aurel-get-aur-cookie)))
  415. (cons action-name "")))
  416. t))))
  417. ;;; Interacting with pacman
  418. (defcustom aurel-pacman-program "pacman"
  419. "Absolute or relative name of `pacman' program."
  420. :type 'string
  421. :group 'aurel)
  422. (defcustom aurel-installed-packages-check t
  423. "If non-nil, check if the found packages are installed.
  424. If nil, searching works faster, because `aurel-pacman-program' is not
  425. called, but it stays unknown if a package is installed or not."
  426. :type 'boolean
  427. :group 'aurel)
  428. (defvar aurel-pacman-buffer-name " *aurel-pacman*"
  429. "Name of the buffer used internally for pacman output.")
  430. (defvar aurel-pacman-info-line-re
  431. (rx line-start
  432. (group (+? (any word " ")))
  433. (+ " ") ":" (+ " ")
  434. (group (+ any) (* (and "\n " (+ any))))
  435. line-end)
  436. "Regexp matching a line of pacman query info output.
  437. Contain 2 parenthesized groups: parameter name and its value.")
  438. (defun aurel-call-pacman (&optional buffer &rest args)
  439. "Call `aurel-pacman-program' with arguments ARGS.
  440. Insert output in BUFFER. If it is nil, use `aurel-pacman-buffer-name'.
  441. Return numeric exit status."
  442. (let ((pacman (executable-find aurel-pacman-program)))
  443. (or pacman
  444. (error (concat "Couldn't find '%s'.\n"
  445. "Set aurel-pacman-program to a proper value")
  446. aurel-pacman-program))
  447. (with-current-buffer
  448. (or buffer (get-buffer-create aurel-pacman-buffer-name))
  449. (erase-buffer)
  450. (apply 'call-process pacman nil t nil args))))
  451. (defun aurel-get-foreign-packages ()
  452. "Return list of names of installed foreign packages."
  453. (let ((buf (get-buffer-create aurel-pacman-buffer-name)))
  454. (aurel-call-pacman buf "--query" "--foreign")
  455. (aurel-pacman-query-names-buffer-parse buf)))
  456. (defun aurel-pacman-query-names-buffer-parse (&optional buffer)
  457. "Parse BUFFER with packages names.
  458. BUFFER should contain an output returned by 'pacman -Q' command.
  459. If BUFFER is nil, use `aurel-pacman-buffer-name'.
  460. Return list of names of packages."
  461. (with-current-buffer
  462. (or buffer (get-buffer-create aurel-pacman-buffer-name))
  463. (goto-char (point-min))
  464. (let (names)
  465. (while (re-search-forward
  466. (concat "^\\(" aurel-package-name-re "\\) ") nil t)
  467. (setq names (cons (match-string 1) names)))
  468. names)))
  469. (defun aurel-get-installed-packages-info (&rest names)
  470. "Return information about installed packages NAMES.
  471. Each name from NAMES should be a string (a name of a package).
  472. Returning value is a list of alists with installed package
  473. parameters (strings from `aurel-installed-param-alist') and their
  474. values."
  475. (let ((buf (get-buffer-create aurel-pacman-buffer-name)))
  476. (apply 'aurel-call-pacman buf "--query" "--info" names)
  477. (aurel-pacman-query-buffer-parse buf)))
  478. (defun aurel-pacman-query-buffer-parse (&optional buffer)
  479. "Parse BUFFER with packages info.
  480. BUFFER should contain an output returned by 'pacman -Qi' command.
  481. If BUFFER is nil, use `aurel-pacman-buffer-name'.
  482. Return list of alists with parameter names and values."
  483. (with-current-buffer
  484. (or buffer (get-buffer-create aurel-pacman-buffer-name))
  485. (let ((max (point-max))
  486. (beg (point-min))
  487. end info)
  488. ;; Packages info are separated with empty lines, search for those
  489. ;; till the end of buffer
  490. (cl-loop
  491. do (progn
  492. (goto-char beg)
  493. (setq end (re-search-forward "^\n" nil t))
  494. (and end
  495. (setq info (aurel-pacman-query-region-parse beg end)
  496. beg end)))
  497. while end
  498. if info collect info))))
  499. (defun aurel-pacman-query-region-parse (beg end)
  500. "Parse text (package info) in current buffer from BEG to END.
  501. Parsing region should be an output for one package returned by
  502. 'pacman -Qi' command.
  503. Return alist with parameter names and values."
  504. (goto-char beg)
  505. (let (point)
  506. (cl-loop
  507. do (setq point (re-search-forward
  508. aurel-pacman-info-line-re end t))
  509. while point
  510. collect (cons (match-string 1) (match-string 2)))))
  511. ;;; Package parameters
  512. (defvar aurel-aur-param-alist
  513. '((pkg-url . "URLPath")
  514. (home-url . "URL")
  515. (last-date . "LastModified")
  516. (first-date . "FirstSubmitted")
  517. (outdated . "OutOfDate")
  518. (votes . "NumVotes")
  519. (license . "License")
  520. (description . "Description")
  521. (category . "CategoryID")
  522. (version . "Version")
  523. (name . "Name")
  524. (id . "ID")
  525. (maintainer . "Maintainer"))
  526. "Association list of symbols and names of package info parameters.
  527. Car of each assoc is a symbol used in code of this package.
  528. Cdr - is a parameter name (string) returned by the AUR server.")
  529. (defvar aurel-pacman-param-alist
  530. '((installed-name . "Name")
  531. (installed-version . "Version")
  532. (architecture . "Architecture")
  533. (provides . "Provides")
  534. (depends . "Depends On")
  535. (depends-opt . "Optional Deps")
  536. (script . "Install Script")
  537. (reason . "Install Reason")
  538. (validated . "Validated By")
  539. (required . "Required By")
  540. (optional-for . "Optional For")
  541. (conflicts . "Conflicts With")
  542. (replaces . "Replaces")
  543. (installed-size . "Installed Size")
  544. (packager . "Packager")
  545. (build-date . "Build Date")
  546. (install-date . "Install Date"))
  547. "Association list of symbols and names of package info parameters.
  548. Car of each assoc is a symbol used in code of this package.
  549. Cdr - is a parameter name (string) returned by pacman.")
  550. (defvar aurel-param-description-alist
  551. '((pkg-url . "Download URL")
  552. (home-url . "Home Page")
  553. (aur-url . "AUR Page")
  554. (last-date . "Last Modified")
  555. (first-date . "Submitted")
  556. (outdated . "Out Of Date")
  557. (votes . "Votes")
  558. (license . "License")
  559. (description . "Description")
  560. (category . "Category")
  561. (version . "Version")
  562. (name . "Name")
  563. (id . "ID")
  564. (maintainer . "Maintainer")
  565. (installed-name . "Name")
  566. (installed-version . "Version")
  567. (architecture . "Architecture")
  568. (provides . "Provides")
  569. (depends . "Depends On")
  570. (depends-opt . "Optional Deps")
  571. (script . "Install Script")
  572. (reason . "Install Reason")
  573. (validated . "Validated By")
  574. (required . "Required By")
  575. (optional-for . "Optional For")
  576. (conflicts . "Conflicts With")
  577. (replaces . "Replaces")
  578. (installed-size . "Size")
  579. (packager . "Packager")
  580. (build-date . "Build Date")
  581. (install-date . "Install Date")
  582. (voted . "Voted")
  583. (subscribed . "Subscribed"))
  584. "Association list of symbols and descriptions of parameters.
  585. Descriptions are used for displaying package information.
  586. Symbols are either from `aurel-aur-param-alist', from
  587. `aurel-pacman-param-alist' or are added by filter functions. See
  588. `aurel-apply-filters' for details.")
  589. (defun aurel-get-aur-param-name (param-symbol)
  590. "Return a name (string) of a parameter.
  591. PARAM-SYMBOL is a symbol from `aurel-aur-param-alist'."
  592. (cdr (assoc param-symbol aurel-aur-param-alist)))
  593. (defun aurel-get-aur-param-symbol (param-name)
  594. "Return a symbol name of a parameter.
  595. PARAM-NAME is a string from `aurel-aur-param-alist'."
  596. (car (rassoc param-name aurel-aur-param-alist)))
  597. (defun aurel-get-pacman-param-name (param-symbol)
  598. "Return a name (string) of a parameter.
  599. PARAM-SYMBOL is a symbol from `aurel-pacman-param-alist'."
  600. (cdr (assoc param-symbol aurel-pacman-param-alist)))
  601. (defun aurel-get-pacman-param-symbol (param-name)
  602. "Return a symbol name of a parameter.
  603. PARAM-NAME is a string from `aurel-pacman-param-alist'."
  604. (car (rassoc param-name aurel-pacman-param-alist)))
  605. (defun aurel-get-param-description (param-symbol)
  606. "Return a description of a parameter PARAM-SYMBOL."
  607. (let ((desc (cdr (assoc param-symbol
  608. aurel-param-description-alist))))
  609. (or desc
  610. (progn
  611. (setq desc (symbol-name param-symbol))
  612. (message "Couldn't find '%s' in aurel-param-description-alist."
  613. desc)
  614. desc))))
  615. (defun aurel-get-param-val (param info)
  616. "Return a value of a parameter PARAM from a package INFO."
  617. (cdr (assoc param info)))
  618. ;;; Filters for processing package info
  619. (defvar aurel-categories
  620. [nil "None" "daemons" "devel" "editors"
  621. "emulators" "games" "gnome" "i18n" "kde" "lib"
  622. "modules" "multimedia" "network" "office"
  623. "science" "system" "x11" "xfce" "kernels" "fonts"]
  624. "Vector of package categories.
  625. Index of an element is a category ID.")
  626. (defvar aurel-filter-params nil
  627. "List of parameters (symbols), that should match specified strings.
  628. Used in `aurel-filter-contains-every-string'.")
  629. (defvar aurel-filter-strings nil
  630. "List of strings, a package info should match.
  631. Used in `aurel-filter-contains-every-string'.")
  632. (defvar aurel-aur-filters
  633. '(aurel-aur-filter-intern aurel-filter-contains-every-string
  634. aurel-aur-filter-date aurel-filter-outdated
  635. aurel-filter-category aurel-filter-pkg-url)
  636. "List of filter functions applied to a package info got from AUR.
  637. Each filter function should accept a single argument - info alist
  638. with package parameters and should return info alist or
  639. nil (which means: ignore this package info). Functions may
  640. modify associations or add the new ones to the alist. In the
  641. latter case you might want to add descriptions of the added
  642. symbols into `aurel-param-description-alist'.
  643. `aurel-aur-filter-intern' should be the first symbol in the list as
  644. other filters use symbols for working with info parameters (see
  645. `aurel-aur-param-alist').
  646. For more information, see `aurel-receive-packages-info'.")
  647. (defvar aurel-pacman-filters
  648. '(aurel-pacman-filter-intern aurel-pacman-filter-date)
  649. "List of filter functions applied to a package info got from pacman.
  650. `aurel-pacman-filter-intern' should be the first symbol in the list as
  651. other filters use symbols for working with info parameters (see
  652. `aurel-pacman-param-alist').
  653. For more information, see `aurel-aur-filters' and
  654. `aurel-receive-packages-info'.")
  655. (defvar aurel-final-filters
  656. '()
  657. "List of filter functions applied to a package info.
  658. For more information, see `aurel-receive-packages-info'.")
  659. (defun aurel-apply-filters (info filters)
  660. "Apply functions from FILTERS list to a package INFO.
  661. INFO is alist with package parameters. It is passed as an
  662. argument to the first function from FILTERS, the returned result
  663. is passed to the second function from that list and so on.
  664. Return filtered info (result of the last filter). Return nil, if
  665. one of the FILTERS returns nil (do not call the rest filters)."
  666. (cl-loop for fun in filters
  667. do (setq info (funcall fun info))
  668. while info
  669. finally return info))
  670. (defun aurel-filter-intern (info param-fun &optional warning)
  671. "Replace names of parameters with symbols in a package INFO.
  672. INFO is alist of parameter names (strings) and values.
  673. PARAM-FUN is a function for getting parameter internal symbol by
  674. its name (string).
  675. If WARNING is non-nil, show a message if unknown parameter is found.
  676. Return modified info."
  677. (delq nil
  678. (mapcar
  679. (lambda (param)
  680. (let* ((param-name (car param))
  681. (param-symbol (funcall param-fun param-name))
  682. (param-val (cdr param)))
  683. (if param-symbol
  684. (cons param-symbol param-val)
  685. (and warning
  686. (message "Warning: unknown parameter '%s'. It will be omitted."
  687. param-name))
  688. nil)))
  689. info)))
  690. (defun aurel-aur-filter-intern (info)
  691. "Replace names of parameters with symbols in a package INFO.
  692. INFO is alist of parameter names (strings) from
  693. `aurel-aur-param-alist' and their values.
  694. Return modified info."
  695. (aurel-filter-intern info 'aurel-get-aur-param-symbol t))
  696. (defun aurel-pacman-filter-intern (info)
  697. "Replace names of parameters with symbols in a package INFO.
  698. INFO is alist of parameter names (strings) from
  699. `aurel-pacman-param-alist' and their values.
  700. Return modified info."
  701. (aurel-filter-intern info 'aurel-get-pacman-param-symbol))
  702. (defun aurel-filter-contains-every-string (info)
  703. "Check if a package INFO contains all necessary strings.
  704. Return INFO, if values of parameters from `aurel-filter-params'
  705. contain all strings from `aurel-filter-strings', otherwise return nil.
  706. Pass the check (return INFO), if `aurel-filter-strings' or
  707. `aurel-filter-params' is nil."
  708. (when (or (null aurel-filter-params)
  709. (null aurel-filter-strings)
  710. (let ((str (mapconcat (lambda (param)
  711. (aurel-get-param-val param info))
  712. aurel-filter-params
  713. "\n")))
  714. (cl-every (lambda (substr)
  715. (string-match-p (regexp-quote substr) str))
  716. aurel-filter-strings)))
  717. info))
  718. (defun aurel-filter-date (info fun &rest params)
  719. "Convert date parameters PARAMS of a package INFO to time values.
  720. INFO is alist of parameter symbols and values.
  721. FUN is a function taking parameter value as an argument and
  722. returning time value.
  723. Return modified info."
  724. (dolist (param info info)
  725. (let ((param-name (car param))
  726. (param-val (cdr param)))
  727. (when (memq param-name params)
  728. (setcdr param
  729. (funcall fun param-val))))))
  730. (defun aurel-aur-filter-date (info)
  731. "Convert date parameters PARAMS of a package INFO to time values.
  732. Converted parameters: `first-date', `last-date'.
  733. INFO is alist of parameter symbols and values.
  734. Return modified info."
  735. (aurel-filter-date info 'seconds-to-time 'first-date 'last-date))
  736. (defun aurel-pacman-filter-date (info)
  737. "Convert date parameters PARAMS of a package INFO to time values.
  738. Converted parameters: `install-date', `build-date'.
  739. INFO is alist of parameter symbols and values.
  740. Return modified info."
  741. (aurel-filter-date info 'date-to-time 'install-date 'build-date))
  742. (defun aurel-filter-outdated (info)
  743. "Change `outdated' parameter of a package INFO.
  744. Replace 1/0 with t/nil.
  745. INFO is alist of parameter symbols and values.
  746. Return modified info."
  747. (let ((param (assoc 'outdated info)))
  748. (setcdr param (null (= 0 (cdr param)))))
  749. info)
  750. (defun aurel-filter-category (info)
  751. "Replace category ID with category name in a package INFO.
  752. INFO is alist of parameter symbols and values.
  753. Return modified info."
  754. (let ((param (assoc 'category info)))
  755. (setcdr param (aref aurel-categories (cdr param))))
  756. info)
  757. (defun aurel-filter-pkg-url (info)
  758. "Update `pkg-url' parameter in a package INFO.
  759. INFO is alist of parameter symbols and values.
  760. Return modified info."
  761. (let ((param (assoc 'pkg-url info)))
  762. (setcdr param (url-expand-file-name (cdr param) aurel-aur-base-url)))
  763. info)
  764. ;;; Searching/showing packages
  765. (defun aurel-receive-packages-info (url)
  766. "Return information about the packages from URL.
  767. Information is received with `aurel-get-aur-packages-info', then
  768. it is passed through `aurel-aur-filters' with
  769. `aurel-apply-filters'. If `aurel-installed-packages-check' is
  770. non-nil, additional information about installed packages is
  771. received with `aurel-get-installed-packages-info' and is passed
  772. through `aurel-installed-filters'. Finally packages info is passed
  773. through `aurel-final-filters'.
  774. Returning value has a form of `aurel-list'."
  775. ;; To speed-up the process, pacman should be called once with the
  776. ;; names of found packages (instead of calling it for each name). So
  777. ;; we need to know the names at first, that's why we don't use a
  778. ;; single filters variable: at first we filter info received from AUR,
  779. ;; then we add information about installed packages from pacman and
  780. ;; finally filter the whole info.
  781. (let (aur-info-list aur-info-alist
  782. pac-info-list pac-info-alist
  783. info-list)
  784. ;; Receive and process information from AUR server
  785. (setq aur-info-list (aurel-get-aur-packages-info url)
  786. aur-info-alist (aurel-get-filtered-alist
  787. aur-info-list aurel-aur-filters 'name))
  788. ;; Receive and process information from pacman
  789. (when aurel-installed-packages-check
  790. (setq pac-info-list (apply 'aurel-get-installed-packages-info
  791. (mapcar #'car aur-info-alist))
  792. pac-info-alist (aurel-get-filtered-alist
  793. pac-info-list
  794. aurel-pacman-filters
  795. 'installed-name)))
  796. ;; Join info and do final processing
  797. (setq info-list
  798. (mapcar (lambda (aur-info-assoc)
  799. (let* ((name (car aur-info-assoc))
  800. (pac-info-assoc (assoc name pac-info-alist)))
  801. (append (cdr aur-info-assoc)
  802. (cdr pac-info-assoc))))
  803. aur-info-alist))
  804. (aurel-get-filtered-alist info-list aurel-final-filters 'id)))
  805. (defun aurel-get-filtered-alist (info-list filters param)
  806. "Return alist with filtered packages info.
  807. INFO-LIST is a list of packages info. Each info is passed through
  808. FILTERS with `aurel-apply-filters'.
  809. Each association of a returned value has a form:
  810. (PARAM-VAL . INFO)
  811. PARAM-VAL is a value of a parameter PARAM.
  812. INFO is a filtered package info."
  813. (delq nil ; ignore filtered (empty) info
  814. (mapcar (lambda (info)
  815. (let ((info (aurel-apply-filters info filters)))
  816. (and info
  817. (cons (aurel-get-param-val param info) info))))
  818. info-list)))
  819. (defun aurel-get-packages-by-name-or-id (&rest names)
  820. "Return packages by NAMES.
  821. Each element from NAMES should be a string. If there is only one
  822. element, it can also be a number (package ID).
  823. Returning value has a form of `aurel-list'."
  824. (aurel-receive-packages-info
  825. ;; AUR RPC service do not support specifying multiple packages by IDs
  826. ;; (only by names), so we can't use `aurel-get-package-multiinfo-url'
  827. ;; as a common case: if there is only one requested package, it can
  828. ;; be either a name or ID.
  829. (if (cdr names)
  830. (aurel-get-package-multiinfo-url names)
  831. (aurel-get-package-info-url (car names)))))
  832. (defun aurel-get-packages-by-string (&rest strings)
  833. "Return packages matching STRINGS.
  834. Returning value has a form of `aurel-list'."
  835. ;; A hack for searching by multiple strings: the actual server search
  836. ;; is done by the biggest string and the rest strings are searched in
  837. ;; the results returned by the server
  838. (let* ((str-list
  839. ;; sort to search by the biggest (first) string
  840. (sort strings
  841. (lambda (a b)
  842. (> (length a) (length b)))))
  843. (aurel-filter-params '(name description))
  844. (aurel-filter-strings (cdr str-list)))
  845. (aurel-receive-packages-info
  846. (aurel-get-package-search-url (car str-list)))))
  847. (defun aurel-get-packages-by-maintainer (name)
  848. "Return packages by maintainer NAME.
  849. Returning value has a form of `aurel-list'."
  850. (aurel-receive-packages-info
  851. (aurel-get-maintainer-search-url name)))
  852. (defvar aurel-search-type-alist
  853. '((name-or-id . aurel-get-packages-by-name-or-id)
  854. (string . aurel-get-packages-by-string)
  855. (maintainer . aurel-get-packages-by-maintainer))
  856. "Alist of available search types and search functions.")
  857. (defun aurel-search-packages (type &rest vals)
  858. "Search for AUR packages and return results.
  859. TYPE is a type of search - symbol from `aurel-search-type-alist'.
  860. It defines a search function which is called with VALS as
  861. arguments.
  862. Returning value has a form of `aurel-list'."
  863. (let ((fun (cdr (assoc type aurel-search-type-alist))))
  864. (or fun
  865. (error "Wrong search type '%s'" type))
  866. (apply fun vals)))
  867. (defun aurel-search-show-packages
  868. (search-type search-vals &optional buffer history)
  869. "Search for packages and show results in BUFFER.
  870. See `aurel-search-packages' for the meaning of SEARCH-TYPE and
  871. SEARCH-VALS.
  872. See `aurel-show-packages' for the meaning of BUFFER and HISTORY."
  873. (aurel-show-packages
  874. (apply 'aurel-search-packages search-type search-vals)
  875. buffer history search-type search-vals))
  876. (defun aurel-show-packages
  877. (packages &optional buffer history search-type search-vals)
  878. "Show PACKAGES in BUFFER.
  879. PACKAGES should have a form of `aurel-list'.
  880. If BUFFER is a buffer object, use it; if BUFFER is nil, use a
  881. default buffer; otherwise, use a unique buffer.
  882. If HISTORY is nil, do not save current item in history; if it is
  883. `add', add item to history; if `replace', replace current item.
  884. History item is a proper call of `aurel-show-packages' itself.
  885. If SEARCH-TYPE and SEARCH-VALS are non-nils, they are used for
  886. setting reverting action. See `aurel-set-revert-action' for
  887. details."
  888. (let ((count (length packages)))
  889. (when (> count 0)
  890. (if (and (= count 1)
  891. (or (eq search-type 'name-or-id)
  892. (null aurel-list-single-package)))
  893. (let ((info (cdar packages)))
  894. ;; Add (maybe) AUR user info if the buffer is reverted or a
  895. ;; new info is shown; if we are moving by a history
  896. ;; (`history' is nil), do not add it.
  897. (and history
  898. aurel-aur-user-package-info-check
  899. (aurel-add-aur-user-package-info info))
  900. (aurel-info-show info
  901. (if (bufferp buffer)
  902. buffer
  903. (aurel-info-get-buffer-name buffer))))
  904. (aurel-list-show packages
  905. (if (bufferp buffer)
  906. buffer
  907. (aurel-list-get-buffer-name buffer))))
  908. (when (and search-type search-vals)
  909. (when history
  910. (aurel-history-add
  911. (list (lambda (packages type vals)
  912. (aurel-show-packages
  913. packages (current-buffer) nil type vals))
  914. packages search-type search-vals)
  915. (eq history 'replace)))
  916. (aurel-set-revert-action search-type search-vals)))
  917. (aurel-found-message packages search-type search-vals)))
  918. (defvar aurel-found-messages
  919. '((name-or-id (0 "The package \"%s\" not found." "Packages not found.")
  920. (1 "The package \"%s\"."))
  921. (string (0 "No packages matching %s.")
  922. (1 "A single package matching %s.")
  923. (many "%d packages matching %s."))
  924. (maintainer (0 "No packages by maintainer %s.")
  925. (1 "A single package by maintainer %s.")
  926. (many "%d packages by maintainer %s.")))
  927. "Alist used by `aurel-found-message'.")
  928. (defun aurel-found-message (packages search-type search-vals)
  929. "Display a proper message about found PACKAGES.
  930. SEARCH-TYPE and SEARCH-VALS are arguments for
  931. `aurel-search-packages', by which the PACKAGES were found."
  932. (let* ((count (length packages))
  933. (found-key (if (> count 1) 'many count))
  934. (type-alist (cdr (assoc search-type aurel-found-messages)))
  935. (found-list (cdr (assoc found-key type-alist)))
  936. (msg (if (or (= 1 (length search-vals))
  937. (null (cdr found-list)))
  938. (car found-list)
  939. (cadr found-list)))
  940. (args (delq nil
  941. (list
  942. (and (eq found-key 'many) count)
  943. (cond
  944. ((eq search-type 'string)
  945. (mapconcat (lambda (str) (concat "\"" str "\""))
  946. search-vals " "))
  947. ((and (= count 1) (eq search-type 'name-or-id))
  948. (aurel-get-param-val 'name (cdar packages)))
  949. (t (car search-vals)))))))
  950. (and msg (apply 'message msg args))))
  951. ;;; History
  952. (defvar-local aurel-history-stack-item nil
  953. "Current item of the history.
  954. A list of the form (FUNCTION [ARGS ...]).
  955. The item is used by calling (apply FUNCTION ARGS).")
  956. (put 'aurel-history-stack-item 'permanent-local t)
  957. (defvar-local aurel-history-back-stack nil
  958. "Stack (list) of visited items.
  959. Each element of the list has a form of `aurel-history-stack-item'.")
  960. (put 'aurel-history-back-stack 'permanent-local t)
  961. (defvar-local aurel-history-forward-stack nil
  962. "Stack (list) of items visited with `aurel-history-back'.
  963. Each element of the list has a form of `aurel-history-stack-item'.")
  964. (put 'aurel-history-forward-stack 'permanent-local t)
  965. (defvar aurel-history-size 0
  966. "Maximum number of items saved in history.
  967. If 0, the history is disabled.")
  968. (defun aurel-history-add (item &optional replace)
  969. "Add ITEM to history.
  970. If REPLACE is non-nil, replace the current item instead of adding."
  971. (if replace
  972. (setq aurel-history-stack-item item)
  973. (and aurel-history-stack-item
  974. (push aurel-history-stack-item aurel-history-back-stack))
  975. (setq aurel-history-forward-stack nil
  976. aurel-history-stack-item item)
  977. (when (>= (length aurel-history-back-stack)
  978. aurel-history-size)
  979. (setq aurel-history-back-stack
  980. (cl-loop for elt in aurel-history-back-stack
  981. for i from 1 to aurel-history-size
  982. collect elt)))))
  983. (defun aurel-history-goto (item)
  984. "Go to the ITEM of history.
  985. ITEM should have the form of `aurel-history-stack-item'."
  986. (or (listp item)
  987. (error "Wrong value of history element"))
  988. (setq aurel-history-stack-item item)
  989. (apply (car item) (cdr item)))
  990. (defun aurel-history-back ()
  991. "Go back to the previous element of history in the current buffer."
  992. (interactive)
  993. (or aurel-history-back-stack
  994. (user-error "No previous element in history"))
  995. (push aurel-history-stack-item aurel-history-forward-stack)
  996. (aurel-history-goto (pop aurel-history-back-stack)))
  997. (defun aurel-history-forward ()
  998. "Go forward to the next element of history in the current buffer."
  999. (interactive)
  1000. (or aurel-history-forward-stack
  1001. (user-error "No next element in history"))
  1002. (push aurel-history-stack-item aurel-history-back-stack)
  1003. (aurel-history-goto (pop aurel-history-forward-stack)))
  1004. ;;; Reverting buffers
  1005. (defcustom aurel-revert-no-confirm nil
  1006. "If non-nil, do not ask to confirm for reverting aurel buffer."
  1007. :type 'boolean
  1008. :group 'aurel)
  1009. (defvar aurel-revert-action nil
  1010. "Action for refreshing information in the current aurel buffer.
  1011. A list of the form (FUNCTION [ARGS ...]).
  1012. The action is performed by calling (apply FUNCTION ARGS).")
  1013. (defun aurel-revert-buffer (ignore-auto noconfirm)
  1014. "Refresh information in the current aurel buffer.
  1015. The function is suitable for `revert-buffer-function'.
  1016. See `revert-buffer' for the meaning of IGNORE-AUTO and NOCONFIRM."
  1017. (when (or aurel-revert-no-confirm
  1018. noconfirm
  1019. (y-or-n-p "Refresh current information? "))
  1020. (apply (car aurel-revert-action)
  1021. (cdr aurel-revert-action))))
  1022. (defun aurel-set-revert-action (search-type search-vals)
  1023. "Set `aurel-revert-action' to a proper value.
  1024. SEARCH-TYPE and SEARCH-VALS are arguments for
  1025. `aurel-search-show-packages' by which refreshing information is
  1026. performed."
  1027. (setq aurel-revert-action
  1028. (list (lambda (type vals)
  1029. (aurel-search-show-packages
  1030. type vals (current-buffer) 'replace))
  1031. search-type search-vals)))
  1032. ;;; Downloading
  1033. (defcustom aurel-download-directory temporary-file-directory
  1034. "Default directory for downloading AUR packages."
  1035. :type 'directory
  1036. :group 'aurel)
  1037. (defcustom aurel-directory-prompt "Download to: "
  1038. "Default directory prompt for downloading AUR packages."
  1039. :type 'string
  1040. :group 'aurel)
  1041. (defvar aurel-download-functions
  1042. '(aurel-download aurel-download-unpack aurel-download-unpack-dired
  1043. aurel-download-unpack-pkgbuild aurel-download-unpack-eshell)
  1044. "List of available download functions.")
  1045. (defun aurel-download-get-defcustom-type ()
  1046. "Return `defcustom' type for selecting a download function."
  1047. `(radio ,@(mapcar (lambda (fun) (list 'function-item fun))
  1048. aurel-download-functions)
  1049. (function :tag "Other function")))
  1050. (defun aurel-download (url dir)
  1051. "Download AUR package from URL to a directory DIR.
  1052. Return a path to the downloaded file."
  1053. ;; Is there a simpler way to download a file?
  1054. (let ((file-name-handler-alist
  1055. (cons (cons url-handler-regexp 'url-file-handler)
  1056. file-name-handler-alist)))
  1057. (with-temp-buffer
  1058. (insert-file-contents-literally url)
  1059. (let ((file (expand-file-name (url-file-nondirectory url) dir)))
  1060. (write-file file)
  1061. file))))
  1062. ;; Code for working with `tar-mode' came from `package-untar-buffer'
  1063. ;; Avoid compilation warnings about tar functions and variables
  1064. (defvar tar-parse-info)
  1065. (defvar tar-data-buffer)
  1066. (declare-function tar-data-swapped-p "tar-mode" ())
  1067. (declare-function tar-untar-buffer "tar-mode" ())
  1068. (declare-function tar-header-name "tar-mode" (tar-header) t)
  1069. (declare-function tar-header-link-type "tar-mode" (tar-header) t)
  1070. (defun aurel-download-unpack (url dir)
  1071. "Download AUR package from URL and unpack it into a directory DIR.
  1072. Use `tar-untar-buffer' from Tar mode. All files should be placed
  1073. in one directory; otherwise, signal an error.
  1074. Return a path to the unpacked directory."
  1075. (let ((file-name-handler-alist
  1076. (cons (cons url-handler-regexp 'url-file-handler)
  1077. file-name-handler-alist)))
  1078. (with-temp-buffer
  1079. (insert-file-contents url)
  1080. (setq default-directory dir)
  1081. (let ((file (expand-file-name (url-file-nondirectory url) dir)))
  1082. (write-file file))
  1083. (tar-mode)
  1084. ;; Make sure the first header is a dir and all files are
  1085. ;; placed in it (is it correct?)
  1086. (let* ((tar-car-data (car tar-parse-info))
  1087. (tar-dir (tar-header-name tar-car-data))
  1088. (tar-dir-re (regexp-quote tar-dir)))
  1089. (or (eq (tar-header-link-type tar-car-data) 5)
  1090. (error "The first entry '%s' in tar file is not a directory"
  1091. tar-dir))
  1092. (dolist (tar-data (cdr tar-parse-info))
  1093. (or (string-match tar-dir-re (tar-header-name tar-data))
  1094. (error "Not all files are extracted into directory '%s'"
  1095. tar-dir)))
  1096. (tar-untar-buffer)
  1097. (expand-file-name tar-dir dir)))))
  1098. (defun aurel-download-unpack-dired (url dir)
  1099. "Download and unpack AUR package, and open the unpacked directory.
  1100. For the meaning of URL and DIR, see `aurel-download-unpack'."
  1101. (dired (aurel-download-unpack url dir)))
  1102. (defun aurel-download-unpack-pkgbuild (url dir)
  1103. "Download and unpack AUR package, and open PKGBUILD file.
  1104. For the meaning of URL and DIR, see `aurel-download-unpack'."
  1105. (let* ((pkg-dir (aurel-download-unpack url dir))
  1106. (file (expand-file-name "PKGBUILD" pkg-dir)))
  1107. (if (file-exists-p file)
  1108. (find-file file)
  1109. (error "File '%s' doesn't exist" file))))
  1110. ;; Avoid compilation warning about `eshell/cd'
  1111. (declare-function eshell/cd "em-dirs" (&rest args))
  1112. (defun aurel-download-unpack-eshell (url dir)
  1113. "Download and unpack AUR package, switch to eshell.
  1114. For the meaning of URL and DIR, see `aurel-download-unpack'."
  1115. (let ((pkg-dir (aurel-download-unpack url dir)))
  1116. (eshell)
  1117. (eshell/cd pkg-dir)))
  1118. ;;; Defining URL
  1119. (defun aurel-get-fields-string (args)
  1120. "Return string of names and values from ARGS alist.
  1121. Each association of ARGS has a form: (NAME . VALUE).
  1122. If NAME and VALUE are not strings, they are converted to strings
  1123. with `prin1-to-string'.
  1124. Returning string has a form: \"NAME=VALUE&...\"."
  1125. (cl-flet ((hexify (arg)
  1126. (url-hexify-string
  1127. (if (stringp arg) arg (prin1-to-string arg)))))
  1128. (mapconcat (lambda (arg)
  1129. (concat (hexify (car arg))
  1130. "="
  1131. (hexify (cdr arg))))
  1132. args
  1133. "&")))
  1134. (defun aurel-get-multi-args-rpc-url (type args &optional type-name arg-name)
  1135. "Return URL for getting info about AUR packages.
  1136. TYPE is the name of an allowed method.
  1137. ARGS is a list of arguments to the call.
  1138. TYPE-NAME is the name of a type field (\"type\" by default).
  1139. ARG-NAME is the name of an arg field (\"arg[]\" by default)."
  1140. (or type-name
  1141. (setq type-name "type"))
  1142. (or arg-name
  1143. (setq arg-name "arg[]"))
  1144. (let ((fields (cons
  1145. (cons type-name type)
  1146. (mapcar (lambda (arg) (cons arg-name arg))
  1147. args))))
  1148. (url-expand-file-name
  1149. (concat "rpc.php?" (aurel-get-fields-string fields))
  1150. aurel-aur-base-url)))
  1151. (defun aurel-get-rpc-url (type arg)
  1152. "Return URL for getting info about AUR packages.
  1153. TYPE is the name of an allowed method.
  1154. ARG is the argument to the call."
  1155. (aurel-get-multi-args-rpc-url type (list arg) "type" "arg"))
  1156. (defun aurel-get-package-multiinfo-url (packages)
  1157. "Return URL for getting info about PACKAGES.
  1158. Each package should be a string (package name)."
  1159. (aurel-get-multi-args-rpc-url "multiinfo" packages))
  1160. (defun aurel-get-package-info-url (package)
  1161. "Return URL for getting info about a PACKAGE.
  1162. PACKAGE can be either a string (name) or a number (ID)."
  1163. (aurel-get-rpc-url "info" package))
  1164. (defun aurel-get-package-search-url (str)
  1165. "Return URL for searching a package by string STR."
  1166. (aurel-get-rpc-url "search" str))
  1167. (defun aurel-get-maintainer-search-url (str)
  1168. "Return URL for searching a maintainer by string STR."
  1169. (aurel-get-rpc-url "msearch" str))
  1170. (defun aurel-get-maintainer-account-url (maintainer)
  1171. "Return URL for MAINTAINER's AUR account."
  1172. (url-expand-file-name (concat "account/" maintainer)
  1173. aurel-aur-base-url))
  1174. (defun aurel-get-aur-package-url (package)
  1175. "Return AUR URL of a PACKAGE."
  1176. (url-expand-file-name (concat "packages/" package)
  1177. aurel-aur-base-url))
  1178. (defun aurel-get-package-action-url (package action)
  1179. "Return URL for the PACKAGE ACTION."
  1180. (concat (aurel-get-aur-package-url package) "/" action))
  1181. ;;; UI
  1182. (defvar aurel-package-info-history nil
  1183. "A history list for `aurel-package-info'.")
  1184. (defvar aurel-package-search-history nil
  1185. "A history list for `aurel-package-search'.")
  1186. (defvar aurel-maintainer-search-history nil
  1187. "A history list for `aurel-maintainer-search'.")
  1188. ;;;###autoload
  1189. (defun aurel-package-info (name-or-id &optional arg)
  1190. "Display information about AUR package NAME-OR-ID.
  1191. NAME-OR-ID may be a string or a number.
  1192. The buffer for showing results is defined by `aurel-info-buffer-name'.
  1193. With prefix (if ARG is non-nil), show results in a new info buffer."
  1194. (interactive
  1195. (list (read-string "Name or ID: "
  1196. nil 'aurel-package-info-history)
  1197. current-prefix-arg))
  1198. (aurel-search-show-packages
  1199. 'name-or-id (list name-or-id) arg 'add))
  1200. ;;;###autoload
  1201. (defun aurel-package-search (string &optional arg)
  1202. "Search for AUR packages matching STRING.
  1203. STRING can be a string of multiple words separated by spaces. To
  1204. search for a string containing spaces, quote it with double
  1205. quotes. For example, the following search is allowed:
  1206. \"python library\" plot
  1207. The buffer for showing results is defined by
  1208. `aurel-list-buffer-name'. With prefix (if ARG is non-nil), show
  1209. results in a new buffer."
  1210. (interactive
  1211. (list (read-string "Search by name/description: "
  1212. nil 'aurel-package-search-history)
  1213. current-prefix-arg))
  1214. (aurel-search-show-packages
  1215. 'string (split-string-and-unquote string) arg 'add))
  1216. ;;;###autoload
  1217. (defun aurel-maintainer-search (name &optional arg)
  1218. "Search for AUR packages by maintainer NAME.
  1219. The buffer for showing results is defined by `aurel-list-buffer-name'.
  1220. With prefix (if ARG is non-nil), show results in a new buffer."
  1221. (interactive
  1222. (list (read-string "Search by maintainer: "
  1223. nil 'aurel-maintainer-search-history)
  1224. current-prefix-arg))
  1225. (aurel-search-show-packages
  1226. 'maintainer (list name) arg 'add))
  1227. ;;;###autoload
  1228. (defun aurel-installed-packages (&optional arg)
  1229. "Display information about AUR packages installed in the system.
  1230. The buffer for showing results is defined by `aurel-list-buffer-name'.
  1231. With prefix (if ARG is non-nil), show results in a new buffer."
  1232. (interactive "P")
  1233. (aurel-search-show-packages
  1234. 'name-or-id (aurel-get-foreign-packages) arg 'add))
  1235. ;;; Package list
  1236. (defgroup aurel-list nil
  1237. "Buffer with a list of AUR packages."
  1238. :group 'aurel)
  1239. (defface aurel-list-marked
  1240. '((t :inherit dired-marked))
  1241. "Face used for the marked packages."
  1242. :group 'aurel-list)
  1243. (defcustom aurel-list-buffer-name "*AUR Package List*"
  1244. "Default name of the buffer with a list of AUR packages."
  1245. :type 'string
  1246. :group 'aurel-list)
  1247. (defcustom aurel-list-mode-name "AURel-List"
  1248. "Default name of `aurel-list-mode', displayed in the mode line."
  1249. :type 'string
  1250. :group 'aurel-list)
  1251. (defcustom aurel-list-download-function 'aurel-download-unpack
  1252. "Function used for downloading a single AUR package from list buffer.
  1253. It should accept 2 arguments: URL of a downloading file and a
  1254. destination directory."
  1255. :type (aurel-download-get-defcustom-type)
  1256. :group 'aurel-list)
  1257. (defcustom aurel-list-multi-download-function 'aurel-download-unpack
  1258. "Function used for downloading multiple AUR packages from list buffer.
  1259. It should accept 2 arguments: URL of a downloading file and a
  1260. destination directory."
  1261. :type (aurel-download-get-defcustom-type)
  1262. :group 'aurel-list)
  1263. (defcustom aurel-list-multi-download-no-confirm nil
  1264. "If non-nil, do not ask to confirm if multiple packages are downloaded."
  1265. :type 'boolean
  1266. :group 'aurel-list)
  1267. (defcustom aurel-list-history-size 10
  1268. "Maximum number of items saved in history of package list buffer.
  1269. If 0, the history is disabled."
  1270. :type 'integer
  1271. :group 'aurel-list)
  1272. (defvar aurel-list-column-name-alist
  1273. '((installed-version . "Installed"))
  1274. "Alist of parameter names used as titles for columns.
  1275. Each association is a cons of parameter symbol and column name.
  1276. If no parameter is not found in this alist, the value from
  1277. `aurel-param-description-alist' is used for a column name.")
  1278. (defvar aurel-list-column-value-alist
  1279. '((name . aurel-list-get-name)
  1280. (maintainer . aurel-list-get-maintainer)
  1281. (installed-version . aurel-list-get-installed-version))
  1282. "Alist for parameter values inserted in columns.
  1283. Each association is a cons of parameter symbol from
  1284. `aurel-param-description-alist' and a function returning a value
  1285. that will be inserted. The function should take a package info
  1286. of the form of `aurel-info' as an argument.")
  1287. (defvar aurel-list nil
  1288. "Alist with packages info.
  1289. Car of each assoc is a package ID (number).
  1290. Cdr - is alist of package info of the form of `aurel-info'.")
  1291. (defvar aurel-list-filters nil
  1292. "List of filter functions applied to a package info.
  1293. Each filter function should accept a single argument - info alist
  1294. with package parameters and should return info alist or
  1295. nil (which means: do not display this package). These filters
  1296. are applied before displaying the list of packages.")
  1297. (defvar aurel-list-available-filters
  1298. '(aurel-list-filter-maintained aurel-list-filter-unmaintained
  1299. aurel-list-filter-outdated aurel-list-filter-not-outdated
  1300. aurel-list-filter-match-regexp aurel-list-filter-not-match-regexp
  1301. aurel-list-filter-different-versions aurel-list-filter-same-versions)
  1302. "List of commands that can be called for filtering a package list.
  1303. Used by `aurel-list-enable-filter'.
  1304. Each function should make a proper filter function and should
  1305. take one argument and pass those to `aurel-list-apply-filter'.")
  1306. (defvar aurel-list-marks nil
  1307. "Alist of current marks.
  1308. Each association is a cons cell of a package ID and overlay used
  1309. to highlight a line with this package.")
  1310. (defvar aurel-list-votes-column nil
  1311. "The number of column with votes in the current tabulated-list.")
  1312. (defvar aurel-list-column-format
  1313. '((name 20 t)
  1314. (version 12 nil)
  1315. (installed-version 12 t)
  1316. (maintainer 13 t)
  1317. ;; We cannot use simple sort for votes as they will be sorted as
  1318. ;; strings, e.g.: (1, 13, 2, 200, 3) instead of (1, 2, 3, 13, 200).
  1319. ;; So we use a special function to compare votes as numbers.
  1320. (votes 8 aurel-list-sort-by-votes)
  1321. (description 30 nil))
  1322. "List specifying columns used in the buffer with a list of packages.
  1323. Each element of the list should have the form (NAME WIDTH SORT . PROPS).
  1324. NAME is a parameter symbol from `aurel-param-description-alist'.
  1325. For the meaning of WIDTH, SORT and PROPS, see `tabulated-list-format'.")
  1326. (defvar aurel-list-mode-map
  1327. (let ((map (make-sparse-keymap)))
  1328. (set-keymap-parent map tabulated-list-mode-map)
  1329. (define-key map "\C-m" 'aurel-list-describe-package)
  1330. (define-key map "d" 'aurel-list-download-package)
  1331. (define-key map "l" 'aurel-history-back)
  1332. (define-key map "r" 'aurel-history-forward)
  1333. (define-key map "m" 'aurel-list-mark)
  1334. (define-key map "u" 'aurel-list-unmark)
  1335. (define-key map "M" 'aurel-list-mark-all)
  1336. (define-key map "U" 'aurel-list-unmark-all)
  1337. (define-key map "\177" 'aurel-list-unmark-backward)
  1338. (define-key map "S" 'aurel-list-sort)
  1339. (define-key map "ff" 'aurel-list-enable-filter)
  1340. (define-key map "fd" 'aurel-list-disable-filters)
  1341. (define-key map "fv" 'aurel-list-filter-same-versions)
  1342. (define-key map "fV" 'aurel-list-filter-different-versions)
  1343. (define-key map "fm" 'aurel-list-filter-unmaintained)
  1344. (define-key map "fM" 'aurel-list-filter-maintained)
  1345. (define-key map "fo" 'aurel-list-filter-outdated)
  1346. (define-key map "fO" 'aurel-list-filter-not-outdated)
  1347. (define-key map "fr" 'aurel-list-filter-not-match-regexp)
  1348. (define-key map "fR" 'aurel-list-filter-match-regexp)
  1349. (define-key map "g" 'revert-buffer)
  1350. map)
  1351. "Keymap for `aurel-list-mode'.")
  1352. (defun aurel-list-sort-by-votes (a b)
  1353. "Compare tabulated entries A and B by the number of votes.
  1354. It is a sort predicate for `tabulated-list-format'.
  1355. Return non-nil, if A has more votes than B."
  1356. (cl-flet ((votes (entry)
  1357. (string-to-number (aref (cadr entry)
  1358. aurel-list-votes-column))))
  1359. (> (votes a) (votes b))))
  1360. (defun aurel-list-get-buffer-name (&optional unique)
  1361. "Return a name of a list buffer.
  1362. If UNIQUE is non-nil, make the name unique."
  1363. (if unique
  1364. (generate-new-buffer aurel-list-buffer-name)
  1365. aurel-list-buffer-name))
  1366. (define-derived-mode aurel-list-mode
  1367. tabulated-list-mode aurel-list-mode-name
  1368. "Major mode for browsing AUR packages.
  1369. \\{aurel-list-mode-map}"
  1370. (make-local-variable 'aurel-list)
  1371. (make-local-variable 'aurel-list-filters)
  1372. (make-local-variable 'aurel-list-marks)
  1373. (make-local-variable 'aurel-revert-action)
  1374. (setq-local revert-buffer-function 'aurel-revert-buffer)
  1375. (setq-local aurel-history-size aurel-list-history-size)
  1376. (setq-local aurel-list-votes-column
  1377. (cl-loop
  1378. for col-spec in aurel-list-column-format
  1379. for i from 0
  1380. until (eq (car col-spec) 'votes)
  1381. finally return i))
  1382. (setq default-directory aurel-download-directory)
  1383. (setq tabulated-list-format
  1384. (apply #'vector
  1385. (mapcar
  1386. (lambda (col-spec)
  1387. (let ((name (car col-spec)))
  1388. (cons (or (cdr (assoc name aurel-list-column-name-alist))
  1389. (aurel-get-param-description name))
  1390. (cdr col-spec))))
  1391. aurel-list-column-format)))
  1392. (setq tabulated-list-sort-key
  1393. (list (aurel-get-param-description 'name)))
  1394. (tabulated-list-init-header))
  1395. (defun aurel-list-show (list &optional buffer)
  1396. "Display a LIST of packages in BUFFER.
  1397. LIST should have the form of `aurel-list'.
  1398. If BUFFER is nil, use (create if needed) buffer with the name
  1399. `aurel-list-buffer-name'."
  1400. (let ((buf (get-buffer-create
  1401. (or buffer aurel-list-buffer-name))))
  1402. (with-current-buffer buf
  1403. (aurel-list-show-in-current-buffer list))
  1404. (pop-to-buffer-same-window buf)))
  1405. (defun aurel-list-show-in-current-buffer (list)
  1406. "Display a LIST of packages in current buffer.
  1407. LIST should have the form of `aurel-list'."
  1408. (let ((inhibit-read-only t))
  1409. (erase-buffer))
  1410. (aurel-list-mode)
  1411. (setq aurel-list list)
  1412. (aurel-list-print))
  1413. (defun aurel-list-print (&optional list)
  1414. "Filter and print package LIST into the current tabulated-list buffer.
  1415. If LIST is nil, use `aurel-list'."
  1416. ;; TODO restore marks for the packages that survive filtering
  1417. (aurel-list-unmark-all)
  1418. (setq tabulated-list-entries
  1419. (aurel-list-get-entries
  1420. (aurel-list-apply-filters (or list aurel-list))))
  1421. (tabulated-list-print))
  1422. (defun aurel-list-get-entries (list)
  1423. "Return list of values suitable for `tabulated-list-entries'.
  1424. Values are taken from LIST which should have the form of
  1425. `aurel-list'.
  1426. Use parameters from `aurel-list-column-format'."
  1427. (mapcar
  1428. (lambda (pkg)
  1429. (let ((id (car pkg))
  1430. (info (cdr pkg)))
  1431. (list id
  1432. (apply #'vector
  1433. (mapcar
  1434. (lambda (col-spec)
  1435. (let* ((param (car col-spec))
  1436. (fun (cdr (assq param
  1437. aurel-list-column-value-alist))))
  1438. (if fun
  1439. (funcall fun info)
  1440. (aurel-get-string
  1441. (aurel-get-param-val param info)))))
  1442. aurel-list-column-format)))))
  1443. list))
  1444. (defun aurel-list-get-name (info)
  1445. "Return name of the package from a package INFO.
  1446. Colorize the name with `aurel-info-outdated' if the package is
  1447. out of date."
  1448. (aurel-get-string
  1449. (aurel-get-param-val 'name info)
  1450. (when (aurel-get-param-val 'outdated info)
  1451. 'aurel-info-outdated)))
  1452. (defun aurel-list-get-maintainer (info)
  1453. "Return maintainer name from a package INFO."
  1454. (or (aurel-get-param-val 'maintainer info)
  1455. aurel-empty-string))
  1456. (defun aurel-list-get-installed-version (info)
  1457. "Return installed version from a package INFO."
  1458. (or (aurel-get-param-val 'installed-version info)
  1459. aurel-empty-string))
  1460. (defun aurel-list-get-current-id ()
  1461. "Return ID of the current package."
  1462. (or (tabulated-list-get-id)
  1463. (user-error "No package here")))
  1464. (defun aurel-list-get-package-info (&optional id)
  1465. "Return info for a package with ID or for the current package."
  1466. (or id
  1467. (setq id (aurel-list-get-current-id)))
  1468. (or (cdr (assoc id aurel-list))
  1469. (error "No package with ID %s in aurel-list" id)))
  1470. (defun aurel-list-describe-package (&optional arg)
  1471. "Describe the current package.
  1472. With prefix (if ARG is non-nil), show results in a new info buffer."
  1473. (interactive "P")
  1474. (let* ((id (aurel-list-get-current-id))
  1475. (info (aurel-list-get-package-info id))
  1476. (list (list (cons id info))))
  1477. (aurel-show-packages list arg 'add 'name-or-id (list id))))
  1478. (defun aurel-list-download-package ()
  1479. "Download marked packages or the current package if nothing is marked.
  1480. With prefix, prompt for a directory with `aurel-directory-prompt'
  1481. to save the package; without prefix, save to
  1482. `aurel-download-directory' without prompting.
  1483. Use `aurel-list-download-function' if a single package is
  1484. downloaded or `aurel-list-multi-download-function' otherwise."
  1485. (interactive)
  1486. (or (derived-mode-p 'aurel-list-mode)
  1487. (user-error "Current buffer is not in aurel-list-mode"))
  1488. (let ((dir (if current-prefix-arg
  1489. (read-directory-name aurel-directory-prompt
  1490. aurel-download-directory)
  1491. aurel-download-directory))
  1492. (count (length aurel-list-marks))
  1493. (ids (mapcar #'car aurel-list-marks)))
  1494. (if (> count 1)
  1495. (when (or aurel-list-multi-download-no-confirm
  1496. (y-or-n-p (format "Download %d marked packages? "
  1497. count)))
  1498. (mapcar (lambda (id)
  1499. (funcall aurel-list-multi-download-function
  1500. (aurel-get-param-val
  1501. 'pkg-url (aurel-list-get-package-info id))
  1502. dir))
  1503. ids))
  1504. (funcall aurel-list-download-function
  1505. (aurel-get-param-val
  1506. 'pkg-url (aurel-list-get-package-info (car ids)))
  1507. dir))))
  1508. (defun aurel-list-sort (&optional n)
  1509. "Sort aurel list entries by the column at point.
  1510. With a numeric prefix argument N, sort the Nth column.
  1511. Same as `tabulated-list-sort', but also restore marks after sorting."
  1512. (interactive "P")
  1513. (let ((marks (mapcar #'car aurel-list-marks)))
  1514. (aurel-list-unmark-all)
  1515. (tabulated-list-sort n)
  1516. (when marks
  1517. (aurel-list-mark-packages marks))))
  1518. ;;; Marking packages
  1519. (defun aurel-list-mark ()
  1520. "Mark a package for downloading and move to the next line."
  1521. (interactive)
  1522. (let ((id (tabulated-list-get-id)))
  1523. (when id
  1524. (let ((beg (line-beginning-position))
  1525. (end (line-end-position)))
  1526. (unless (overlays-at beg)
  1527. (let ((overlay (make-overlay beg end)))
  1528. (overlay-put overlay 'face 'aurel-list-marked)
  1529. (add-to-list 'aurel-list-marks
  1530. (cons id overlay))))))
  1531. (forward-line)))
  1532. (defun aurel-list-mark-packages (ids)
  1533. "Mark specified packages.
  1534. IDS is a list of packages ID to mark. If IDS is t, mark all packages."
  1535. (save-excursion
  1536. (goto-char (point-min))
  1537. (while (not (= (point) (point-max)))
  1538. (if (or (eq ids t)
  1539. (member (tabulated-list-get-id) ids))
  1540. (aurel-list-mark)
  1541. (forward-line)))))
  1542. (defun aurel-list-mark-all ()
  1543. "Mark all packages for downloading."
  1544. (interactive)
  1545. (aurel-list-mark-packages t))
  1546. (defun aurel-list--unmark ()
  1547. "Unmark a package on the current line."
  1548. (let ((id (tabulated-list-get-id)))
  1549. (setq aurel-list-marks
  1550. (cl-delete-if (lambda (assoc)
  1551. (when (equal id (car assoc))
  1552. (delete-overlay (cdr assoc))
  1553. t))
  1554. aurel-list-marks))))
  1555. (defun aurel-list-unmark ()
  1556. "Unmark a package and move to the next line."
  1557. (interactive)
  1558. (aurel-list--unmark)
  1559. (forward-line))
  1560. (defun aurel-list-unmark-backward ()
  1561. "Move up one line and unmark a package on that line."
  1562. (interactive)
  1563. (forward-line -1)
  1564. (aurel-list--unmark))
  1565. (defun aurel-list-unmark-all ()
  1566. "Unmark all packages."
  1567. (interactive)
  1568. (dolist (assoc aurel-list-marks)
  1569. (delete-overlay (cdr assoc)))
  1570. (setq aurel-list-marks nil))
  1571. ;;; Filtering package list
  1572. (defun aurel-list-apply-filters (list &optional filters)
  1573. "Apply FILTERS to the LIST of packages.
  1574. LIST should have the form of `aurel-list'.
  1575. If FILTERS is nil, use `aurel-list-filters'.
  1576. Each package info from LIST is passed as an argument to the first
  1577. function from FILTERS, the returned result is passed to the
  1578. second function from that list and so on. If one of the FILTERS
  1579. returns nil, this package info is not passed (do not call the
  1580. rest filters in this case).
  1581. Return a list containing all passed packages info."
  1582. (if (setq filters (or filters aurel-list-filters))
  1583. (cl-remove-if-not
  1584. (lambda (package)
  1585. (aurel-apply-filters (cdr package) filters))
  1586. list)
  1587. list))
  1588. (defun aurel-list-apply-filter (filter &optional replace)
  1589. "Apply FILTER to the current package list and print results.
  1590. If REPLACE is nil, add FILTER to the existing ones; if it is
  1591. non-nil, remove other filters and make FILTER the only active
  1592. one."
  1593. (if replace
  1594. (setq aurel-list-filters (list filter))
  1595. (add-to-list 'aurel-list-filters filter))
  1596. (aurel-list-print))
  1597. (defun aurel-list-enable-filter (arg)
  1598. "Prompt for a function for filtering package list and call it.
  1599. Choose candidates from `aurel-list-available-filters'.
  1600. If ARG is non-nil (with prefix), make selected filter the only
  1601. active one (remove other filters)."
  1602. (interactive "P")
  1603. (let ((fun (intern (completing-read
  1604. (if current-prefix-arg
  1605. "Add filter: "
  1606. "Enable filter: ")
  1607. aurel-list-available-filters))))
  1608. (or (fboundp fun)
  1609. (error "Wrong function %s" fun))
  1610. (funcall fun arg)))
  1611. (defun aurel-list-disable-filters ()
  1612. "Disable all current filters and redisplay packages."
  1613. (interactive)
  1614. (setq aurel-list-filters nil)
  1615. (aurel-list-print))
  1616. (defun aurel-list-filter-maintained (arg)
  1617. "Filter current list by hiding maintained packages.
  1618. See `aurel-list-enable-filter' for the meaning of ARG."
  1619. (interactive "P")
  1620. (aurel-list-apply-filter
  1621. (lambda (info)
  1622. (unless (aurel-get-param-val 'maintainer info)
  1623. info))
  1624. arg))
  1625. (defun aurel-list-filter-unmaintained (arg)
  1626. "Filter current list by hiding unmaintained packages.
  1627. See `aurel-list-enable-filter' for the meaning of ARG."
  1628. (interactive "P")
  1629. (aurel-list-apply-filter
  1630. (lambda (info)
  1631. (when (aurel-get-param-val 'maintainer info)
  1632. info))
  1633. arg))
  1634. (defun aurel-list-filter-outdated (arg)
  1635. "Filter current list by hiding outdated packages.
  1636. See `aurel-list-enable-filter' for the meaning of ARG."
  1637. (interactive "P")
  1638. (aurel-list-apply-filter
  1639. (lambda (info)
  1640. (unless (aurel-get-param-val 'outdated info)
  1641. info))
  1642. arg))
  1643. (defun aurel-list-filter-not-outdated (arg)
  1644. "Filter current list by hiding not outdated packages.
  1645. See `aurel-list-enable-filter' for the meaning of ARG."
  1646. (interactive "P")
  1647. (aurel-list-apply-filter
  1648. (lambda (info)
  1649. (when (aurel-get-param-val 'outdated info)
  1650. info))
  1651. arg))
  1652. (defun aurel-list-filter-same-versions (arg)
  1653. "Hide packages with the same installed and available AUR versions.
  1654. See `aurel-list-enable-filter' for the meaning of ARG."
  1655. (interactive "P")
  1656. (aurel-list-apply-filter
  1657. (lambda (info)
  1658. (unless (equal (aurel-get-param-val 'version info)
  1659. (aurel-get-param-val 'installed-version info))
  1660. info))
  1661. arg))
  1662. (defun aurel-list-filter-different-versions (arg)
  1663. "Hide packages with different installed and available AUR versions.
  1664. See `aurel-list-enable-filter' for the meaning of ARG."
  1665. (interactive "P")
  1666. (aurel-list-apply-filter
  1667. (lambda (info)
  1668. (when (equal (aurel-get-param-val 'version info)
  1669. (aurel-get-param-val 'installed-version info))
  1670. info))
  1671. arg))
  1672. (defun aurel-list-filter-match-regexp (arg)
  1673. "Hide packages with names or descriptions matching prompted regexp.
  1674. See `aurel-list-enable-filter' for the meaning of ARG."
  1675. (interactive "P")
  1676. (let ((re (read-regexp "Hide packages matching regexp: ")))
  1677. (aurel-list-apply-filter
  1678. `(lambda (info)
  1679. (unless (or (string-match-p ,re (aurel-get-param-val 'name info))
  1680. (string-match-p ,re (aurel-get-param-val 'description info)))
  1681. info))
  1682. arg)))
  1683. (defun aurel-list-filter-not-match-regexp (arg)
  1684. "Hide packages with names or descriptions not matching prompted regexp.
  1685. See `aurel-list-enable-filter' for the meaning of ARG."
  1686. (interactive "P")
  1687. (let ((re (read-regexp "Hide packages not matching regexp: ")))
  1688. (aurel-list-apply-filter
  1689. `(lambda (info)
  1690. (when (or (string-match-p ,re (aurel-get-param-val 'name info))
  1691. (string-match-p ,re (aurel-get-param-val 'description info)))
  1692. info))
  1693. arg)))
  1694. ;;; Package info
  1695. (defgroup aurel-info nil
  1696. "Buffer with information about AUR package."
  1697. :group 'aurel)
  1698. (defface aurel-info-id
  1699. '((t))
  1700. "Face used for ID of a package."
  1701. :group 'aurel-info)
  1702. (defface aurel-info-name
  1703. '((t :inherit font-lock-keyword-face))
  1704. "Face used for a name of a package."
  1705. :group 'aurel-info)
  1706. (defface aurel-info-maintainer
  1707. '((t :inherit button))
  1708. "Face used for a maintainer of a package."
  1709. :group 'aurel-info)
  1710. (defface aurel-info-url
  1711. '((t :inherit button))
  1712. "Face used for URLs."
  1713. :group 'aurel-info)
  1714. (defface aurel-info-version
  1715. '((t :inherit font-lock-builtin-face))
  1716. "Face used for a version of a package."
  1717. :group 'aurel-info)
  1718. (defface aurel-info-category
  1719. '((t :inherit font-lock-comment-face))
  1720. "Face used for a category of a package."
  1721. :group 'aurel-info)
  1722. (defface aurel-info-description
  1723. '((t))
  1724. "Face used for a description of a package."
  1725. :group 'aurel-info)
  1726. (defface aurel-info-license
  1727. '((t))
  1728. "Face used for a license of a package."
  1729. :group 'aurel-info)
  1730. (defface aurel-info-votes
  1731. '((t :weight bold))
  1732. "Face used for a number of votes of a package."
  1733. :group 'aurel-info)
  1734. (defface aurel-info-voted-mark
  1735. '((t :inherit aurel-info-voted))
  1736. "Face used for `aurel-info-voted-mark' string."
  1737. :group 'aurel-info)
  1738. (defface aurel-info-outdated
  1739. '((t :inherit font-lock-warning-face))
  1740. "Face used if a package is out of date."
  1741. :group 'aurel-info)
  1742. (defface aurel-info-not-outdated
  1743. '((t))
  1744. "Face used if a package is not out of date."
  1745. :group 'aurel-info)
  1746. (defface aurel-info-voted
  1747. '((default :weight bold)
  1748. (((class color) (min-colors 88) (background light))
  1749. :foreground "ForestGreen")
  1750. (((class color) (min-colors 88) (background dark))
  1751. :foreground "PaleGreen")
  1752. (((class color) (min-colors 8))
  1753. :foreground "green")
  1754. (t :underline t))
  1755. "Face used if a package is voted."
  1756. :group 'aurel-info)
  1757. (defface aurel-info-not-voted
  1758. '((t))
  1759. "Face used if a package is not voted."
  1760. :group 'aurel-info)
  1761. (defface aurel-info-subscribed
  1762. '((t :inherit aurel-info-voted))
  1763. "Face used if a package is subscribed."
  1764. :group 'aurel-info)
  1765. (defface aurel-info-not-subscribed
  1766. '((t :inherit aurel-info-not-voted))
  1767. "Face used if a package is not subscribed."
  1768. :group 'aurel-info)
  1769. (defface aurel-info-date
  1770. '((t :inherit font-lock-constant-face))
  1771. "Face used for dates."
  1772. :group 'aurel-info)
  1773. (defface aurel-info-size
  1774. '((t :inherit font-lock-variable-name-face))
  1775. "Face used for size of installed package."
  1776. :group 'aurel-info)
  1777. (defface aurel-info-architecture
  1778. '((t))
  1779. "Face used for 'Architecture' parameter."
  1780. :group 'aurel-info)
  1781. (defface aurel-info-provides
  1782. '((t :inherit font-lock-function-name-face))
  1783. "Face used for 'Provides' parameter."
  1784. :group 'aurel-info)
  1785. (defface aurel-info-replaces
  1786. '((t :inherit aurel-info-provides))
  1787. "Face used for 'Replaces' parameter."
  1788. :group 'aurel-info)
  1789. (defface aurel-info-conflicts
  1790. '((t :inherit aurel-info-provides))
  1791. "Face used for 'Conflicts With' parameter."
  1792. :group 'aurel-info)
  1793. (defface aurel-info-depends
  1794. '((t))
  1795. "Face used for 'Depends On' parameter."
  1796. :group 'aurel-info)
  1797. (defface aurel-info-depends-opt
  1798. '((t :inherit aurel-info-depends))
  1799. "Face used for 'Optional Deps' parameter."
  1800. :group 'aurel-info)
  1801. (defface aurel-info-required
  1802. '((t))
  1803. "Face used for 'Required By' parameter."
  1804. :group 'aurel-info)
  1805. (defface aurel-info-optional-for
  1806. '((t :inherit aurel-info-required))
  1807. "Face used for 'Optional For' parameter."
  1808. :group 'aurel-info)
  1809. (defcustom aurel-info-buffer-name "*AUR Package Info*"
  1810. "Default name of the buffer with information about an AUR package."
  1811. :type 'string
  1812. :group 'aurel-info)
  1813. (defcustom aurel-info-mode-name "AURel-Info"
  1814. "Default name of `aurel-info-mode', displayed in the mode line."
  1815. :type 'string
  1816. :group 'aurel-info)
  1817. (defcustom aurel-info-ignore-empty-vals nil
  1818. "If non-nil, do not display empty values of package parameters."
  1819. :type 'boolean
  1820. :group 'aurel-info)
  1821. (defcustom aurel-info-format "%-16s: "
  1822. "String used to format a description of each package parameter.
  1823. It should be a '%s'-sequence. After inserting a description
  1824. formatted with this string, a value of the paramter is inserted."
  1825. :type 'string
  1826. :group 'aurel-info)
  1827. (defcustom aurel-info-fill-column 60
  1828. "Column used for filling (word wrapping) a description of a package.
  1829. This value does not include the length of a description of the
  1830. parameter, it is added to it; see `aurel-info-format'."
  1831. :type 'integer
  1832. :group 'aurel-info)
  1833. (defcustom aurel-info-download-function 'aurel-download-unpack-dired
  1834. "Function used for downloading AUR package from package info buffer.
  1835. It should accept 2 arguments: URL of a downloading file and a
  1836. destination directory."
  1837. :type (aurel-download-get-defcustom-type)
  1838. :group 'aurel-info)
  1839. (defcustom aurel-info-history-size 100
  1840. "Maximum number of items saved in history of package info buffer.
  1841. If 0, the history is disabled."
  1842. :type 'integer
  1843. :group 'aurel-info)
  1844. (defcustom aurel-info-voted-mark "*"
  1845. "String inserted after the number of votes in info buffer.
  1846. See `aurel-info-display-voted-mark' for details."
  1847. :type 'string
  1848. :group 'aurel-info)
  1849. (defcustom aurel-info-display-voted-mark t
  1850. "If non-nil, display `aurel-info-voted-mark' in info buffer.
  1851. It is displayed only if a package is voted by you (this
  1852. information is available if `aurel-aur-user-package-info-check'
  1853. is non-nil)."
  1854. :type 'boolean
  1855. :group 'aurel-info)
  1856. (defcustom aurel-info-installed-package-string
  1857. "\n\nThe package is installed:\n\n"
  1858. "String inserted in info buffer if a package is installed.
  1859. It is inserted after printing info from AUR and before info from pacman."
  1860. :type 'string
  1861. :group 'aurel-info)
  1862. (defcustom aurel-info-aur-user-string
  1863. "\n"
  1864. "String inserted before printing info specific for AUR user."
  1865. :type 'string
  1866. :group 'aurel-info)
  1867. (defcustom aurel-info-show-maintainer-account t
  1868. "If non-nil, display a link to maintainer's AUR account."
  1869. :type 'boolean
  1870. :group 'aurel-info)
  1871. (defvar aurel-info-insert-params-alist
  1872. '((id . aurel-info-id)
  1873. (name . aurel-info-name)
  1874. (maintainer . aurel-info-insert-maintainer)
  1875. (version . aurel-info-version)
  1876. (installed-version . aurel-info-version)
  1877. (category . aurel-info-category)
  1878. (license . aurel-info-license)
  1879. (votes . aurel-info-insert-votes)
  1880. (first-date . aurel-info-date)
  1881. (last-date . aurel-info-date)
  1882. (install-date . aurel-info-date)
  1883. (build-date . aurel-info-date)
  1884. (description . aurel-info-description)
  1885. (outdated . aurel-info-insert-outdated)
  1886. (voted . aurel-info-insert-voted)
  1887. (subscribed . aurel-info-insert-subscribed)
  1888. (pkg-url . aurel-info-insert-url)
  1889. (home-url . aurel-info-insert-url)
  1890. (aur-url . aurel-info-insert-aur-url)
  1891. (architecture . aurel-info-architecture)
  1892. (provides . aurel-info-provides)
  1893. (replaces . aurel-info-replaces)
  1894. (conflicts . aurel-info-conflicts)
  1895. (depends . aurel-info-depends)
  1896. (depends-opt . aurel-info-depends-opt)
  1897. (required . aurel-info-required)
  1898. (optional-for . aurel-info-optional-for)
  1899. (installed-size . aurel-info-size))
  1900. "Alist for parameters inserted into info buffer.
  1901. Car of each assoc is a symbol from `aurel-param-description-alist'.
  1902. Cdr is a symbol for inserting a value of a parameter. If the
  1903. symbol is a face name, it is used for the value; if it is a function,
  1904. it is called with the value of the parameter.")
  1905. (defvar aurel-info-parameters
  1906. '(id name version maintainer description home-url aur-url
  1907. license category votes outdated first-date last-date)
  1908. "List of parameters displayed in package info buffer.
  1909. Each parameter should be a symbol from `aurel-param-description-alist'.
  1910. The order of displayed parameters is the same as in this list.
  1911. If nil, display all parameters with no particular order.")
  1912. (defvar aurel-info-installed-parameters
  1913. '(installed-version architecture installed-size provides depends
  1914. depends-opt required optional-for conflicts replaces packager
  1915. build-date install-date script validated)
  1916. "List of parameters of an installed package displayed in info buffer.
  1917. Each parameter should be a symbol from `aurel-param-description-alist'.
  1918. The order of displayed parameters is the same as in this list.
  1919. If nil, display all parameters with no particular order.")
  1920. (defvar aurel-info-aur-user-parameters
  1921. '(voted subscribed)
  1922. "List of parameters specific for AUR user displayed in info buffer.
  1923. Each parameter should be a symbol from `aurel-param-description-alist'.
  1924. The order of displayed parameters is the same as in this list.
  1925. If nil, display all parameters with no particular order.")
  1926. (defvar aurel-info nil
  1927. "Alist with package info.
  1928. Car of each assoc is a symbol from `aurel-param-description-alist'.
  1929. Cdr - is a value of that parameter.")
  1930. (defvar aurel-info-mode-map
  1931. (let ((map (copy-keymap special-mode-map)))
  1932. (define-key map "d" 'aurel-info-download-package)
  1933. (define-key map "\t" 'forward-button)
  1934. (define-key map [backtab] 'backward-button)
  1935. (define-key map "l" 'aurel-history-back)
  1936. (define-key map "r" 'aurel-history-forward)
  1937. (define-key map "v" 'aurel-info-vote-unvote)
  1938. (define-key map "s" 'aurel-info-subscribe-unsubscribe)
  1939. map)
  1940. "Keymap for `aurel-info-mode'.")
  1941. (defun aurel-info-get-buffer-name (&optional unique)
  1942. "Return a name of an info buffer.
  1943. If UNIQUE is non-nil, make the name unique."
  1944. (if unique
  1945. (generate-new-buffer aurel-info-buffer-name)
  1946. aurel-info-buffer-name))
  1947. (define-derived-mode aurel-info-mode
  1948. nil aurel-info-mode-name
  1949. "Major mode for displaying information about an AUR package.
  1950. \\{aurel-info-mode-map}"
  1951. (make-local-variable 'aurel-info)
  1952. (make-local-variable 'aurel-revert-action)
  1953. (setq-local revert-buffer-function 'aurel-revert-buffer)
  1954. (setq-local aurel-history-size aurel-info-history-size)
  1955. (setq buffer-read-only t)
  1956. (setq default-directory aurel-download-directory))
  1957. (defun aurel-info-show (info &optional buffer)
  1958. "Display package information INFO in BUFFER.
  1959. INFO should have the form of `aurel-info'.
  1960. If BUFFER is nil, use (create if needed) buffer with the name
  1961. `aurel-info-buffer-name'."
  1962. (let ((buf (get-buffer-create
  1963. (or buffer aurel-info-buffer-name))))
  1964. (with-current-buffer buf
  1965. (aurel-info-show-in-current-buffer info))
  1966. (pop-to-buffer-same-window buf)))
  1967. (defun aurel-info-show-in-current-buffer (info)
  1968. "Display package information INFO in current buffer.
  1969. INFO should have the form of `aurel-info'."
  1970. (aurel-info-mode)
  1971. (setq aurel-info info)
  1972. (let ((inhibit-read-only t))
  1973. (erase-buffer)
  1974. (apply 'aurel-info-print
  1975. info aurel-info-parameters)
  1976. (when (assoc 'voted info)
  1977. (insert aurel-info-aur-user-string)
  1978. (apply 'aurel-info-print
  1979. info aurel-info-aur-user-parameters))
  1980. (when (aurel-get-param-val 'installed-name info)
  1981. (insert aurel-info-installed-package-string)
  1982. (apply 'aurel-info-print
  1983. info aurel-info-installed-parameters)))
  1984. (goto-char (point-min)))
  1985. (defun aurel-info-print (info &rest params)
  1986. "Insert (pretty print) package INFO into current buffer.
  1987. Each element from PARAMS is a parameter to insert (symbol from
  1988. `aurel-param-description-alist'."
  1989. (mapc (lambda (param)
  1990. (aurel-info-print-param
  1991. param (aurel-get-param-val param info)))
  1992. params))
  1993. (defun aurel-info-print-param (param val)
  1994. "Insert description and value VAL of a parameter PARAM at point.
  1995. PARAM is a symbol from `aurel-param-description-alist'.
  1996. Use `aurel-info-format' to format descriptions of parameters."
  1997. (unless (and aurel-info-ignore-empty-vals
  1998. (equal val aurel-none-string))
  1999. (let ((desc (aurel-get-param-description param))
  2000. (insert-val (cdr (assoc param
  2001. aurel-info-insert-params-alist))))
  2002. (insert (format aurel-info-format desc))
  2003. (if (functionp insert-val)
  2004. (funcall insert-val val)
  2005. (aurel-info-insert-val
  2006. val (and (facep insert-val) insert-val)))
  2007. (insert "\n"))))
  2008. (defun aurel-info-insert-votes (votes)
  2009. "Insert the number of VOTES at point.
  2010. If `aurel-info-display-voted-mark' is non-nil, insert
  2011. `aurel-info-voted-mark' after."
  2012. (aurel-info-insert-val votes 'aurel-info-votes)
  2013. (and aurel-info-display-voted-mark
  2014. (aurel-get-param-val 'voted aurel-info)
  2015. (aurel-info-insert-val aurel-info-voted-mark
  2016. 'aurel-info-voted-mark)))
  2017. (defun aurel-info-insert-maintainer (name)
  2018. "Make button from maintainer NAME and insert it at point."
  2019. (if (null name)
  2020. (insert aurel-empty-string)
  2021. (insert-button
  2022. name
  2023. 'face 'aurel-info-maintainer
  2024. 'action (lambda (btn)
  2025. (aurel-maintainer-search (button-label btn)
  2026. current-prefix-arg))
  2027. 'follow-link t
  2028. 'help-echo "mouse-2, RET: Find the packages by this maintainer")
  2029. (when aurel-info-show-maintainer-account
  2030. (insert "\n"
  2031. (format aurel-info-format ""))
  2032. (aurel-info-insert-url (aurel-get-maintainer-account-url name)))))
  2033. (defun aurel-info-insert-aur-url (url)
  2034. "Insert URL of the AUR package."
  2035. (aurel-info-insert-url
  2036. (aurel-get-aur-package-url (aurel-get-param-val 'name aurel-info))))
  2037. (defun aurel-info-insert-url (url)
  2038. "Make button from URL and insert it at point."
  2039. (insert-button
  2040. url
  2041. 'face 'aurel-info-url
  2042. 'action (lambda (btn) (browse-url (button-label btn)))
  2043. 'follow-link t
  2044. 'help-echo "mouse-2, RET: Browse URL"))
  2045. (defun aurel-info-insert-boolean (val &optional t-face nil-face)
  2046. "Insert boolean value VAL at point.
  2047. If VAL is t, use T-FACE; if VAL is nil, use NIL-FACE.
  2048. If VAL is not boolean, insert it as is."
  2049. (let ((face (and (booleanp val)
  2050. (if val t-face nil-face))))
  2051. (insert (aurel-get-string val face))))
  2052. (defun aurel-info-insert-outdated (val)
  2053. "Insert value VAL of the `outdated' parameter at point."
  2054. (aurel-info-insert-boolean
  2055. val 'aurel-info-outdated 'aurel-info-not-outdated))
  2056. (defun aurel-info-insert-voted (val)
  2057. "Insert value VAL of the `voted' parameter at point."
  2058. (aurel-info-insert-boolean
  2059. val 'aurel-info-voted 'aurel-info-not-voted))
  2060. (defun aurel-info-insert-subscribed (val)
  2061. "Insert value VAL of the `subscribed' parameter at point."
  2062. (aurel-info-insert-boolean
  2063. val 'aurel-info-subscribed 'aurel-info-not-subscribed))
  2064. (defun aurel-info-get-filled-string (str col)
  2065. "Return string by filling a string STR.
  2066. COL controls the width for filling."
  2067. (with-temp-buffer
  2068. (insert str)
  2069. (let ((fill-column col)) (fill-region (point-min) (point-max)))
  2070. (buffer-string)))
  2071. (defun aurel-info-insert-strings (strings &optional face)
  2072. "Insert STRINGS at point.
  2073. Each string is inserted on a new line after an empty string
  2074. formatted with `aurel-info-format'.
  2075. If FACE is non-nil, propertize inserted lines with this FACE."
  2076. (dolist (str strings)
  2077. (insert "\n"
  2078. (format aurel-info-format "")
  2079. (aurel-get-string str face))))
  2080. (defun aurel-info-insert-val (val &optional face)
  2081. "Format and insert parameter value VAL at point.
  2082. If VAL is string longer than `aurel-info-fill-column', convert it
  2083. into several shorter lines.
  2084. If FACE is non-nil, propertize inserted line(s) with this FACE."
  2085. (if (stringp val)
  2086. (let ((strings (split-string val "\n *")))
  2087. (and (null (cdr strings)) ; if not multi-line
  2088. (> (length val) aurel-info-fill-column)
  2089. (setq strings
  2090. (split-string (aurel-info-get-filled-string
  2091. val aurel-info-fill-column)
  2092. "\n")))
  2093. (insert (aurel-get-string (car strings) face))
  2094. (aurel-info-insert-strings (cdr strings) face))
  2095. (insert (aurel-get-string val face))))
  2096. (defun aurel-info-download-package ()
  2097. "Download current package.
  2098. With prefix, prompt for a directory with `aurel-directory-prompt'
  2099. to save the package; without prefix, save to
  2100. `aurel-download-directory' without prompting.
  2101. Use `aurel-info-download-function'."
  2102. (interactive)
  2103. (or (derived-mode-p 'aurel-info-mode)
  2104. (user-error "Current buffer is not in aurel-info-mode"))
  2105. (let ((dir (if current-prefix-arg
  2106. (read-directory-name aurel-directory-prompt
  2107. aurel-download-directory)
  2108. aurel-download-directory)))
  2109. (funcall aurel-info-download-function
  2110. (aurel-get-param-val 'pkg-url aurel-info)
  2111. dir)))
  2112. (defun aurel-info-aur-user-action (action &optional norevert)
  2113. "Perform AUR user ACTION on the current package.
  2114. ACTION is a symbol from `aurel-aur-user-actions'.
  2115. If NOREVERT is non-nil, do not revert the buffer (i.e. do not
  2116. refresh package information) after ACTION."
  2117. (and (aurel-aur-user-action
  2118. action (aurel-get-param-val 'name aurel-info))
  2119. (null norevert)
  2120. (revert-buffer nil t)))
  2121. (defun aurel-info-vote-unvote (arg)
  2122. "Vote for the current package.
  2123. With prefix (if ARG is non-nil), unvote."
  2124. (interactive "P")
  2125. (aurel-info-aur-user-action (if arg 'unvote 'vote)))
  2126. (defun aurel-info-subscribe-unsubscribe (arg)
  2127. "Subscribe to the new comments of the current package.
  2128. With prefix (if ARG is non-nil), unsubscribe."
  2129. (interactive "P")
  2130. (aurel-info-aur-user-action (if arg 'unsubscribe 'subscribe)))
  2131. (provide 'aurel)
  2132. ;;; aurel.el ends here