1 ;;; cfengine3.el --- mode for editing Cfengine 3 files
3 ;; Copyright (C) 2001-2011 Free Software Foundation, Inc.
5 ;; Author: Ted Zlatanov <tzz@lifelogs.com>
8 ;; This file is part of GNU Emacs.
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.
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.
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/>.
25 ;; Supports only cfengine 3, unlike the older cfengine.el which
26 ;; supports 1.x and 2.x.
28 ;; Possible customization for auto-mode selection:
30 ;; (push '(("^cfagent.conf\\'" . cfengine3-mode)) auto-mode-alist)
31 ;; (push '(("^cf\\." . cfengine3-mode)) auto-mode-alist)
32 ;; (push '(("\\.cf\\'" . cfengine3-mode)) auto-mode-alist)
36 (defgroup cfengine3
()
37 "Editing CFEngine 3 files."
40 (defcustom cfengine3-indent
2
41 "*Size of a CFEngine 3 indentation step in columns."
46 (defconst cfengine3-defuns
50 "List of the CFEngine 3.x defun headings.")
52 (defconst cfengine3-defuns-regex
53 (regexp-opt cfengine3-defuns t
)
54 "Regex to match the CFEngine 3.x defuns.")
56 (defconst cfengine3-class-selector-regex
"\\([[:alnum:]_().&|!]+\\)::")
58 (defconst cfengine3-category-regex
"\\([[:alnum:]_]+\\):")
60 (defconst cfengine3-vartypes
63 '(string int real slist ilist rlist irange rrange counter
))
64 "List of the CFEngine 3.x variable types."))
66 (defvar cfengine3-font-lock-keywords
68 (,(concat "^[ \t]*" cfengine3-class-selector-regex
)
69 1 font-lock-keyword-face
)
70 (,(concat "^[ \t]*" cfengine3-category-regex
)
71 1 font-lock-builtin-face
)
72 ;; Variables, including scope, e.g. module.var
73 ("[@$](\\([[:alnum:]_.]+\\))" 1 font-lock-variable-name-face
)
74 ("[@$]{\\([[:alnum:]_.]+\\)}" 1 font-lock-variable-name-face
)
75 ;; Variable definitions.
76 ("\\<\\([[:alnum:]_]+\\)[ \t]*=[ \t]*(" 1 font-lock-variable-name-face
)
80 (,(concat "\\<" cfengine3-defuns-regex
"\\>"
81 "[ \t]+\\<\\([[:alnum:]_]+\\)\\>"
82 "[ \t]+\\<\\([[:alnum:]_]+\\)\\((\\([^)]*\\))\\)?")
83 (1 font-lock-builtin-face
)
84 (2 font-lock-constant-name-face
)
85 (3 font-lock-function-name-face
)
86 (5 font-lock-variable-name-face
))
88 (,(concat "\\<" (eval-when-compile (regexp-opt cfengine3-vartypes t
)) "\\>")
89 1 font-lock-type-face
)))
91 (defun cfengine3-beginning-of-defun ()
92 "`beginning-of-defun' function for Cfengine 3 mode.
93 Treats body/bundle blocks as defuns."
94 (unless (<= (current-column) (current-indentation))
96 (if (re-search-backward (concat "^[ \t]*" cfengine3-defuns-regex
"\\>") nil t
)
98 (goto-char (point-min)))
101 (defun cfengine3-end-of-defun ()
102 "`end-of-defun' function for Cfengine 3 mode.
103 Treats body/bundle blocks as defuns."
105 (if (re-search-forward (concat "^[ \t]*" cfengine3-defuns-regex
"\\>") nil t
)
107 (goto-char (point-max)))
110 (defun cfengine3-indent-line ()
111 "Indent a line in Cfengine mode.
112 Intended as the value of `indent-line-function'."
113 (let ((pos (- (point-max) (point)))
117 (back-to-indentation)
118 (setq parse
(parse-partial-sexp (point-min) (point)))
121 ;; body/bundle blocks start at 0
122 ((looking-at (concat cfengine3-defuns-regex
"\\>"))
124 ;; categories are indented one step
125 ((looking-at (concat cfengine3-category-regex
"[ \t]*$"))
126 (indent-line-to cfengine3-indent
))
127 ;; class selectors are indented two steps
128 ((looking-at (concat cfengine3-class-selector-regex
"[ \t]*$"))
129 (indent-line-to (* 2 cfengine3-indent
)))
130 ;; Outdent leading close brackets one step.
131 ((or (eq ?\
} (char-after))
132 (eq ?\
) (char-after)))
134 (indent-line-to (save-excursion
139 ;; inside a string and it starts before this line
141 (< (nth 8 parse
) (save-excursion (beginning-of-line) (point))))
143 ;; inside a defun, but not a nested list (depth is 1)
145 (indent-line-to (* (+ 2 (nth 0 parse
)) cfengine3-indent
)))
146 ;; Inside brackets/parens: indent to start column of non-comment
147 ;; token on line following open bracket or by one step from open
150 (progn (indent-line-to (save-excursion
153 (skip-chars-forward " \t")
155 ((looking-at "[^\n#]")
157 ((looking-at "[^\n#]")
160 (skip-chars-backward " \t")
161 (+ (current-column) -
1
162 cfengine3-indent
)))))
165 ;; Else don't indent.
166 (t (indent-line-to 0))))
167 ;; If initial point was within line's indentation,
168 ;; position after the indentation. Else stay at same point in text.
169 (if (> (- (point-max) pos
) (point))
170 (goto-char (- (point-max) pos
)))))
172 ;; (defvar cfengine3-smie-grammar
173 ;; (smie-prec2->grammar
174 ;; (smie-merge-prec2s
177 ;; (decls (decls "body" decls)
178 ;; (decls "bundle" decls))
179 ;; (insts (token ":" insts)))
180 ;; '((assoc "body" "bundle")))
181 ;; (smie-precs->prec2
188 ;; (defun cfengine3-smie-rules (kind token)
189 ;; (pcase (cons kind token)
190 ;; (`(:elem . basic) 2)
191 ;; (`(:list-intro . ,(or `"body" `"bundle")) t)
192 ;; (`(:after . ":") 2)
193 ;; (`(:after . "::") 2)))
195 ;; (defun cfengine3-show-all-tokens ()
197 ;; (goto-char (point-min))
198 ;; (while (not (eobp))
199 ;; (let* ((p (point))
200 ;; (token (funcall smie-forward-token-function)))
201 ;; (delete-region p (point))
202 ;; (insert-before-markers token)
205 ;; (defun cfengine3-line-classes ()
208 ;; (beginning-of-line)
209 ;; (let* ((todo (buffer-substring (point)
210 ;; (save-excursion (end-of-line) (point))))
211 ;; (original (concat (loop for c across todo
212 ;; collect (char-syntax c)))))
213 ;; (format "%s\n%s" original todo))))
215 ;; (defun cfengine3-show-all-classes ()
217 ;; (goto-char (point-min))
218 ;; (while (not (eobp))
219 ;; (let ((repl (cfengine3-line-classes)))
224 ;; specification: blocks
225 ;; blocks: block | blocks block;
226 ;; block: bundle typeid blockid bundlebody
227 ;; | bundle typeid blockid usearglist bundlebody
228 ;; | body typeid blockid bodybody
229 ;; | body typeid blockid usearglist bodybody;
233 ;; usearglist: '(' aitems ')';
234 ;; aitems: aitem | aitem ',' aitems |;
237 ;; bundlebody: '{' statements '}'
238 ;; statements: statement | statements statement;
239 ;; statement: category | classpromises;
241 ;; bodybody: '{' bodyattribs '}'
242 ;; bodyattribs: bodyattrib | bodyattribs bodyattrib;
243 ;; bodyattrib: class | selections;
244 ;; selections: selection | selections selection;
245 ;; selection: id ASSIGN rval ';' ;
247 ;; classpromises: classpromise | classpromises classpromise;
248 ;; classpromise: class | promises;
249 ;; promises: promise | promises promise;
250 ;; category: CATEGORY
251 ;; promise: promiser ARROW rval constraints ';' | promiser constraints ';';
252 ;; constraints: constraint | constraints ',' constraint |;
253 ;; constraint: id ASSIGN rval;
256 ;; rval: ID | QSTRING | NAKEDVAR | list | usefunction
257 ;; list: '{' litems '}' ;
258 ;; litems: litem | litem ',' litems |;
259 ;; litem: ID | QSTRING | NAKEDVAR | list | usefunction
261 ;; functionid: ID | NAKEDVAR
263 ;; usefunction: functionid givearglist
264 ;; givearglist: '(' gaitems ')'
265 ;; gaitems: gaitem | gaitems ',' gaitem |;
266 ;; gaitem: ID | QSTRING | NAKEDVAR | list | usefunction
273 ;; NAKEDVAR [$@][(][a-zA-Z0-9_\200-\377.]+[)]|[$@][{][a-zA-Z0-9_\200-\377.]+[}]
274 ;; ID: [a-zA-Z0-9_\200-\377]+
277 ;; QSTRING: \"((\\\")|[^"])*\"|\'((\\\')|[^'])*\'|`[^`]*`
278 ;; CLASS: [.|&!()a-zA-Z0-9_\200-\377]+::
279 ;; CATEGORY: [a-zA-Z_]+:
282 (define-derived-mode cfengine3-mode prog-mode
"CFEngine3"
283 "Major mode for editing cfengine input.
284 There are no special keybindings by default.
286 Action blocks are treated as defuns, i.e. \\[beginning-of-defun] moves
287 to the action header."
288 (modify-syntax-entry ?
# "<" cfengine3-mode-syntax-table
)
289 (modify-syntax-entry ?
\n ">#" cfengine3-mode-syntax-table
)
290 (modify-syntax-entry ?
\" "\"" cfengine3-mode-syntax-table
)
291 ;; variable substitution:
292 (modify-syntax-entry ?$
"." cfengine3-mode-syntax-table
)
293 ;; Doze path separators:
294 (modify-syntax-entry ?
\\ "." cfengine3-mode-syntax-table
)
295 ;; Otherwise, syntax defaults seem OK to give reasonable word
298 ;; (smie-setup cfengine3-smie-grammar #'cfengine3-smie-rules)
299 ;; ;; :forward-token #'cfengine3-smie-forward-token
300 ;; ;; :backward-token #'cfengine3-smie-backward-token)
301 ;; (set (make-local-variable 'smie-indent-basic) 'cfengine3-indent)
303 (set (make-local-variable 'parens-require-spaces
) nil
)
304 (set (make-local-variable 'comment-start
) "# ")
305 (set (make-local-variable 'comment-start-skip
)
306 "\\(\\(?:^\\|[^\\\\\n]\\)\\(?:\\\\\\\\\\)*\\)#+[ \t]*")
307 (set (make-local-variable 'indent-line-function
) #'cfengine3-indent-line
)
308 (setq font-lock-defaults
309 '(cfengine3-font-lock-keywords nil nil nil beginning-of-defun
))
310 ;; Fixme: set the args of functions in evaluated classes to string
311 ;; syntax, and then obey syntax properties.
312 (set (make-local-variable 'syntax-propertize-function
)
313 ;; In the main syntax-table, \ is marked as a punctuation, because
314 ;; of its use in DOS-style directory separators. Here we try to
315 ;; recognize the cases where \ is used as an escape inside strings.
316 (syntax-propertize-rules ("\\(\\(?:\\\\\\)+\\)\"" (1 "\\"))))
318 ;; use defuns as the essential syntax block
319 (set (make-local-variable 'beginning-of-defun-function
)
320 #'cfengine3-beginning-of-defun
)
321 (set (make-local-variable 'end-of-defun-function
)
322 #'cfengine3-end-of-defun
)
324 ;; Like Lisp mode. Without this, we lose with, say,
325 ;; `backward-up-list' when there's an unbalanced quote in a
326 ;; preceding comment.
327 (set (make-local-variable 'parse-sexp-ignore-comments
) t
))
331 ;;; cfengine3.el ends here