1 ;;; package-x.el --- Package extras
3 ;; Copyright (C) 2007-2011 Free Software Foundation, Inc.
5 ;; Author: Tom Tromey <tromey@redhat.com>
6 ;; Created: 10 Mar 2007
11 ;; This file is part of GNU Emacs.
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, or (at your option)
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.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs; see the file COPYING. If not, write to the
25 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26 ;; Boston, MA 02110-1301, USA.
30 ;; This file currently contains parts of the package system that many
31 ;; won't need, such as package uploading.
33 ;; To upload to an archive, first set `package-archive-upload-base' to
34 ;; some desired directory. For testing purposes, you can specify any
35 ;; directory you want, but if you want the archive to be accessible to
36 ;; others via http, this is typically a directory in the /var/www tree
37 ;; (possibly one on a remote machine, accessed via Tramp).
39 ;; Then call M-x package-upload-file, which prompts for a file to
40 ;; upload. Alternatively, M-x package-upload-buffer uploads the
41 ;; current buffer, if it's visiting a package file.
43 ;; Once a package is uploaded, users can access it via the Package
44 ;; Menu, by adding the archive to `package-archives'.
49 (defvar gnus-article-buffer
)
51 (defcustom package-archive-upload-base
"/path/to/archive"
52 "The base location of the archive to which packages are uploaded.
53 This should be an absolute directory name. If the archive is on
54 another machine, you may specify a remote name in the usual way,
55 e.g. \"/ssh:foo@example.com:/var/www/packages/\".
56 See Info node `(emacs)Remote Files'.
58 Unlike `package-archives', you can't specify a HTTP URL."
63 (defvar package-update-news-on-upload nil
64 "Whether uploading a package should also update NEWS and RSS feeds.")
66 (defun package--encode (string)
67 "Encode a string by replacing some characters with XML entities."
68 ;; We need a special case for translating "&" to "&".
70 (while (setq index
(string-match "[&]" string index
))
71 (setq string
(replace-match "&" t nil string
))
72 (setq index
(1+ index
))))
73 (while (string-match "[<]" string
)
74 (setq string
(replace-match "<" t nil string
)))
75 (while (string-match "[>]" string
)
76 (setq string
(replace-match ">" t nil string
)))
77 (while (string-match "[']" string
)
78 (setq string
(replace-match "'" t nil string
)))
79 (while (string-match "[\"]" string
)
80 (setq string
(replace-match """ t nil string
)))
83 (defun package--make-rss-entry (title text archive-url
)
84 (let ((date-string (format-time-string "%a, %d %B %Y %T %z")))
86 "<title>" (package--encode title
) "</title>\n"
87 ;; FIXME: should have a link in the web page.
88 "<link>" archive-url
"news.html</link>\n"
89 "<description>" (package--encode text
) "</description>\n"
90 "<pubDate>" date-string
"</pubDate>\n"
93 (defun package--make-html-entry (title text
)
94 (concat "<li> " (format-time-string "%B %e") " - "
95 title
" - " (package--encode text
)
98 (defun package--update-file (file tag text
)
99 "Update the package archive file named FILE.
100 FILE should be relative to `package-archive-upload-base'.
101 TAG is a string that can be found within the file; TEXT is
102 inserted after its first occurrence in the file."
103 (setq file
(expand-file-name file package-archive-upload-base
))
105 (let ((old-buffer (find-buffer-visiting file
)))
106 (with-current-buffer (let ((find-file-visit-truename t
))
107 (or old-buffer
(find-file-noselect file
)))
108 (goto-char (point-min))
112 (let ((file-precious-flag t
))
115 (kill-buffer (current-buffer)))))))
117 (defun package--archive-contents-from-url (archive-url)
118 "Parse archive-contents file at ARCHIVE-URL.
119 Return the file contents, as a string, or nil if unsuccessful."
122 (let* ((buffer (url-retrieve-synchronously
123 (concat archive-url
"archive-contents"))))
125 (package-handle-response)
126 (re-search-forward "^$" nil
'move
)
128 (delete-region (point-min) (point))
129 (prog1 (package-read-from-string
130 (buffer-substring-no-properties (point-min) (point-max)))
131 (kill-buffer buffer
))))))
133 (defun package--archive-contents-from-file ()
134 "Parse the archive-contents at `package-archive-upload-base'"
135 (let ((file (expand-file-name "archive-contents"
136 package-archive-upload-base
)))
137 (if (not (file-exists-p file
))
138 ;; No existing archive-contents means a new archive.
139 (list package-archive-version
)
140 (let ((dont-kill (find-buffer-visiting file
)))
141 (with-current-buffer (let ((find-file-visit-truename t
))
142 (find-file-noselect file
))
144 (package-read-from-string
145 (buffer-substring-no-properties (point-min) (point-max)))
147 (kill-buffer (current-buffer)))))))))
149 (defun package-maint-add-news-item (title description archive-url
)
150 "Add a news item to the webpages associated with the package archive.
151 TITLE is the title of the news item.
152 DESCRIPTION is the text of the news item."
153 (interactive "sTitle: \nsText: ")
154 (package--update-file "elpa.rss"
156 (package--make-rss-entry title description archive-url
))
157 (package--update-file "news.html"
158 "New entries go here"
159 (package--make-html-entry title description
)))
161 (defun package--update-news (package version description archive-url
)
162 "Update the ELPA web pages when a package is uploaded."
163 (package-maint-add-news-item (concat package
" version " version
)
167 (defun package-upload-buffer-internal (pkg-info extension
&optional archive-url
)
168 "Upload a package whose contents are in the current buffer.
169 PKG-INFO is the package info, see `package-buffer-info'.
170 EXTENSION is the file extension, a string. It can be either
173 The upload destination is given by `package-archive-upload-base'.
174 If its value is invalid, prompt for a directory.
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.
182 If ARCHIVE-URL is nil, compute the new \"archive-contents\" file
183 from the \"archive-contents\" at `package-archive-upload-base',
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
190 (get 'package-archive-upload-base
'standard-value
))))
191 (setq package-archive-upload-base
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
)
201 (let* ((file-type (cond
202 ((equal extension
"el") 'single
)
203 ((equal extension
"tar") 'tar
)
204 (t (error "Unknown extension `%s'" extension
))))
205 (file-name (aref pkg-info
0))
206 (pkg-name (intern file-name
))
207 (requires (aref pkg-info
1))
208 (desc (if (string= (aref pkg-info
2) "")
209 (read-string "Description of package: ")
211 (pkg-version (aref pkg-info
3))
212 (commentary (aref pkg-info
4))
213 (split-version (version-to-list pkg-version
))
214 (pkg-buffer (current-buffer)))
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 (vector split-version requires desc file-type
)))
221 (if (> (car contents
) package-archive-version
)
222 (error "Unrecognized archive version %d" (car contents
)))
223 (let ((elt (assq pkg-name
(cdr contents
))))
225 (if (version-list-<= split-version
226 (package-desc-vers (cdr elt
)))
227 (error "New package has smaller version: %s" pkg-version
)
228 (setcdr elt new-desc
))
229 (setq contents
(cons (car contents
)
230 (cons (cons pkg-name new-desc
)
233 ;; Now CONTENTS is the updated archive contents. Upload
234 ;; this and the package itself. For now we assume ELPA is
235 ;; writable via file primitives.
236 (let ((print-level nil
)
238 (write-region (concat (pp-to-string contents
) "\n")
240 (expand-file-name "archive-contents"
241 package-archive-upload-base
)))
243 ;; If there is a commentary section, write it.
245 (write-region commentary nil
247 (concat (symbol-name pkg-name
) "-readme.txt")
248 package-archive-upload-base
)))
250 (set-buffer pkg-buffer
)
251 (write-region (point-min) (point-max)
253 (concat file-name
"-" pkg-version
"." extension
)
254 package-archive-upload-base
)
257 ;; Write a news entry.
258 (and package-update-news-on-upload
260 (package--update-news (concat file-name
"." extension
)
261 pkg-version desc archive-url
))
263 ;; special-case "package": write a second copy so that the
264 ;; installer can easily find the latest version.
265 (if (string= file-name
"package")
266 (write-region (point-min) (point-max)
268 (concat file-name
"." extension
)
269 package-archive-upload-base
)
270 nil nil nil
'ask
))))))))
272 (defun package-upload-buffer ()
273 "Upload the current buffer as a single-file Emacs Lisp package.
274 If `package-archive-upload-base' does not specify a valid upload
275 destination, prompt for one."
279 ;; Find the package in this buffer.
280 (let ((pkg-info (package-buffer-info)))
281 (package-upload-buffer-internal pkg-info
"el")))))
283 (defun package-upload-file (file)
284 "Upload the Emacs Lisp package FILE to the package archive.
285 Interactively, prompt for FILE. The package is considered a
286 single-file package if FILE ends in \".el\", and a multi-file
287 package if FILE ends in \".tar\".
288 If `package-archive-upload-base' does not specify a valid upload
289 destination, prompt for one."
290 (interactive "fPackage file name: ")
292 (insert-file-contents-literally file
)
294 ((string-match "\\.tar$" file
) (package-tar-file-info file
))
295 ((string-match "\\.el$" file
) (package-buffer-info))
296 (t (error "Unrecognized extension `%s'"
297 (file-name-extension file
))))))
298 (package-upload-buffer-internal info
(file-name-extension file
)))))
300 (defun package-gnus-summary-upload ()
301 "Upload a package contained in the current *Article* buffer.
302 This should be invoked from the gnus *Summary* buffer."
304 (with-current-buffer gnus-article-buffer
305 (package-upload-buffer)))
309 ;;; package-x.el ends here