Appendix A. Editing CFEngine 3 Configurations in Emacs

Ted Zlatanov

If you are an Emacs user, you will not be surprised to learn that there is an Emacs mode for editing CFEngine configuration files. In this Appendix you will learn how to set up and use the CFEngine editing mode in Emacs.

Setting Up

You need GNU Emacs 23.1 or higher. Earlier versions or XEmacs may work as well, but have not been extensively tested.

First, you need to download cfengine.el from https://raw.github.com/cfengine/core/master/contrib/cfengine.el. Emacs currently includes an older version of this file. Emacs 24.2 and higher, when released, will include the latest version, but 24.1 and earlier do not, so you need to manually download it.

Assuming you have downloaded the library to your computer, you can add it to your load path. Follow the generic instructions from the Emacs wiki, or just put the following code in your .emacs file:

(autoload 'cfengine-mode "cfengine" "cfengine editing" t)
(add-to-list 'load-path "/directory/where/the/library/lives/")

You will probably want to tell Emacs to associate .cf files with the cfengine-mode, although that could cause problems if you use other files ending with .cf. See http://www.emacswiki.org/emacs/AutoModeAlist for more information or just add the following line to your .emacs file:

(add-to-list 'auto-mode-alist '("\\.cf\\'" . cfengine-mode))

If you use other files ending with .cf and would prefer not to associate this extension with cfengine-mode by default, just run M-x cfengine-mode after you open the file and it will switch to the CFEngine mode.

The Emacs Lisp function cfengine-mode is actually a wrapper that tries to figure out if you are editing CFEngine 3 or CFEngine 2 configurations. If you open a blank file, it will assume CFEngine 2, but the next time it has some text to examine, it will do the right thing. To eliminate the uncertainty, replace cfengine-mode with cfengine3-mode in the previous section. It does the exact same thing except it always assumes CFEngine 3 syntax.

After you open a file with cfengine-mode, in the Emacs modeline (the summary status bar under the content) you will see CFE2 or CFE3. That tells you unambiguously which one is the active mode.

You can now edit the file. Hurrah! Type your masterpiece. The CFEngine mode offers some very useful commands. The following list summarize those I use a lot, but the Emacs command set contains a lot more that can apply. Each command is shown with both its default keybinding and the name of the function that invokes it.

Once you are in cfengine-mode, syntax highlighting will be done according to CFEngine syntax, and indentation will be done according to the parameters described in the following section.

There are many other editing commands available in cfengine-mode that are inherited from the Emacs general text editing facilities. Explore Emacs and you will be able to edit not just CFEngine configurations, but other kinds of text and code easily and efficiently.

General information about Emacs can be found in its copious Info files, and in Learning GNU Emacs, by Debra Cameron, James Elliott, Marc Loy, Eric S. Raymond, and Bill Rosenblatt (O’Reilly).

You need to customize only two parameters to control indentation in cfengine-mode. Do this by typing M-x customize-variable and then typing the parameter name (you can use TAB completion so you don’t have to type it out).

cfengine-indent

The size in spaces of one “indentation step.” Default value is 2.

The indentation step is the basic unit of measure for indenting different parts of a CFEngine policy:

Note that these indentation steps remain even when one of the elements before is not present. This means that a promise will always be indented three steps, even when it is not preceded by a class selector line. The idea is that all the elements of the same type are indented consistently throughout the policy file.

cfengine-parameters-indent

Indentation of CFEngine 3 promise parameters and of attributes in body declarations.

This is the screen you will see when you choose to customize this variable:

Cfengine Parameters Indent:
Choice: [Value Menu] Anchor at beginning of promise
Choice: [Value Menu] Indent parameter name
Indentation amount from anchor: 0

To understand what the different parts of the parameter mean, consider this code as reference:

bundle x y
{
  section:
    class::
      "promiser"
      promiseparameter => value;
}

In the first choice, you select whether promiseparameter will be anchored “at the beginning of the line” (absolutely) or “at the beginning of the promise” (relative to "promiser"). In the second choice, you select whether you want to “indent the parameter name” (the start of the word promiseparameter will be indented to a certain position) or to “indent the arrow” (this means that the position of the arrow that separates the parameter and its value will be calculated, and the rest of the line will be oriented around it). Finally, you can choose the amount of the indentation, in spaces.

The default is to anchor at promise, indent parameter name, and offset by 0 characters, which results in this (observe that, according to these parameters, the promise attributes, comment and perms, start at the same column as the promiser "/tmp/netrc"):

bundle agent rcfiles
{
  files:
    any::
      "/tmp/netrc"
      comment => "my netrc",
      perms => mog("600", "tzz", "tzz");
}

If we change the offset to 2, we get this (promise attributes are indented two spaces with respect to the promiser):

bundle agent rcfiles
{
  files:
    any::
      "/tmp/netrc"
        comment => "my netrc",
        perms => mog("600", "tzz", "tzz");
}

If we choose “anchor at the beginning of line,” “indent the arrow,” and offset by 10, we get this (the arrows in the parameters start at column 10 counted from the beginning of the line):

bundle agent rcfiles
{
  files:
    any::
      "/tmp/netrc"
  comment => "my netrc",
    perms => mog("600", "tzz", "tzz");
}

Some coders like to “anchor at promise,” “indent arrow,” and offset 16 (in this case, the arrows start 16 columns after the promiser):

bundle agent rcfiles
{
  files:
    any::
      "/tmp/netrc"
              comment => "my netrc",
                perms => mog("600", "tzz", "tzz");
}