1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2015 Andy Wingo <wingo@igalia.com>
4 ;;; This file is part of GNU Guix.
6 ;;; GNU Guix is free software; you can redistribute it and/or modify it
7 ;;; under the terms of the GNU General Public License as published by
8 ;;; the Free Software Foundation; either version 3 of the License, or (at
9 ;;; your option) any later version.
11 ;;; GNU Guix is distributed in the hope that it will be useful, but
12 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;;; GNU General Public License for more details.
16 ;;; You should have received a copy of the GNU General Public License
17 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19 (define-module (gnu services configuration)
20 #:use-module (guix packages)
21 #:use-module (guix records)
22 #:use-module (guix gexp)
23 #:autoload (texinfo) (texi-fragment->stexi)
24 #:autoload (texinfo serialize) (stexi->texi)
25 #:use-module (ice-9 match)
26 #:use-module ((srfi srfi-1) #:select (append-map))
27 #:use-module (srfi srfi-34)
28 #:use-module (srfi srfi-35)
29 #:export (configuration-field
30 configuration-field-name
31 configuration-field-type
32 configuration-missing-field
33 configuration-field-error
34 configuration-field-serializer
35 configuration-field-getter
36 configuration-field-default-value-thunk
37 configuration-field-documentation
38 serialize-configuration
40 validate-configuration
41 generate-documentation
45 serialize-space-separated-string-list
46 space-separated-string-list?
54 ;;; Syntax for creating Scheme bindings to complex configuration files.
58 (define-condition-type &configuration-error &error
61 (define (configuration-error message)
62 (raise (condition (&message (message message))
63 (&configuration-error))))
64 (define (configuration-field-error field val)
66 (format #f "Invalid value for field ~a: ~s" field val)))
67 (define (configuration-missing-field kind field)
69 (format #f "~a configuration missing required field ~a" kind field)))
71 (define-record-type* <configuration-field>
72 configuration-field make-configuration-field configuration-field?
73 (name configuration-field-name)
74 (type configuration-field-type)
75 (getter configuration-field-getter)
76 (predicate configuration-field-predicate)
77 (serializer configuration-field-serializer)
78 (default-value-thunk configuration-field-default-value-thunk)
79 (documentation configuration-field-documentation))
81 (define (serialize-configuration config fields)
82 (for-each (lambda (field)
83 ((configuration-field-serializer field)
84 (configuration-field-name field)
85 ((configuration-field-getter field) config)))
88 (define (validate-configuration config fields)
89 (for-each (lambda (field)
90 (let ((val ((configuration-field-getter field) config)))
91 (unless ((configuration-field-predicate field) val)
92 (configuration-field-error
93 (configuration-field-name field) val))))
96 (define-syntax define-configuration
98 (define (id ctx part . parts)
99 (let ((part (syntax->datum part)))
104 (parts (symbol-append part
105 (syntax->datum (apply id ctx parts))))))))
107 ((_ stem (field (field-type def) doc) ...)
108 (with-syntax (((field-getter ...)
110 (id #'stem #'stem #'- field))
112 ((field-predicate ...)
114 (id #'stem type #'?))
116 ((field-serializer ...)
118 (id #'stem #'serialize- type))
119 #'(field-type ...))))
121 (define-record-type* #,(id #'stem #'< #'stem #'>)
122 #,(id #'stem #'% #'stem)
123 #,(id #'stem #'make- #'stem)
124 #,(id #'stem #'stem #'?)
125 (field field-getter (default def))
127 (define #,(id #'stem #'stem #'-fields)
128 (list (configuration-field
131 (getter field-getter)
132 (predicate field-predicate)
133 (serializer field-serializer)
134 (default-value-thunk (lambda () def))
137 (define-syntax-rule (stem arg (... ...))
138 (let ((conf (#,(id #'stem #'% #'stem) arg (... ...))))
139 (validate-configuration conf
140 #,(id #'stem #'stem #'-fields))
143 (define (uglify-field-name field-name)
144 (let ((str (symbol->string field-name)))
146 (map string-titlecase
147 (string-split (if (string-suffix? "?" str)
148 (substring str 0 (1- (string-length str)))
152 (define (serialize-field field-name val)
153 (format #t "~a ~a\n" (uglify-field-name field-name) val))
155 (define (serialize-package field-name val)
158 (define (serialize-string field-name val)
159 (serialize-field field-name val))
161 (define (space-separated-string-list? val)
164 (and (string? x) (not (string-index x #\space))))
166 (define (serialize-space-separated-string-list field-name val)
167 (serialize-field field-name (string-join val " ")))
169 (define (file-name? val)
171 (string-prefix? "/" val)))
172 (define (serialize-file-name field-name val)
173 (serialize-string field-name val))
175 (define (serialize-boolean field-name val)
176 (serialize-string field-name (if val "yes" "no")))
178 ;; A little helper to make it easier to document all those fields.
179 (define (generate-documentation documentation documentation-name)
180 (define (str x) (object->string x))
181 (define (generate configuration-name)
182 (match (assq-ref documentation configuration-name)
183 ((fields . sub-documentation)
184 `((para "Available " (code ,(str configuration-name)) " fields are:")
187 (let ((field-name (configuration-field-name f))
188 (field-type (configuration-field-type f))
189 (field-docs (cdr (texi-fragment->stexi
190 (configuration-field-documentation f))))
192 (configuration-field-default-value-thunk f)
193 (lambda _ '%invalid))))
194 (define (show-default? val)
195 (or (string? default) (number? default) (boolean? default)
196 (and (symbol? val) (not (eq? val '%invalid)))
197 (and (list? val) (and-map show-default? val))))
198 `(deftypevr (% (category
199 (code ,(str configuration-name)) " parameter")
200 (data-type ,(str field-type))
201 (name ,(str field-name)))
203 ,@(if (show-default? default)
204 `((para "Defaults to " (samp ,(str default)) "."))
208 (or (assq-ref sub-documentation field-name) '())))))
210 (stexi->texi `(*fragment* . ,(generate documentation-name))))