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