-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathffap-makefile-vars.el
233 lines (193 loc) · 8.64 KB
/
ffap-makefile-vars.el
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
222
223
224
225
226
227
228
229
230
231
232
233
;;; ffap-makefile-vars.el --- find file with makefile variables expanded
;; Copyright 2009, 2010, 2015, 2016, 2019 Kevin Ryde
;; Author: Kevin Ryde <user42_kevin@yahoo.com.au>
;; Version: 6
;; Keywords: files, ffap, make
;; URL: http://user42.tuxfamily.org/ffap-makefile-vars/index.html
;; EmacsWiki: FindFileAtPoint
;; ffap-makefile-vars.el 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 3, or (at your option)
;; any later version.
;;
;; ffap-makefile-vars.el 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 can get a copy of the GNU General Public License online at
;; <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This spot of code lets M-x ffap expand makefile macros $(FOO) in a
;; filename, like
;;
;; PREFIX = /usr
;;
;; $(PREFIX)/share/foo
;;
;; With point on the "$(PREFIX)/share/foo" an M-x ffap expands to offer
;; "/usr/share/foo". This is good for constructed filenames in makefiles.
;;
;; Macros are expanded from the definitions in the file and also from
;; `process-environment' like "make" does. There's no support for the
;; various smart expansions GNU make can do though.
;;; Install:
;; Put ffap-makefile-vars.el in one of your `load-path' directories and the
;; following in your .emacs
;;
;; (eval-after-load "ffap" '(require 'ffap-makefile-vars))
;;
;; There's an autoload cookie below for this, if you know how to use
;; `update-file-autoloads' and friends.
;;; History:
;;
;; Version 1 - the first version
;; Version 2 - cooperate better with other advice on ffap-string-at-point
;; Version 3 - undo defadvice on unload-feature
;; Version 4 - express dependency on 'advice
;; Version 5 - new email
;; Version 6 - use push for local variables
;;; Code:
;;;###autoload (eval-after-load "ffap" '(require 'ffap-makefile-vars))
;; Explicit dependency on advice.el since
;; `ffap-makefile-vars-unload-function' needs `ad-find-advice' macro when
;; running not byte compiled, and that macro is not autoloaded.
(require 'advice)
(eval-when-compile
(unless (fboundp 'push)
(require 'cl))) ;; for macros in emacs20
;;----------------------------------------------------------------------------
;; `replace-regexp-in-string' compatibility
;; [same in man-completion.el]
;;
(eval-and-compile ;; quieten emacs byte compiler
;; no `eval-when-compile' on this fboundp because in xemacs 21.4.22
;; easy-mmode.el (which is define-minor-mode etc) rudely defines a
;; replace-regexp-in-string, so a compile-time test is unreliable
(if (fboundp 'replace-regexp-in-string)
;; emacs (21 up)
(defalias 'ffap-makefile-vars--replace-regexp-in-string
'replace-regexp-in-string)
;; xemacs21
(defun ffap-makefile-vars--replace-regexp-in-string
(regexp rep string fixedcase literal)
"`replace-regexp-in-string' made available in xemacs.
The FIXEDCASE argument is ignored, case is always fixed."
(replace-in-string string regexp rep literal))))
;;----------------------------------------------------------------------------
(defun ffap-makefile-vars-find (name)
"Return the value of makefile macro/variable NAME.
A definition like \"FOO = xyz\" is sought in the current buffer.
If there's none the return is nil. If there's more than one
definition the last is used, the same as \"make\" does.
Backslashed newlines for multi-line values are recognised. Those
backslashes and newlines are collapsed out of the return, the
same as \"make\" does when it uses the value."
;; The first part of the regexp doesn't match a preceding backslashed
;; newline, ie. it's a non-backslash and a newline, or start of buffer and
;; a newline, or just start of buffer. This protects against a "NAME="
;; within some unrelated multi-line value etc. The rest is similar to a
;; comment with `makefile-macroassign-regex' in the way it matches
;; backslashes for multi-line values.
;;
(save-excursion
(goto-char (point-max))
(and (let ((case-fold-search nil))
(re-search-backward
(concat "\\(?:\\(?:[^\\]\\|\\`\\)\n\\|\\`\\) *"
(regexp-quote name)
"[ \t*:+!?]*=[ \t]*\\(\\(.*\\\\\n\\)*.*\\)")
nil t))
(ffap-makefile-vars--replace-regexp-in-string
"\\\\\n" "" (match-string 1) t t)))) ;; fixedcase and literal
(defun ffap-makefile-vars-substitute (str)
"Return STR with makefile vars like $(FOO) substituted to their values.
Variable definitions are sought in the current buffer or in
`process-environment' and definitions are expanded recursively.
Variable definitions in the buffer are preferred over environment
values if both exist. The \"make -e\" option reverses that, but
there's no equivalent here yet.
Values from the environment are treated the same as from the
buffer, so further $(BAR) forms are expanded recursively in them
too. This is what \"make\" does, though it'd be unusual to have
$(BAR) forms in an environment variable.
Circular definitions like \"FOO=$(FOO)\" are noticed and
currently are left unexpanded. Perhaps that will change (an
error, a warning, a special return ...).
Single-letter forms $X are not expanded, since for ffap they're
more likely to be a shell style variable name, and are a bit
unusual in makefiles anyway. Perhaps this will change, but for
now write $(X) instead.
See `substitute-in-file-name' for similar expansion of shell
style environment variables."
(let ((case-fold-search nil)
(in-progress (list (cons nil str)))
var-value
seen
name elem)
(while str
(let ((start 0))
(while (string-match "\
\\(?:\\`\\|[^$]\\)\
\\(?:\\$\\$\\)*\
\\(\\$(\\([A-Za-z_][A-Za-z_0-9]*\\))\\)" str start)
(setq name (match-string 2 str))
(if (assoc name in-progress)
(push name seen))
(if (member name seen)
;; circular definition, skip
(setq start (match-end 1))
(if (setq elem (assoc name var-value))
;; have value for NAME
(setq str (concat (substring str 0 (match-beginning 1))
(cdr elem)
(substring str (match-end 0))))
;; find NAME and recursively expand
(push (cons name str) in-progress)
(setq str (or (ffap-makefile-vars-find name)
(getenv name)
"")))
(setq start 0))))
(push (cons (caar in-progress) str) var-value)
(setq str (cdr (pop in-progress))))
(ffap-makefile-vars--replace-regexp-in-string
"\\$\\$" "$" (cdar var-value) t t)))
;; This hack is applied to `ffap-string-at-point' instead of
;; `ffap-file-at-point' because the default `file' chars in
;; `ffap-string-at-point-mode-alist' don't include ( or ) so for instance on
;; "$(HOME)/foo.txt" it otherwise gives just "$".
;;
;; Run ad-do-it with "()" added, and check that the only "(" chars are "$(".
;; If there's any which are not "$(" then go a plain ad-do-it instead. In
;; either case `ffap-makefile-vars-substitute' is applied to the return.
;;
(defadvice ffap-string-at-point (around ffap-makefile-vars activate)
"Expand makefile forms $(FOO) in filenames."
(let ((ffap-string-at-point-mode-alist
(let ((elem (assq 'file ffap-string-at-point-mode-alist)))
(cons (cons (car elem)
(cons (concat (cadr elem) "()/")
(cddr elem)))
ffap-string-at-point-mode-alist))))
ad-do-it)
(if ad-return-value
;; "(" other than "$(" is no good
;; ")" without "(" is no good
(if (or (string-match "\\(\\`\\|[^$]\\)(" ad-return-value)
(string-match "\\`[^(]*)" ad-return-value))
ad-do-it))
(if ad-return-value
(setq ad-return-value
(setq ffap-string-at-point
(ffap-makefile-vars-substitute ad-return-value)))))
(defun ffap-makefile-vars-unload-function ()
"Remove defadvice from function `ffap-string-at-point'.
This is called by `unload-feature'."
(when (ad-find-advice 'ffap-string-at-point 'around 'ffap-makefile-vars)
(ad-remove-advice 'ffap-string-at-point 'around 'ffap-makefile-vars)
(ad-activate 'ffap-string-at-point))
nil) ;; and do normal unload-feature actions too
;; LocalWords: makefile makefiles usr filenames xyz Backslashed unexpanded
;; LocalWords: foo vars
(provide 'ffap-makefile-vars)
;;; ffap-makefile-vars.el ends here