diff options
Diffstat (limited to 'gnu/services/configuration.scm')
-rw-r--r-- | gnu/services/configuration.scm | 303 |
1 files changed, 219 insertions, 84 deletions
diff --git a/gnu/services/configuration.scm b/gnu/services/configuration.scm index 90f12a8d39..df3d3b6f9b 100644 --- a/gnu/services/configuration.scm +++ b/gnu/services/configuration.scm @@ -2,6 +2,8 @@ ;;; Copyright © 2015 Andy Wingo <[email protected]> ;;; Copyright © 2017 Mathieu Othacehe <[email protected]> ;;; Copyright © 2017, 2018 Clément Lassieur <[email protected]> +;;; Copyright © 2021 Xinglu Chen <[email protected]> +;;; Copyright © 2021 Maxim Cournoyer <[email protected]> ;;; ;;; This file is part of GNU Guix. ;;; @@ -23,10 +25,12 @@ #:use-module (guix records) #:use-module (guix gexp) #:use-module ((guix utils) #:select (source-properties->location)) + #:use-module ((guix diagnostics) #:select (location-file)) + #:use-module ((guix modules) #:select (file-name->module-name)) #:autoload (texinfo) (texi-fragment->stexi) #:autoload (texinfo serialize) (stexi->texi) #:use-module (ice-9 match) - #:use-module ((srfi srfi-1) #:select (append-map)) + #:use-module (srfi srfi-1) #:use-module (srfi srfi-34) #:use-module (srfi srfi-35) #:export (configuration-field @@ -38,11 +42,20 @@ configuration-field-getter configuration-field-default-value-thunk configuration-field-documentation + + configuration-error? + + define-configuration + define-configuration/no-serialization + no-serialization + serialize-configuration define-maybe - define-configuration + define-maybe/no-serialization validate-configuration generate-documentation + configuration->documentation + empty-serializer serialize-package)) ;;; Commentary: @@ -63,6 +76,10 @@ (define (configuration-missing-field kind field) (configuration-error (format #f "~a configuration missing required field ~a" kind field))) +(define (configuration-no-default-value kind field) + (configuration-error + (format #f "The field `~a' of the `~a' configuration record \ +does not have a default value" field kind))) (define-record-type* <configuration-field> configuration-field make-configuration-field configuration-field? @@ -91,100 +108,218 @@ fields)) (define-syntax-rule (id ctx parts ...) - "Assemble PARTS into a raw (unhygienic) identifier." + "Assemble PARTS into a raw (unhygienic) identifier." (datum->syntax ctx (symbol-append (syntax->datum parts) ...))) +(define (define-maybe-helper serialize? prefix syn) + (syntax-case syn () + ((_ stem) + (with-syntax + ((stem? (id #'stem #'stem #'?)) + (maybe-stem? (id #'stem #'maybe- #'stem #'?)) + (serialize-stem (if prefix + (id #'stem prefix #'serialize- #'stem) + (id #'stem #'serialize- #'stem))) + (serialize-maybe-stem (if prefix + (id #'stem prefix #'serialize-maybe- #'stem) + (id #'stem #'serialize-maybe- #'stem)))) + #`(begin + (define (maybe-stem? val) + (or (eq? val 'disabled) (stem? val))) + #,@(if serialize? + (list #'(define (serialize-maybe-stem field-name val) + (if (stem? val) + (serialize-stem field-name val) + ""))) + '())))))) + (define-syntax define-maybe (lambda (x) - (syntax-case x () + (syntax-case x (no-serialization prefix) + ((_ stem (no-serialization)) + (define-maybe-helper #f #f #'(_ stem))) + ((_ stem (prefix serializer-prefix)) + (define-maybe-helper #t #'serializer-prefix #'(_ stem))) ((_ stem) - (with-syntax - ((stem? (id #'stem #'stem #'?)) - (maybe-stem? (id #'stem #'maybe- #'stem #'?)) - (serialize-stem (id #'stem #'serialize- #'stem)) - (serialize-maybe-stem (id #'stem #'serialize-maybe- #'stem))) - #'(begin - (define (maybe-stem? val) - (or (eq? val 'disabled) (stem? val))) - (define (serialize-maybe-stem field-name val) - (if (stem? val) (serialize-stem field-name val) "")))))))) + (define-maybe-helper #t #f #'(_ stem)))))) + +(define-syntax-rule (define-maybe/no-serialization stem) + (define-maybe stem (no-serialization))) + +(define (define-configuration-helper serialize? serializer-prefix syn) + (syntax-case syn () + ((_ stem (field (field-type def ...) doc custom-serializer ...) ...) + (with-syntax (((field-getter ...) + (map (lambda (field) + (id #'stem #'stem #'- field)) + #'(field ...))) + ((field-predicate ...) + (map (lambda (type) + (id #'stem type #'?)) + #'(field-type ...))) + ((field-default ...) + (map (match-lambda + ((field-type default-value) + default-value) + ((field-type) + ;; Quote `undefined' to prevent a possibly + ;; unbound warning. + (syntax 'undefined))) + #'((field-type def ...) ...))) + ((field-serializer ...) + (map (lambda (type custom-serializer) + (and serialize? + (match custom-serializer + ((serializer) + serializer) + (() + (if serializer-prefix + (id #'stem + serializer-prefix + #'serialize- type) + (id #'stem #'serialize- type)))))) + #'(field-type ...) + #'((custom-serializer ...) ...)))) + #`(begin + (define-record-type* #,(id #'stem #'< #'stem #'>) + #,(id #'stem #'% #'stem) + #,(id #'stem #'make- #'stem) + #,(id #'stem #'stem #'?) + (%location #,(id #'stem #'stem #'-location) + (default (and=> (current-source-location) + source-properties->location)) + (innate)) + #,@(map (lambda (name getter def) + (if (eq? (syntax->datum def) (quote 'undefined)) + #`(#,name #,getter) + #`(#,name #,getter (default #,def)))) + #'(field ...) + #'(field-getter ...) + #'(field-default ...))) + (define #,(id #'stem #'stem #'-fields) + (list (configuration-field + (name 'field) + (type 'field-type) + (getter field-getter) + (predicate field-predicate) + (serializer field-serializer) + (default-value-thunk + (lambda () + (display '#,(id #'stem #'% #'stem)) + (if (eq? (syntax->datum field-default) + 'undefined) + (configuration-no-default-value + '#,(id #'stem #'% #'stem) 'field) + field-default))) + (documentation doc)) + ...)) + (define-syntax-rule (stem arg (... ...)) + (let ((conf (#,(id #'stem #'% #'stem) arg (... ...)))) + (validate-configuration conf + #,(id #'stem #'stem #'-fields)) + conf))))))) + +(define no-serialization ;syntactic keyword for 'define-configuration' + '(no serialization)) (define-syntax define-configuration - (lambda (stx) - (syntax-case stx () - ((_ stem (field (field-type def) doc) ...) - (with-syntax (((field-getter ...) - (map (lambda (field) - (id #'stem #'stem #'- field)) - #'(field ...))) - ((field-predicate ...) - (map (lambda (type) - (id #'stem type #'?)) - #'(field-type ...))) - ((field-serializer ...) - (map (lambda (type) - (id #'stem #'serialize- type)) - #'(field-type ...)))) - #`(begin - (define-record-type* #,(id #'stem #'< #'stem #'>) - #,(id #'stem #'% #'stem) - #,(id #'stem #'make- #'stem) - #,(id #'stem #'stem #'?) - (%location #,(id #'stem #'-location) - (default (and=> (current-source-location) - source-properties->location)) - (innate)) - (field field-getter (default def)) - ...) - (define #,(id #'stem #'stem #'-fields) - (list (configuration-field - (name 'field) - (type 'field-type) - (getter field-getter) - (predicate field-predicate) - (serializer field-serializer) - (default-value-thunk (lambda () def)) - (documentation doc)) - ...)) - (define-syntax-rule (stem arg (... ...)) - (let ((conf (#,(id #'stem #'% #'stem) arg (... ...)))) - (validate-configuration conf - #,(id #'stem #'stem #'-fields)) - conf)))))))) - -(define (serialize-package field-name val) - "") + (lambda (s) + (syntax-case s (no-serialization prefix) + ((_ stem (field (field-type def ...) doc custom-serializer ...) ... + (no-serialization)) + (define-configuration-helper + #f #f #'(_ stem (field (field-type def ...) doc custom-serializer ...) + ...))) + ((_ stem (field (field-type def ...) doc custom-serializer ...) ... + (prefix serializer-prefix)) + (define-configuration-helper + #t #'serializer-prefix #'(_ stem (field (field-type def ...) + doc custom-serializer ...) + ...))) + ((_ stem (field (field-type def ...) doc custom-serializer ...) ...) + (define-configuration-helper + #t #f #'(_ stem (field (field-type def ...) doc custom-serializer ...) + ...)))))) + +(define-syntax-rule (define-configuration/no-serialization + stem (field (field-type def ...) + doc custom-serializer ...) ...) + (define-configuration stem (field (field-type def ...) + doc custom-serializer ...) ... + (no-serialization))) + +(define (empty-serializer field-name val) "") +(define serialize-package empty-serializer) ;; A little helper to make it easier to document all those fields. (define (generate-documentation documentation documentation-name) (define (str x) (object->string x)) + + (define (package->symbol package) + "Return the first symbol name of a package that matches PACKAGE, else #f." + (let* ((module (file-name->module-name + (location-file (package-location package)))) + (symbols (filter-map + identity + (module-map (lambda (symbol var) + (and (equal? package (variable-ref var)) + symbol)) + (resolve-module module))))) + (if (null? symbols) + #f + (first symbols)))) + (define (generate configuration-name) (match (assq-ref documentation configuration-name) ((fields . sub-documentation) - `((para "Available " (code ,(str configuration-name)) " fields are:") - ,@(map - (lambda (f) - (let ((field-name (configuration-field-name f)) - (field-type (configuration-field-type f)) - (field-docs (cdr (texi-fragment->stexi - (configuration-field-documentation f)))) - (default (catch #t - (configuration-field-default-value-thunk f) - (lambda _ '%invalid)))) - (define (show-default? val) - (or (string? val) (number? val) (boolean? val) - (and (symbol? val) (not (eq? val '%invalid))) - (and (list? val) (and-map show-default? val)))) - `(deftypevr (% (category - (code ,(str configuration-name)) " parameter") - (data-type ,(str field-type)) - (name ,(str field-name))) - ,@field-docs - ,@(if (show-default? default) - `((para "Defaults to " (samp ,(str default)) ".")) - '()) - ,@(append-map - generate - (or (assq-ref sub-documentation field-name) '()))))) - fields))))) + `((deftp (% (category "Data Type") (name ,(str configuration-name))) + (para "Available " (code ,(str configuration-name)) " fields are:") + (table + (% (formatter (asis))) + ,@(map + (lambda (f) + (let ((field-name (configuration-field-name f)) + (field-type (configuration-field-type f)) + (field-docs (cdr (texi-fragment->stexi + (configuration-field-documentation f)))) + (default (catch #t + (configuration-field-default-value-thunk f) + (lambda _ '%invalid)))) + (define (show-default? val) + (or (string? val) (number? val) (boolean? val) + (package? val) + (and (symbol? val) (not (eq? val '%invalid))) + (and (list? val) (and-map show-default? val)))) + + (define (show-default val) + (cond + ((package? val) + (symbol->string (package->symbol val))) + (else (str val)))) + + `(entry (% (heading + (code ,(str field-name)) + ,@(if (show-default? default) + `(" (default: " + (code ,(show-default default)) ")") + '()) + " (type: " ,(str field-type) ")")) + (para ,@field-docs) + ,@(append-map + generate + (or (assq-ref sub-documentation field-name) + '()))))) + fields))))))) (stexi->texi `(*fragment* . ,(generate documentation-name)))) + +(define (configuration->documentation configuration-symbol) + "Take CONFIGURATION-SYMBOL, the symbol corresponding to the name used when +defining a configuration record with DEFINE-CONFIGURATION, and output the +Texinfo documentation of its fields." + ;; This is helper for a simple, straight-forward application of + ;; GENERATE-DOCUMENTATION. + (let ((fields-getter (module-ref (current-module) + (symbol-append configuration-symbol + '-fields)))) + (display (generate-documentation `((,configuration-symbol ,fields-getter)) + configuration-symbol)))) |