(defgroup cfengine ()
"Editing Cfengine files."
:group 'languages)
(defcustom cfengine-indent 2
"*Size of a Cfengine indentation step in columns."
:group 'cfengine
:type 'integer)
(defcustom cfengine-mode-abbrevs nil
"Abbrevs for Cfengine mode."
:group 'cfengine
:type '(repeat (list (string :tag "Name")
(string :tag "Expansion")
(choice :tag "Hook" (const nil) function))))
(eval-and-compile
(defconst cfengine-actions
'("acl" "alerts" "binservers" "broadcast" "control" "classes" "copy"
"defaultroute" "disks" "directories" "disable" "editfiles" "files"
"filters" "groups" "homeservers" "ignore" "import" "interfaces"
"links" "mailserver" "methods" "miscmounts" "mountables"
"processes" "packages" "rename" "required" "resolve"
"shellcommands" "tidy" "unmount"
"admit" "grant" "deny")
"List of the action keywords supported by Cfengine.
This includes those for cfservd as well as cfagent."))
(defvar cfengine-font-lock-keywords
`( (,(concat "^[ \t]*" (eval-when-compile
(regexp-opt cfengine-actions t))
":")
1 font-lock-keyword-face)
("^[ \t]*\\([[:alnum:]_().|!]+\\)::" 1 font-lock-function-name-face)
("$(\\([[:alnum:]_]+\\))" 1 font-lock-variable-name-face)
("${\\([[:alnum:]_]+\\)}" 1 font-lock-variable-name-face)
("\\<\\([[:alnum:]_]+\\)[ \t]*=[ \t]*(" 1 font-lock-variable-name-face)
("{[ \t]*\\([^ \t\n]+\\)" 1 font-lock-constant-face)))
(defconst cfengine-font-lock-syntactic-keywords
'(("\\(\\(?:\\\\\\)+\\)\"" 1 "\\")))
(defvar cfengine-imenu-expression
`((nil ,(concat "^[ \t]*" (eval-when-compile
(regexp-opt cfengine-actions t))
":[^:]")
1)
("Variables/classes" "\\<\\([[:alnum:]_]+\\)[ \t]*=[ \t]*(" 1)
("Variables/classes" "\\<define=\\([[:alnum:]_]+\\)" 1)
("Variables/classes" "\\<DefineClass\\>[ \t]+\\([[:alnum:]_]+\\)" 1))
"`imenu-generic-expression' for Cfengine mode.")
(defun cfengine-outline-level ()
"`outline-level' function for Cfengine mode."
(if (looking-at "[^:]+\\(?:[:]+\\)$")
(length (match-string 1))))
(defun cfengine-beginning-of-defun ()
"`beginning-of-defun' function for Cfengine mode.
Treats actions as defuns."
(unless (<= (current-column) (current-indentation))
(end-of-line))
(if (re-search-backward "^[[:alpha:]]+: *$" nil t)
(beginning-of-line)
(goto-char (point-min)))
t)
(defun cfengine-end-of-defun ()
"`end-of-defun' function for Cfengine mode.
Treats actions as defuns."
(end-of-line)
(if (re-search-forward "^[[:alpha:]]+: *$" nil t)
(beginning-of-line)
(goto-char (point-max)))
t)
(defun cfengine-indent-line ()
"Indent a line in Cfengine mode.
Intended as the value of `indent-line-function'."
(let ((pos (- (point-max) (point))))
(save-restriction
(narrow-to-defun)
(back-to-indentation)
(cond
((looking-at "[[:alnum:]_().|!]+:\\(:\\)?")
(if (match-string 1)
(indent-line-to cfengine-indent)
(indent-line-to 0)))
((or (eq ?\} (char-after))
(eq ?\) (char-after)))
(condition-case ()
(indent-line-to (save-excursion
(forward-char)
(backward-sexp)
(current-column)))
(error nil)))
((condition-case ()
(progn (indent-line-to (save-excursion
(backward-up-list)
(forward-char)
(skip-chars-forward " \t")
(if (looking-at "[^\n#]")
(current-column)
(skip-chars-backward " \t")
(+ (current-column) -1
cfengine-indent))))
t)
(error nil)))
((save-excursion
(re-search-backward "^[ \t]*[[:alnum:]_().|!]+::" nil t))
(indent-line-to (* 2 cfengine-indent)))
((save-excursion
(goto-char (point-min))
(looking-at "[[:alpha:]]+:[ \t]*$"))
(indent-line-to cfengine-indent))
(t
(indent-line-to 0))))
(if (> (- (point-max) pos) (point))
(goto-char (- (point-max) pos)))))
(defun cfengine-fill-paragraph (&optional justify)
"Fill `paragraphs' in Cfengine code."
(interactive "P")
(or (if (fboundp 'fill-comment-paragraph)
(fill-comment-paragraph justify) (nth 4 (parse-partial-sexp (save-excursion
(beginning-of-defun)
(point))
(point))))
(let ((paragraph-start
"\f\\|[ \t]*$\\|.*\(")
(paragraph-separate
"[ \t\f]*$\\|.*#\\|.*[\){}]\\|\\s-*[[:alpha:]_().|!]+:")
fill-paragraph-function)
(fill-paragraph justify))
t))
(define-derived-mode cfengine-mode fundamental-mode "Cfengine"
"Major mode for editing cfengine input.
There are no special keybindings by default.
Action blocks are treated as defuns, i.e. \\[beginning-of-defun] moves
to the action header."
(modify-syntax-entry ?# "<" cfengine-mode-syntax-table)
(modify-syntax-entry ?\n ">#" cfengine-mode-syntax-table)
(modify-syntax-entry ?\" "\"" cfengine-mode-syntax-table)
(modify-syntax-entry ?\' "\"" cfengine-mode-syntax-table)
(modify-syntax-entry ?\` "\"" cfengine-mode-syntax-table)
(modify-syntax-entry ?$ "." cfengine-mode-syntax-table)
(modify-syntax-entry ?\\ "." cfengine-mode-syntax-table)
(set (make-local-variable 'parens-require-spaces) nil)
(set (make-local-variable 'require-final-newline) mode-require-final-newline)
(set (make-local-variable 'comment-start) "# ")
(set (make-local-variable 'comment-start-skip)
"\\(\\(?:^\\|[^\\\\\n]\\)\\(?:\\\\\\\\\\)*\\)#+[ \t]*")
(set (make-local-variable 'indent-line-function) #'cfengine-indent-line)
(set (make-local-variable 'outline-regexp) "[ \t]*\\(\\sw\\|\\s_\\)+:+")
(set (make-local-variable 'outline-level) #'cfengine-outline-level)
(set (make-local-variable 'fill-paragraph-function)
#'cfengine-fill-paragraph)
(define-abbrev-table 'cfengine-mode-abbrev-table cfengine-mode-abbrevs)
(setq font-lock-defaults
'(cfengine-font-lock-keywords nil nil nil beginning-of-line
(font-lock-syntactic-keywords
. cfengine-font-lock-syntactic-keywords)))
(setq imenu-generic-expression cfengine-imenu-expression)
(set (make-local-variable 'beginning-of-defun-function)
#'cfengine-beginning-of-defun)
(set (make-local-variable 'end-of-defun-function) #'cfengine-end-of-defun)
(set (make-local-variable 'parse-sexp-ignore-comments) t))
(provide 'cfengine)