Commit | Line | Data |
---|---|---|
5467ec88 DL |
1 | ;;; cfengine.el --- mode for editing Cfengine files |
2 | ||
5df4f04c | 3 | ;; Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 |
d91362c9 | 4 | ;; Free Software Foundation, Inc. |
5467ec88 DL |
5 | |
6 | ;; Author: Dave Love <fx@gnu.org> | |
7 | ;; Keywords: languages | |
8 | ||
9 | ;; This file is part of GNU Emacs. | |
10 | ||
b1fc2b50 | 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
5467ec88 | 12 | ;; it under the terms of the GNU General Public License as published by |
b1fc2b50 GM |
13 | ;; the Free Software Foundation, either version 3 of the License, or |
14 | ;; (at your option) any later version. | |
5467ec88 DL |
15 | |
16 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | ;; GNU General Public License for more details. | |
20 | ||
21 | ;; You should have received a copy of the GNU General Public License | |
b1fc2b50 | 22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
5467ec88 DL |
23 | |
24 | ;;; Commentary: | |
25 | ||
26 | ;; Provides support for editing GNU Cfengine files, including | |
27 | ;; font-locking, Imenu and indention, but with no special keybindings. | |
28 | ||
29 | ;; Possible customization for auto-mode selection: | |
30 | ;; (push '(("^cfagent.conf\\'" . cfengine-mode)) auto-mode-alist) | |
31 | ;; (push '(("^cf\\." . cfengine-mode)) auto-mode-alist) | |
32 | ||
33 | ;; This is not the same as the mode written by Rolf Ebert | |
34 | ;; <ebert@waporo.muc.de>, distributed with cfengine-2.0.5. It does | |
35 | ;; better fontification and indentation, inter alia. | |
36 | ||
37 | ;;; Code: | |
38 | ||
39 | (defgroup cfengine () | |
40 | "Editing Cfengine files." | |
41 | :group 'languages) | |
42 | ||
43 | (defcustom cfengine-indent 2 | |
44 | "*Size of a Cfengine indentation step in columns." | |
45 | :group 'cfengine | |
46 | :type 'integer) | |
47 | ||
48 | (defcustom cfengine-mode-abbrevs nil | |
49 | "Abbrevs for Cfengine mode." | |
50 | :group 'cfengine | |
51 | :type '(repeat (list (string :tag "Name") | |
52 | (string :tag "Expansion") | |
53 | (choice :tag "Hook" (const nil) function)))) | |
54 | ||
55 | ;; Taken from the doc for pre-release 2.1. | |
56 | (eval-and-compile | |
57 | (defconst cfengine-actions | |
58 | '("acl" "alerts" "binservers" "broadcast" "control" "classes" "copy" | |
59 | "defaultroute" "disks" "directories" "disable" "editfiles" "files" | |
60 | "filters" "groups" "homeservers" "ignore" "import" "interfaces" | |
61 | "links" "mailserver" "methods" "miscmounts" "mountables" | |
62 | "processes" "packages" "rename" "required" "resolve" | |
63 | "shellcommands" "tidy" "unmount" | |
64 | ;; cfservd | |
65 | "admit" "grant" "deny") | |
66 | "List of the action keywords supported by Cfengine. | |
67 | This includes those for cfservd as well as cfagent.")) | |
68 | ||
69 | (defvar cfengine-font-lock-keywords | |
70 | `(;; Actions. | |
71 | ;; List the allowed actions explicitly, so that errors are more obvious. | |
72 | (,(concat "^[ \t]*" (eval-when-compile | |
73 | (regexp-opt cfengine-actions t)) | |
74 | ":") | |
75 | 1 font-lock-keyword-face) | |
76 | ;; Classes. | |
77 | ("^[ \t]*\\([[:alnum:]_().|!]+\\)::" 1 font-lock-function-name-face) | |
78 | ;; Variables. | |
79 | ("$(\\([[:alnum:]_]+\\))" 1 font-lock-variable-name-face) | |
80 | ("${\\([[:alnum:]_]+\\)}" 1 font-lock-variable-name-face) | |
81 | ;; Variable definitions. | |
82 | ("\\<\\([[:alnum:]_]+\\)[ \t]*=[ \t]*(" 1 font-lock-variable-name-face) | |
83 | ;; File, acl &c in group: { token ... } | |
84 | ("{[ \t]*\\([^ \t\n]+\\)" 1 font-lock-constant-face))) | |
85 | ||
86 | (defvar cfengine-imenu-expression | |
87 | `((nil ,(concat "^[ \t]*" (eval-when-compile | |
88 | (regexp-opt cfengine-actions t)) | |
89 | ":[^:]") | |
90 | 1) | |
91 | ("Variables/classes" "\\<\\([[:alnum:]_]+\\)[ \t]*=[ \t]*(" 1) | |
92 | ("Variables/classes" "\\<define=\\([[:alnum:]_]+\\)" 1) | |
93 | ("Variables/classes" "\\<DefineClass\\>[ \t]+\\([[:alnum:]_]+\\)" 1)) | |
94 | "`imenu-generic-expression' for Cfengine mode.") | |
95 | ||
96 | (defun cfengine-outline-level () | |
97 | "`outline-level' function for Cfengine mode." | |
98 | (if (looking-at "[^:]+\\(?:[:]+\\)$") | |
99 | (length (match-string 1)))) | |
100 | ||
101 | (defun cfengine-beginning-of-defun () | |
102 | "`beginning-of-defun' function for Cfengine mode. | |
103 | Treats actions as defuns." | |
7d5f942b SM |
104 | (unless (<= (current-column) (current-indentation)) |
105 | (end-of-line)) | |
5467ec88 DL |
106 | (if (re-search-backward "^[[:alpha:]]+: *$" nil t) |
107 | (beginning-of-line) | |
108 | (goto-char (point-min))) | |
109 | t) | |
110 | ||
111 | (defun cfengine-end-of-defun () | |
112 | "`end-of-defun' function for Cfengine mode. | |
113 | Treats actions as defuns." | |
114 | (end-of-line) | |
115 | (if (re-search-forward "^[[:alpha:]]+: *$" nil t) | |
7d5f942b | 116 | (beginning-of-line) |
5467ec88 DL |
117 | (goto-char (point-max))) |
118 | t) | |
119 | ||
120 | ;; Fixme: Should get an extra indent step in editfiles BeginGroup...s. | |
121 | ||
122 | (defun cfengine-indent-line () | |
123 | "Indent a line in Cfengine mode. | |
124 | Intended as the value of `indent-line-function'." | |
125 | (let ((pos (- (point-max) (point)))) | |
126 | (save-restriction | |
127 | (narrow-to-defun) | |
128 | (back-to-indentation) | |
129 | (cond | |
130 | ;; Action selectors aren't indented; class selectors are | |
131 | ;; indented one step. | |
132 | ((looking-at "[[:alnum:]_().|!]+:\\(:\\)?") | |
133 | (if (match-string 1) | |
134 | (indent-line-to cfengine-indent) | |
135 | (indent-line-to 0))) | |
136 | ;; Outdent leading close brackets one step. | |
137 | ((or (eq ?\} (char-after)) | |
138 | (eq ?\) (char-after))) | |
139 | (condition-case () | |
140 | (indent-line-to (save-excursion | |
141 | (forward-char) | |
142 | (backward-sexp) | |
143 | (current-column))) | |
144 | (error nil))) | |
145 | ;; Inside brackets/parens: indent to start column of non-comment | |
146 | ;; token on line following open bracket or by one step from open | |
147 | ;; bracket's column. | |
148 | ((condition-case () | |
149 | (progn (indent-line-to (save-excursion | |
150 | (backward-up-list) | |
151 | (forward-char) | |
152 | (skip-chars-forward " \t") | |
153 | (if (looking-at "[^\n#]") | |
154 | (current-column) | |
155 | (skip-chars-backward " \t") | |
156 | (+ (current-column) -1 | |
157 | cfengine-indent)))) | |
158 | t) | |
159 | (error nil))) | |
160 | ;; Indent by two steps after a class selector. | |
161 | ((save-excursion | |
162 | (re-search-backward "^[ \t]*[[:alnum:]_().|!]+::" nil t)) | |
163 | (indent-line-to (* 2 cfengine-indent))) | |
164 | ;; Indent by one step if we're after an action header. | |
165 | ((save-excursion | |
166 | (goto-char (point-min)) | |
167 | (looking-at "[[:alpha:]]+:[ \t]*$")) | |
168 | (indent-line-to cfengine-indent)) | |
169 | ;; Else don't indent. | |
170 | (t | |
171 | (indent-line-to 0)))) | |
172 | ;; If initial point was within line's indentation, | |
173 | ;; position after the indentation. Else stay at same point in text. | |
174 | (if (> (- (point-max) pos) (point)) | |
175 | (goto-char (- (point-max) pos))))) | |
176 | ||
bf247b6e | 177 | ;; This doesn't work too well in Emacs 21.2. See 22.1 development |
5467ec88 DL |
178 | ;; code. |
179 | (defun cfengine-fill-paragraph (&optional justify) | |
180 | "Fill `paragraphs' in Cfengine code." | |
181 | (interactive "P") | |
182 | (or (if (fboundp 'fill-comment-paragraph) | |
183 | (fill-comment-paragraph justify) ; post Emacs 21.3 | |
184 | ;; else do nothing in a comment | |
185 | (nth 4 (parse-partial-sexp (save-excursion | |
186 | (beginning-of-defun) | |
187 | (point)) | |
188 | (point)))) | |
189 | (let ((paragraph-start | |
190 | ;; Include start of parenthesized block. | |
191 | "\f\\|[ \t]*$\\|.*\(") | |
192 | (paragraph-separate | |
193 | ;; Include action and class lines, start and end of | |
194 | ;; bracketed blocks and end of parenthesized blocks to | |
195 | ;; avoid including these in fill. This isn't ideal. | |
196 | "[ \t\f]*$\\|.*#\\|.*[\){}]\\|\\s-*[[:alpha:]_().|!]+:") | |
197 | fill-paragraph-function) | |
198 | (fill-paragraph justify)) | |
199 | t)) | |
200 | ||
201 | ;;;###autoload | |
202 | (define-derived-mode cfengine-mode fundamental-mode "Cfengine" | |
203 | "Major mode for editing cfengine input. | |
204 | There are no special keybindings by default. | |
205 | ||
206 | Action blocks are treated as defuns, i.e. \\[beginning-of-defun] moves | |
207 | to the action header." | |
208 | (modify-syntax-entry ?# "<" cfengine-mode-syntax-table) | |
209 | (modify-syntax-entry ?\n ">#" cfengine-mode-syntax-table) | |
210 | ;; Shell commands can be quoted by single, double or back quotes. | |
211 | ;; It's debatable whether we should define string syntax, but it | |
212 | ;; should avoid potential confusion in some cases. | |
213 | (modify-syntax-entry ?\" "\"" cfengine-mode-syntax-table) | |
214 | (modify-syntax-entry ?\' "\"" cfengine-mode-syntax-table) | |
215 | (modify-syntax-entry ?\` "\"" cfengine-mode-syntax-table) | |
216 | ;; variable substitution: | |
217 | (modify-syntax-entry ?$ "." cfengine-mode-syntax-table) | |
218 | ;; Doze path separators: | |
7efd03c3 | 219 | (modify-syntax-entry ?\\ "." cfengine-mode-syntax-table) |
5467ec88 DL |
220 | ;; Otherwise, syntax defaults seem OK to give reasonable word |
221 | ;; movement. | |
222 | ||
223 | (set (make-local-variable 'parens-require-spaces) nil) | |
5467ec88 DL |
224 | (set (make-local-variable 'comment-start) "# ") |
225 | (set (make-local-variable 'comment-start-skip) | |
226 | "\\(\\(?:^\\|[^\\\\\n]\\)\\(?:\\\\\\\\\\)*\\)#+[ \t]*") | |
227 | (set (make-local-variable 'indent-line-function) #'cfengine-indent-line) | |
228 | (set (make-local-variable 'outline-regexp) "[ \t]*\\(\\sw\\|\\s_\\)+:+") | |
229 | (set (make-local-variable 'outline-level) #'cfengine-outline-level) | |
230 | (set (make-local-variable 'fill-paragraph-function) | |
231 | #'cfengine-fill-paragraph) | |
232 | (define-abbrev-table 'cfengine-mode-abbrev-table cfengine-mode-abbrevs) | |
5467ec88 | 233 | (setq font-lock-defaults |
cf38dd42 SM |
234 | '(cfengine-font-lock-keywords nil nil nil beginning-of-line)) |
235 | ;; Fixme: set the args of functions in evaluated classes to string | |
236 | ;; syntax, and then obey syntax properties. | |
237 | (set (make-local-variable 'syntax-propertize-function) | |
238 | ;; In the main syntax-table, \ is marked as a punctuation, because | |
239 | ;; of its use in DOS-style directory separators. Here we try to | |
240 | ;; recognize the cases where \ is used as an escape inside strings. | |
241 | (syntax-propertize-rules ("\\(\\(?:\\\\\\)+\\)\"" (1 "\\")))) | |
5467ec88 DL |
242 | (setq imenu-generic-expression cfengine-imenu-expression) |
243 | (set (make-local-variable 'beginning-of-defun-function) | |
244 | #'cfengine-beginning-of-defun) | |
f7da2617 JB |
245 | (set (make-local-variable 'end-of-defun-function) #'cfengine-end-of-defun) |
246 | ;; Like Lisp mode. Without this, we lose with, say, | |
247 | ;; `backward-up-list' when there's an unbalanced quote in a | |
248 | ;; preceding comment. | |
249 | (set (make-local-variable 'parse-sexp-ignore-comments) t)) | |
5467ec88 DL |
250 | |
251 | (provide 'cfengine) | |
252 | ||
7efd03c3 | 253 | ;; arch-tag: 6b931be2-1505-4124-afa6-9675971e26d4 |
5467ec88 | 254 | ;;; cfengine.el ends here |