summaryrefslogtreecommitdiff
path: root/lisp/progmodes/executable.el
blob: 1706cff0f2312551ac997e5388c07644e32950aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
;;; executable.el --- base functionality for executable interpreter scripts

;; Copyright (C) 1994, 1995, 1996 by Free Software Foundation, Inc.

;; Author: Daniel.Pfeiffer@Informatik.START.dbp.de, fax (+49 69) 7588-2389
;; Keywords: languages, unix

;; This file is part of GNU Emacs.

;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; executable.el is used by certain major modes to insert a suitable
;; #! line at the beginning of the file, if the file does not already
;; have one.

;; This is support code for the likes of sh-, awk-, perl-, tcl- or
;; makefile-mode.  Those mode-setting commands can call the like of
;; `(executable-set-magic "sh")' or `(executable-set-magic "perl" "-f")'.
;; Unless the file name matches `executable-magicless-file-regexp' this will
;; search $PATH if the given interpreter isn't absolute, and then insert a
;; first line like `#! /bin/sh' or `#! /usr/local/bin/perl -f'.
;; Also it makes the file executable as soon as it's saved, if it wasn't.

;;; Code:

(defvar executable-insert 'not-modified
  "*What to do when newly found file has no or wrong magic number:
	nil	do nothing
	t	insert or update magic number
	other	insert or update magic number, but mark as unmodified.
When the insertion is marked as unmodified, you can save it with  \\[write-file] RET.
This variable is used when `executable-set-magic' is called as a function,
e.g. when Emacs sets some Un*x interpreter script mode.
With \\[executable-set-magic], this is always treated as if it were `t'.")


(defvar executable-query 'function
  "*If non-`nil', ask user before inserting or changing magic number.
When this is `function', only ask when called non-interactively.")


(defvar executable-magicless-file-regexp "/[Mm]akefile$\\|/\\.\\(z?profile\\|bash_profile\\|z?login\\|bash_login\\|z?logout\\|bash_logout\\|.+shrc\\|esrc\\|rcrc\\|[kz]shenv\\)$"
  "*On files with this kind of name no magic is inserted or changed.")


(defvar executable-prefix "#! "
  "*Interpreter magic number prefix inserted when there was no magic number.")



(defvar executable-chmod 73
  "*After saving, if the file is not executable, set this mode.
This mode passed to `set-file-modes' is taken absolutely when negative, or
relative to the files existing modes.  Do nothing if this is nil.
Typical values are 73 (+x) or -493 (rwxr-xr-x).")


(defvar executable-command nil)

(defvar executable-self-display "tail"
  "*Command you use with argument `+2' to make text files self-display.
Note that the like of `more' doesn't work too well under Emacs  \\[shell].")


(defvar executable-font-lock-keywords
  '(("\\`#!.*/\\([^ \t\n]+\\)" 1 font-lock-keyword-face t))
  "*Rules for highlighting executable scripts' magic number.
This can be included in `font-lock-keywords' by modes that call `executable'.")


(defvar executable-error-regexp-alist
  '(;; /bin/xyz: syntax error at line 14: `(' unexpected
    ;; /bin/xyz[5]: syntax error at line 8 : ``' unmatched
    ("^\\(.*[^[/]\\)\\(\\[[0-9]+\\]\\)?: .* error .* line \\([0-9]+\\)" 1 3)
    ;; /bin/xyz[27]: ehco:  not found
    ("^\\(.*[^/]\\)\\[\\([0-9]+\\)\\]: .*: " 1 2)
    ;; /bin/xyz: syntax error near unexpected token `)'
    ;; /bin/xyz: /bin/xyz: line 2: `)'
    ("^\\(.*[^/]\\): [^0-9\n]+\n\\1: \\1: line \\([0-9]+\\):" 1 2)
    ;; /usr/bin/awk: syntax error at line 5 of file /bin/xyz
    (" error .* line \\([0-9]+\\) of file \\(.+\\)$" 2 1)
    ;; /usr/bin/awk: calling undefined function toto
    ;;  input record number 3, file awktestdata
    ;;  source line 4 of file /bin/xyz
    ("^[^ ].+\n\\( .+\n\\)* line \\([0-9]+\\) of file \\(.+\\)$" 3 2)
    ;; makefile:1: *** target pattern contains no `%'.  Stop.
    ("^\\(.+\\):\\([0-9]+\\): " 1 2))
  "Alist of regexps used to match script errors.
See `compilation-error-regexp-alist'.")

;; The C function openp slightly modified would do the trick fine
(defun executable-find (command)
  "Search for COMMAND in $PATH and return the absolute file name.
Return nil if COMMAND is not found anywhere in $PATH."
  (let ((list exec-path)
	file)
    (while list
      (setq list (if (and (setq file (expand-file-name command (car list)))
			  (file-executable-p file)
			  (not (file-directory-p file)))
		     nil
		   (setq file nil)
		   (cdr list))))
    file))


(defun executable-chmod ()
  "This gets called after saving a file to assure that it be executable.
You can set the absolute or relative mode in variable `executable-chmod' for
non-executable files."
  (and executable-chmod
       buffer-file-name
       (or (file-executable-p buffer-file-name)
	   (set-file-modes buffer-file-name
			   (if (< executable-chmod 0)
			       (- executable-chmod)
			     (logior executable-chmod
				     (file-modes buffer-file-name)))))))


(defun executable-interpret (command)
  "Run script with user-specified args, and collect output in a buffer.
While script runs asynchronously, you can use the \\[next-error] command
to find the next error."
  (interactive (list (read-string "Run script: "
				  (or executable-command
				      buffer-file-name))))
  (require 'compile)
  (save-some-buffers (not compilation-ask-about-save))
  (make-local-variable 'executable-command)
  (compile-internal (setq executable-command command)
		    "No more errors." "Interpretation"
		    ;; Give it a simpler regexp to match.
		    nil executable-error-regexp-alist))



;;;###autoload
(defun executable-set-magic (interpreter &optional argument
					 no-query-flag insert-flag)
  "Set this buffer's interpreter to INTERPRETER with optional ARGUMENT.
The variables `executable-magicless-file-regexp', `executable-prefix',
`executable-insert', `executable-query' and `executable-chmod' control
when and how magic numbers are inserted or replaced and scripts made
executable."
  (interactive
   (let* ((name (read-string "Name or file name of interpreter: "))
	  (arg (read-string (format "Argument for %s: " name))))
     (list name arg (eq executable-query 'function) t)))
  (setq interpreter (if (file-name-absolute-p interpreter)
			interpreter
		      (or (executable-find interpreter)
			  (error "Interpreter %s not recognized" interpreter)))
	argument (concat interpreter
			 (and argument (string< "" argument) " ")
			 argument))
  (or buffer-read-only
      (if buffer-file-name
	  (string-match executable-magicless-file-regexp
			buffer-file-name))
      (not (or insert-flag executable-insert))
      (> (point-min) 1)
      (save-excursion
	(let ((point (point-marker))
	      (buffer-modified-p (buffer-modified-p)))
	  (goto-char (point-min))
	  (make-local-hook 'after-save-hook)
	  (add-hook 'after-save-hook 'executable-chmod nil t)
	  (if (looking-at "#![ \t]*\\(.*\\)$")
	      (and (goto-char (match-beginning 1))
		   (not (string= argument
				 (buffer-substring (point) (match-end 1))))
		   (or (not executable-query) no-query-flag
		       (save-window-excursion
			 ;; Make buffer visible before question.
			 (switch-to-buffer (current-buffer))
			 (y-or-n-p (concat "Replace magic number by `"
					   executable-prefix argument "'? "))))
		   (progn
		     (replace-match argument t t nil 1)
		     (message "Magic number changed to `%s'"
			      (concat executable-prefix argument))))
	    (insert executable-prefix argument ?\n)
	    (message "Magic number changed to `%s'"
		     (concat executable-prefix argument)))
	  (or insert-flag
	      (eq executable-insert t)
	      (set-buffer-modified-p buffer-modified-p)))))
  interpreter)



;;;###autoload
(defun executable-self-display ()
  "Turn a text file into a self-displaying Un*x command.
The magic number of such a command displays all lines but itself."
  (interactive)
  (if (eq this-command 'executable-self-display)
      (setq this-command 'executable-set-magic))
  (executable-set-magic executable-self-display "+2"))



(provide 'executable)

;; executable.el ends here