Previous: , Up: Создание служб   [Contents][Index]


11.19.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, a helper used to define a Scheme record type (see Record Overview in GNU Guile Reference Manual). The fields from this Scheme record can be serialized using serializers, which are procedures that take some kind of Scheme value and translates them into another Scheme value or G-Expressions.

Macro: define-configuration name clause1 clause2 …

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

A clause has the following form:

(field-name
 type-decl
 documentation
 option*
 …)

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

type-decl is either type for fields that require a value to be set or (type default-value) otherwise.

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.

option* is one of the following subclauses:

empty-serializer

Exclude this field from serialization.

(serializer serializer)

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-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.

An example of a simple serializer procedure:

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

sanitizer is a procedure which takes one argument, a user-supplied value, and returns a “sanitized” value for the field. If no sanitizer is specified, a default sanitizer is used, which raises an error if the value is not of type type.

An example of a sanitizer for a field that accepts both strings and symbols looks like this:

(define (sanitize-foo value)
  (cond ((string? value) value)
        ((symbol? value) (symbol->string value))
        (else (error "bad 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."))
Macro: 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."))
Procedure: maybe-value-set? value

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

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.

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.

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. The same value may be used for multiple field-names, in case a field accepts different types of configurations.

sub-documentation is only needed if there are nested configuration records. For example, the getmail-configuration record (see Почтовые сервисы) 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.

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-configuration} 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, Up: Создание служб   [Contents][Index]