vc-hooks.el workaround for bug#11490
[emacs.git] / lisp / eshell / em-unix.el
blobd3ddab8af1bb3c518ae7276a34c4efe34056e9d8
1 ;;; em-unix.el --- UNIX command aliases
3 ;; Copyright (C) 1999-2012 Free Software Foundation, Inc.
5 ;; Author: John Wiegley <johnw@gnu.org>
7 ;; 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 of the License, or
12 ;; (at your option) any later version.
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22 ;;; Commentary:
24 ;; This file contains implementations of several UNIX command in Emacs
25 ;; Lisp, for several reasons:
27 ;; 1) it makes them available on all platforms where the Lisp
28 ;; functions used are available
30 ;; 2) it makes their functionality accessible and modified by the
31 ;; Lisp programmer.
33 ;; 3) it allows Eshell to refrain from having to invoke external
34 ;; processes for common operations.
36 ;;; Code:
38 (require 'eshell)
39 (require 'esh-opt)
40 (require 'pcomplete)
42 ;;;###autoload
43 (progn
44 (defgroup eshell-unix nil
45 "This module defines many of the more common UNIX utilities as
46 aliases implemented in Lisp. These include mv, ln, cp, rm, etc. If
47 the user passes arguments which are too complex, or are unrecognized
48 by the Lisp variant, the external version will be called (if
49 available). The only reason not to use them would be because they are
50 usually much slower. But in several cases their tight integration
51 with Eshell makes them more versatile than their traditional cousins
52 \(such as being able to use `kill' to kill Eshell background processes
53 by name)."
54 :tag "UNIX commands in Lisp"
55 :group 'eshell-module))
57 (defcustom eshell-unix-load-hook nil
58 "A list of functions to run when `eshell-unix' is loaded."
59 :version "24.1" ; removed eshell-unix-initialize
60 :type 'hook
61 :group 'eshell-unix)
63 (defcustom eshell-plain-grep-behavior nil
64 "If non-nil, standalone \"grep\" commands will behave normally.
65 Standalone in this context means not redirected, and not on the
66 receiving side of a command pipeline."
67 :type 'boolean
68 :group 'eshell-unix)
70 (defcustom eshell-no-grep-available (not (eshell-search-path "grep"))
71 "If non-nil, no grep is available on the current machine."
72 :type 'boolean
73 :group 'eshell-unix)
75 (defcustom eshell-plain-diff-behavior nil
76 "If non-nil, standalone \"diff\" commands will behave normally.
77 Standalone in this context means not redirected, and not on the
78 receiving side of a command pipeline."
79 :type 'boolean
80 :group 'eshell-unix)
82 (defcustom eshell-plain-locate-behavior (featurep 'xemacs)
83 "If non-nil, standalone \"locate\" commands will behave normally.
84 Standalone in this context means not redirected, and not on the
85 receiving side of a command pipeline."
86 :type 'boolean
87 :group 'eshell-unix)
89 (defcustom eshell-rm-removes-directories nil
90 "If non-nil, `rm' will remove directory entries.
91 Otherwise, `rmdir' is required."
92 :type 'boolean
93 :group 'eshell-unix)
95 (defcustom eshell-rm-interactive-query (= (user-uid) 0)
96 "If non-nil, `rm' will query before removing anything."
97 :type 'boolean
98 :group 'eshell-unix)
100 (defcustom eshell-mv-interactive-query (= (user-uid) 0)
101 "If non-nil, `mv' will query before overwriting anything."
102 :type 'boolean
103 :group 'eshell-unix)
105 (defcustom eshell-mv-overwrite-files t
106 "If non-nil, `mv' will overwrite files without warning."
107 :type 'boolean
108 :group 'eshell-unix)
110 (defcustom eshell-cp-interactive-query (= (user-uid) 0)
111 "If non-nil, `cp' will query before overwriting anything."
112 :type 'boolean
113 :group 'eshell-unix)
115 (defcustom eshell-cp-overwrite-files t
116 "If non-nil, `cp' will overwrite files without warning."
117 :type 'boolean
118 :group 'eshell-unix)
120 (defcustom eshell-ln-interactive-query (= (user-uid) 0)
121 "If non-nil, `ln' will query before overwriting anything."
122 :type 'boolean
123 :group 'eshell-unix)
125 (defcustom eshell-ln-overwrite-files nil
126 "If non-nil, `ln' will overwrite files without warning."
127 :type 'boolean
128 :group 'eshell-unix)
130 (defcustom eshell-default-target-is-dot nil
131 "If non-nil, the default destination for cp, mv or ln is `.'."
132 :type 'boolean
133 :group 'eshell-unix)
135 (defcustom eshell-du-prefer-over-ange nil
136 "Use Eshell's du in ange-ftp remote directories.
137 Otherwise, Emacs will attempt to use rsh to invoke du on the remote machine."
138 :type 'boolean
139 :group 'eshell-unix)
141 ;;; Functions:
143 (defun eshell-unix-initialize ()
144 "Initialize the UNIX support/emulation code."
145 (when (eshell-using-module 'eshell-cmpl)
146 (add-hook 'pcomplete-try-first-hook
147 'eshell-complete-host-reference nil t))
148 (make-local-variable 'eshell-complex-commands)
149 (setq eshell-complex-commands
150 (append '("grep" "egrep" "fgrep" "agrep" "glimpse" "locate"
151 "cat" "time" "cp" "mv" "make" "du" "diff" "su" "sudo")
152 eshell-complex-commands)))
154 (defalias 'eshell/date 'current-time-string)
155 (defalias 'eshell/basename 'file-name-nondirectory)
156 (defalias 'eshell/dirname 'file-name-directory)
158 (defvar em-interactive)
159 (defvar em-preview)
160 (defvar em-recursive)
161 (defvar em-verbose)
163 (defun eshell/man (&rest args)
164 "Invoke man, flattening the arguments appropriately."
165 (funcall 'man (apply 'eshell-flatten-and-stringify args)))
167 (put 'eshell/man 'eshell-no-numeric-conversions t)
169 (defun eshell/info (&rest args)
170 "Run the info command in-frame with the same behavior as command-line `info', ie:
171 'info' => goes to top info window
172 'info arg1' => IF arg1 is a file, then visits arg1
173 'info arg1' => OTHERWISE goes to top info window and then menu item arg1
174 'info arg1 arg2' => does action for arg1 (either visit-file or menu-item) and then menu item arg2
175 etc."
176 (eval-and-compile (require 'info))
177 (let ((file (cond
178 ((not (stringp (car args)))
179 nil)
180 ((file-exists-p (expand-file-name (car args)))
181 (expand-file-name (car args)))
182 ((file-exists-p (concat (expand-file-name (car args)) ".info"))
183 (concat (expand-file-name (car args)) ".info")))))
185 ;; If the first arg is a file, then go to that file's Top node
186 ;; Otherwise, go to the global directory
187 (if file
188 (progn
189 (setq args (cdr args))
190 (Info-find-node file "Top"))
191 (Info-directory))
193 ;; Treat all remaining args as menu references
194 (while args
195 (Info-menu (car args))
196 (setq args (cdr args)))))
198 (defun eshell-remove-entries (path files &optional top-level)
199 "From PATH, remove all of the given FILES, perhaps interactively."
200 (while files
201 (if (string-match "\\`\\.\\.?\\'"
202 (file-name-nondirectory (car files)))
203 (if top-level
204 (eshell-error "rm: cannot remove `.' or `..'\n"))
205 (if (and (file-directory-p (car files))
206 (not (file-symlink-p (car files))))
207 (progn
208 (if em-verbose
209 (eshell-printn (format "rm: removing directory `%s'"
210 (car files))))
211 (unless
212 (or em-preview
213 (and em-interactive
214 (not (y-or-n-p
215 (format "rm: remove directory `%s'? "
216 (car files))))))
217 (eshell-funcalln 'delete-directory (car files) t t)))
218 (if em-verbose
219 (eshell-printn (format "rm: removing file `%s'"
220 (car files))))
221 (unless (or em-preview
222 (and em-interactive
223 (not (y-or-n-p
224 (format "rm: remove `%s'? "
225 (car files))))))
226 (eshell-funcalln 'delete-file (car files) t))))
227 (setq files (cdr files))))
229 (defun eshell/rm (&rest args)
230 "Implementation of rm in Lisp.
231 This is implemented to call either `delete-file', `kill-buffer',
232 `kill-process', or `unintern', depending on the nature of the
233 argument."
234 (setq args (eshell-flatten-list args))
235 (eshell-eval-using-options
236 "rm" args
237 '((?h "help" nil nil "show this usage screen")
238 (?f "force" nil force-removal "force removal")
239 (?i "interactive" nil em-interactive "prompt before any removal")
240 (?n "preview" nil em-preview "don't change anything on disk")
241 (?r "recursive" nil em-recursive
242 "remove the contents of directories recursively")
243 (?R nil nil em-recursive "(same)")
244 (?v "verbose" nil em-verbose "explain what is being done")
245 :preserve-args
246 :external "rm"
247 :show-usage
248 :usage "[OPTION]... FILE...
249 Remove (unlink) the FILE(s).")
250 (unless em-interactive
251 (setq em-interactive eshell-rm-interactive-query))
252 (if (and force-removal em-interactive)
253 (setq em-interactive nil))
254 (while args
255 (let ((entry (if (stringp (car args))
256 (directory-file-name (car args))
257 (if (numberp (car args))
258 (number-to-string (car args))
259 (car args)))))
260 (cond
261 ((bufferp entry)
262 (if em-verbose
263 (eshell-printn (format "rm: removing buffer `%s'" entry)))
264 (unless (or em-preview
265 (and em-interactive
266 (not (y-or-n-p (format "rm: delete buffer `%s'? "
267 entry)))))
268 (eshell-funcalln 'kill-buffer entry)))
269 ((eshell-processp entry)
270 (if em-verbose
271 (eshell-printn (format "rm: killing process `%s'" entry)))
272 (unless (or em-preview
273 (and em-interactive
274 (not (y-or-n-p (format "rm: kill process `%s'? "
275 entry)))))
276 (eshell-funcalln 'kill-process entry)))
277 ((symbolp entry)
278 (if em-verbose
279 (eshell-printn (format "rm: uninterning symbol `%s'" entry)))
280 (unless
281 (or em-preview
282 (and em-interactive
283 (not (y-or-n-p (format "rm: unintern symbol `%s'? "
284 entry)))))
285 (eshell-funcalln 'unintern entry)))
286 ((stringp entry)
287 (if (and (file-directory-p entry)
288 (not (file-symlink-p entry)))
289 (if (or em-recursive
290 eshell-rm-removes-directories)
291 (if (or em-preview
292 (not em-interactive)
293 (y-or-n-p
294 (format "rm: descend into directory `%s'? "
295 entry)))
296 (eshell-remove-entries nil (list entry) t))
297 (eshell-error (format "rm: %s: is a directory\n" entry)))
298 (eshell-remove-entries nil (list entry) t)))))
299 (setq args (cdr args)))
300 nil))
302 (put 'eshell/rm 'eshell-no-numeric-conversions t)
304 (defun eshell/mkdir (&rest args)
305 "Implementation of mkdir in Lisp."
306 (eshell-eval-using-options
307 "mkdir" args
308 '((?h "help" nil nil "show this usage screen")
309 :external "mkdir"
310 :show-usage
311 :usage "[OPTION] DIRECTORY...
312 Create the DIRECTORY(ies), if they do not already exist.")
313 (while args
314 (eshell-funcalln 'make-directory (car args))
315 (setq args (cdr args)))
316 nil))
318 (put 'eshell/mkdir 'eshell-no-numeric-conversions t)
320 (defun eshell/rmdir (&rest args)
321 "Implementation of rmdir in Lisp."
322 (eshell-eval-using-options
323 "rmdir" args
324 '((?h "help" nil nil "show this usage screen")
325 :external "rmdir"
326 :show-usage
327 :usage "[OPTION] DIRECTORY...
328 Remove the DIRECTORY(ies), if they are empty.")
329 (while args
330 (eshell-funcalln 'delete-directory (car args))
331 (setq args (cdr args)))
332 nil))
334 (put 'eshell/rmdir 'eshell-no-numeric-conversions t)
336 (defvar no-dereference)
338 (defvar eshell-warn-dot-directories t)
340 (defun eshell-shuffle-files (command action files target func deep &rest args)
341 "Shuffle around some filesystem entries, using FUNC to do the work."
342 (let ((attr-target (eshell-file-attributes target))
343 (is-dir (or (file-directory-p target)
344 (and em-preview (not eshell-warn-dot-directories))))
345 attr)
346 (if (and (not em-preview) (not is-dir)
347 (> (length files) 1))
348 (error "%s: when %s multiple files, last argument must be a directory"
349 command action))
350 (while files
351 (setcar files (directory-file-name (car files)))
352 (cond
353 ((string-match "\\`\\.\\.?\\'"
354 (file-name-nondirectory (car files)))
355 (if eshell-warn-dot-directories
356 (eshell-error (format "%s: %s: omitting directory\n"
357 command (car files)))))
358 ((and attr-target
359 (or (not (eshell-under-windows-p))
360 (eq system-type 'ms-dos))
361 (setq attr (eshell-file-attributes (car files)))
362 (nth 10 attr-target) (nth 10 attr)
363 ;; Use equal, not -, since the inode and the device could
364 ;; cons cells.
365 (equal (nth 10 attr-target) (nth 10 attr))
366 (nth 11 attr-target) (nth 11 attr)
367 (equal (nth 11 attr-target) (nth 11 attr)))
368 (eshell-error (format "%s: `%s' and `%s' are the same file\n"
369 command (car files) target)))
371 (let ((source (car files))
372 (target (if is-dir
373 (expand-file-name
374 (file-name-nondirectory (car files)) target)
375 target))
376 link)
377 (if (and (file-directory-p source)
378 (or (not no-dereference)
379 (not (file-symlink-p source)))
380 (not (memq func '(make-symbolic-link
381 add-name-to-file))))
382 (if (and (eq func 'copy-file)
383 (not em-recursive))
384 (eshell-error (format "%s: %s: omitting directory\n"
385 command (car files)))
386 (let (eshell-warn-dot-directories)
387 (if (and (not deep)
388 (eq func 'rename-file)
389 ;; Use equal, since the device might be a
390 ;; cons cell.
391 (equal (nth 11 (eshell-file-attributes
392 (file-name-directory
393 (directory-file-name
394 (expand-file-name source)))))
395 (nth 11 (eshell-file-attributes
396 (file-name-directory
397 (directory-file-name
398 (expand-file-name target)))))))
399 (apply 'eshell-funcalln func source target args)
400 (unless (file-directory-p target)
401 (if em-verbose
402 (eshell-printn
403 (format "%s: making directory %s"
404 command target)))
405 (unless em-preview
406 (eshell-funcalln 'make-directory target)))
407 (apply 'eshell-shuffle-files
408 command action
409 (mapcar
410 (function
411 (lambda (file)
412 (concat source "/" file)))
413 (directory-files source))
414 target func t args)
415 (when (eq func 'rename-file)
416 (if em-verbose
417 (eshell-printn
418 (format "%s: deleting directory %s"
419 command source)))
420 (unless em-preview
421 (eshell-funcalln 'delete-directory source))))))
422 (if em-verbose
423 (eshell-printn (format "%s: %s -> %s" command
424 source target)))
425 (unless em-preview
426 (if (and no-dereference
427 (setq link (file-symlink-p source)))
428 (progn
429 (apply 'eshell-funcalln 'make-symbolic-link
430 link target args)
431 (if (eq func 'rename-file)
432 (if (and (file-directory-p source)
433 (not (file-symlink-p source)))
434 (eshell-funcalln 'delete-directory source)
435 (eshell-funcalln 'delete-file source))))
436 (apply 'eshell-funcalln func source target args)))))))
437 (setq files (cdr files)))))
439 (defun eshell-shorthand-tar-command (command args)
440 "Rewrite `cp -v dir a.tar.gz' to `tar cvzf a.tar.gz dir'."
441 (let* ((archive (car (last args)))
442 (tar-args
443 (cond ((string-match "z2" archive) "If")
444 ((string-match "gz" archive) "zf")
445 ((string-match "\\(az\\|Z\\)" archive) "Zf")
446 (t "f"))))
447 (if (file-exists-p archive)
448 (setq tar-args (concat "u" tar-args))
449 (setq tar-args (concat "c" tar-args)))
450 (if em-verbose
451 (setq tar-args (concat "v" tar-args)))
452 (if (equal command "mv")
453 (setq tar-args (concat "--remove-files -" tar-args)))
454 ;; truncate the archive name from the arguments
455 (setcdr (last args 2) nil)
456 (throw 'eshell-replace-command
457 (eshell-parse-command
458 (format "tar %s %s" tar-args archive) args))))
460 ;; this is to avoid duplicating code...
461 (defmacro eshell-mvcpln-template (command action func query-var
462 force-var &optional preserve)
463 `(let ((len (length args)))
464 (if (or (= len 0)
465 (and (= len 1) (null eshell-default-target-is-dot)))
466 (error "%s: missing destination file or directory" ,command))
467 (if (= len 1)
468 (nconc args '(".")))
469 (setq args (eshell-stringify-list (eshell-flatten-list args)))
470 (if (and ,(not (equal command "ln"))
471 (string-match eshell-tar-regexp (car (last args)))
472 (or (> (length args) 2)
473 (and (file-directory-p (car args))
474 (or (not no-dereference)
475 (not (file-symlink-p (car args)))))))
476 (eshell-shorthand-tar-command ,command args)
477 (let ((target (car (last args)))
478 ange-cache)
479 (setcdr (last args 2) nil)
480 (eshell-shuffle-files
481 ,command ,action args target ,func nil
482 ,@(append
483 `((if (and (or em-interactive
484 ,query-var)
485 (not force))
486 1 (or force ,force-var)))
487 (if preserve
488 (list preserve)))))
489 nil)))
491 (defun eshell/mv (&rest args)
492 "Implementation of mv in Lisp."
493 (eshell-eval-using-options
494 "mv" args
495 '((?f "force" nil force
496 "remove existing destinations, never prompt")
497 (?i "interactive" nil em-interactive
498 "request confirmation if target already exists")
499 (?n "preview" nil em-preview
500 "don't change anything on disk")
501 (?v "verbose" nil em-verbose
502 "explain what is being done")
503 (nil "help" nil nil "show this usage screen")
504 :preserve-args
505 :external "mv"
506 :show-usage
507 :usage "[OPTION]... SOURCE DEST
508 or: mv [OPTION]... SOURCE... DIRECTORY
509 Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
510 \[OPTION] DIRECTORY...")
511 (let ((no-dereference t))
512 (eshell-mvcpln-template "mv" "moving" 'rename-file
513 eshell-mv-interactive-query
514 eshell-mv-overwrite-files))))
516 (put 'eshell/mv 'eshell-no-numeric-conversions t)
518 (defun eshell/cp (&rest args)
519 "Implementation of cp in Lisp."
520 (eshell-eval-using-options
521 "cp" args
522 '((?a "archive" nil archive
523 "same as -dpR")
524 (?d "no-dereference" nil no-dereference
525 "preserve links")
526 (?f "force" nil force
527 "remove existing destinations, never prompt")
528 (?i "interactive" nil em-interactive
529 "request confirmation if target already exists")
530 (?n "preview" nil em-preview
531 "don't change anything on disk")
532 (?p "preserve" nil preserve
533 "preserve file attributes if possible")
534 (?R "recursive" nil em-recursive
535 "copy directories recursively")
536 (?v "verbose" nil em-verbose
537 "explain what is being done")
538 (nil "help" nil nil "show this usage screen")
539 :preserve-args
540 :external "cp"
541 :show-usage
542 :usage "[OPTION]... SOURCE DEST
543 or: cp [OPTION]... SOURCE... DIRECTORY
544 Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.")
545 (if archive
546 (setq preserve t no-dereference t em-recursive t))
547 (eshell-mvcpln-template "cp" "copying" 'copy-file
548 eshell-cp-interactive-query
549 eshell-cp-overwrite-files preserve)))
551 (put 'eshell/cp 'eshell-no-numeric-conversions t)
553 (defun eshell/ln (&rest args)
554 "Implementation of ln in Lisp."
555 (eshell-eval-using-options
556 "ln" args
557 '((?h "help" nil nil "show this usage screen")
558 (?s "symbolic" nil symbolic
559 "make symbolic links instead of hard links")
560 (?i "interactive" nil em-interactive
561 "request confirmation if target already exists")
562 (?f "force" nil force "remove existing destinations, never prompt")
563 (?n "preview" nil em-preview
564 "don't change anything on disk")
565 (?v "verbose" nil em-verbose "explain what is being done")
566 :preserve-args
567 :external "ln"
568 :show-usage
569 :usage "[OPTION]... TARGET [LINK_NAME]
570 or: ln [OPTION]... TARGET... DIRECTORY
571 Create a link to the specified TARGET with optional LINK_NAME. If there is
572 more than one TARGET, the last argument must be a directory; create links
573 in DIRECTORY to each TARGET. Create hard links by default, symbolic links
574 with '--symbolic'. When creating hard links, each TARGET must exist.")
575 (let ((no-dereference t))
576 (eshell-mvcpln-template "ln" "linking"
577 (if symbolic
578 'make-symbolic-link
579 'add-name-to-file)
580 eshell-ln-interactive-query
581 eshell-ln-overwrite-files))))
583 (put 'eshell/ln 'eshell-no-numeric-conversions t)
585 (defun eshell/cat (&rest args)
586 "Implementation of cat in Lisp.
587 If in a pipeline, or the file is not a regular file, directory or
588 symlink, then revert to the system's definition of cat."
589 (setq args (eshell-stringify-list (eshell-flatten-list args)))
590 (if (or eshell-in-pipeline-p
591 (catch 'special
592 (dolist (arg args)
593 (unless (or (and (stringp arg)
594 (> (length arg) 0)
595 (eq (aref arg 0) ?-))
596 (let ((attrs (eshell-file-attributes arg)))
597 (and attrs (memq (aref (nth 8 attrs) 0)
598 '(?d ?l ?-)))))
599 (throw 'special t)))))
600 (let ((ext-cat (eshell-search-path "cat")))
601 (if ext-cat
602 (throw 'eshell-replace-command
603 (eshell-parse-command (eshell-quote-argument ext-cat) args))
604 (if eshell-in-pipeline-p
605 (error "Eshell's `cat' does not work in pipelines")
606 (error "Eshell's `cat' cannot display one of the files given"))))
607 (eshell-init-print-buffer)
608 (eshell-eval-using-options
609 "cat" args
610 '((?h "help" nil nil "show this usage screen")
611 :external "cat"
612 :show-usage
613 :usage "[OPTION] FILE...
614 Concatenate FILE(s), or standard input, to standard output.")
615 (dolist (file args)
616 (if (string= file "-")
617 (throw 'eshell-external
618 (eshell-external-command "cat" args))))
619 (let ((curbuf (current-buffer)))
620 (dolist (file args)
621 (with-temp-buffer
622 (insert-file-contents file)
623 (goto-char (point-min))
624 (while (not (eobp))
625 (let ((str (buffer-substring
626 (point) (min (1+ (line-end-position))
627 (point-max)))))
628 (with-current-buffer curbuf
629 (eshell-buffered-print str)))
630 (forward-line)))))
631 (eshell-flush)
632 ;; if the file does not end in a newline, do not emit one
633 (setq eshell-ensure-newline-p nil))))
635 (put 'eshell/cat 'eshell-no-numeric-conversions t)
637 ;; special front-end functions for compilation-mode buffers
639 (defun eshell/make (&rest args)
640 "Use `compile' to do background makes."
641 (if (and eshell-current-subjob-p
642 (eshell-interactive-output-p))
643 (let ((compilation-process-setup-function
644 (list 'lambda nil
645 (list 'setq 'process-environment
646 (list 'quote (eshell-copy-environment))))))
647 (compile (concat "make " (eshell-flatten-and-stringify args))))
648 (throw 'eshell-replace-command
649 (eshell-parse-command "*make" (eshell-stringify-list
650 (eshell-flatten-list args))))))
652 (put 'eshell/make 'eshell-no-numeric-conversions t)
654 (defun eshell-occur-mode-goto-occurrence ()
655 "Go to the occurrence the current line describes."
656 (interactive)
657 (let ((pos (occur-mode-find-occurrence)))
658 (pop-to-buffer (marker-buffer pos))
659 (goto-char (marker-position pos))))
661 (defun eshell-occur-mode-mouse-goto (event)
662 "In Occur mode, go to the occurrence whose line you click on."
663 (interactive "e")
664 (let (pos)
665 (with-current-buffer (window-buffer (posn-window (event-end event)))
666 (save-excursion
667 (goto-char (posn-point (event-end event)))
668 (setq pos (occur-mode-find-occurrence))))
669 (pop-to-buffer (marker-buffer pos))
670 (goto-char (marker-position pos))))
672 (defun eshell-poor-mans-grep (args)
673 "A poor version of grep that opens every file and uses `occur'.
674 This eats up memory, since it leaves the buffers open (to speed future
675 searches), and it's very slow. But, if your system has no grep
676 available..."
677 (save-selected-window
678 (let ((default-dir default-directory))
679 (with-current-buffer (get-buffer-create "*grep*")
680 (let ((inhibit-read-only t)
681 (default-directory default-dir))
682 (erase-buffer)
683 (occur-mode)
684 (let ((files (eshell-stringify-list
685 (eshell-flatten-list (cdr args))))
686 (inhibit-redisplay t)
687 string)
688 (when (car args)
689 (if (get-buffer "*Occur*")
690 (kill-buffer (get-buffer "*Occur*")))
691 (setq string nil)
692 (while files
693 (with-current-buffer (find-file-noselect (car files))
694 (save-excursion
695 (ignore-errors
696 (occur (car args))))
697 (if (get-buffer "*Occur*")
698 (with-current-buffer (get-buffer "*Occur*")
699 (setq string (buffer-string))
700 (kill-buffer (current-buffer)))))
701 (if string (insert string))
702 (setq string nil
703 files (cdr files)))))
704 (local-set-key [mouse-2] 'eshell-occur-mode-mouse-goto)
705 (local-set-key [(control ?c) (control ?c)]
706 'eshell-occur-mode-goto-occurrence)
707 (local-set-key [(control ?m)]
708 'eshell-occur-mode-goto-occurrence)
709 (local-set-key [return] 'eshell-occur-mode-goto-occurrence)
710 (pop-to-buffer (current-buffer) t)
711 (goto-char (point-min))
712 (resize-temp-buffer-window))))))
714 (defun eshell-grep (command args &optional maybe-use-occur)
715 "Generic service function for the various grep aliases.
716 It calls Emacs's grep utility if the command is not redirecting output,
717 and if it's not part of a command pipeline. Otherwise, it calls the
718 external command."
719 (if (and maybe-use-occur eshell-no-grep-available)
720 (eshell-poor-mans-grep args)
721 (if (or eshell-plain-grep-behavior
722 (not (and (eshell-interactive-output-p)
723 (not eshell-in-pipeline-p)
724 (not eshell-in-subcommand-p))))
725 (throw 'eshell-replace-command
726 (eshell-parse-command (concat "*" command)
727 (eshell-stringify-list
728 (eshell-flatten-list args))))
729 (let* ((args (mapconcat 'identity
730 (mapcar 'shell-quote-argument
731 (eshell-stringify-list
732 (eshell-flatten-list args)))
733 " "))
734 (cmd (progn
735 (set-text-properties 0 (length args)
736 '(invisible t) args)
737 (format "%s -n %s" command args)))
738 compilation-scroll-output)
739 (grep cmd)))))
741 (defun eshell/grep (&rest args)
742 "Use Emacs grep facility instead of calling external grep."
743 (eshell-grep "grep" args t))
745 (defun eshell/egrep (&rest args)
746 "Use Emacs grep facility instead of calling external egrep."
747 (eshell-grep "egrep" args t))
749 (defun eshell/fgrep (&rest args)
750 "Use Emacs grep facility instead of calling external fgrep."
751 (eshell-grep "fgrep" args t))
753 (defun eshell/agrep (&rest args)
754 "Use Emacs grep facility instead of calling external agrep."
755 (eshell-grep "agrep" args))
757 (defun eshell/glimpse (&rest args)
758 "Use Emacs grep facility instead of calling external glimpse."
759 (let (null-device)
760 (eshell-grep "glimpse" (append '("-z" "-y") args))))
762 ;; completions rules for some common UNIX commands
764 (defsubst eshell-complete-hostname ()
765 "Complete a command that wants a hostname for an argument."
766 (pcomplete-here (eshell-read-host-names)))
768 (defun eshell-complete-host-reference ()
769 "If there is a host reference, complete it."
770 (let ((arg (pcomplete-actual-arg))
771 index)
772 (when (setq index (string-match "@[a-z.]*\\'" arg))
773 (setq pcomplete-stub (substring arg (1+ index))
774 pcomplete-last-completion-raw t)
775 (throw 'pcomplete-completions (eshell-read-host-names)))))
777 (defalias 'pcomplete/ftp 'eshell-complete-hostname)
778 (defalias 'pcomplete/ncftp 'eshell-complete-hostname)
779 (defalias 'pcomplete/ping 'eshell-complete-hostname)
780 (defalias 'pcomplete/rlogin 'eshell-complete-hostname)
782 (defun pcomplete/telnet ()
783 (require 'pcmpl-unix)
784 (pcomplete-opt "xl(pcmpl-unix-user-names)")
785 (eshell-complete-hostname))
787 (defun pcomplete/rsh ()
788 "Complete `rsh', which, after the user and hostname, is like xargs."
789 (require 'pcmpl-unix)
790 (pcomplete-opt "l(pcmpl-unix-user-names)")
791 (eshell-complete-hostname)
792 (pcomplete-here (funcall pcomplete-command-completion-function))
793 (funcall (or (pcomplete-find-completion-function (pcomplete-arg 1))
794 pcomplete-default-completion-function)))
796 (defvar block-size)
797 (defvar by-bytes)
798 (defvar dereference-links)
799 (defvar grand-total)
800 (defvar human-readable)
801 (defvar max-depth)
802 (defvar only-one-filesystem)
803 (defvar show-all)
805 (defsubst eshell-du-size-string (size)
806 (let* ((str (eshell-printable-size size human-readable block-size t))
807 (len (length str)))
808 (concat str (if (< len 8)
809 (make-string (- 8 len) ? )))))
811 (defun eshell-du-sum-directory (path depth)
812 "Summarize PATH, and its member directories."
813 (let ((entries (eshell-directory-files-and-attributes path))
814 (size 0.0))
815 (while entries
816 (unless (string-match "\\`\\.\\.?\\'" (caar entries))
817 (let* ((entry (concat path "/"
818 (caar entries)))
819 (symlink (and (stringp (cadr (car entries)))
820 (cadr (car entries)))))
821 (unless (or (and symlink (not dereference-links))
822 (and only-one-filesystem
823 (/= only-one-filesystem
824 (nth 12 (car entries)))))
825 (if symlink
826 (setq entry symlink))
827 (setq size
828 (+ size
829 (if (eq t (cadr (car entries)))
830 (eshell-du-sum-directory entry (1+ depth))
831 (let ((file-size (nth 8 (car entries))))
832 (prog1
833 file-size
834 (if show-all
835 (eshell-print
836 (concat (eshell-du-size-string file-size)
837 entry "\n")))))))))))
838 (setq entries (cdr entries)))
839 (if (or (not max-depth)
840 (= depth max-depth)
841 (= depth 0))
842 (eshell-print (concat (eshell-du-size-string size)
843 (directory-file-name path) "\n")))
844 size))
846 (defun eshell/du (&rest args)
847 "Implementation of \"du\" in Lisp, passing ARGS."
848 (setq args (if args
849 (eshell-stringify-list (eshell-flatten-list args))
850 '(".")))
851 (let ((ext-du (eshell-search-path "du")))
852 (if (and ext-du
853 (not (catch 'have-ange-path
854 (dolist (arg args)
855 (if (string-equal
856 (file-remote-p (expand-file-name arg) 'method) "ftp")
857 (throw 'have-ange-path t))))))
858 (throw 'eshell-replace-command
859 (eshell-parse-command (eshell-quote-argument ext-du) args))
860 (eshell-eval-using-options
861 "du" args
862 '((?a "all" nil show-all
863 "write counts for all files, not just directories")
864 (nil "block-size" t block-size
865 "use SIZE-byte blocks (i.e., --block-size SIZE)")
866 (?b "bytes" nil by-bytes
867 "print size in bytes")
868 (?c "total" nil grand-total
869 "produce a grand total")
870 (?d "max-depth" t max-depth
871 "display data only this many levels of data")
872 (?h "human-readable" 1024 human-readable
873 "print sizes in human readable format")
874 (?H "is" 1000 human-readable
875 "likewise, but use powers of 1000 not 1024")
876 (?k "kilobytes" 1024 block-size
877 "like --block-size 1024")
878 (?L "dereference" nil dereference-links
879 "dereference all symbolic links")
880 (?m "megabytes" 1048576 block-size
881 "like --block-size 1048576")
882 (?s "summarize" 0 max-depth
883 "display only a total for each argument")
884 (?x "one-file-system" nil only-one-filesystem
885 "skip directories on different filesystems")
886 (nil "help" nil nil
887 "show this usage screen")
888 :external "du"
889 :usage "[OPTION]... FILE...
890 Summarize disk usage of each FILE, recursively for directories.")
891 (unless by-bytes
892 (setq block-size (or block-size 1024)))
893 (if (and max-depth (stringp max-depth))
894 (setq max-depth (string-to-number max-depth)))
895 ;; filesystem support means nothing under Windows
896 (if (eshell-under-windows-p)
897 (setq only-one-filesystem nil))
898 (let ((size 0.0) ange-cache)
899 (while args
900 (if only-one-filesystem
901 (setq only-one-filesystem
902 (nth 11 (eshell-file-attributes
903 (file-name-as-directory (car args))))))
904 (setq size (+ size (eshell-du-sum-directory
905 (directory-file-name (car args)) 0)))
906 (setq args (cdr args)))
907 (if grand-total
908 (eshell-print (concat (eshell-du-size-string size)
909 "total\n"))))))))
911 (defvar eshell-time-start nil)
913 (defun eshell-show-elapsed-time ()
914 (let ((elapsed (format "%.3f secs\n" (- (float-time) eshell-time-start))))
915 (set-text-properties 0 (length elapsed) '(face bold) elapsed)
916 (eshell-interactive-print elapsed))
917 (remove-hook 'eshell-post-command-hook 'eshell-show-elapsed-time t))
919 (defun eshell/time (&rest args)
920 "Implementation of \"time\" in Lisp."
921 (let ((time-args (copy-alist args))
922 (continue t)
923 last-arg)
924 (while (and continue args)
925 (if (not (string-match "^-" (car args)))
926 (progn
927 (if last-arg
928 (setcdr last-arg nil)
929 (setq args '("")))
930 (setq continue nil))
931 (setq last-arg args
932 args (cdr args))))
933 (eshell-eval-using-options
934 "time" args
935 '((?h "help" nil nil "show this usage screen")
936 :external "time"
937 :show-usage
938 :usage "COMMAND...
939 Show wall-clock time elapsed during execution of COMMAND.")
940 (setq eshell-time-start (float-time))
941 (add-hook 'eshell-post-command-hook 'eshell-show-elapsed-time nil t)
942 ;; after setting
943 (throw 'eshell-replace-command
944 (eshell-parse-command (car time-args)
945 ;;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2007-08/msg00205.html
946 (eshell-stringify-list
947 (eshell-flatten-list (cdr time-args))))))))
949 (defun eshell/whoami (&rest args)
950 "Make \"whoami\" Tramp aware."
951 (or (file-remote-p default-directory 'user) (user-login-name)))
953 (defvar eshell-diff-window-config nil)
955 (defun eshell-diff-quit ()
956 "Restore the window configuration previous to diff'ing."
957 (interactive)
958 (if eshell-diff-window-config
959 (set-window-configuration eshell-diff-window-config)))
961 (defun nil-blank-string (string)
962 "Return STRING, or nil if STRING contains only non-blank characters."
963 (cond
964 ((string-match "[^[:blank:]]" string) string)
965 (nil)))
967 (autoload 'diff-no-select "diff")
969 (defun eshell/diff (&rest args)
970 "Alias \"diff\" to call Emacs `diff' function."
971 (let ((orig-args (eshell-stringify-list (eshell-flatten-list args))))
972 (if (or eshell-plain-diff-behavior
973 (not (and (eshell-interactive-output-p)
974 (not eshell-in-pipeline-p)
975 (not eshell-in-subcommand-p))))
976 (throw 'eshell-replace-command
977 (eshell-parse-command "*diff" orig-args))
978 (setq args (copy-sequence orig-args))
979 (if (< (length args) 2)
980 (throw 'eshell-replace-command
981 (eshell-parse-command "*diff" orig-args)))
982 (let ((old (car (last args 2)))
983 (new (car (last args)))
984 (config (current-window-configuration)))
985 (if (= (length args) 2)
986 (setq args nil)
987 (setcdr (last args 3) nil))
988 (with-current-buffer
989 (condition-case err
990 (diff-no-select
991 old new
992 (nil-blank-string (eshell-flatten-and-stringify args)))
993 (error
994 (throw 'eshell-replace-command
995 (eshell-parse-command "*diff" orig-args))))
996 (when (fboundp 'diff-mode)
997 (make-local-variable 'compilation-finish-functions)
998 (add-hook
999 'compilation-finish-functions
1000 `(lambda (buff msg)
1001 (with-current-buffer buff
1002 (diff-mode)
1003 (set (make-local-variable 'eshell-diff-window-config)
1004 ,config)
1005 (local-set-key [?q] 'eshell-diff-quit)
1006 (if (fboundp 'turn-on-font-lock-if-enabled)
1007 (turn-on-font-lock-if-enabled))
1008 (goto-char (point-min))))))
1009 (pop-to-buffer (current-buffer))))))
1010 nil)
1012 (put 'eshell/diff 'eshell-no-numeric-conversions t)
1014 (defun eshell/locate (&rest args)
1015 "Alias \"locate\" to call Emacs `locate' function."
1016 (if (or eshell-plain-locate-behavior
1017 (not (and (eshell-interactive-output-p)
1018 (not eshell-in-pipeline-p)
1019 (not eshell-in-subcommand-p)))
1020 (and (stringp (car args))
1021 (string-match "^-" (car args))))
1022 (throw 'eshell-replace-command
1023 (eshell-parse-command "*locate" (eshell-stringify-list
1024 (eshell-flatten-list args))))
1025 (save-selected-window
1026 (let ((locate-history-list (list (car args))))
1027 (locate-with-filter (car args) (cadr args))))))
1029 (put 'eshell/locate 'eshell-no-numeric-conversions t)
1031 (defun eshell/occur (&rest args)
1032 "Alias \"occur\" to call Emacs `occur' function."
1033 (let ((inhibit-read-only t))
1034 (if (> (length args) 2)
1035 (error "usage: occur: (REGEXP &optional NLINES)")
1036 (apply 'occur args))))
1038 (put 'eshell/occur 'eshell-no-numeric-conversions t)
1040 (defun eshell/su (&rest args)
1041 "Alias \"su\" to call Tramp."
1042 (setq args (eshell-stringify-list (eshell-flatten-list args)))
1043 (let ((orig-args (copy-tree args)))
1044 (eshell-eval-using-options
1045 "su" args
1046 '((?h "help" nil nil "show this usage screen")
1047 (?l "login" nil login "provide a login environment")
1048 (? nil nil login "provide a login environment")
1049 :usage "[- | -l | --login] [USER]
1050 Become another USER during a login session.")
1051 (throw 'eshell-replace-command
1052 (let ((user "root")
1053 (host (or (file-remote-p default-directory 'host)
1054 "localhost"))
1055 (dir (or (file-remote-p default-directory 'localname)
1056 (expand-file-name default-directory)))
1057 (prefix (file-remote-p default-directory)))
1058 (dolist (arg args)
1059 (if (string-equal arg "-") (setq login t) (setq user arg)))
1060 ;; `eshell-eval-using-options' does not handle "-".
1061 (if (member "-" orig-args) (setq login t))
1062 (if login (setq dir "~/"))
1063 (if (and prefix
1065 (not (string-equal
1066 "su" (file-remote-p default-directory 'method)))
1067 (not (string-equal
1068 user (file-remote-p default-directory 'user)))))
1069 (eshell-parse-command
1070 "cd" (list (format "%s|su:%s@%s:%s"
1071 (substring prefix 0 -1) user host dir)))
1072 (eshell-parse-command
1073 "cd" (list (format "/su:%s@%s:%s" user host dir)))))))))
1075 (put 'eshell/su 'eshell-no-numeric-conversions t)
1077 (defun eshell/sudo (&rest args)
1078 "Alias \"sudo\" to call Tramp."
1079 (setq args (eshell-stringify-list (eshell-flatten-list args)))
1080 (let ((orig-args (copy-tree args)))
1081 (eshell-eval-using-options
1082 "sudo" args
1083 '((?h "help" nil nil "show this usage screen")
1084 (?u "user" t user "execute a command as another USER")
1085 :show-usage
1086 :usage "[(-u | --user) USER] COMMAND
1087 Execute a COMMAND as the superuser or another USER.")
1088 (throw 'eshell-external
1089 (let ((user (or user "root"))
1090 (host (or (file-remote-p default-directory 'host)
1091 "localhost"))
1092 (dir (or (file-remote-p default-directory 'localname)
1093 (expand-file-name default-directory)))
1094 (prefix (file-remote-p default-directory)))
1095 ;; `eshell-eval-using-options' reads options of COMMAND.
1096 (while (and (stringp (car orig-args))
1097 (member (car orig-args) '("-u" "--user")))
1098 (setq orig-args (cddr orig-args)))
1099 (let ((default-directory
1100 (if (and prefix
1102 (not
1103 (string-equal
1104 "sudo"
1105 (file-remote-p default-directory 'method)))
1106 (not
1107 (string-equal
1108 user
1109 (file-remote-p default-directory 'user)))))
1110 (format "%s|sudo:%s@%s:%s"
1111 (substring prefix 0 -1) user host dir)
1112 (format "/sudo:%s@%s:%s" user host dir))))
1113 ;; Ensure, that Tramp has connected to that construct already.
1114 (ignore (file-exists-p default-directory))
1115 (eshell-named-command (car orig-args) (cdr orig-args))))))))
1117 (put 'eshell/sudo 'eshell-no-numeric-conversions t)
1119 (provide 'em-unix)
1121 ;; Local Variables:
1122 ;; generated-autoload-file: "esh-groups.el"
1123 ;; End:
1125 ;;; em-unix.el ends here