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