Previous: Shepherd Services, Up: Defining Services [Contents][Index]
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.
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.
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."))
Predicate to check whether a user explicitly specified the value of a maybe field.
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.
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 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.
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 Services, Up: Defining Services [Contents][Index]