From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp1 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms0.migadu.com with LMTPS id QNyUK9/6vWEvNAAAgWs5BA (envelope-from ) for ; Sat, 18 Dec 2021 16:14:39 +0100 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp1 with LMTPS id GNcqJ9/6vWFzSQAAbx9fmQ (envelope-from ) for ; Sat, 18 Dec 2021 15:14:39 +0000 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 3DA02B073 for ; Sat, 18 Dec 2021 16:14:39 +0100 (CET) Received: from localhost ([::1]:47932 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mybPx-0007UD-Ni for larch@yhetil.org; Sat, 18 Dec 2021 10:14:37 -0500 Received: from eggs.gnu.org ([209.51.188.92]:45102) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mybOT-0004YD-6e for guix-patches@gnu.org; Sat, 18 Dec 2021 10:13:07 -0500 Received: from debbugs.gnu.org ([209.51.188.43]:60273) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mybOQ-0007FP-Qy for guix-patches@gnu.org; Sat, 18 Dec 2021 10:13:04 -0500 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1mybOQ-0002Bl-Mo for guix-patches@gnu.org; Sat, 18 Dec 2021 10:13:02 -0500 X-Loop: help-debbugs@gnu.org Subject: [bug#52600] [PATCH] doc: Document (gnu services configuration). Resent-From: Xinglu Chen Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Sat, 18 Dec 2021 15:13:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 52600 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 52600@debbugs.gnu.org Cc: Ludovic =?UTF-8?Q?Court=C3=A8s?= , Maxim Cournoyer , Andrew Tropin X-Debbugs-Original-To: guix-patches@gnu.org Received: via spool by submit@debbugs.gnu.org id=B.16398403808403 (code B ref -1); Sat, 18 Dec 2021 15:13:02 +0000 Received: (at submit) by debbugs.gnu.org; 18 Dec 2021 15:13:00 +0000 Received: from localhost ([127.0.0.1]:43586 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1mybOO-0002BS-30 for submit@debbugs.gnu.org; Sat, 18 Dec 2021 10:13:00 -0500 Received: from lists.gnu.org ([209.51.188.17]:45140) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1mybOL-0002BJ-Rh for submit@debbugs.gnu.org; Sat, 18 Dec 2021 10:12:58 -0500 Received: from eggs.gnu.org ([209.51.188.92]:45082) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mybOG-0004UH-H6 for guix-patches@gnu.org; Sat, 18 Dec 2021 10:12:55 -0500 Received: from h87-96-130-155.cust.a3fiber.se ([87.96.130.155]:37554 helo=mail.yoctocell.xyz) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mybOD-0007Dr-Ob; Sat, 18 Dec 2021 10:12:52 -0500 From: Xinglu Chen DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=yoctocell.xyz; s=mail; t=1639840361; bh=yHmI8mrHsfWr/ITSy6dOpUUJ0laUd4YGDTAHiUKIMXo=; h=From:To:Cc:Subject:Date; b=GMc/ucYF1EUvCurvpxlS60/8R2VLMUvTq1nn0pM8xquH/7Iuxhk/XxwGS5ib2NQY6 RPowxXCD784m33B+h2BINdSHVbGqxLHqow4Ca+/e0axBqOmRn2dbFOULBoGA9/fhoJ Ab9Nah5w8tQd80YuOmowqKpLlIytrUldLu+qhKjg= Message-Id: <665c4d2070de80af1d3594a268f0f6d3fb596d15.1639839498.git.public@yoctocell.xyz> Date: Sat, 18 Dec 2021 16:12:38 +0100 MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Received-SPF: pass client-ip=87.96.130.155; envelope-from=public@yoctocell.xyz; helo=mail.yoctocell.xyz X-Spam_score_int: 53 X-Spam_score: 5.3 X-Spam_bar: +++++ X-Spam_report: (5.3 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FROM_SUSPICIOUS_NTLD=0.498, FROM_SUSPICIOUS_NTLD_FP=0.295, PDS_OTHER_BAD_TLD=1.997, PDS_RDNS_DYNAMIC_FP=0.001, RCVD_IN_PBL=3.335, RDNS_DYNAMIC=0.982, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, TO_NO_BRKTS_DYNIP=0.245 autolearn=no autolearn_force=no X-Spam_action: reject X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+larch=yhetil.org@gnu.org Sender: "Guix-patches" X-Migadu-Flow: FLOW_IN X-Migadu-Country: US ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1639840479; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding:resent-cc: resent-from:resent-sender:resent-message-id:list-id:list-help: list-unsubscribe:list-subscribe:list-post:dkim-signature; bh=q0cFmkNc60rJJE5ja4l+ACnILZws4Ohd6/VNHU8QRj8=; b=e1iscNWwInxH5yswGEDZkKgqRh70Wmen5hNted8TNCSzNmI2p20FW278nrPtPB2OvWtes2 IVmZYnDHFaxSBU4M+BfcLfyNC8WzV3hSnw+fPWNZ08kLiDf0S0CEJcUf0np/MQBNR9JMmD it9p+Ej5bdltoRYCq1lTljc29BPcxEnNsNSIxxTmqO9Tlho7Uoe/2Gsn4jB5IEMrP9Mpm/ KdJohT+B7GweNNasdFRZwtfaxtsrMFoNyuQZhSc/bkcpoye9QuCwgLxDz6AQYADZR7VVPp DWMVwWrA6ftTgZID6+OJR3cBqnDrVHT4OkecYcicgFOWYeaZTZQNVBCU7yzt9A== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1639840479; a=rsa-sha256; cv=none; b=rmsggotj+VNqEGlAqkLpzBYZKjPJv6LzgpGGko7VD75oyax5Ppw3Fl9edKNaalMdK6es56 0P53kIH/6CEwzU7KkkSoUIUC60XDNibe8cJIYmNhgWHeF3rgZNP5EkJf9qrwZi+RbD//Mu tqjzMwJLJ+iC5o60U5Xa+bpFqmUm6KKfIsXef6zmswRn8I6ckuPJbmiaHL9fFJ7zihpJ3/ Zc2edMfa+lQoKrriKoSm3G0RgI0MfV04d7g5TjAVFHk4xzxx0pOO67TzXlNBi4btpY030y V2MpDYwIlve2GhfH0Z7Dwa4vNue42E63+aC7/8NvP6/ILXykmzosQjq4FJwPJg== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=yoctocell.xyz header.s=mail header.b="GMc/ucYF"; dmarc=fail reason="SPF not aligned (relaxed)" header.from=yoctocell.xyz (policy=none); spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org" X-Migadu-Spam-Score: -0.11 Authentication-Results: aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=yoctocell.xyz header.s=mail header.b="GMc/ucYF"; dmarc=fail reason="SPF not aligned (relaxed)" header.from=yoctocell.xyz (policy=none); spf=pass (aspmx1.migadu.com: domain of "guix-patches-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-patches-bounces+larch=yhetil.org@gnu.org" X-Migadu-Queue-Id: 3DA02B073 X-Spam-Score: -0.11 X-Migadu-Scanner: scn0.migadu.com X-TUID: /6BoPXnUEy8p * guix.texi (Complex Configurations): New node. --- Hi! This patch documents the complex beast that is the (gnu services configuration) module. I only documented the things that existed before the Guix Home merge, though. There were a lot of things to document, and I hope that my explanations aren=E2=80=99t too confusing (It took me a whil= e to wrap my head around all of this). :-) What is still missing is some kind of style guide for writing Guix services: When should one use (gnu services configuration) vs plain (guix records)? Should we try to create bindings for all config options or should we provide an =E2=80=9Cescape hatch=E2=80=9D for users? I would personally prefer if most (if not all) services were written using (gnu services configuration), but I don=E2=80=99t really think refact= oring existing services would really be worth it. But that=E2=80=99s another dis= cussion. doc/guix.texi | 372 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) diff --git a/doc/guix.texi b/doc/guix.texi index aca88cdada..79b87d2eac 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -383,6 +383,7 @@ * Service Types and Services:: Types and services. * Service Reference:: API reference. * Shepherd Services:: A particular type of service. +* Complex Configurations:: Defining bindgs for complex configurations. =20 Installing Debugging Files =20 @@ -35656,6 +35657,7 @@ * Service Types and Services:: Types and services. * Service Reference:: API reference. * Shepherd Services:: A particular type of service. +* Complex Configurations:: Defining bindgs for complex configurations. @end menu =20 @node Service Composition @@ -36389,6 +36391,376 @@ This service represents PID@tie{}1. @end defvr =20 +@node Complex Configurations +@subsection Complex Configurations +@cindex 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 @code{(gnu services +configuration)} module. + +The main utility is the @code{define-configuration} macro, which you +will use to define a Scheme record type (@pxref{Record Overview,,, +guile, GNU Guile Reference Manual}). The Scheme record will be +serialized to a configuration file by using @dfn{serializers}, which are +procedures that take some kind of Scheme value and returns a +G-expression (@pxref{G-Expressions}), which should, once serialized to +the disk, return a string. More details are listed below. + +@deffn {Scheme Syntax} define-configuration @var{name} @var{clause1} @ +@var{clause2} ... +Create a record type named @code{@var{name}} that contains the +fields found in the clauses. + +A clause can have one the following forms + +@example +(@var{field-name} + (@var{type} @var{default-value}) + @var{documentation}) +=20 +(@var{field-name} + (@var{type} @var{default-value}) + @var{documentation} + @var{serializer}) + +(@var{field-name} + (@var{type}) + @var{documentation}) + +(@var{field-name} + (@var{type}) + @var{documentation} + @var{serializer}) +@end example + +@var{field-name} is an identifier that denotes the name of the field in +the generated record. + +@var{type} is the type of the value corresponding to @var{field-name}; +since Guile is untyped, a predicate +procedure---@code{@var{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, @var{type} is @code{package}, then a +procedure named @code{package?} will be applied on the value to make +sure that it is indeed a @code{} object. + +@var{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. + +@c XXX: Should these be full sentences or are they allow to be very +@c short like package synopses? +@var{documentation} is a string formatted with Texinfo syntax which +should provide a description of what setting this field does. + +@var{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 (@pxref{G-Expressions}) that represents the content that +will be serialized to the configuration file. If none is specified, a +procedure of the name @code{serialize-@var{type}} will be used. + +A simple serializer procedure could look like this. + +@lisp +(define (serialize-boolean field-name value) + (let ((value (if value "true" "false"))) + #~(string-append #$field-name #$value))) +@end lisp=20=20 + +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 @code{serialize-boolean} procedure for the Getmail service +would have to be different for the one for the Transmission service. To +make it easier to deal with this situation, one can specify a serializer +prefix by using the @code{prefix} literal in the +@code{define-configuration} form. This means that one doesn't have to +manually specify a custom @var{serializer} for every field. + +@lisp +(define (foo-serialize-string field-name value) + @dots{}) + +(define (bar-serialize-string field-name value) + @dots{}) +=20=20 +(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-)) +@end lisp + +However, in some cases you might not want to serialize any of the values +of the record, to do this, you can use the @code{no-serialization} +literal. There is also the @code{define-configuration/no-serialization} +macro which is a shorthand of this. + +@lisp +;; 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.")) +@end lisp=20=20=20 +@end deffn + +@deffn {Scheme Syntax} define-maybe @var{type} +Sometimes a field should not be serialized if the user doesn=E2=80=99t spe= cify a +value. To achieve this, you can use the @code{define-maybe} macro to +define a ``maybe type''; if the value of a maybe type is set to the +@code{disabled}, 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 +@code{maybe-string} will be serialized using the @code{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, unless it is set to the @code{disabled} symbol. + +@lisp +(define-maybe string) + +(define (serialize-string field-name value) + @dots{}) + +(define-configuration baz-configuration + (name + ;; Nothing will be serialized by default. If set to a string, the + ;; `serialize-string' procedure will be used to serialize the string. + (maybe-string 'disabled) + "The name of this module.")) +@end lisp + +Like with @code{define-configuration}, one can set a prefix for the +serializer name by using the @code{prefix} literal. + +@lisp +(define-maybe integer + (prefix baz-)) + +(define (baz-serialize-interger field-name value) + @dots{}) +@end lisp + +There is also the @code{no-serialization} literal, which when set means +that no serializer will be defined for the ``maybe type'', regardless of +its value is @code{disabled} or not. +@code{define-maybe/no-serialization} is a shorthand for specifying the +@code{no-serialization} literal. + +@lisp +(define-maybe/no-serialization symbol) + +(define-configuration/no-serialization test-configuration + (mode + (maybe-symbol 'disabled) + "Docstring.")) +@end lisp +@end deffn + +@deffn {Scheme Procedure} serialize-configuration @var{configuration} @ +@var{fields} +Return a G-expression that contains the values corresponding to the +@var{fields} of @var{configuration}, a record that has been generated by +@code{define-configuration}. The G-expression can then be serialized to +disk by using something like @code{mixed-text-file}. +@end deffn + +@deffn {Scheme Procedure} validate-configuration @var{configuration} +@var{fields} +Type-check @var{fields}, a list of field names of @var{configuration}, a +configuration record created by @code{define-configuration}. +@end deffn + +@deffn {Scheme Procedure} empty-serializer @var{field-name} @var{value} +A serializer that just returns an empty string. The +@code{serialize-package} procedure is an alias for this. +@end deffn + +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. + +@deffn {Scheme Procedure} generate-documentation @var{documentation} @ +@var{documentation-name} +Generate a Texinfo fragment from the docstrings in @var{documentation}, +a list of @code{(@var{label} @var{fields} @var{sub-documentation} ...)}. +@var{label} should be a symbol and should be the name of the +configuration record. @var{fields} should be a list of all the fields +available for the configuration record. + +@var{sub-documentation} is a @code{(@var{field-name} +@var{configuration-name})} tuple. @var{field-name} is the name of the +field which takes another configuration record as its value, and +@var{configuration-name} is the name of that configuration record. + +@var{sub-documentation} is only needed if there are nested configuration +records. For example, the @code{getmail-configuration} record +(@pxref{Mail Services}) accepts a @code{getmail-configuration-file} +record in one of its @code{rcfile} field, therefore documentation for +@code{getmail-configuration-file} is nested in +@code{getmail-configuration}. + +@lisp +(generate-documentation + `((getmail-configuration ,getmail-configuration-fields + (rcfile getmail-configuration-file)) + @dots{}) + 'getmail-configuration) +@end lisp + +@var{documentation-name} should be a symbol and should be the name of +the configuration record. + +@end deffn + +@deffn {Scheme Procedure} configuration->documentation +@var{configuration-symbol} +Take @var{configuration-symbol}, the symbol corresponding to the name +used when defining a configuration record with +@code{define-configuration}, and print the Texinfo documentation of its +fields. This is useful if there aren=E2=80=99t any nested configuration r= ecords +since it only prints the documentation for the top-level fields. +@end deffn + +As of right now, there is no automated way to generate documentation for +and 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 @code{generate-documentation} or +@code{configuration->documentation}, and paste the output into the +@file{doc/guix.texi} file. + +@c TODO: Actually test this +Below is an example of a record type created using +@code{define-configuration} and friends. + +@lisp +(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) " =3D " #$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 'disabled) + "The person's phone number.") + (email + (maybe-string 'disabled) + "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.")) +@end lisp + +A contacts list configuration could then be created like this: + +@lisp +(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)))))) +@end lisp + +After serializing the configuration to disk, the resulting file would +look like this: + +@example +[owner] +name =3D Alice +email =3D alice@@example.org + +[Bob] +phone-number =3D 1234 +email =3D bob@@gnu.org +is-married =3D false + +[Charlie] +phone-number =3D 0 +is-married =3D true +@end example + + @node Home Configuration @chapter Home Configuration @cindex home configuration base-commit: 6061540e30269934dae3395ab9fc1b905a414247 --=20 2.33.1