Use external tool `w3m' for dumping html to plain texts.
[xwl-elisp.git] / generic-apt / generic-apt.el
blob0985a89b25910a1ad32df28664f06df0d674eb4d
1 ;;; generic-apt.el --- Generic apt alike interfaces for various package management tools
3 ;; Copyright (C) 2008 William Xu
5 ;; Author: William Xu <william.xwl@gmail.com>
6 ;; Version: 0.2a
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, or (at your option)
11 ;; any later version.
13 ;; This program is distributed in the hope that it will be useful, but
14 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 ;; General Public License for more details.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program; see the file COPYING. If not, write to the
20 ;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
21 ;; MA 02110-1301, USA.
23 ;;; Commentary:
25 ;; This extenstion tries to provide a generic apt(as used in Debian
26 ;; GNU/Linux) alike interface over various package management tools,
27 ;; such as: apt-get(Debian GNU/Linux), yum(redhat/fedora), emerge(Gentoo
28 ;; GNU/Linux), fink(Mac OS X), pkg-get(Solaris), etc.
30 ;; Put generic-apt files into your load-path first. Then add something similar
31 ;; to the following example to your .emacs. 192.168.1.20 is a remote debian
32 ;; machine, while localhost is a Mac OS X with fink installed.
34 ;; ;; Add this so that we can edit file on remote machine as root. Also
35 ;; ;; note that you should config your ssh agent not to prompt password
36 ;; ;; while logining the remote host.
38 ;; (eval-after-load 'tramp
39 ;; '(progn
40 ;; (add-to-list 'tramp-default-proxies-alist
41 ;; '("192.168.1.20" "\\`root\\'" "/ssh:%h:"))
42 ;; ))
44 ;; (require 'generic-apt-install)
45 ;; (setq generic-apt-select-methods
46 ;; '((apt-get "ssh 192.168.1.20 sudo apt-get")
47 ;; (fink "sudo fink")))
49 ;; Then type: `M-x generic-apt'.
51 ;;; Code:
53 (require 'tramp)
54 (eval-when-compile
55 (require 'cl))
57 ;;; Customizations
59 (defgroup generic-apt nil
60 "Generic apt alike interfaces for various package management tools."
61 :group 'generic-apt)
63 (defcustom generic-apt-mode-hook nil
64 "Normal hook run after entering `generic-apt-mode'."
65 :type 'hook
66 :group 'generic-apt)
68 ;; (defcustom generic-apt-source-download-dir "~/download"
69 ;; "Directory for saving source downloads."
70 ;; :type 'string
71 ;; :group 'generic-apt)
73 (defcustom generic-apt-cache-filename "~/.generic-apt-cache.el"
74 "Generic-Apt cache file."
75 :type 'string
76 :group 'generic-apt)
78 (defcustom generic-apt-select-methods '((apt-get "sudo apt-get")
79 (fink "sudo fink"))
80 "Package management tool lists.
81 Each element is the essential command prefix string. For
82 example, \"ssh foo sudo apt-get\". Then the command will execute
83 as: \"$ ssh foo sudo apt-get ...\""
84 :type 'list
85 :group 'generic-apt)
87 (defvar generic-apt-protocol nil)
88 (make-variable-buffer-local 'generic-apt-protocol)
90 (defvar generic-apt-command "")
91 (make-variable-buffer-local 'generic-apt-command)
93 (defvar generic-apt-buffer-name "")
94 (make-variable-buffer-local 'generic-apt-buffer-name)
96 (defvar generic-apt-available-pkgs '())
97 (make-variable-buffer-local 'generic-apt-available-pkgs)
99 (defvar generic-apt-font-lock-keywords nil
100 "Keywords to highlight in generic-apt mode.")
101 (make-variable-buffer-local 'generic-apt-font-lock-keywords)
103 (defvar generic-apt-sources-file ""
104 "Config file for the package management tool.")
105 (make-variable-buffer-local 'generic-apt-sources-file)
108 ;;; Generic-Apt Mode
110 (defvar generic-apt-mode-map
111 (let ((map (make-sparse-keymap)))
112 ;; Already defined for all protocols.
113 (define-key map "h" 'generic-apt-help)
114 (define-key map "I" 'generic-apt-install-at-point)
115 (define-key map " " 'generic-apt-show-at-point)
116 (define-key map "n" 'next-line)
117 (define-key map "p" 'previous-line)
118 (define-key map "K" 'generic-apt-kill)
119 (define-key map "E" 'generic-apt-edit-sources)
121 ;; RFC for each protocol.
122 (define-key map "u" 'generic-apt-update)
123 (define-key map "s" 'generic-apt-search-by-name)
124 (define-key map "S" 'generic-apt-search)
125 (define-key map "o" 'generic-apt-show)
126 (define-key map "i" 'generic-apt-install)
127 (define-key map "l" 'generic-apt-listfiles)
128 (define-key map "U" 'generic-apt-upgrade)
129 (define-key map "C" 'generic-apt-clean)
130 (define-key map "R" 'generic-apt-remove)
131 map)
132 "Keymap for `generic-apt-mode'.")
134 (defvar generic-apt-mode-syntax-table
135 (let ((st (make-syntax-table)))
136 (modify-syntax-entry ?- "w" st)
138 "Syntax table used while in `generic-apt-mode'.")
140 (define-derived-mode generic-apt-mode nil "Generic-Apt"
141 "Major mode for generic apt alike interfaces for various package management tools.
142 \\{generic-apt-mode-map}"
143 (set-syntax-table generic-apt-mode-syntax-table)
144 (setq font-lock-defaults '(generic-apt-font-lock-keywords))
145 (setq buffer-read-only t)
147 ;; Take special care with these two!
148 (setq generic-apt-command generic-apt-command)
149 (setq generic-apt-buffer-name generic-apt-buffer-name)
151 (setq generic-apt-protocol
152 (let ((methods generic-apt-select-methods)
153 (i nil)
154 (ret nil))
155 (while methods
156 (setq i (car methods)
157 methods (cdr methods))
158 (when (string= generic-apt-command (cadr i))
159 (setq ret (car i)
160 methods nil)))
161 ret))
163 (setq generic-apt-font-lock-keywords
164 (intern (format "generic-apt-%S-font-lock-keywords"
165 generic-apt-protocol)))
167 (setq generic-apt-sources-file
168 (eval
169 (intern (format "generic-apt-%S-sources-file"
170 generic-apt-protocol))))
172 ;; initial variables
173 (if (file-readable-p generic-apt-cache-filename)
174 (load-file generic-apt-cache-filename)
175 (generic-apt-update-cache))
177 (setq generic-apt-available-pkgs
178 (eval
179 (intern (format "generic-apt-%S-available-pkgs"
180 generic-apt-protocol))))
182 (run-hooks 'generic-apt-mode-hook)
183 (generic-apt-help))
185 ;;;###autoload
186 (defun generic-apt (&optional method)
187 "Create or switch to a generic-apt buffer."
188 (interactive)
189 ;; Wrap around them so that even when current buffer is another
190 ;; generic-apt buffer, we won't mess with its local variables.
191 (let* ((generic-apt-command
192 (or method
193 (ido-completing-read "generic-apt: "
194 (mapcar (lambda (i) (cadr i))
195 generic-apt-select-methods))))
196 (generic-apt-buffer-name
197 (format "*Generic-Apt/%s*" generic-apt-command)))
198 (switch-to-buffer generic-apt-buffer-name)
199 (unless (eq major-mode 'generic-apt-mode)
200 (generic-apt-mode))))
203 ;;; Interfaces
205 (defun generic-apt-help ()
206 "Help page for `generic-apt-mode'."
207 (interactive)
208 (let ((inhibit-read-only t))
209 (erase-buffer)
210 (insert
211 "Welcome to generic apt -- The apt-get with *super* power!
213 Here is a brief list of the most useful commamnds:
215 u - Selfupdate package database cache
216 s - Search packages by name
217 S - Search packages by content
218 o - Describe a package
219 i - Install a package
220 l - List installed files by a package
221 U - Upgrade a package
222 R - Remove a package
223 C - Cleanup
224 E - Edit config file
226 ------------- done --------------
228 (message "For a list of all available commands, press `F1 m'.")))
230 (defun generic-apt-edit-sources ()
231 "Edit /etc/apt/sources.list using sudo, with `tramp' when necessary."
232 (interactive)
233 (let ((f generic-apt-sources-file))
234 (if (string-match "^ssh" generic-apt-command)
235 (let ((hostname "")
236 (proxies tramp-default-proxies-alist)
237 (i '()))
238 (while proxies
239 (setq i (car proxies)
240 proxies (cdr proxies))
241 (when (string-match (regexp-opt (list (car i)))
242 generic-apt-command)
243 (setq hostname (car i)
244 f (format "/ssh:%s:%s" hostname f))
245 (setq proxies nil)))
246 (find-file f))
247 (find-file (concat "/sudo::" f)))))
249 (defun generic-apt-search (pkg)
250 "Search PKG by package name."
251 (interactive "sSearch: ")
252 (funcall (intern (format "generic-apt-%S-search" generic-apt-protocol))
253 pkg))
255 (defun generic-apt-search-by-name (pkg)
256 "Search PKG by package name, `-n'."
257 (interactive "sSearch(by name): ")
258 (funcall (intern (format "generic-apt-%S-search-by-name" generic-apt-protocol))
259 pkg))
261 (defun generic-apt-update ()
262 "Update package database cache."
263 (interactive)
264 (funcall (intern (format "generic-apt-%S-update" generic-apt-protocol))))
266 (defun generic-apt-install (pkg)
267 "Install PKG."
268 (interactive
269 (list
270 (ido-completing-read "Install: " generic-apt-available-pkgs)))
271 (funcall (intern (format "generic-apt-%S-install" generic-apt-protocol))
272 pkg))
274 (defun generic-apt-install-at-point ()
275 "Install package at point."
276 (interactive)
277 (funcall (intern (format "generic-apt-%s-install" generic-apt-protocol))
278 (current-word)))
280 (defun generic-apt-upgrade (pkg)
281 "Upgrade PKG."
282 (interactive
283 (list
284 (ido-completing-read "Upgrade: " generic-apt-available-pkgs)))
285 (funcall (intern (format "generic-apt-%S-upgrade" generic-apt-protocol)) pkg))
287 (defun generic-apt-remove (pkg)
288 "Remove PKG."
289 (interactive
290 (list
291 (ido-completing-read "Remove: " generic-apt-available-pkgs)))
292 (funcall (intern (format "generic-apt-%S-remove" generic-apt-protocol)) pkg))
294 (defun generic-apt-show (pkg)
295 "Describe PKG."
296 (interactive
297 (list
298 (ido-completing-read "Show: " generic-apt-available-pkgs)))
299 (funcall (intern (format "generic-apt-%S-show" generic-apt-protocol)) pkg))
301 (defun generic-apt-show-at-point ()
302 "Run `generic-apt show' on current word(pkg name)."
303 (interactive)
304 (funcall (intern (format "generic-apt-%s-show" generic-apt-protocol))
305 (current-word)))
307 (defun generic-apt-upgrade-all ()
308 "Upgrade all installed packages."
309 (interactive)
310 (funcall (intern (format "generic-apt-%S-upgrade-all" generic-apt-protocol))))
312 (defun generic-apt-listfiles (pkg)
313 "List files installed by PKG."
314 (interactive
315 (list
316 (ido-completing-read "Listfiles: " generic-apt-available-pkgs)))
317 (funcall (intern (format "generic-apt-%S-listfiles" generic-apt-protocol))
318 pkg))
320 (defun generic-apt-clean ()
321 "Clean cache."
322 (interactive)
323 (funcall (intern (format "generic-apt-%S-clean" generic-apt-protocol))))
326 ;;; Root Command, Buffer, Process Management
328 (defvar generic-apt-process nil)
329 (make-variable-buffer-local 'generic-apt-process)
331 (defvar generic-apt-running nil)
332 (make-variable-buffer-local 'generic-apt-running)
334 (defun generic-apt-update-cache ()
335 "Update generic-apt cache saved in `generic-apt-cache-filename'."
336 (interactive)
337 (message "Updating generic-apt cache...")
338 (funcall (intern (format "generic-apt-%S-update-available-pkgs"
339 generic-apt-protocol)))
340 (let ((protocol generic-apt-protocol)
341 (pkgs generic-apt-available-pkgs))
342 (with-temp-buffer
343 (if (and (not (string= generic-apt-cache-filename ""))
344 (file-readable-p generic-apt-cache-filename))
345 (insert-file-contents generic-apt-cache-filename)
346 (insert ";;; automatically generated by generic-apt, edit with care!!\n\n"))
347 (goto-char (point-min))
348 (let ((str-name (format "generic-apt-%S-available-pkgs" protocol)))
349 (if (re-search-forward (format "(setq %s" str-name) nil t 1)
350 (progn
351 (backward-up-list)
352 (kill-sexp))
353 (goto-char (point-max)))
354 (insert (format "(setq %s '%S)\n\n" str-name pkgs)))
355 (write-region (point-min) (point-max) generic-apt-cache-filename))
356 (message "Updating generic-apt cache...done")))
358 (defun generic-apt-process-sentinel (process event)
359 "Set buffer read-only after a generic-apt command finishes."
360 (with-current-buffer (process-buffer process)
361 (save-excursion
362 (setq generic-apt-running nil)
363 (let ((inhibit-read-only t))
364 (cond
365 ((eq (process-status process) 'exit)
366 (goto-char (point-max))
367 (insert "------------- done --------------\n"))
368 ((eq (process-status process) 'signal)
369 (message "generic-apt process killed")))))))
371 (defun generic-apt-process-filter (process output)
372 "Filter generic-apt command outputs."
373 (with-current-buffer (process-buffer process)
374 (let ((moving (= (point) (process-mark process)))
375 (inhibit-read-only t)
376 (percentage-match "[0-9]\\{1,3\\}%"))
377 (save-excursion
378 (goto-char (process-mark process))
379 (setq output
380 (replace-regexp-in-string "\r" "\n" output))
381 ;; make percentage output nicer
382 ;; (cond ((string-match percentage-match output)
383 ;; (message "generic-apt: %s" output))
384 ;; ((string-match "^\\ +$\\|^\n$" output)
385 ;; nil)
386 ;; (t
387 ;; (forward-line 0)
388 ;; ;; (insert output)))
389 (insert output)
390 (set-marker (process-mark process) (point)))
391 (and moving (goto-char (process-mark process))))))
393 (defun generic-apt-kill ()
394 "Kill generic-apt process."
395 (interactive)
396 (when generic-apt-process
397 (unless (eq (process-status generic-apt-process) 'exit)
398 (delete-process generic-apt-process))
399 (setq generic-apt-running nil)))
401 (defun generic-apt-run-command (args)
402 (generic-apt-run-1 (append (split-string generic-apt-command " ") args)))
404 (defun generic-apt-run-command-to-string (args-string)
405 (shell-command-to-string (concat generic-apt-command " " args-string)))
407 (defun generic-apt-run-other-command (other-cmd-and-args)
408 (generic-apt-run-1
409 (append (split-string (generic-apt-extract-prefix)) other-cmd-and-args)))
411 (defun generic-apt-run-other-command-to-string (other-cmd-and-args-string)
412 (shell-command-to-string
413 (concat (generic-apt-extract-prefix) " " other-cmd-and-args-string)))
415 (defun generic-apt-extract-prefix ()
416 "Extract prefix from `generic-apt-command'.
418 For instance, \"sudo fink\" => \"sudo\""
419 (replace-regexp-in-string " ?[^ ]+$" "" generic-apt-command))
421 (defun generic-apt-run-1 (full-command-and-args)
422 (let ((inhibit-read-only t))
423 (erase-buffer)
424 (if generic-apt-running
425 (error "Generic-Apt process already exists")
426 (setq generic-apt-running t)
427 (setq generic-apt-process
428 (apply 'start-process "generic-apt" generic-apt-buffer-name
429 full-command-and-args))
430 (set-process-filter generic-apt-process 'generic-apt-process-filter)
431 (set-process-sentinel generic-apt-process 'generic-apt-process-sentinel))))
434 ;;; Utilities
436 (defun generic-apt-info-unsupported ()
437 (message "Requested operation is not yet supported for `%S' backend"
438 generic-apt-protocol))
441 ;;; Compatibility
443 ;; (defalias 'generic-apt-completing-read
444 ;; (if (and (fboundp 'ido-completing-read)
445 ;; ido-mode)
446 ;; 'ido-completing-read ; added in Emacs 22
447 ;; 'completing-read))
450 (provide 'generic-apt)
452 ;;; generic-apt.el ends here