1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
| | ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2023 Bruno Victal <mirai@makinata.eu>
;;;
;;; 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 <http://www.gnu.org/licenses/>.
(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
;;;
\f
;;;
;;; 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 defined
;; using define-configuration.
;; It expects that the serialize-<type> procedures return a list with
;; three elements of the form:
;; (list section key value)
;; Where ‘section’ and ‘key’ are symbols and ‘value’ is a string.
;; For serializing procedures that have to return multiple entries at once,
;; such as encountered when synthesizing configuration from a record object
;; or “escape hatch fields”, it must wrap the result by calling ‘ini-entries’
;; with a list of INI-entries as described above.
;; This is implemented as a constructor for a SRFI-9 record type named
;; “<ini-entries>”.
;;
;; The fields within define-configuration do not have to be ordered in,
;; any way whatsoever as the ‘serialize-ini’ will group them up automatically.
;; This implies that no assumptions should be made regarding the order of the
;; values in the serializied INI output.
;;
;; Additional notes:
;; Q: Why not replace rcons with string-append and forego the ungexp-splice?
;; A: The transduction happens outside of the G-Exp while the final string-append
;; takes place in the G-Exp.
;;
;; Debugging tips: Open a REPL and try one transducer at a time from
;; ‘ini-transducer’.
;;
;; A “bag” holding multiple ini-entries.
(define-record-type <ini-entries>
(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=~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 ‘tconcatenate’ but for ‘<ini-entries>’ 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 “first-pass” serialization is performed and sorted in order
;; to group up the fields by “section” before passing through the
;; transducer.
(define (serialize-ini-configuration config fields)
(let* ((srfi-233-IR
;; First pass: “serialize” 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<=? (symbol->string (car x))
(symbol->string (car y)))))
(sorted-entries (sort srfi-233-IR comparator)))
#~(string-append
#$@(list-transduce ini-transducer rcons sorted-entries))))
\f
;;;
;;; Serializers
;;;
;; These are “gratuitous” serializers that can be readily used by
;; using the literal (prefix generic-ini-) within define-configuration.
;; Notes: field-name-transform can be used to “uglify” 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))
|