Previous: , Up: Defining Services   [Contents][Index]


12.18.5 Complex Configurations

Some programs might have rather complex configuration files or formats, and to make it easier to create Scheme bindings for these configuration files, you can use the utilities defined in the (gnu services configuration) module.

The main utility is the define-configuration macro, which you will use to define a Scheme record type (see Record Overview in GNU Guile Reference Manual). The Scheme record will be serialized to a configuration file by using serializers, which are procedures that take some kind of Scheme value and returns a G-expression (see G-Expressions), which should, once serialized to the disk, return a string. More details are listed below.

Scheme Syntax: define-configuration name clause1 clause2 ...

Create a record type named name that contains the fields found in the clauses.

A clause can have one of the following forms:

(field-name
 (type default-value)
 documentation)
 
(field-name
 (type default-value)
 documentation
 serializer)

(field-name
 (type)
 documentation)

(field-name
 (type)
 documentation
 serializer)

field-name is an identifier that denotes the name of the field in the generated record.

type is the type of the value corresponding to field-name; since Guile is untyped, a predicate procedure—type?—will be called on the value corresponding to the field to ensure that the value is of the correct type. This means that if say, type is package, then a procedure named package? will be applied on the value to make sure that it is indeed a <package> object.

default-value is the default value corresponding to the field; if none is specified, the user is forced to provide a value when creating an object of the record type.

documentation is a string formatted with Texinfo syntax which should provide a description of what setting this field does.

serializer is the name of a procedure which takes two arguments, the first is the name of the field, and the second is the value corresponding to the field. The procedure should return a string or G-expression (see G-Expressions) that represents the content that will be serialized to the configuration file. If none is specified, a procedure of the name serialize-type will be used.

A simple serializer procedure could look like this:

(define (serialize-boolean field-name value)
  (let ((value (if value "true" "false")))
    #~(string-append #$field-name #$value)))

In some cases multiple different configuration records might be defined in the same file, but their serializers for the same type might have to be different, because they have different configuration formats. For example, the serialize-boolean procedure for the Getmail service would have to be different from the one for the Transmission service. To make it easier to deal with this situation, one can specify a serializer prefix by using the prefix literal in the define-configuration form. This means that one doesn’t have to manually specify a custom serializer for every field.

(define (foo-serialize-string field-name value)
  )

(define (bar-serialize-string field-name value)
  )
  
(define-configuration foo-configuration
  (label
   (string)
   "The name of label.")
  (prefix foo-))

(define-configuration bar-configuration
  (ip-address
   (string)
   "The IPv4 address for this device.")
  (prefix bar-))

However, in some cases you might not want to serialize any of the values of the record, to do this, you can use the no-serialization literal. There is also the define-configuration/no-serialization macro which is a shorthand of this.

;; Nothing will be serialized to disk.
(define-configuration foo-configuration
  (field
   (string "test")
   "Some documentation.")
  (no-serialization))

;; The same thing as above.
(define-configuration/no-serialization bar-configuration
  (field
   (string "test")
   "Some documentation."))
Scheme Syntax: define-maybe type

Sometimes a field should not be serialized if the user doesn’t specify a value. To achieve this, you can use the define-maybe macro to define a “maybe type”; if the value of a maybe type is left unset, or is set to the %unset-value value, then it will not be serialized.

When defining a “maybe type”, the corresponding serializer for the regular type will be used by default. For example, a field of type maybe-string will be serialized using the serialize-string procedure by default, you can of course change this by specifying a custom serializer procedure. Likewise, the type of the value would have to be a string, or left unspecified.

(define-maybe string)

(define (serialize-string field-name value)
  )

(define-configuration baz-configuration
  (name
   ;; If set to a string, the `serialize-string' procedure will be used
   ;; to serialize the string.  Otherwise this field is not serialized.
   maybe-string
   "The name of this module."))

Like with define-configuration, one can set a prefix for the serializer name by using the prefix literal.

(define-maybe integer
  (prefix baz-))

(define (baz-serialize-integer field-name value)
  )

There is also the no-serialization literal, which when set means that no serializer will be defined for the “maybe type”, regardless of whether its value is set or not. define-maybe/no-serialization is a shorthand for specifying the no-serialization literal.

(define-maybe/no-serialization symbol)

(define-configuration/no-serialization test-configuration
  (mode
   maybe-symbol
   "Docstring."))
(Scheme: Procedure) maybe-value-set? value

Predicate to check whether a user explicitly specified the value of a maybe field.

Scheme Procedure: serialize-configuration configuration fields

Return a G-expression that contains the values corresponding to the fields of configuration, a record that has been generated by define-configuration. The G-expression can then be serialized to disk by using something like mixed-text-file.

Scheme Procedure: empty-serializer field-name value

A serializer that just returns an empty string. The serialize-package procedure is an alias for this.

Once you have defined a configuration record, you will most likely also want to document it so that other people know to use it. To help with that, there are two procedures, both of which are documented below.

Scheme Procedure: generate-documentation documentation documentation-name

Generate a Texinfo fragment from the docstrings in documentation, a list of (label fields sub-documentation ...). label should be a symbol and should be the name of the configuration record. fields should be a list of all the fields available for the configuration record.

sub-documentation is a (field-name configuration-name) tuple. field-name is the name of the field which takes another configuration record as its value, and configuration-name is the name of that configuration record.

sub-documentation is only needed if there are nested configuration records. For example, the getmail-configuration record (see Mail Services) accepts a getmail-configuration-file record in one of its rcfile field, therefore documentation for getmail-configuration-file is nested in getmail-configuration.

(generate-documentation
  `((getmail-configuration ,getmail-configuration-fields
     (rcfile getmail-configuration-file))
    )
  'getmail-configuration)

documentation-name should be a symbol and should be the name of the configuration record.

Scheme Procedure: configuration->documentation

configuration-symbol Take configuration-symbol, the symbol corresponding to the name used when defining a configuration record with define-configuration, and print the Texinfo documentation of its fields. This is useful if there aren’t any nested configuration records since it only prints the documentation for the top-level fields.

As of right now, there is no automated way to generate documentation for configuration records and put them in the manual. Instead, every time you make a change to the docstrings of a configuration record, you have to manually call generate-documentation or configuration->documentation, and paste the output into the doc/guix.texi file.

Below is an example of a record type created using define-configuration and friends.

(use-modules (gnu services)
             (guix gexp)
             (gnu services configuration)
             (srfi srfi-26)
             (srfi srfi-1))

;; Turn field names, which are Scheme symbols into strings
(define (uglify-field-name field-name)
  (let ((str (symbol->string field-name)))
    ;; field? -> is-field
    (if (string-suffix? "?" str)
        (string-append "is-" (string-drop-right str 1))
        str)))

(define (serialize-string field-name value)
  #~(string-append #$(uglify-field-name field-name) " = " #$value "\n"))

(define (serialize-integer field-name value)
  (serialize-string field-name (number->string value)))

(define (serialize-boolean field-name value)
  (serialize-string field-name (if value "true" "false")))

(define (serialize-contact-name field-name value)
  #~(string-append "\n[" #$value "]\n"))

(define (list-of-contact-configurations? lst)
  (every contact-configuration? lst))

(define (serialize-list-of-contact-configurations field-name value)
  #~(string-append #$@(map (cut serialize-configuration <>
                                contact-configuration-fields)
                           value)))

(define (serialize-contacts-list-configuration configuration)
  (mixed-text-file
   "contactrc"
   #~(string-append "[Owner]\n"
                    #$(serialize-configuration
                       configuration contacts-list-configuration-fields))))

(define-maybe integer)
(define-maybe string)

(define-configuration contact-configuration
  (name
   (string)
   "The name of the contact."
   serialize-contact-name)
  (phone-number
   maybe-integer
   "The person's phone number.")
  (email
   maybe-string
   "The person's email address.")
  (married?
   (boolean)
   "Whether the person is married."))

(define-configuration contacts-list-configuration
  (name
   (string)
   "The name of the owner of this contact list.")
  (email
   (string)
   "The owner's email address.")
  (contacts
   (list-of-contact-configurations '())
   "A list of @code{contact-configuation} records which contain
information about all your contacts."))

A contacts list configuration could then be created like this:

(define my-contacts
  (contacts-list-configuration
   (name "Alice")
   (email "alice@example.org")
   (contacts
    (list (contact-configuration
           (name "Bob")
           (phone-number 1234)
           (email "bob@gnu.org")
           (married? #f))
          (contact-configuration
           (name "Charlie")
           (phone-number 0000)
           (married? #t))))))

After serializing the configuration to disk, the resulting file would look like this:

[owner]
name = Alice
email = alice@example.org

[Bob]
phone-number = 1234
email = bob@gnu.org
is-married = false

[Charlie]
phone-number = 0
is-married = true

Previous: Shepherd Services, Up: Defining Services   [Contents][Index]