Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
12 KiB

  1. ;;; pkg-info.el --- Information about packages -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2013-2015 Sebastian Wiesner <swiesner@lunaryorn.com>
  3. ;; Author: Sebastian Wiesner <swiesner@lunaryorn.com>
  4. ;; URL: https://github.com/lunaryorn/pkg-info.el
  5. ;; Package-Version: 0.6
  6. ;; Package-Commit: f9bb471ee95d1c5fe9adc6b0e98db2ddff3ddc0e
  7. ;; Keywords: convenience
  8. ;; Version: 0.6
  9. ;; Package-Requires: ((epl "0.8"))
  10. ;; This file is not part of GNU Emacs.
  11. ;; This program is free software; you can redistribute it and/or modify
  12. ;; it under the terms of the GNU General Public License as published by
  13. ;; the Free Software Foundation, either version 3 of the License, or
  14. ;; (at your option) any later version.
  15. ;; This program is distributed in the hope that it will be useful,
  16. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;; GNU General Public License for more details.
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. ;;; Commentary:
  22. ;; This library extracts information from installed packages.
  23. ;;;; Functions:
  24. ;; `pkg-info-library-version' extracts the version from the header of a library.
  25. ;;
  26. ;; `pkg-info-defining-library-version' extracts the version from the header of a
  27. ;; library defining a function.
  28. ;;
  29. ;; `pkg-info-package-version' gets the version of an installed package.
  30. ;;
  31. ;; `pkg-info-format-version' formats a version list as human readable string.
  32. ;;
  33. ;; `pkg-info-version-info' returns complete version information for a specific
  34. ;; package.
  35. ;;
  36. ;; `pkg-info-get-melpa-recipe' gets the MELPA recipe for a package.
  37. ;;
  38. ;; `pkg-info-get-melpa-fetcher' gets the fetcher used to build a package on
  39. ;; MELPA.
  40. ;;
  41. ;; `pkg-info-wiki-package-p' determines whether a package was build from
  42. ;; EmacsWiki on MELPA.
  43. ;;; Code:
  44. (require 'epl)
  45. (require 'lisp-mnt)
  46. (require 'find-func)
  47. (require 'json) ; `json-read'
  48. (require 'url-http) ; `url-http-parse-response'
  49. (defvar url-http-end-of-headers)
  50. ;;; Version information
  51. (defun pkg-info-format-version (version)
  52. "Format VERSION as human-readable string.
  53. Return a human-readable string representing VERSION."
  54. ;; XXX: Find a better, more flexible way of formatting?
  55. (package-version-join version))
  56. (defsubst pkg-info--show-version-and-return (version show)
  57. "Show and return VERSION.
  58. When SHOW is non-nil, show VERSION in minibuffer.
  59. Return VERSION."
  60. (when show
  61. (message (if (listp version) (pkg-info-format-version version) version)))
  62. version)
  63. (defun pkg-info--read-library ()
  64. "Read a library from minibuffer."
  65. (completing-read "Load library: "
  66. (apply-partially 'locate-file-completion-table
  67. load-path
  68. (get-load-suffixes))))
  69. (defun pkg-info--read-function ()
  70. "Read a function name from minibuffer."
  71. (let ((input (completing-read "Function: " obarray #'boundp :require-match)))
  72. (if (string= input "") nil (intern input))))
  73. (defun pkg-info--read-package ()
  74. "Read a package name from minibuffer."
  75. (let* ((installed (epl-installed-packages))
  76. (names (sort (mapcar (lambda (pkg)
  77. (symbol-name (epl-package-name pkg)))
  78. installed)
  79. #'string<))
  80. (default (car names)))
  81. (completing-read "Installed package: " names nil 'require-match
  82. nil nil default)))
  83. (defun pkg-info-library-source (library)
  84. "Get the source file of LIBRARY.
  85. LIBRARY is either a symbol denoting a named feature, or a library
  86. name as string.
  87. Return the source file of LIBRARY as string."
  88. (find-library-name (if (symbolp library) (symbol-name library) library)))
  89. (defun pkg-info-defining-library (function)
  90. "Get the source file of the library defining FUNCTION.
  91. FUNCTION is a function symbol.
  92. Return the file name of the library as string. Signal an error
  93. if the library does not exist, or if the definition of FUNCTION
  94. was not found."
  95. (unless (functionp function)
  96. (signal 'wrong-type-argument (list 'functionp function)))
  97. (let ((library (symbol-file function 'defun)))
  98. (unless library
  99. (error "Can't find definition of %s" function))
  100. library))
  101. (defun pkg-info-x-original-version (file)
  102. "Read the X-Original-Version header from FILE.
  103. Return the value as version list, or return nil if FILE lacks
  104. this header. Signal an error, if the value of the header is not
  105. a valid version."
  106. (let ((version-str (with-temp-buffer
  107. (insert-file-contents file)
  108. (lm-header "X-Original-Version"))))
  109. (when version-str
  110. (version-to-list version-str))))
  111. ;;;###autoload
  112. (defun pkg-info-library-original-version (library &optional show)
  113. "Get the original version in the header of LIBRARY.
  114. The original version is stored in the X-Original-Version header.
  115. This header is added by the MELPA package archive to preserve
  116. upstream version numbers.
  117. LIBRARY is either a symbol denoting a named feature, or a library
  118. name as string.
  119. If SHOW is non-nil, show the version in the minibuffer.
  120. Return the version from the header of LIBRARY as list. Signal an
  121. error if the LIBRARY was not found or had no X-Original-Version
  122. header.
  123. See Info node `(elisp)Library Headers' for more information
  124. about library headers."
  125. (interactive (list (pkg-info--read-library) t))
  126. (let ((version (pkg-info-x-original-version
  127. (pkg-info-library-source library))))
  128. (if version
  129. (pkg-info--show-version-and-return version show)
  130. (error "Library %s has no original version" library))))
  131. ;;;###autoload
  132. (defun pkg-info-library-version (library &optional show)
  133. "Get the version in the header of LIBRARY.
  134. LIBRARY is either a symbol denoting a named feature, or a library
  135. name as string.
  136. If SHOW is non-nil, show the version in the minibuffer.
  137. Return the version from the header of LIBRARY as list. Signal an
  138. error if the LIBRARY was not found or had no proper header.
  139. See Info node `(elisp)Library Headers' for more information
  140. about library headers."
  141. (interactive (list (pkg-info--read-library) t))
  142. (let* ((source (pkg-info-library-source library))
  143. (version (epl-package-version (epl-package-from-file source))))
  144. (pkg-info--show-version-and-return version show)))
  145. ;;;###autoload
  146. (defun pkg-info-defining-library-original-version (function &optional show)
  147. "Get the original version of the library defining FUNCTION.
  148. The original version is stored in the X-Original-Version header.
  149. This header is added by the MELPA package archive to preserve
  150. upstream version numbers.
  151. If SHOW is non-nil, show the version in mini-buffer.
  152. This function is mainly intended to find the version of a major
  153. or minor mode, i.e.
  154. (pkg-info-defining-library-version 'flycheck-mode)
  155. Return the version of the library defining FUNCTION. Signal an
  156. error if FUNCTION is not a valid function, if its defining
  157. library was not found, or if the library had no proper version
  158. header."
  159. (interactive (list (pkg-info--read-function) t))
  160. (pkg-info-library-original-version (pkg-info-defining-library function) show))
  161. ;;;###autoload
  162. (defun pkg-info-defining-library-version (function &optional show)
  163. "Get the version of the library defining FUNCTION.
  164. If SHOW is non-nil, show the version in mini-buffer.
  165. This function is mainly intended to find the version of a major
  166. or minor mode, i.e.
  167. (pkg-info-defining-library-version 'flycheck-mode)
  168. Return the version of the library defining FUNCTION. Signal an
  169. error if FUNCTION is not a valid function, if its defining
  170. library was not found, or if the library had no proper version
  171. header."
  172. (interactive (list (pkg-info--read-function) t))
  173. (pkg-info-library-version (pkg-info-defining-library function) show))
  174. ;;;###autoload
  175. (defun pkg-info-package-version (package &optional show)
  176. "Get the version of an installed PACKAGE.
  177. If SHOW is non-nil, show the version in the minibuffer.
  178. Return the version as list, or nil if PACKAGE is not installed."
  179. (interactive (list (pkg-info--read-package) t))
  180. (let* ((name (if (stringp package) (intern package) package))
  181. (package (car (epl-find-installed-packages name))))
  182. (unless package
  183. (error "Can't find installed package %s" name))
  184. (pkg-info--show-version-and-return (epl-package-version package) show)))
  185. ;;;###autoload
  186. (defun pkg-info-version-info (library &optional package show)
  187. "Obtain complete version info for LIBRARY and PACKAGE.
  188. LIBRARY is a symbol denoting a named feature, or a library name
  189. as string. PACKAGE is a symbol denoting an ELPA package. If
  190. omitted or nil, default to LIBRARY.
  191. If SHOW is non-nil, show the version in the minibuffer.
  192. When called interactively, prompt for LIBRARY. When called
  193. interactively with prefix argument, prompt for PACKAGE as well.
  194. Return a string with complete version information for LIBRARY.
  195. This version information contains the version from the headers of
  196. LIBRARY, and the version of the installed PACKAGE, the LIBRARY is
  197. part of. If PACKAGE is not installed, or if the PACKAGE version
  198. is the same as the LIBRARY version, do not include a package
  199. version."
  200. (interactive (list (pkg-info--read-library)
  201. (when current-prefix-arg
  202. (pkg-info--read-package))
  203. t))
  204. (let* ((package (or package (if (stringp library) (intern library) library)))
  205. (orig-version (condition-case nil
  206. (pkg-info-library-original-version library)
  207. (error nil)))
  208. ;; If we have X-Original-Version, we assume that MELPA replaced the
  209. ;; library version with its generated version, so we use the
  210. ;; X-Original-Version header instead, and ignore the library version
  211. ;; header
  212. (lib-version (or orig-version (pkg-info-library-version library)))
  213. (pkg-version (condition-case nil
  214. (pkg-info-package-version package)
  215. (error nil)))
  216. (version (if (and pkg-version
  217. (not (version-list-= lib-version pkg-version)))
  218. (format "%s (package: %s)"
  219. (pkg-info-format-version lib-version)
  220. (pkg-info-format-version pkg-version))
  221. (pkg-info-format-version lib-version))))
  222. (pkg-info--show-version-and-return version show)))
  223. (defconst pkg-info-melpa-recipe-url "http://melpa.org/recipes.json"
  224. "The URL from which to fetch MELPA recipes.")
  225. (defvar pkg-info-melpa-recipes nil
  226. "An alist of MELPA recipes.")
  227. (defun pkg-info-retrieve-melpa-recipes ()
  228. "Retrieve MELPA recipes from MELPA archive."
  229. (let ((buffer (url-retrieve-synchronously pkg-info-melpa-recipe-url)))
  230. (with-current-buffer buffer
  231. (unwind-protect
  232. (let ((response-code (url-http-parse-response)))
  233. (unless (equal response-code 200)
  234. (error "Failed to retrieve MELPA recipes from %s (code %s)"
  235. pkg-info-melpa-recipe-url response-code))
  236. (goto-char url-http-end-of-headers)
  237. (json-read))
  238. (when (and buffer (buffer-live-p buffer))
  239. (kill-buffer buffer))))))
  240. (defun pkg-info-get-melpa-recipes ()
  241. "Get MELPA recipes."
  242. (setq pkg-info-melpa-recipes
  243. (or pkg-info-melpa-recipes
  244. (pkg-info-retrieve-melpa-recipes))))
  245. (defun pkg-info-get-melpa-recipe (package)
  246. "Get the MELPA recipe for PACKAGE.
  247. Return nil if PACKAGE is not on MELPA."
  248. (cdr (assq package (pkg-info-get-melpa-recipes))))
  249. (defun pkg-info-get-melpa-fetcher (package)
  250. "Get the MELPA fetcher for PACKAGE."
  251. (cdr (assq 'fetcher (pkg-info-get-melpa-recipe package))))
  252. (defun pkg-info-wiki-package-p (package)
  253. "Determine whether PACKAGE is build from the EmacsWiki."
  254. (equal (pkg-info-get-melpa-fetcher package) "wiki"))
  255. (provide 'pkg-info)
  256. ;; Local Variables:
  257. ;; indent-tabs-mode: nil
  258. ;; coding: utf-8
  259. ;; End:
  260. ;;; pkg-info.el ends here