From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp12.migadu.com ([2001:41d0:403:4789::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms9.migadu.com with LMTPS id YK+4HvYWG2XteQAAauVa8A:P1 (envelope-from ) for ; Mon, 02 Oct 2023 21:16:06 +0200 Received: from aspmx1.migadu.com ([2001:41d0:403:4789::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp12.migadu.com with LMTPS id YK+4HvYWG2XteQAAauVa8A (envelope-from ) for ; Mon, 02 Oct 2023 21:16:06 +0200 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 EB2E54AB47 for ; Mon, 2 Oct 2023 21:16:05 +0200 (CEST) Authentication-Results: aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20230601 header.b=cEx2jw0F; 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"; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none) ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1696274166; 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:in-reply-to:in-reply-to: references:references:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=Xr5XTBvVispbh+w+ddG7Jwlfq33q8lF4KBxDXlxLFYg=; b=aXU/dtLRhjWxL8UgeKQ9fq4X0NUOXfIVRP1X25acXxA5R+SgIUdFqZ6y1wAlVmW8vBb4Wi SbS/q8x08SeAWCEA2KyDHyym4JyFw3Ha+gtHEudwTUeH+PDHgYRBMES215jlnN+TsAatJX UhYK84HByWit8ecgxbY5RjLBDenbfpCB/y7Hi1JVE1l8iXAFxj6Z8rTpMnujYDMhoivgkd hTt7ifreVo9hTWYLvqGe92+zGEu+SlyG2YTlgk8VYG37xEFg4yMz/vYygF/JdHtQQIUP1M g0/HBXCY1qtDMY67gKM/IYmLQTmSvH0so4OAGahMBYdfCw5L+Fu/tl72VQPhRg== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1696274166; a=rsa-sha256; cv=none; b=MuSQcMyVQZfjWV1qzUxGpdBu6D30bBi+ajULE6Lme4/LIIrY8J9g9fKq2a8oV+TVHitP07 0x9DvR6BX6DhzI/vzwVi6pz4YxumjqXaqdTP/A7Cwk2YdSKwZ//9UlMR9bxyV5T2hRaKr4 k8iipNPVh6cldg3NMsG5qzTj/+ModKxdbHiQYRU9bIS6onpryPwgeaGKU3jN6tdOEdAe9R d5C0jc10mpAfnGWphqIOmeT1Z84pWX2rSB9vsE0ueDLEGspZeZxDAT+KJgoMdW5fvhU55B fcZncBBxbQyfByTqsjd1BXXIuhaMS9c4U6ICOuq7YVyz39JsltfZc6v2yWwTYA== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20230601 header.b=cEx2jw0F; 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"; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qnOOe-0001Bh-Aa; Mon, 02 Oct 2023 15:16:00 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qnOOT-00017d-Rk for guix-patches@gnu.org; Mon, 02 Oct 2023 15:15:54 -0400 Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1qnOOO-0002o9-VM for guix-patches@gnu.org; Mon, 02 Oct 2023 15:15:48 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1qnOOf-0004Gt-Lf for guix-patches@gnu.org; Mon, 02 Oct 2023 15:16:01 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#63985] [PATCH RFC 0/5] Generic INI serializer & SRFI-171 for define-configuration Resent-From: Maxim Cournoyer Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Mon, 02 Oct 2023 19:16:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 63985 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: Bruno Victal Cc: 63985@debbugs.gnu.org Received: via spool by 63985-submit@debbugs.gnu.org id=B63985.169627413216375 (code B ref 63985); Mon, 02 Oct 2023 19:16:01 +0000 Received: (at 63985) by debbugs.gnu.org; 2 Oct 2023 19:15:32 +0000 Received: from localhost ([127.0.0.1]:38011 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qnOOB-0004G2-A4 for submit@debbugs.gnu.org; Mon, 02 Oct 2023 15:15:32 -0400 Received: from mail-qv1-xf33.google.com ([2607:f8b0:4864:20::f33]:50298) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qnOO9-0004Fl-8C for 63985@debbugs.gnu.org; Mon, 02 Oct 2023 15:15:30 -0400 Received: by mail-qv1-xf33.google.com with SMTP id 6a1803df08f44-65d0da28fa8so763166d6.0 for <63985@debbugs.gnu.org>; Mon, 02 Oct 2023 12:15:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1696274106; x=1696878906; darn=debbugs.gnu.org; h=content-transfer-encoding:mime-version:user-agent:message-id :in-reply-to:date:references:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to; bh=axzhz9AgpaStvrG/HAWdHc+woBmktbHiKx5zy8lxi/g=; b=cEx2jw0FSj6nvFfVT35Ak8yb6kMsgfr8vZ/dLtU8KZMP3/csrGKxKIIZB38mjXT3pd wr4NnGUW1DAYeseXHeoPhy92VWWmdFk+/pOzxDzNcAhpa+T2qacYHq4BTWsGe7Tr+eqf +QTScuBtCiGKCpVo4Dw1GCoAVB9u+ZPUkM9AnaI37OQX2ifNdq0Ja0gPLOdmICl59UMn LmcgMCUVEX12ZoBnBzln6vL0MgHegoVb53tp50wh3+YUms2cTItV8fQOt5rzJbDiYssI ROvugygjoJE+rGZ5zWtRC0lo8Yl687uLOiPmq86YmAxhJzDPbaoEBkl2nj4qCXOUBpld 6xaQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1696274106; x=1696878906; h=content-transfer-encoding:mime-version:user-agent:message-id :in-reply-to:date:references:subject:cc:to:from:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=axzhz9AgpaStvrG/HAWdHc+woBmktbHiKx5zy8lxi/g=; b=Cky+3v1IY5rBAPwJ67t4PVhSbjKIxqJIN/XOXbFE9pXZ9wkOgU4Ju9vQhtTMrwNQt9 PtgKARRn5ipsuPwRfX7/iKXzpitSakfX5iVjTWVHELbpIVzdqaO9VewL5hEH7pIbm7Hx y4455nxbbnf1J2n9JvpW0dG0IxvHZhqJm5DH33lxmaW8ux1wPjX3pirc8jCw+GR89rbu wKHaj9h/DXCNt2/LF6M745v5Mop3llYiYg/et4TbLVP8GsHcuFCnASNB593uF/7g/xnh 0MkJospP6hDgiiBaGuJV8NMnODeaKr5DcA9x8Igzai+fhV1lFbGwVHHAXY3FcqI/A4+Z 19ig== X-Gm-Message-State: AOJu0YzUsf+Vr1+f0s5Jxj1cf443qN0IddhABGFNE8KhXTVax1j6xUx3 b2rpYs9aAVhkFhyQSsJgScgHyakS8jw= X-Google-Smtp-Source: AGHT+IGH5B1d3FUZzIfIl9LatS/pennKbaDdzglsD2BejY4MLxW4zlUh+f4YmxL3/WIlbpmHBKjo6A== X-Received: by 2002:a0c:a998:0:b0:65b:a20:ed5f with SMTP id a24-20020a0ca998000000b0065b0a20ed5fmr14333503qvb.27.1696274106393; Mon, 02 Oct 2023 12:15:06 -0700 (PDT) Received: from hurd (dsl-10-136-90.b2b2c.ca. [72.10.136.90]) by smtp.gmail.com with ESMTPSA id y17-20020a0c9a91000000b0065afd35c762sm6593143qvd.91.2023.10.02.12.15.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Oct 2023 12:15:05 -0700 (PDT) From: Maxim Cournoyer References: Date: Mon, 02 Oct 2023 15:15:04 -0400 In-Reply-To: (Bruno Victal's message of "Mon, 26 Jun 2023 22:59:32 +0100") Message-ID: <87h6n83kg7.fsf_-_@gmail.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable 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-bounces+larch=yhetil.org@gnu.org X-Migadu-Country: US X-Migadu-Flow: FLOW_IN X-Spam-Score: 5.75 X-Migadu-Spam-Score: 5.75 X-Migadu-Scanner: mx1.migadu.com X-Migadu-Queue-Id: EB2E54AB47 X-TUID: uqK8qf+xH2At Hi, Bruno Victal writes: > Implements a =E2=80=98serialize-ini-configuration=E2=80=99 procedure for = serializing > record-types defined with define-configuration into generic INI files. > > * gnu/services/configuration/generic-ini.scm: New module. > * tests/services/configuration/generic-ini.scm: Add tests for new module. > * Makefile.am: Register tests. > * gnu/local.mk: Register module. Nitpick: I'd perhaps rename the module simply 'ini', to shorten a bit the module namespace, already quite long. > --- > Makefile.am | 1 + > gnu/local.mk | 1 + > gnu/services/configuration/generic-ini.scm | 165 +++++++++++++++++++ > tests/services/configuration/generic-ini.scm | 129 +++++++++++++++ > 4 files changed, 296 insertions(+) > create mode 100644 gnu/services/configuration/generic-ini.scm > create mode 100644 tests/services/configuration/generic-ini.scm > > diff --git a/Makefile.am b/Makefile.am > index a386e6033c..b6d048f140 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -553,6 +553,7 @@ SCM_TESTS =3D \ > tests/services.scm \ > tests/services/file-sharing.scm \ > tests/services/configuration.scm \ > + tests/services/configuration/generic-ini.scm \ > tests/services/lightdm.scm \ > tests/services/linux.scm \ > tests/services/telephony.scm \ > diff --git a/gnu/local.mk b/gnu/local.mk > index e65888a044..796ac33107 100644 > --- a/gnu/local.mk > +++ b/gnu/local.mk > @@ -670,6 +670,7 @@ GNU_SYSTEM_MODULES =3D \ > %D%/services/cgit.scm \ > %D%/services/ci.scm \ > %D%/services/configuration.scm \ > + %D%/services/configuration/generic-ini.scm \ > %D%/services/cuirass.scm \ > %D%/services/cups.scm \ > %D%/services/databases.scm \ > diff --git a/gnu/services/configuration/generic-ini.scm b/gnu/services/co= nfiguration/generic-ini.scm > new file mode 100644 > index 0000000000..4f83cce13a > --- /dev/null > +++ b/gnu/services/configuration/generic-ini.scm > @@ -0,0 +1,165 @@ > +;;; GNU Guix --- Functional package management for GNU > +;;; Copyright =C2=A9 2023 Bruno Victal > +;;; > +;;; This file is part of GNU Guix. > +;;; > +;;; GNU Guix is free software; you can redistribute it and/or modify it > +;;; under the terms of the GNU General Public License as published by > +;;; the Free Software Foundation; either version 3 of the License, or (at > +;;; your option) any later version. > +;;; > +;;; GNU Guix is distributed in the hope that it will be useful, but > +;;; WITHOUT ANY WARRANTY; without even the implied warranty of > +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +;;; GNU General Public License for more details. > +;;; > +;;; You should have received a copy of the GNU General Public License > +;;; along with GNU Guix. If not, see . > + > +(define-module (gnu services configuration generic-ini) > + #:use-module (gnu services configuration) > + #:use-module (guix gexp) > + #:use-module (srfi srfi-9) > + #:use-module (srfi srfi-171) > + #:use-module (srfi srfi-171 meta) > + #:use-module (ice-9 match) > + #:export (ini-entry? > + list-of-ini-entries? > + > + ini-entries > + ini-entries? > + entries > + > + serialize-ini-configuration > + generic-ini-serialize-string > + generic-ini-serialize-boolean > + generic-ini-serialize-ini-entry > + generic-ini-serialize-list-of-ini-entries)) > + > +;;; > +;;; Generic INI serializer > +;;; > + Nothing here? I'd turn this into a Commentary comment, and document that this is intended to match in behavior SRFI-233, so that it can eventually be replaced without much fuss when it lands to Guile. > + > +;;; > +;;; Predicates > +;;; > + > +;; This is the same format used in SRFI-233 but without comment support. > +(define ini-entry? > + (match-lambda > + (((? symbol?) (? symbol?) (? string?)) #t) > + (_ #f))) > + > +(define list-of-ini-entries? > + (list-of ini-entry?)) > + > +;; > +;; Overall design document > +;; > +;; This module implements a generic INI serializer for a record-type def= ined > +;; using define-configuration. > +;; It expects that the serialize- procedures return a list with > +;; three elements of the form: > +;; (list section key value) > +;; Where =E2=80=98section=E2=80=99 and =E2=80=98key=E2=80=99 are symbols= and =E2=80=98value=E2=80=99 is a string. > +;; For serializing procedures that have to return multiple entries at on= ce, > +;; such as encountered when synthesizing configuration from a record obj= ect > +;; or =E2=80=9Cescape hatch fields=E2=80=9D, it must wrap the result by = calling =E2=80=98ini-entries=E2=80=99 > +;; with a list of INI-entries as described above. > +;; This is implemented as a constructor for a SRFI-9 record type named > +;; =E2=80=9C=E2=80=9D. > +;; > +;; The fields within define-configuration do not have to be ordered in, > +;; any way whatsoever as the =E2=80=98serialize-ini=E2=80=99 will group = them up automatically. > +;; This implies that no assumptions should be made regarding the order o= f the > +;; values in the serializied INI output. > +;; > +;; Additional notes: > +;; Q: Why not replace rcons with string-append and forego the ungexp-spl= ice? > +;; A: The transduction happens outside of the G-Exp while the final stri= ng-append > +;; takes place in the G-Exp. > +;; > +;; Debugging tips: Open a REPL and try one transducer at a time from > +;; =E2=80=98ini-transducer=E2=80=99. > +;; This should go to the Commentary section. > + > +;; A =E2=80=9Cbag=E2=80=9D holding multiple ini-entries. > +(define-record-type > + (ini-entries val) > + ini-entries? > + (val entries)) > + > +(define (add-section-header partition) > + (let ((header (caar partition))) > + (cons (list header) > + partition))) > + > +(define serializer > + (match-lambda > + ((section) > + #~(format #f "[~a]~%" '#$section)) > + ((section key value) > + #~(format #f "~a=3D~a~%" '#$key #$value)) > + ;; Used for the newline between sections. > + ('*section-separator* "\n"))) > + > +(define ini-transducer > + (compose (tpartition car) > + (tmap add-section-header) > + (tadd-between '(*section-separator*)) > + tconcatenate > + (tmap serializer))) > + > +;; A selective version of =E2=80=98tconcatenate=E2=80=99 but for =E2=80= =98=E2=80=99 objects only. > +(define (tconcatenate-ini-entries reducer) > + (case-lambda > + (() '()) > + ((result) (reducer result)) > + ((result input) > + (if (ini-entries? input) > + (list-reduce (preserving-reduced reducer) result (entries input= )) > + (reducer result input))))) > + > +;; A =E2=80=9Cfirst-pass=E2=80=9D serialization is performed and sorted = in order > +;; to group up the fields by =E2=80=9Csection=E2=80=9D before passing th= rough the > +;; transducer. > +(define (serialize-ini-configuration config fields) > + (let* ((srfi-233-IR > + ;; First pass: =E2=80=9Cserialize=E2=80=9D into a (disordered)= list of > + ;; SRFI-233 entries. > + (list-transduce (compose (base-transducer config) > + tconcatenate-ini-entries) > + rcons fields)) > + (comparator (lambda (x y) > + ;; Sort the SRFI-233 entries by section. > + (string<=3D? (symbol->string (car x)) > + (symbol->string (car y))))) > + (sorted-entries (sort srfi-233-IR comparator))) > + #~(string-append > + #$@(list-transduce ini-transducer rcons sorted-entries)))) > + Please add doc strings to all new procedures. I think comments in Scheme are more commonly nested inside the procedure than on top of it, like is custom in C, though that's mostly based on what I saw in Guix. > + > +;;; > +;;; Serializers > +;;; > + > +;; These are =E2=80=9Cgratuitous=E2=80=9D serializers that can be readil= y used by > +;; using the literal (prefix generic-ini-) within define-configuration. > + Instead of gratuitous, which sounds pejorative to me, I'd reword to "Convenience serializers that can be ..." > +;; Notes: field-name-transform can be used to =E2=80=9Cuglify=E2=80=9D a= field-name, > +;; e.g. want-ipv6? -> want_ipv6 > +(define* (generic-ini-serialize-string field-name value #:key section > + (field-name-transform identity)) > + (list section (field-name-transform field-name) value)) > + > +(define* (generic-ini-serialize-boolean field-name value #:key section > + (field-name-transform identity)) > + (list section (field-name-transform field-name) > + (if value "true" "false"))) > + > +(define (generic-ini-serialize-ini-entry field-name value) > + value) > + > +(define (generic-ini-serialize-list-of-ini-entries field-name value) > + (ini-entries value)) Here also, please add docstrings. > diff --git a/tests/services/configuration/generic-ini.scm b/tests/service= s/configuration/generic-ini.scm > new file mode 100644 > index 0000000000..797a01af31 > --- /dev/null > +++ b/tests/services/configuration/generic-ini.scm > @@ -0,0 +1,129 @@ > +;;; GNU Guix --- Functional package management for GNU > +;;; Copyright =C2=A9 2023 Bruno Victal > +;;; > +;;; This file is part of GNU Guix. > +;;; > +;;; GNU Guix is free software; you can redistribute it and/or modify it > +;;; under the terms of the GNU General Public License as published by > +;;; the Free Software Foundation; either version 3 of the License, or (at > +;;; your option) any later version. > +;;; > +;;; GNU Guix is distributed in the hope that it will be useful, but > +;;; WITHOUT ANY WARRANTY; without even the implied warranty of > +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +;;; GNU General Public License for more details. > +;;; > +;;; You should have received a copy of the GNU General Public License > +;;; along with GNU Guix. If not, see . > + > +(define-module (tests services configuration generic-ini) > + #:use-module (gnu services configuration) > + #:use-module (gnu services configuration generic-ini) > + #:use-module (guix diagnostics) > + #:use-module (guix gexp) > + #:use-module (guix store) > + #:autoload (guix i18n) (G_) Are all these modules truly needed, e.g. the i18n one and (guix diagnostics)? > + #:use-module (srfi srfi-34) > + #:use-module (srfi srfi-64) > + #:use-module (srfi srfi-71)) > + > +;;; Tests for the (gnu services configuration generic-ini) module. > + > +(test-begin "generic-ini serializer") > + > + > +(define expected-output "\ > +[guardians] > +llamas=3DTommy,Isabella > +donkeys=3DFranz,Olly > + > +[ranch] > +shepherd=3DEmma > + > +[shed] > +colours=3DAlizarin > +enabled=3Dtrue > +capacity=3D50 > +production=3Dwool > + > +[vehicles] > +cars=3D313 > +bikes=3DAmaryllis > +") > + > + > +;;; > +;;; Serializers > +;;; > +(define (strip-trailing-?-character field-name) nitpick: 'strip-trailing-?' seems explicit enough to me :-). > + "Drop rightmost '?' character" > + (let ((str (symbol->string field-name))) > + (if (string-suffix? "?" str) > + (string->symbol (string-drop-right str 1)) > + field-name))) > + > +(define* (serialize-string field-name value #:key section) > + (list section field-name value)) > + > +(define* (serialize-number field-name value #:key section) > + (list section field-name (number->string value))) > + > +(define* (serialize-boolean field-name value #:key section) > + (list section (strip-trailing-?-character field-name) > + (if value "true" "false"))) > + > +(define serialize-ini-entry > + generic-ini-serialize-ini-entry) > + > +(define serialize-list-of-ini-entries > + generic-ini-serialize-list-of-ini-entries) > + > + > +;;; > +;;; Record-type definition > +;;; > + > +(define-configuration foo-configuration > + (production > + (string "wool") > + "Lorem Ipsum =E2=80=A6" > + (serializer-options '(#:section shed))) > + > + (capacity > + (number 50) > + "Lorem Ipsum =E2=80=A6" > + (serializer-options '(#:section shed))) > + > + (enabled? > + (boolean #t) > + "Lorem Ipsum =E2=80=A6" > + (serializer-options '(#:section shed))) > + > + (shepherd > + (string "Emma") > + "Lorem Ipsum =E2=80=A6" > + (serializer-options '(#:section ranch))) > + > + (raw-entry > + (ini-entry '(shed colours "Alizarin")) > + "Lorem Ipsum =E2=80=A6") > + > + (escape-hatch > + (list-of-ini-entries '((vehicles bikes "Amaryllis") > + (vehicles cars "313") > + (guardians donkeys "Franz,Olly") > + (guardians llamas "Tommy,Isabella"))) > + "Lorem Ipsum =E2=80=A6")) > + > +(test-equal "Well-formed INI output from serialize-ini" > + expected-output > + ;; Serialize the above into a string, properly resolving any potential > + ;; nested G-Exps as well. > + (let* ((serialized-ini > + (serialize-ini-configuration (foo-configuration) > + foo-configuration-fields)) > + (lowered conn (with-store store > + ((lower-gexp serialized-ini) store)))) > + (eval (lowered-gexp-sexp lowered) (current-module)))) > + Since 'conn' appears unused, I think you don't need to bind it at all, and can then drop (srfi srfi-71). -- Thanks, Maxim