aboutsummaryrefslogtreecommitdiff
path: root/scripts/yaml.scm
blob: f9409119484fc45dc405e00554860cdde0497d67 (plain)
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
;;
;; scm->yaml
;;
;; This file implements a library to convert a scm tree to yaml.

(define-module (yaml)
  #:use-module (ice-9 match)
  #:export (scm->yaml))

(define (kebab->camel string)
  "Convert STRING from kebab case to CAMEL case."
  (match (string-split string #\-)
    ((head tail ...)
     (string-concatenate
      (cons head (map string-titlecase tail))))))

(define (display-atom atom port)
  "Display ATOM in PORT converting from kebab case to camel case if
ATOM is a symbol."
  (cond
   ((symbol? atom)
    (display (string->symbol (kebab->camel (symbol->string atom))) port))
   ((number? atom)
    (display atom port))
   ((string? atom)
    ;; Escape string with double quotes if
    ;; - every character is a digit or period, and the unescaped
    ;; string can therefore be misinterpreted as a number
    ;; - string contains the colon character
    (if (or (string-every (char-set-union char-set:digit (char-set #\.)) atom)
            (string-any #\: atom))
        (write atom port)
        (display atom port)))
   ((boolean? atom)
    (display (if atom "true" "false") port))
   (else (error "Unknown atom" atom))))

(define (indent-level port level)
  "Emit whitespaces to PORT corresponding to nesting LEVEL."
  (display (make-string (* 2 level) #\space) port))

(define (display-array-element element port level)
  "Display array ELEMENT to PORT at nesting LEVEL."
  (display "- " port)
  (scm->yaml element port (1+ level)))

(define (display-dictionary-entry entry port level)
  "Display dictionary ENTRY to PORT at nesting LEVEL."
  (match entry
    ((key . value)
     (display-atom key port)
     (display ":" port)
     (match value
       ((or #(_ ...)
            ((_ . _) (_ . _) ...))
        (newline port)
        (indent-level port (1+ level))
        (scm->yaml value port (1+ level)))
       (_ (display " " port)
          (scm->yaml value port level))))))

(define* (scm->yaml scm #:optional (port (current-output-port)) (level 0))
  "Convert SCM, an S-expression tree, to YAML and display to
PORT. LEVEL is an internal recursion variable."
  (match scm
    (#(head tail ...)
     (display-array-element head port level)
     (for-each (lambda (element)
                 (indent-level port level)
                 (display-array-element element port level))
               tail))
    (#()
     (display "[]" port))
    ((head tail ...)
     (display-dictionary-entry head port level)
     (for-each (lambda (entry)
                 (indent-level port level)
                 (display-dictionary-entry entry port level))
               tail))
    (()
     (display "{}" port)
     (newline port))
    (symbol
     (display-atom symbol port)
     (newline port))))