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>
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)
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.
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
40 ;; (add-to-list 'tramp-default-proxies-alist
41 ;; '("192.168.1.20" "\\`root\\'" "/ssh:%h:"))
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'.
59 (defgroup generic-apt nil
60 "Generic apt alike interfaces for various package management tools."
63 (defcustom generic-apt-mode-hook nil
64 "Normal hook run after entering `generic-apt-mode'."
68 ;; (defcustom generic-apt-source-download-dir "~/download"
69 ;; "Directory for saving source downloads."
71 ;; :group 'generic-apt)
73 (defcustom generic-apt-cache-filename
"~/.generic-apt-cache.el"
74 "Generic-Apt cache file."
78 (defcustom generic-apt-select-methods
'((apt-get "sudo apt-get")
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 ...\""
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
)
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
)
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
)
156 (setq i
(car methods
)
157 methods
(cdr methods
))
158 (when (string= generic-apt-command
(cadr i
))
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
169 (intern (format "generic-apt-%S-sources-file"
170 generic-apt-protocol
))))
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
179 (intern (format "generic-apt-%S-available-pkgs"
180 generic-apt-protocol
))))
182 (run-hooks 'generic-apt-mode-hook
)
186 (defun generic-apt (&optional method
)
187 "Create or switch to a generic-apt buffer."
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
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))))
205 (defun generic-apt-help ()
206 "Help page for `generic-apt-mode'."
208 (let ((inhibit-read-only t
))
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
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."
233 (let ((f generic-apt-sources-file
))
234 (if (string-match "^ssh" generic-apt-command
)
236 (proxies tramp-default-proxies-alist
)
239 (setq i
(car proxies
)
240 proxies
(cdr proxies
))
241 (when (string-match (regexp-opt (list (car i
)))
243 (setq hostname
(car i
)
244 f
(format "/ssh:%s:%s" hostname 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
))
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
))
261 (defun generic-apt-update ()
262 "Update package database cache."
264 (funcall (intern (format "generic-apt-%S-update" generic-apt-protocol
))))
266 (defun generic-apt-install (pkg)
270 (ido-completing-read "Install: " generic-apt-available-pkgs
)))
271 (funcall (intern (format "generic-apt-%S-install" generic-apt-protocol
))
274 (defun generic-apt-install-at-point ()
275 "Install package at point."
277 (funcall (intern (format "generic-apt-%s-install" generic-apt-protocol
))
280 (defun generic-apt-upgrade (pkg)
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)
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)
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)."
304 (funcall (intern (format "generic-apt-%s-show" generic-apt-protocol
))
307 (defun generic-apt-upgrade-all ()
308 "Upgrade all installed packages."
310 (funcall (intern (format "generic-apt-%S-upgrade-all" generic-apt-protocol
))))
312 (defun generic-apt-listfiles (pkg)
313 "List files installed by PKG."
316 (ido-completing-read "Listfiles: " generic-apt-available-pkgs
)))
317 (funcall (intern (format "generic-apt-%S-listfiles" generic-apt-protocol
))
320 (defun generic-apt-clean ()
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'."
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
))
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)
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
)
362 (setq generic-apt-running nil
)
363 (let ((inhibit-read-only t
))
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\\}%"))
378 (goto-char (process-mark process
))
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)
388 ;; ;; (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."
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)
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
))
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
))))
436 (defun generic-apt-info-unsupported ()
437 (message "Requested operation is not yet supported for `%S' backend"
438 generic-apt-protocol
))
443 ;; (defalias 'generic-apt-completing-read
444 ;; (if (and (fboundp 'ido-completing-read)
446 ;; 'ido-completing-read ; added in Emacs 22
447 ;; 'completing-read))
450 (provide 'generic-apt
)
452 ;;; generic-apt.el ends here