Commit | Line | Data |
---|---|---|
5467ec88 DL |
1 | ;;; cfengine.el --- mode for editing Cfengine files |
2 | ||
ab422c4d | 3 | ;; Copyright (C) 2001-2013 Free Software Foundation, Inc. |
5467ec88 DL |
4 | |
5 | ;; Author: Dave Love <fx@gnu.org> | |
eee8207a | 6 | ;; Maintainer: Ted Zlatanov <tzz@lifelogs.com> |
5467ec88 | 7 | ;; Keywords: languages |
526cb962 | 8 | ;; Version: 1.2 |
5467ec88 DL |
9 | |
10 | ;; This file is part of GNU Emacs. | |
11 | ||
b1fc2b50 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
5467ec88 | 13 | ;; it under the terms of the GNU General Public License as published by |
b1fc2b50 GM |
14 | ;; the Free Software Foundation, either version 3 of the License, or |
15 | ;; (at your option) any later version. | |
5467ec88 DL |
16 | |
17 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;; GNU General Public License for more details. | |
21 | ||
22 | ;; You should have received a copy of the GNU General Public License | |
b1fc2b50 | 23 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
5467ec88 DL |
24 | |
25 | ;;; Commentary: | |
26 | ||
27 | ;; Provides support for editing GNU Cfengine files, including | |
cd1181db | 28 | ;; font-locking, Imenu and indentation, but with no special keybindings. |
5467ec88 | 29 | |
f3f98342 TZ |
30 | ;; The CFEngine 3.x support doesn't have Imenu support but patches are |
31 | ;; welcome. | |
0da3f8bc | 32 | |
9bb0d822 TZ |
33 | ;; By default, CFEngine 3.x syntax is used. |
34 | ||
0d373f73 TZ |
35 | ;; You can set it up so either `cfengine2-mode' (2.x and earlier) or |
36 | ;; `cfengine3-mode' (3.x) will be picked, depending on the buffer | |
f3f98342 | 37 | ;; contents: |
0da3f8bc | 38 | |
9bb0d822 | 39 | ;; (add-to-list 'auto-mode-alist '("\\.cf\\'" . cfengine-auto-mode)) |
f3f98342 TZ |
40 | |
41 | ;; OR you can choose to always use a specific version, if you prefer | |
0d373f73 | 42 | ;; it: |
f3f98342 TZ |
43 | |
44 | ;; (add-to-list 'auto-mode-alist '("\\.cf\\'" . cfengine3-mode)) | |
0d373f73 TZ |
45 | ;; (add-to-list 'auto-mode-alist '("^cf\\." . cfengine2-mode)) |
46 | ;; (add-to-list 'auto-mode-alist '("^cfagent.conf\\'" . cfengine2-mode)) | |
5467ec88 DL |
47 | |
48 | ;; This is not the same as the mode written by Rolf Ebert | |
49 | ;; <ebert@waporo.muc.de>, distributed with cfengine-2.0.5. It does | |
50 | ;; better fontification and indentation, inter alia. | |
51 | ||
52 | ;;; Code: | |
53 | ||
54 | (defgroup cfengine () | |
0d373f73 | 55 | "Editing CFEngine files." |
5467ec88 DL |
56 | :group 'languages) |
57 | ||
58 | (defcustom cfengine-indent 2 | |
fb7ada5f | 59 | "Size of a CFEngine indentation step in columns." |
5467ec88 DL |
60 | :group 'cfengine |
61 | :type 'integer) | |
62 | ||
ca68a22e TZ |
63 | (defcustom cfengine-parameters-indent '(promise pname 0) |
64 | "*Indentation of CFEngine3 promise parameters (hanging indent). | |
65 | ||
66 | For example, say you have this code: | |
67 | ||
68 | bundle x y | |
69 | { | |
70 | section: | |
71 | class:: | |
72 | promise ... | |
73 | promiseparameter => ... | |
74 | } | |
75 | ||
76 | You can choose to indent promiseparameter from the beginning of | |
77 | the line (absolutely) or from the word \"promise\" (relatively). | |
78 | ||
79 | You can also choose to indent the start of the word | |
80 | \"promiseparameter\" or the arrow that follows it. | |
81 | ||
82 | Finally, you can choose the amount of the indent. | |
83 | ||
84 | The default is to anchor at promise, indent parameter name, and offset 0: | |
85 | ||
86 | bundle agent rcfiles | |
87 | { | |
88 | files: | |
89 | any:: | |
90 | \"/tmp/netrc\" | |
91 | comment => \"my netrc\", | |
92 | perms => mog(\"600\", \"tzz\", \"tzz\"); | |
93 | } | |
94 | ||
95 | Here we anchor at beginning of line, indent arrow, and offset 10: | |
96 | ||
97 | bundle agent rcfiles | |
98 | { | |
99 | files: | |
100 | any:: | |
101 | \"/tmp/netrc\" | |
102 | comment => \"my netrc\", | |
103 | perms => mog(\"600\", \"tzz\", \"tzz\"); | |
104 | } | |
105 | ||
106 | Some, including cfengine_stdlib.cf, like to anchor at promise, indent | |
107 | arrow, and offset 16 or so: | |
108 | ||
109 | bundle agent rcfiles | |
110 | { | |
111 | files: | |
112 | any:: | |
113 | \"/tmp/netrc\" | |
114 | comment => \"my netrc\", | |
115 | perms => mog(\"600\", \"tzz\", \"tzz\"); | |
116 | } | |
117 | " | |
118 | ||
119 | :group 'cfengine | |
120 | :type '(list | |
121 | (choice (const :tag "Anchor at beginning of promise" promise) | |
122 | (const :tag "Anchor at beginning of line" bol)) | |
123 | (choice (const :tag "Indent parameter name" pname) | |
124 | (const :tag "Indent arrow" arrow)) | |
125 | (integer :tag "Indentation amount from anchor"))) | |
126 | ||
0d373f73 TZ |
127 | (defvar cfengine-mode-debug nil |
128 | "Whether `cfengine-mode' should print debugging info.") | |
129 | ||
5467ec88 | 130 | (defcustom cfengine-mode-abbrevs nil |
0d373f73 | 131 | "Abbrevs for CFEngine2 mode." |
5467ec88 DL |
132 | :group 'cfengine |
133 | :type '(repeat (list (string :tag "Name") | |
134 | (string :tag "Expansion") | |
135 | (choice :tag "Hook" (const nil) function)))) | |
136 | ||
0d373f73 TZ |
137 | (make-obsolete-variable 'cfengine-mode-abbrevs 'edit-abbrevs "24.1") |
138 | ||
5467ec88 DL |
139 | ;; Taken from the doc for pre-release 2.1. |
140 | (eval-and-compile | |
0d373f73 | 141 | (defconst cfengine2-actions |
5467ec88 DL |
142 | '("acl" "alerts" "binservers" "broadcast" "control" "classes" "copy" |
143 | "defaultroute" "disks" "directories" "disable" "editfiles" "files" | |
144 | "filters" "groups" "homeservers" "ignore" "import" "interfaces" | |
145 | "links" "mailserver" "methods" "miscmounts" "mountables" | |
146 | "processes" "packages" "rename" "required" "resolve" | |
147 | "shellcommands" "tidy" "unmount" | |
0d373f73 | 148 | ;; Keywords for cfservd. |
5467ec88 DL |
149 | "admit" "grant" "deny") |
150 | "List of the action keywords supported by Cfengine. | |
eee8207a TZ |
151 | This includes those for cfservd as well as cfagent.") |
152 | ||
153 | (defconst cfengine3-defuns | |
154 | (mapcar | |
155 | 'symbol-name | |
156 | '(bundle body)) | |
157 | "List of the CFEngine 3.x defun headings.") | |
158 | ||
159 | (defconst cfengine3-defuns-regex | |
160 | (regexp-opt cfengine3-defuns t) | |
161 | "Regex to match the CFEngine 3.x defuns.") | |
162 | ||
526cb962 | 163 | (defconst cfengine3-class-selector-regex "\\([[:alnum:]_().&|!:]+\\)::") |
eee8207a TZ |
164 | |
165 | (defconst cfengine3-category-regex "\\([[:alnum:]_]+\\):") | |
166 | ||
167 | (defconst cfengine3-vartypes | |
168 | (mapcar | |
169 | 'symbol-name | |
170 | '(string int real slist ilist rlist irange rrange counter)) | |
171 | "List of the CFEngine 3.x variable types.")) | |
5467ec88 | 172 | |
0d373f73 | 173 | (defvar cfengine2-font-lock-keywords |
5467ec88 DL |
174 | `(;; Actions. |
175 | ;; List the allowed actions explicitly, so that errors are more obvious. | |
176 | (,(concat "^[ \t]*" (eval-when-compile | |
0d373f73 | 177 | (regexp-opt cfengine2-actions t)) |
5467ec88 DL |
178 | ":") |
179 | 1 font-lock-keyword-face) | |
180 | ;; Classes. | |
181 | ("^[ \t]*\\([[:alnum:]_().|!]+\\)::" 1 font-lock-function-name-face) | |
182 | ;; Variables. | |
183 | ("$(\\([[:alnum:]_]+\\))" 1 font-lock-variable-name-face) | |
184 | ("${\\([[:alnum:]_]+\\)}" 1 font-lock-variable-name-face) | |
185 | ;; Variable definitions. | |
9bb0d822 | 186 | ("\\_<\\([[:alnum:]_]+\\)[ \t]*=[ \t]*(" 1 font-lock-variable-name-face) |
5467ec88 DL |
187 | ;; File, acl &c in group: { token ... } |
188 | ("{[ \t]*\\([^ \t\n]+\\)" 1 font-lock-constant-face))) | |
189 | ||
eee8207a TZ |
190 | (defvar cfengine3-font-lock-keywords |
191 | `( | |
0d373f73 TZ |
192 | ;; Defuns. This happens early so they don't get caught by looser |
193 | ;; patterns. | |
9bb0d822 TZ |
194 | (,(concat "\\_<" cfengine3-defuns-regex "\\_>" |
195 | "[ \t]+\\_<\\([[:alnum:]_.:]+\\)\\_>" | |
196 | "[ \t]+\\_<\\([[:alnum:]_.:]+\\)" | |
0d373f73 TZ |
197 | ;; Optional parentheses with variable names inside. |
198 | "\\(?:(\\([^)]*\\))\\)?") | |
199 | (1 font-lock-builtin-face) | |
200 | (2 font-lock-constant-face) | |
201 | (3 font-lock-function-name-face) | |
202 | (4 font-lock-variable-name-face nil t)) | |
203 | ||
204 | ;; Class selectors. | |
eee8207a TZ |
205 | (,(concat "^[ \t]*" cfengine3-class-selector-regex) |
206 | 1 font-lock-keyword-face) | |
0d373f73 TZ |
207 | |
208 | ;; Categories. | |
eee8207a TZ |
209 | (,(concat "^[ \t]*" cfengine3-category-regex) |
210 | 1 font-lock-builtin-face) | |
0d373f73 | 211 | |
eee8207a | 212 | ;; Variables, including scope, e.g. module.var |
526cb962 TZ |
213 | ("[@$](\\([[:alnum:]_.:]+\\))" 1 font-lock-variable-name-face) |
214 | ("[@$]{\\([[:alnum:]_.:]+\\)}" 1 font-lock-variable-name-face) | |
0d373f73 | 215 | |
eee8207a | 216 | ;; Variable definitions. |
9bb0d822 | 217 | ("\\_<\\([[:alnum:]_]+\\)[ \t]*=[ \t]*(" 1 font-lock-variable-name-face) |
eee8207a | 218 | |
0d373f73 | 219 | ;; Variable types. |
9bb0d822 | 220 | (,(concat "\\_<" (eval-when-compile (regexp-opt cfengine3-vartypes t)) "\\_>") |
eee8207a TZ |
221 | 1 font-lock-type-face))) |
222 | ||
0d373f73 | 223 | (defvar cfengine2-imenu-expression |
5467ec88 | 224 | `((nil ,(concat "^[ \t]*" (eval-when-compile |
0d373f73 | 225 | (regexp-opt cfengine2-actions t)) |
5467ec88 DL |
226 | ":[^:]") |
227 | 1) | |
9bb0d822 TZ |
228 | ("Variables/classes" "\\_<\\([[:alnum:]_]+\\)[ \t]*=[ \t]*(" 1) |
229 | ("Variables/classes" "\\_<define=\\([[:alnum:]_]+\\)" 1) | |
230 | ("Variables/classes" "\\_<DefineClass\\>[ \t]+\\([[:alnum:]_]+\\)" 1)) | |
0d373f73 | 231 | "`imenu-generic-expression' for CFEngine mode.") |
5467ec88 | 232 | |
0d373f73 TZ |
233 | (defun cfengine2-outline-level () |
234 | "`outline-level' function for CFEngine mode." | |
5467ec88 DL |
235 | (if (looking-at "[^:]+\\(?:[:]+\\)$") |
236 | (length (match-string 1)))) | |
237 | ||
0d373f73 TZ |
238 | (defun cfengine2-beginning-of-defun () |
239 | "`beginning-of-defun' function for CFEngine mode. | |
5467ec88 | 240 | Treats actions as defuns." |
7d5f942b SM |
241 | (unless (<= (current-column) (current-indentation)) |
242 | (end-of-line)) | |
5467ec88 DL |
243 | (if (re-search-backward "^[[:alpha:]]+: *$" nil t) |
244 | (beginning-of-line) | |
245 | (goto-char (point-min))) | |
246 | t) | |
247 | ||
0d373f73 TZ |
248 | (defun cfengine2-end-of-defun () |
249 | "`end-of-defun' function for CFEngine mode. | |
5467ec88 DL |
250 | Treats actions as defuns." |
251 | (end-of-line) | |
252 | (if (re-search-forward "^[[:alpha:]]+: *$" nil t) | |
7d5f942b | 253 | (beginning-of-line) |
5467ec88 DL |
254 | (goto-char (point-max))) |
255 | t) | |
256 | ||
257 | ;; Fixme: Should get an extra indent step in editfiles BeginGroup...s. | |
258 | ||
0d373f73 | 259 | (defun cfengine2-indent-line () |
5467ec88 DL |
260 | "Indent a line in Cfengine mode. |
261 | Intended as the value of `indent-line-function'." | |
262 | (let ((pos (- (point-max) (point)))) | |
263 | (save-restriction | |
264 | (narrow-to-defun) | |
265 | (back-to-indentation) | |
266 | (cond | |
267 | ;; Action selectors aren't indented; class selectors are | |
268 | ;; indented one step. | |
269 | ((looking-at "[[:alnum:]_().|!]+:\\(:\\)?") | |
270 | (if (match-string 1) | |
271 | (indent-line-to cfengine-indent) | |
272 | (indent-line-to 0))) | |
273 | ;; Outdent leading close brackets one step. | |
274 | ((or (eq ?\} (char-after)) | |
275 | (eq ?\) (char-after))) | |
276 | (condition-case () | |
277 | (indent-line-to (save-excursion | |
278 | (forward-char) | |
279 | (backward-sexp) | |
280 | (current-column))) | |
281 | (error nil))) | |
282 | ;; Inside brackets/parens: indent to start column of non-comment | |
283 | ;; token on line following open bracket or by one step from open | |
284 | ;; bracket's column. | |
285 | ((condition-case () | |
286 | (progn (indent-line-to (save-excursion | |
287 | (backward-up-list) | |
288 | (forward-char) | |
289 | (skip-chars-forward " \t") | |
290 | (if (looking-at "[^\n#]") | |
291 | (current-column) | |
292 | (skip-chars-backward " \t") | |
293 | (+ (current-column) -1 | |
294 | cfengine-indent)))) | |
295 | t) | |
296 | (error nil))) | |
297 | ;; Indent by two steps after a class selector. | |
298 | ((save-excursion | |
299 | (re-search-backward "^[ \t]*[[:alnum:]_().|!]+::" nil t)) | |
300 | (indent-line-to (* 2 cfengine-indent))) | |
301 | ;; Indent by one step if we're after an action header. | |
302 | ((save-excursion | |
303 | (goto-char (point-min)) | |
304 | (looking-at "[[:alpha:]]+:[ \t]*$")) | |
305 | (indent-line-to cfengine-indent)) | |
306 | ;; Else don't indent. | |
307 | (t | |
308 | (indent-line-to 0)))) | |
309 | ;; If initial point was within line's indentation, | |
310 | ;; position after the indentation. Else stay at same point in text. | |
311 | (if (> (- (point-max) pos) (point)) | |
312 | (goto-char (- (point-max) pos))))) | |
313 | ||
bf247b6e | 314 | ;; This doesn't work too well in Emacs 21.2. See 22.1 development |
5467ec88 DL |
315 | ;; code. |
316 | (defun cfengine-fill-paragraph (&optional justify) | |
317 | "Fill `paragraphs' in Cfengine code." | |
318 | (interactive "P") | |
319 | (or (if (fboundp 'fill-comment-paragraph) | |
320 | (fill-comment-paragraph justify) ; post Emacs 21.3 | |
321 | ;; else do nothing in a comment | |
322 | (nth 4 (parse-partial-sexp (save-excursion | |
323 | (beginning-of-defun) | |
324 | (point)) | |
325 | (point)))) | |
326 | (let ((paragraph-start | |
327 | ;; Include start of parenthesized block. | |
328 | "\f\\|[ \t]*$\\|.*\(") | |
329 | (paragraph-separate | |
330 | ;; Include action and class lines, start and end of | |
331 | ;; bracketed blocks and end of parenthesized blocks to | |
332 | ;; avoid including these in fill. This isn't ideal. | |
333 | "[ \t\f]*$\\|.*#\\|.*[\){}]\\|\\s-*[[:alpha:]_().|!]+:") | |
334 | fill-paragraph-function) | |
335 | (fill-paragraph justify)) | |
336 | t)) | |
337 | ||
eee8207a TZ |
338 | (defun cfengine3-beginning-of-defun () |
339 | "`beginning-of-defun' function for Cfengine 3 mode. | |
340 | Treats body/bundle blocks as defuns." | |
341 | (unless (<= (current-column) (current-indentation)) | |
342 | (end-of-line)) | |
9bb0d822 | 343 | (if (re-search-backward (concat "^[ \t]*" cfengine3-defuns-regex "\\_>") nil t) |
eee8207a TZ |
344 | (beginning-of-line) |
345 | (goto-char (point-min))) | |
346 | t) | |
347 | ||
348 | (defun cfengine3-end-of-defun () | |
349 | "`end-of-defun' function for Cfengine 3 mode. | |
350 | Treats body/bundle blocks as defuns." | |
351 | (end-of-line) | |
9bb0d822 | 352 | (if (re-search-forward (concat "^[ \t]*" cfengine3-defuns-regex "\\_>") nil t) |
eee8207a TZ |
353 | (beginning-of-line) |
354 | (goto-char (point-max))) | |
355 | t) | |
356 | ||
357 | (defun cfengine3-indent-line () | |
358 | "Indent a line in Cfengine 3 mode. | |
359 | Intended as the value of `indent-line-function'." | |
360 | (let ((pos (- (point-max) (point))) | |
361 | parse) | |
362 | (save-restriction | |
363 | (narrow-to-defun) | |
364 | (back-to-indentation) | |
365 | (setq parse (parse-partial-sexp (point-min) (point))) | |
0d373f73 TZ |
366 | (when cfengine-mode-debug |
367 | (message "%S" parse)) | |
368 | ||
eee8207a | 369 | (cond |
0d373f73 | 370 | ;; Body/bundle blocks start at 0. |
9bb0d822 | 371 | ((looking-at (concat cfengine3-defuns-regex "\\_>")) |
eee8207a | 372 | (indent-line-to 0)) |
0d373f73 | 373 | ;; Categories are indented one step. |
ca68a22e | 374 | ((looking-at (concat cfengine3-category-regex "[ \t]*\\(#.*\\)*$")) |
eee8207a | 375 | (indent-line-to cfengine-indent)) |
0d373f73 | 376 | ;; Class selectors are indented two steps. |
ca68a22e | 377 | ((looking-at (concat cfengine3-class-selector-regex "[ \t]*\\(#.*\\)*$")) |
eee8207a TZ |
378 | (indent-line-to (* 2 cfengine-indent))) |
379 | ;; Outdent leading close brackets one step. | |
380 | ((or (eq ?\} (char-after)) | |
381 | (eq ?\) (char-after))) | |
382 | (condition-case () | |
383 | (indent-line-to (save-excursion | |
384 | (forward-char) | |
385 | (backward-sexp) | |
526cb962 TZ |
386 | (move-beginning-of-line nil) |
387 | (skip-chars-forward " \t") | |
eee8207a TZ |
388 | (current-column))) |
389 | (error nil))) | |
0d373f73 | 390 | ;; Inside a string and it starts before this line. |
eee8207a TZ |
391 | ((and (nth 3 parse) |
392 | (< (nth 8 parse) (save-excursion (beginning-of-line) (point)))) | |
393 | (indent-line-to 0)) | |
0d373f73 TZ |
394 | |
395 | ;; Inside a defun, but not a nested list (depth is 1). This is | |
396 | ;; a promise, usually. | |
397 | ||
398 | ;; Indent to cfengine-indent times the nested depth | |
399 | ;; plus 2. That way, promises indent deeper than class | |
400 | ;; selectors, which in turn are one deeper than categories. | |
eee8207a | 401 | ((= 1 (nth 0 parse)) |
ca68a22e TZ |
402 | (let ((p-anchor (nth 0 cfengine-parameters-indent)) |
403 | (p-what (nth 1 cfengine-parameters-indent)) | |
404 | (p-indent (nth 2 cfengine-parameters-indent))) | |
405 | ;; Do we have the parameter anchor and location and indent | |
406 | ;; defined, and are we looking at a promise parameter? | |
407 | (if (and p-anchor p-what p-indent | |
408 | (looking-at "\\([[:alnum:]_]+[ \t]*\\)=>")) | |
409 | (let* ((arrow-offset (* -1 (length (match-string 1)))) | |
410 | (extra-offset (if (eq p-what 'arrow) arrow-offset 0)) | |
411 | (base-offset (if (eq p-anchor 'promise) | |
412 | (* (+ 2 (nth 0 parse)) cfengine-indent) | |
413 | 0))) | |
414 | (indent-line-to (max 0 (+ p-indent base-offset extra-offset)))) | |
415 | ;; Else, indent to cfengine-indent times the nested depth | |
416 | ;; plus 2. That way, promises indent deeper than class | |
417 | ;; selectors, which in turn are one deeper than categories. | |
418 | (indent-line-to (* (+ 2 (nth 0 parse)) cfengine-indent))))) | |
eee8207a TZ |
419 | ;; Inside brackets/parens: indent to start column of non-comment |
420 | ;; token on line following open bracket or by one step from open | |
421 | ;; bracket's column. | |
422 | ((condition-case () | |
423 | (progn (indent-line-to (save-excursion | |
424 | (backward-up-list) | |
425 | (forward-char) | |
426 | (skip-chars-forward " \t") | |
427 | (cond | |
428 | ((looking-at "[^\n#]") | |
429 | (current-column)) | |
430 | ((looking-at "[^\n#]") | |
431 | (current-column)) | |
432 | (t | |
433 | (skip-chars-backward " \t") | |
434 | (+ (current-column) -1 | |
435 | cfengine-indent))))) | |
436 | t) | |
437 | (error nil))) | |
438 | ;; Else don't indent. | |
439 | (t (indent-line-to 0)))) | |
440 | ;; If initial point was within line's indentation, | |
441 | ;; position after the indentation. Else stay at same point in text. | |
442 | (if (> (- (point-max) pos) (point)) | |
443 | (goto-char (- (point-max) pos))))) | |
444 | ||
445 | ;; CFEngine 3.x grammar | |
446 | ||
447 | ;; specification: blocks | |
448 | ;; blocks: block | blocks block; | |
449 | ;; block: bundle typeid blockid bundlebody | |
450 | ;; | bundle typeid blockid usearglist bundlebody | |
451 | ;; | body typeid blockid bodybody | |
452 | ;; | body typeid blockid usearglist bodybody; | |
453 | ||
454 | ;; typeid: id | |
455 | ;; blockid: id | |
456 | ;; usearglist: '(' aitems ')'; | |
457 | ;; aitems: aitem | aitem ',' aitems |; | |
458 | ;; aitem: id | |
459 | ||
460 | ;; bundlebody: '{' statements '}' | |
461 | ;; statements: statement | statements statement; | |
462 | ;; statement: category | classpromises; | |
463 | ||
464 | ;; bodybody: '{' bodyattribs '}' | |
465 | ;; bodyattribs: bodyattrib | bodyattribs bodyattrib; | |
466 | ;; bodyattrib: class | selections; | |
467 | ;; selections: selection | selections selection; | |
468 | ;; selection: id ASSIGN rval ';' ; | |
469 | ||
470 | ;; classpromises: classpromise | classpromises classpromise; | |
471 | ;; classpromise: class | promises; | |
472 | ;; promises: promise | promises promise; | |
473 | ;; category: CATEGORY | |
474 | ;; promise: promiser ARROW rval constraints ';' | promiser constraints ';'; | |
475 | ;; constraints: constraint | constraints ',' constraint |; | |
476 | ;; constraint: id ASSIGN rval; | |
477 | ;; class: CLASS | |
478 | ;; id: ID | |
479 | ;; rval: ID | QSTRING | NAKEDVAR | list | usefunction | |
480 | ;; list: '{' litems '}' ; | |
481 | ;; litems: litem | litem ',' litems |; | |
482 | ;; litem: ID | QSTRING | NAKEDVAR | list | usefunction | |
483 | ||
484 | ;; functionid: ID | NAKEDVAR | |
485 | ;; promiser: QSTRING | |
486 | ;; usefunction: functionid givearglist | |
487 | ;; givearglist: '(' gaitems ')' | |
488 | ;; gaitems: gaitem | gaitems ',' gaitem |; | |
489 | ;; gaitem: ID | QSTRING | NAKEDVAR | list | usefunction | |
490 | ||
491 | ;; # from lexer: | |
492 | ||
493 | ;; bundle: "bundle" | |
494 | ;; body: "body" | |
495 | ;; COMMENT #[^\n]* | |
496 | ;; NAKEDVAR [$@][(][a-zA-Z0-9_\200-\377.]+[)]|[$@][{][a-zA-Z0-9_\200-\377.]+[}] | |
497 | ;; ID: [a-zA-Z0-9_\200-\377]+ | |
498 | ;; ASSIGN: "=>" | |
499 | ;; ARROW: "->" | |
500 | ;; QSTRING: \"((\\\")|[^"])*\"|\'((\\\')|[^'])*\'|`[^`]*` | |
501 | ;; CLASS: [.|&!()a-zA-Z0-9_\200-\377]+:: | |
502 | ;; CATEGORY: [a-zA-Z_]+: | |
503 | ||
504 | (defun cfengine-common-settings () | |
505 | (set (make-local-variable 'syntax-propertize-function) | |
506 | ;; In the main syntax-table, \ is marked as a punctuation, because | |
507 | ;; of its use in DOS-style directory separators. Here we try to | |
508 | ;; recognize the cases where \ is used as an escape inside strings. | |
509 | (syntax-propertize-rules ("\\(\\(?:\\\\\\)+\\)\"" (1 "\\")))) | |
510 | (set (make-local-variable 'parens-require-spaces) nil) | |
511 | (set (make-local-variable 'comment-start) "# ") | |
512 | (set (make-local-variable 'comment-start-skip) | |
513 | "\\(\\(?:^\\|[^\\\\\n]\\)\\(?:\\\\\\\\\\)*\\)#+[ \t]*") | |
514 | ;; Like Lisp mode. Without this, we lose with, say, | |
515 | ;; `backward-up-list' when there's an unbalanced quote in a | |
516 | ;; preceding comment. | |
517 | (set (make-local-variable 'parse-sexp-ignore-comments) t)) | |
518 | ||
519 | (defun cfengine-common-syntax (table) | |
0d373f73 | 520 | ;; The syntax defaults seem OK to give reasonable word movement. |
eee8207a TZ |
521 | (modify-syntax-entry ?# "<" table) |
522 | (modify-syntax-entry ?\n ">#" table) | |
526cb962 TZ |
523 | (modify-syntax-entry ?\" "\"" table) ; "string" |
524 | (modify-syntax-entry ?\' "\"" table) ; 'string' | |
0d373f73 | 525 | ;; Variable substitution. |
eee8207a | 526 | (modify-syntax-entry ?$ "." table) |
0d373f73 | 527 | ;; Doze path separators. |
eee8207a TZ |
528 | (modify-syntax-entry ?\\ "." table)) |
529 | ||
530 | ;;;###autoload | |
0d373f73 TZ |
531 | (define-derived-mode cfengine3-mode prog-mode "CFE3" |
532 | "Major mode for editing CFEngine3 input. | |
eee8207a TZ |
533 | There are no special keybindings by default. |
534 | ||
535 | Action blocks are treated as defuns, i.e. \\[beginning-of-defun] moves | |
536 | to the action header." | |
537 | (cfengine-common-settings) | |
538 | (cfengine-common-syntax cfengine3-mode-syntax-table) | |
539 | ||
540 | (set (make-local-variable 'indent-line-function) #'cfengine3-indent-line) | |
541 | (setq font-lock-defaults | |
542 | '(cfengine3-font-lock-keywords nil nil nil beginning-of-defun)) | |
543 | ||
0d373f73 | 544 | ;; Use defuns as the essential syntax block. |
eee8207a TZ |
545 | (set (make-local-variable 'beginning-of-defun-function) |
546 | #'cfengine3-beginning-of-defun) | |
547 | (set (make-local-variable 'end-of-defun-function) | |
548 | #'cfengine3-end-of-defun)) | |
549 | ||
5467ec88 | 550 | ;;;###autoload |
0d373f73 TZ |
551 | (define-derived-mode cfengine2-mode prog-mode "CFE2" |
552 | "Major mode for editing CFEngine2 input. | |
5467ec88 DL |
553 | There are no special keybindings by default. |
554 | ||
555 | Action blocks are treated as defuns, i.e. \\[beginning-of-defun] moves | |
556 | to the action header." | |
eee8207a | 557 | (cfengine-common-settings) |
0d373f73 | 558 | (cfengine-common-syntax cfengine2-mode-syntax-table) |
eee8207a | 559 | |
5467ec88 DL |
560 | ;; Shell commands can be quoted by single, double or back quotes. |
561 | ;; It's debatable whether we should define string syntax, but it | |
562 | ;; should avoid potential confusion in some cases. | |
0d373f73 | 563 | (modify-syntax-entry ?\` "\"" cfengine2-mode-syntax-table) |
5467ec88 | 564 | |
0d373f73 | 565 | (set (make-local-variable 'indent-line-function) #'cfengine2-indent-line) |
5467ec88 | 566 | (set (make-local-variable 'outline-regexp) "[ \t]*\\(\\sw\\|\\s_\\)+:+") |
0d373f73 | 567 | (set (make-local-variable 'outline-level) #'cfengine2-outline-level) |
5467ec88 DL |
568 | (set (make-local-variable 'fill-paragraph-function) |
569 | #'cfengine-fill-paragraph) | |
0d373f73 | 570 | (define-abbrev-table 'cfengine2-mode-abbrev-table cfengine-mode-abbrevs) |
5467ec88 | 571 | (setq font-lock-defaults |
0d373f73 | 572 | '(cfengine2-font-lock-keywords nil nil nil beginning-of-line)) |
cf38dd42 SM |
573 | ;; Fixme: set the args of functions in evaluated classes to string |
574 | ;; syntax, and then obey syntax properties. | |
0d373f73 | 575 | (setq imenu-generic-expression cfengine2-imenu-expression) |
5467ec88 | 576 | (set (make-local-variable 'beginning-of-defun-function) |
0d373f73 TZ |
577 | #'cfengine2-beginning-of-defun) |
578 | (set (make-local-variable 'end-of-defun-function) #'cfengine2-end-of-defun)) | |
5467ec88 | 579 | |
f3f98342 TZ |
580 | ;;;###autoload |
581 | (defun cfengine-auto-mode () | |
0d373f73 | 582 | "Choose between `cfengine2-mode' and `cfengine3-mode' depending |
f3f98342 TZ |
583 | on the buffer contents" |
584 | (let ((v3 nil)) | |
585 | (save-restriction | |
586 | (goto-char (point-min)) | |
587 | (while (not (or (eobp) v3)) | |
9bb0d822 | 588 | (setq v3 (looking-at (concat cfengine3-defuns-regex "\\_>"))) |
f3f98342 | 589 | (forward-line))) |
0d373f73 TZ |
590 | (if v3 (cfengine3-mode) (cfengine2-mode)))) |
591 | ||
526cb962 | 592 | (defalias 'cfengine-mode 'cfengine3-mode) |
f3f98342 | 593 | |
eee8207a | 594 | (provide 'cfengine3) |
5467ec88 DL |
595 | (provide 'cfengine) |
596 | ||
597 | ;;; cfengine.el ends here |