package-x.el 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. ;;; package-x.el --- Package extras
  2. ;; Copyright (C) 2007-2012 Free Software Foundation, Inc.
  3. ;; Author: Tom Tromey <tromey@redhat.com>
  4. ;; Created: 10 Mar 2007
  5. ;; Version: 0.9
  6. ;; Keywords: tools
  7. ;; Package: package
  8. ;; This file is part of GNU Emacs.
  9. ;; GNU Emacs is free software; you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation; either version 3, or (at your option)
  12. ;; any later version.
  13. ;; GNU Emacs is distributed in the hope that it will be useful,
  14. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;; GNU General Public License for more details.
  17. ;; You should have received a copy of the GNU General Public License
  18. ;; along with GNU Emacs; see the file COPYING. If not, write to the
  19. ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  20. ;; Boston, MA 02110-1301, USA.
  21. ;;; Commentary:
  22. ;; This file currently contains parts of the package system that many
  23. ;; won't need, such as package uploading.
  24. ;; To upload to an archive, first set `package-archive-upload-base' to
  25. ;; some desired directory. For testing purposes, you can specify any
  26. ;; directory you want, but if you want the archive to be accessible to
  27. ;; others via http, this is typically a directory in the /var/www tree
  28. ;; (possibly one on a remote machine, accessed via Tramp).
  29. ;; Then call M-x package-upload-file, which prompts for a file to
  30. ;; upload. Alternatively, M-x package-upload-buffer uploads the
  31. ;; current buffer, if it's visiting a package file.
  32. ;; Once a package is uploaded, users can access it via the Package
  33. ;; Menu, by adding the archive to `package-archives'.
  34. ;;; Code:
  35. (require 'package)
  36. (defvar gnus-article-buffer)
  37. (defcustom package-archive-upload-base "/path/to/archive"
  38. "The base location of the archive to which packages are uploaded.
  39. This should be an absolute directory name. If the archive is on
  40. another machine, you may specify a remote name in the usual way,
  41. e.g. \"/ssh:foo@example.com:/var/www/packages/\".
  42. See Info node `(emacs)Remote Files'.
  43. Unlike `package-archives', you can't specify a HTTP URL."
  44. :type 'directory
  45. :group 'package
  46. :version "24.1")
  47. (defvar package-update-news-on-upload nil
  48. "Whether uploading a package should also update NEWS and RSS feeds.")
  49. (defun package--encode (string)
  50. "Encode a string by replacing some characters with XML entities."
  51. ;; We need a special case for translating "&" to "&amp;".
  52. (let ((index))
  53. (while (setq index (string-match "[&]" string index))
  54. (setq string (replace-match "&amp;" t nil string))
  55. (setq index (1+ index))))
  56. (while (string-match "[<]" string)
  57. (setq string (replace-match "&lt;" t nil string)))
  58. (while (string-match "[>]" string)
  59. (setq string (replace-match "&gt;" t nil string)))
  60. (while (string-match "[']" string)
  61. (setq string (replace-match "&apos;" t nil string)))
  62. (while (string-match "[\"]" string)
  63. (setq string (replace-match "&quot;" t nil string)))
  64. string)
  65. (defun package--make-rss-entry (title text archive-url)
  66. (let ((date-string (format-time-string "%a, %d %B %Y %T %z")))
  67. (concat "<item>\n"
  68. "<title>" (package--encode title) "</title>\n"
  69. ;; FIXME: should have a link in the web page.
  70. "<link>" archive-url "news.html</link>\n"
  71. "<description>" (package--encode text) "</description>\n"
  72. "<pubDate>" date-string "</pubDate>\n"
  73. "</item>\n")))
  74. (defun package--make-html-entry (title text)
  75. (concat "<li> " (format-time-string "%B %e") " - "
  76. title " - " (package--encode text)
  77. " </li>\n"))
  78. (defun package--update-file (file tag text)
  79. "Update the package archive file named FILE.
  80. FILE should be relative to `package-archive-upload-base'.
  81. TAG is a string that can be found within the file; TEXT is
  82. inserted after its first occurrence in the file."
  83. (setq file (expand-file-name file package-archive-upload-base))
  84. (save-excursion
  85. (let ((old-buffer (find-buffer-visiting file)))
  86. (with-current-buffer (let ((find-file-visit-truename t))
  87. (or old-buffer (find-file-noselect file)))
  88. (goto-char (point-min))
  89. (search-forward tag)
  90. (forward-line)
  91. (insert text)
  92. (let ((file-precious-flag t))
  93. (save-buffer))
  94. (unless old-buffer
  95. (kill-buffer (current-buffer)))))))
  96. (defun package--archive-contents-from-url (archive-url)
  97. "Parse archive-contents file at ARCHIVE-URL.
  98. Return the file contents, as a string, or nil if unsuccessful."
  99. (ignore-errors
  100. (when archive-url
  101. (let* ((buffer (url-retrieve-synchronously
  102. (concat archive-url "archive-contents"))))
  103. (set-buffer buffer)
  104. (package-handle-response)
  105. (re-search-forward "^$" nil 'move)
  106. (forward-char)
  107. (delete-region (point-min) (point))
  108. (prog1 (package-read-from-string
  109. (buffer-substring-no-properties (point-min) (point-max)))
  110. (kill-buffer buffer))))))
  111. (defun package--archive-contents-from-file ()
  112. "Parse the archive-contents at `package-archive-upload-base'"
  113. (let ((file (expand-file-name "archive-contents"
  114. package-archive-upload-base)))
  115. (if (not (file-exists-p file))
  116. ;; No existing archive-contents means a new archive.
  117. (list package-archive-version)
  118. (let ((dont-kill (find-buffer-visiting file)))
  119. (with-current-buffer (let ((find-file-visit-truename t))
  120. (find-file-noselect file))
  121. (prog1
  122. (package-read-from-string
  123. (buffer-substring-no-properties (point-min) (point-max)))
  124. (unless dont-kill
  125. (kill-buffer (current-buffer)))))))))
  126. (defun package-maint-add-news-item (title description archive-url)
  127. "Add a news item to the webpages associated with the package archive.
  128. TITLE is the title of the news item.
  129. DESCRIPTION is the text of the news item."
  130. (interactive "sTitle: \nsText: ")
  131. (package--update-file "elpa.rss"
  132. "<description>"
  133. (package--make-rss-entry title description archive-url))
  134. (package--update-file "news.html"
  135. "New entries go here"
  136. (package--make-html-entry title description)))
  137. (defun package--update-news (package version description archive-url)
  138. "Update the ELPA web pages when a package is uploaded."
  139. (package-maint-add-news-item (concat package " version " version)
  140. description
  141. archive-url))
  142. (defun package-upload-buffer-internal (pkg-info extension &optional archive-url)
  143. "Upload a package whose contents are in the current buffer.
  144. PKG-INFO is the package info, see `package-buffer-info'.
  145. EXTENSION is the file extension, a string. It can be either
  146. \"el\" or \"tar\".
  147. The upload destination is given by `package-archive-upload-base'.
  148. If its value is invalid, prompt for a directory.
  149. Optional arg ARCHIVE-URL is the URL of the destination archive.
  150. If it is non-nil, compute the new \"archive-contents\" file
  151. starting from the existing \"archive-contents\" at that URL. In
  152. addition, if `package-update-news-on-upload' is non-nil, call
  153. `package--update-news' to add a news item at that URL.
  154. If ARCHIVE-URL is nil, compute the new \"archive-contents\" file
  155. from the \"archive-contents\" at `package-archive-upload-base',
  156. if it exists."
  157. (let ((package-archive-upload-base package-archive-upload-base))
  158. ;; Check if `package-archive-upload-base' is valid.
  159. (when (or (not (stringp package-archive-upload-base))
  160. (equal package-archive-upload-base
  161. (car-safe
  162. (get 'package-archive-upload-base 'standard-value))))
  163. (setq package-archive-upload-base
  164. (read-directory-name
  165. "Base directory for package archive: ")))
  166. (unless (file-directory-p package-archive-upload-base)
  167. (if (y-or-n-p (format "%s does not exist; create it? "
  168. package-archive-upload-base))
  169. (make-directory package-archive-upload-base t)
  170. (error "Aborted")))
  171. (save-excursion
  172. (save-restriction
  173. (let* ((file-type (cond
  174. ((equal extension "el") 'single)
  175. ((equal extension "tar") 'tar)
  176. (t (error "Unknown extension `%s'" extension))))
  177. (file-name (aref pkg-info 0))
  178. (pkg-name (intern file-name))
  179. (requires (aref pkg-info 1))
  180. (desc (if (string= (aref pkg-info 2) "")
  181. (read-string "Description of package: ")
  182. (aref pkg-info 2)))
  183. (pkg-version (aref pkg-info 3))
  184. (commentary (aref pkg-info 4))
  185. (split-version (version-to-list pkg-version))
  186. (pkg-buffer (current-buffer)))
  187. ;; Get archive-contents from ARCHIVE-URL if it's non-nil, or
  188. ;; from `package-archive-upload-base' otherwise.
  189. (let ((contents (or (package--archive-contents-from-url archive-url)
  190. (package--archive-contents-from-file)))
  191. (new-desc (vector split-version requires desc file-type)))
  192. (if (> (car contents) package-archive-version)
  193. (error "Unrecognized archive version %d" (car contents)))
  194. (let ((elt (assq pkg-name (cdr contents))))
  195. (if elt
  196. (if (version-list-<= split-version
  197. (package-desc-vers (cdr elt)))
  198. (error "New package has smaller version: %s" pkg-version)
  199. (setcdr elt new-desc))
  200. (setq contents (cons (car contents)
  201. (cons (cons pkg-name new-desc)
  202. (cdr contents))))))
  203. ;; Now CONTENTS is the updated archive contents. Upload
  204. ;; this and the package itself. For now we assume ELPA is
  205. ;; writable via file primitives.
  206. (let ((print-level nil)
  207. (print-length nil))
  208. (write-region (concat (pp-to-string contents) "\n")
  209. nil
  210. (expand-file-name "archive-contents"
  211. package-archive-upload-base)))
  212. ;; If there is a commentary section, write it.
  213. (when commentary
  214. (write-region commentary nil
  215. (expand-file-name
  216. (concat (symbol-name pkg-name) "-readme.txt")
  217. package-archive-upload-base)))
  218. (set-buffer pkg-buffer)
  219. (write-region (point-min) (point-max)
  220. (expand-file-name
  221. (concat file-name "-" pkg-version "." extension)
  222. package-archive-upload-base)
  223. nil nil nil 'excl)
  224. ;; Write a news entry.
  225. (and package-update-news-on-upload
  226. archive-url
  227. (package--update-news (concat file-name "." extension)
  228. pkg-version desc archive-url))
  229. ;; special-case "package": write a second copy so that the
  230. ;; installer can easily find the latest version.
  231. (if (string= file-name "package")
  232. (write-region (point-min) (point-max)
  233. (expand-file-name
  234. (concat file-name "." extension)
  235. package-archive-upload-base)
  236. nil nil nil 'ask))))))))
  237. (defun package-upload-buffer ()
  238. "Upload the current buffer as a single-file Emacs Lisp package.
  239. If `package-archive-upload-base' does not specify a valid upload
  240. destination, prompt for one."
  241. (interactive)
  242. (save-excursion
  243. (save-restriction
  244. ;; Find the package in this buffer.
  245. (let ((pkg-info (package-buffer-info)))
  246. (package-upload-buffer-internal pkg-info "el")))))
  247. (defun package-upload-file (file)
  248. "Upload the Emacs Lisp package FILE to the package archive.
  249. Interactively, prompt for FILE. The package is considered a
  250. single-file package if FILE ends in \".el\", and a multi-file
  251. package if FILE ends in \".tar\".
  252. If `package-archive-upload-base' does not specify a valid upload
  253. destination, prompt for one."
  254. (interactive "fPackage file name: ")
  255. (with-temp-buffer
  256. (insert-file-contents-literally file)
  257. (let ((info (cond
  258. ((string-match "\\.tar$" file) (package-tar-file-info file))
  259. ((string-match "\\.el$" file) (package-buffer-info))
  260. (t (error "Unrecognized extension `%s'"
  261. (file-name-extension file))))))
  262. (package-upload-buffer-internal info (file-name-extension file)))))
  263. (defun package-gnus-summary-upload ()
  264. "Upload a package contained in the current *Article* buffer.
  265. This should be invoked from the gnus *Summary* buffer."
  266. (interactive)
  267. (with-current-buffer gnus-article-buffer
  268. (package-upload-buffer)))
  269. (provide 'package-x)
  270. ;;; package-x.el ends here