;;; SPDX-FileCopyrightText: Copyright © 2022 Andrew Tropin ;;; ;;; SPDX-License-Identifier: GPL-3.0-or-later (define-module (rosenthal utils serializers ini) #:use-module (ice-9 match) #:use-module (ice-9 format) #:use-module (srfi srfi-1) #:use-module (rosenthal utils serializers) #:use-module (guix packages) #:use-module (guix gexp) #:use-module (guix diagnostics) #:use-module (guix ui) #:use-module (gnu services configuration) #:export (ini-serialize ini-print ini-merge ini-append serialize-ini-config ini-config?)) (define ini-config? list?) (define sample-ini `((global ((daemon) (log . file))) (http ((host . 127.0.0.1) (port . 1234))))) (define (format-ini-section section) (match section ('global "") (name (format #f "[~a]\n" (symbol->string name))))) (define* (ini-serialize config #:key (equal-string " = ") (format-ini-section format-ini-section)) "For global properties use global section, the properties will be added to the beginning of the list before any section, this behavior can be adjusted with FORMAT-INI-SECTION argument. @lisp `((global ((daemon) (log . file))) (http ((host . 127.0.0.1) (port . 1234)))) @end lisp would yeld @example @end example " (define (serialize-ini-term term) (match term (#t "true") (#f "false") ((? symbol? e) (symbol->string e)) ((? number? e) (number->string e)) ((? string? e) (object->string e)) ((lst ...) (raise (formatted-message (G_ "INI term should be a non-list value (string, \ boolean, number, symbol, or gexp). Provided term is:\n ~a") lst))) (e e))) (define (serialize-ini-properties properties) (unless (alist? properties) (raise (formatted-message (G_ "INI properties should be an alist, \ but provided value is:\n ~a") properties))) (append-map (match-lambda ((? gexp? e) (list e "\n")) ((k) (list (serialize-ini-term k) equal-string "\n")) ((k . v) (list (serialize-ini-term k) equal-string (serialize-ini-term v) "\n"))) properties)) (define (serialize-ini-section section) (match section ((name properties) (append (list (format-ini-section name)) (serialize-ini-properties properties))) (e (raise (formatted-message (G_ "INI section should be a list containing a section name as \ the first element and alist of properties as the second, but provided value \ is:\n~a") e))))) ;; TODO: serialize global section before all other sections. (append-map (lambda (expr) (append (match expr ((? gexp? e) (list e)) (e (serialize-ini-section e))) (list "\n"))) config)) (define (ini-merge ini1 ini2) "Combine to INIs. Naive quadratic implementation, which can be rediculously slow." (define keys-to-merge (fold (match-lambda* (((k v) acc) (if (assoc-ref ini2 k) (cons k acc) acc)) (((? gexp? e) acc) acc)) '() ini1)) (define enriched-ini1 (fold-right (match-lambda* (((k v) acc) (cons (cons k (list (append v (car (or (assoc-ref ini2 k) '(())))))) acc)) (((? gexp? e) acc) (cons e acc))) '() ini1)) (define stripped-ini2 (remove (match-lambda ((k . v) (memq k keys-to-merge)) (e #f)) ini2)) (append enriched-ini1 stripped-ini2)) (define (ini-append x acc) (ini-merge acc x)) (define serialize-ini-config ini-serialize) ;; (display ;; (merge-ini ;; '((section1 ((k1 . v1))) ;; (section2 ((k4 . v4)))) ;; '((section1 ((k2 . v2) ;; (k3 . v3))) ;; (section3 ((k5 . v5)))))) ;; (cdr '(a ((k . v) (k2 . v2)))) (define (ini-print ini) "Prints generated INI, useful for debugging." (display (apply string-append (ini-serialize ini))))