Previous: Сервисы Shepherd, Up: Создание служб [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 Почтовые сервисы) 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, Up: Создание служб [Contents][Index]