extract meta data from lilypond compilation
* write that as yaml file next to the other outputs * extract lyrics * makes chordpro export work in songbooks * more metadata for chordpro export
This commit is contained in:
@@ -1,154 +1,259 @@
|
||||
(use-modules (ice-9 rdelim) (ice-9 regex) (ice-9 pretty-print) (srfi srfi-1))
|
||||
(use-modules (ice-9 rdelim) (ice-9 regex) (ice-9 receive) (srfi srfi-1))
|
||||
|
||||
;; YAML-Parser: liest eine Teilmenge von YAML (Block-Syntax, keine
|
||||
;; Flow-Collections, keine Block-Skalare) in verschachtelte
|
||||
;; alist/list-Strukturen. Gegenstück zu yaml_writer.scm.
|
||||
;;
|
||||
;; Ergebnisformat:
|
||||
;; - Mapping -> alist mit String-Keys, Reihenfolge wie in der Datei
|
||||
;; - Liste -> Liste (auch als "- key: value"-Inline-Mappings)
|
||||
;; - Skalare -> String, Zahl, #t/#f, '() (null / {} / [])
|
||||
;;
|
||||
;; Skalar-Interpretation:
|
||||
;; - unquoted und 'single-quoted' Werte werden interpretiert
|
||||
;; (Zahl, true/false/null). Das Interpretieren von single-quoted
|
||||
;; Werten ist Kompatibilitätsverhalten zum alten Parser:
|
||||
;; authors.yml notiert Zahlen als '1898'.
|
||||
;; - "double-quoted" Werte bleiben immer Strings; \\, \" und \n
|
||||
;; werden entescaped (so schreibt sie der yaml_writer).
|
||||
;; - Kommentare (#) werden nur außerhalb von Quotes entfernt und nur,
|
||||
;; wenn ihnen ein Leerzeichen oder der Zeilenanfang vorausgeht.
|
||||
|
||||
;; Hauptparsingfunktion
|
||||
(define (yml-file->scm filename)
|
||||
|
||||
;; Utility: Zeile einlesen
|
||||
(define (read-lines filename)
|
||||
;; --- Zeilen einlesen ------------------------------------------------
|
||||
|
||||
;; Anzahl führender Leerzeichen
|
||||
(define (line-indent line)
|
||||
(let loop ((i 0))
|
||||
(if (and (< i (string-length line))
|
||||
(char=? (string-ref line i) #\space))
|
||||
(loop (+ i 1))
|
||||
i)))
|
||||
|
||||
;; Liefert Items (indent . inhalt); Leerzeilen, reine Kommentarzeilen
|
||||
;; und Dokumentmarker (--- / ...) werden übersprungen.
|
||||
(define (read-items filename)
|
||||
(call-with-input-file filename
|
||||
(lambda (port)
|
||||
(let loop ((lines '()))
|
||||
(let loop ((items '()))
|
||||
(let ((line (read-line port)))
|
||||
(if (eof-object? line)
|
||||
(reverse lines)
|
||||
(let ((clean (string-trim line)))
|
||||
(if (or (string=? clean "---") (string-null? clean))
|
||||
(loop lines) ;; Ignoriere "---" oder leere Zeile
|
||||
(loop (cons line lines))))))))))
|
||||
(reverse items)
|
||||
(let* ((line (if (string-suffix? "\r" line)
|
||||
(string-drop-right line 1)
|
||||
line))
|
||||
(content (string-trim-both line)))
|
||||
(if (or (string-null? content)
|
||||
(string-prefix? "#" content)
|
||||
(string=? content "---")
|
||||
(string=? content "..."))
|
||||
(loop items)
|
||||
(loop (cons (cons (line-indent line) content)
|
||||
items))))))))))
|
||||
|
||||
;; Einrückung bestimmen (Anzahl Leerzeichen am Anfang)
|
||||
(define (line-indent line)
|
||||
(let ((match (string-match "^ *" line)))
|
||||
(if match
|
||||
(match:end match) ; Anzahl der Leerzeichen = Position nach Leerzeichen
|
||||
0))) ; Falls kein Match → 0
|
||||
(define (item-indent item) (car item))
|
||||
(define (item-content item) (cdr item))
|
||||
|
||||
;; Kommentar entfernen
|
||||
(define (strip-comment line)
|
||||
(let ((m (string-match "#.*" line)))
|
||||
(if m
|
||||
(string-trim-right (string-take line (match:start m)))
|
||||
line)))
|
||||
;; --- Skalare ----------------------------------------------------------
|
||||
|
||||
;; Hilfsfunktion: Whitespace entfernen
|
||||
(define (clean-line line)
|
||||
(string-trim (strip-comment line)))
|
||||
;; Position des schließenden Double-Quotes ab start (oder #f);
|
||||
;; Backslash escaped das Folgezeichen.
|
||||
(define (closing-double-quote s start)
|
||||
(let loop ((i start))
|
||||
(cond ((>= i (string-length s)) #f)
|
||||
((char=? (string-ref s i) #\\) (loop (+ i 2)))
|
||||
((char=? (string-ref s i) #\") i)
|
||||
(else (loop (+ i 1))))))
|
||||
|
||||
;; Ist Zeile leer (nach Entfernen von Kommentar & Whitespace)?
|
||||
(define (blank-or-comment? line)
|
||||
(string-null? (clean-line line)))
|
||||
;; Position des schließenden Single-Quotes ab start (oder #f);
|
||||
;; '' ist ein escaptes Quote.
|
||||
(define (closing-single-quote s start)
|
||||
(let loop ((i start))
|
||||
(cond ((>= i (string-length s)) #f)
|
||||
((char=? (string-ref s i) #\')
|
||||
(if (and (< (+ i 1) (string-length s))
|
||||
(char=? (string-ref s (+ i 1)) #\'))
|
||||
(loop (+ i 2))
|
||||
i))
|
||||
(else (loop (+ i 1))))))
|
||||
|
||||
;; Skalare Werte interpretieren
|
||||
;; \\ -> \, \" -> ", \n -> Zeilenumbruch
|
||||
(define (unescape-double s)
|
||||
(let loop ((chars (string->list s)) (acc '()))
|
||||
(cond ((null? chars) (list->string (reverse acc)))
|
||||
((and (char=? (car chars) #\\) (pair? (cdr chars)))
|
||||
(loop (cddr chars)
|
||||
(cons (if (char=? (cadr chars) #\n)
|
||||
#\newline
|
||||
(cadr chars))
|
||||
acc)))
|
||||
(else (loop (cdr chars) (cons (car chars) acc))))))
|
||||
|
||||
;; '' -> '
|
||||
(define (unescape-single s)
|
||||
(regexp-substitute/global #f "''" s 'pre "'" 'post))
|
||||
|
||||
;; Kommentar in einem unquoted Wert entfernen:
|
||||
;; # zählt nur am Anfang oder nach Leerzeichen
|
||||
(define (strip-plain-comment s)
|
||||
(let loop ((i 0))
|
||||
(cond ((>= i (string-length s)) s)
|
||||
((and (char=? (string-ref s i) #\#)
|
||||
(or (zero? i)
|
||||
(char-whitespace? (string-ref s (- i 1)))))
|
||||
(string-take s i))
|
||||
(else (loop (+ i 1))))))
|
||||
|
||||
;; Unquoted/single-quoted Werte interpretieren
|
||||
(define (interpret-plain s)
|
||||
(cond
|
||||
((member s '("{}" "[]" "null")) '())
|
||||
((string=? s "true") #t)
|
||||
((string=? s "false") #f)
|
||||
((string-match "^-?[0-9]+(\\.[0-9]+)?$" s) (string->number s))
|
||||
(else s)))
|
||||
|
||||
;; Skalar parsen; Rest hinter einem schließenden Quote wird ignoriert
|
||||
;; (darf nur ein Kommentar sein)
|
||||
(define (parse-scalar str)
|
||||
(define (strip-quotes s)
|
||||
(let ((s (string-trim-both str)))
|
||||
(cond
|
||||
((and (string-prefix? "\"" s) (string-suffix? "\"" s))
|
||||
(string-drop-right (string-drop s 1) 1))
|
||||
((and (string-prefix? "'" s) (string-suffix? "'" s))
|
||||
(string-drop-right (string-drop s 1) 1))
|
||||
(else s)))
|
||||
(let ((s (strip-quotes (string-trim str))))
|
||||
((string-null? s) "")
|
||||
((char=? (string-ref s 0) #\")
|
||||
(let ((end (closing-double-quote s 1)))
|
||||
(if end
|
||||
(unescape-double (substring s 1 end))
|
||||
(string-trim-right (strip-plain-comment s)))))
|
||||
((char=? (string-ref s 0) #\')
|
||||
(let ((end (closing-single-quote s 1)))
|
||||
(if end
|
||||
(interpret-plain (unescape-single (substring s 1 end)))
|
||||
(string-trim-right (strip-plain-comment s)))))
|
||||
(else
|
||||
(interpret-plain (string-trim-right (strip-plain-comment s)))))))
|
||||
|
||||
;; --- Struktur ----------------------------------------------------------
|
||||
|
||||
;; Erste Key-Trennstelle: ":" am Zeilenende oder gefolgt von Leerzeichen.
|
||||
;; Beginnt die Zeile mit einem gequoteten Key (oder Skalar), zaehlt ein
|
||||
;; ":" innerhalb der Quotes nicht als Trennstelle.
|
||||
(define (key-split-index s)
|
||||
(let ((start (if (string-null? s)
|
||||
0
|
||||
(case (string-ref s 0)
|
||||
((#\") (let ((end (closing-double-quote s 1)))
|
||||
(if end (+ end 1) 0)))
|
||||
((#\') (let ((end (closing-single-quote s 1)))
|
||||
(if end (+ end 1) 0)))
|
||||
(else 0)))))
|
||||
(let loop ((i start))
|
||||
(cond ((>= i (string-length s)) #f)
|
||||
((and (char=? (string-ref s i) #\:)
|
||||
(or (= i (- (string-length s) 1))
|
||||
(char=? (string-ref s (+ i 1)) #\space)))
|
||||
i)
|
||||
(else (loop (+ i 1)))))))
|
||||
|
||||
;; Key interpretieren: gequotete Keys werden entquotet (Strings bleiben
|
||||
;; Strings), unquoted Keys bleiben unveraendert
|
||||
(define (parse-key s)
|
||||
(let ((k (string-trim-both s)))
|
||||
(cond
|
||||
((string=? s "{}") '()) ;; leere Map
|
||||
((string=? s "[]") '()) ;; leere Liste
|
||||
((string-match "^[0-9]+$" s) (string->number s))
|
||||
((string=? s "true") #t)
|
||||
((string=? s "false") #f)
|
||||
((string=? s "null") '())
|
||||
(else s))))
|
||||
((string-null? k) k)
|
||||
((char=? (string-ref k 0) #\")
|
||||
(let ((end (closing-double-quote k 1)))
|
||||
(if end (unescape-double (substring k 1 end)) k)))
|
||||
((char=? (string-ref k 0) #\')
|
||||
(let ((end (closing-single-quote k 1)))
|
||||
(if end (unescape-single (substring k 1 end)) k)))
|
||||
(else k))))
|
||||
|
||||
(define (dash-line? content)
|
||||
(or (string=? content "-") (string-prefix? "- " content)))
|
||||
|
||||
;; Hilfsfunktion: Zeilen mit gleicher oder höherer Einrückung sammeln
|
||||
(define (take-indented lines min-indent)
|
||||
(let loop ((ls lines) (acc '()))
|
||||
(if (null? ls)
|
||||
(reverse acc)
|
||||
(let ((line (car ls)))
|
||||
(if (or (blank-or-comment? line)
|
||||
(>= (line-indent line) min-indent))
|
||||
(loop (cdr ls) (cons line acc))
|
||||
(reverse acc))))))
|
||||
;; Wert-String hinter "key:" bzw. "-": reiner Kommentar zählt als leer
|
||||
(define (effective-value str)
|
||||
(let ((s (string-trim-both str)))
|
||||
(if (string-prefix? "#" s) "" s)))
|
||||
|
||||
;; Hilfsfunktion: N Zeilen überspringen
|
||||
(define (drop lst n)
|
||||
(let loop ((l lst) (i n))
|
||||
(if (or (zero? i) (null? l))
|
||||
l
|
||||
(loop (cdr l) (- i 1)))))
|
||||
;; Einen Block von Items parsen; das erste Item bestimmt die Blockart
|
||||
(define (parse-block items)
|
||||
(cond
|
||||
((null? items) '())
|
||||
((dash-line? (item-content (car items))) (parse-list items))
|
||||
;; einzelne Zeile ohne Key: Skalar (z.B. eine Datei, die nur {} enthält)
|
||||
((and (null? (cdr items))
|
||||
(not (key-split-index (item-content (car items)))))
|
||||
(parse-scalar (item-content (car items))))
|
||||
(else (parse-map items))))
|
||||
|
||||
;; Listenparsing: Liest Zeilen mit `-` als Listeneinträge
|
||||
(define (parse-list lines current-indent)
|
||||
(let loop ((ls lines) (result '()))
|
||||
(if (null? ls)
|
||||
(reverse result)
|
||||
(let* ((line (clean-line (car ls))))
|
||||
(if (string-match "^-" line)
|
||||
(let* ((indent (line-indent (car ls)))
|
||||
(item-str (string-trim (string-drop line 1)))
|
||||
(next-lines (cdr ls)))
|
||||
(if (or (null? next-lines)
|
||||
(> (line-indent (car next-lines)) indent))
|
||||
;; Verschachtelter Inhalt
|
||||
(let* ((sub (take-indented next-lines (+ indent 2)))
|
||||
(parsed (if (null? sub)
|
||||
(parse-scalar item-str)
|
||||
(parse-lines sub (+ indent 2))))
|
||||
(remaining (drop next-lines (length sub))))
|
||||
(loop remaining (cons parsed result)))
|
||||
;; Einfacher Skalar
|
||||
(loop next-lines (cons (parse-scalar item-str) result))))
|
||||
;; Nicht mehr Teil der Liste
|
||||
(reverse result))))))
|
||||
;; Mapping: alle Items auf map-indent sind Keys, tiefer eingerückte
|
||||
;; Items gehören zum jeweils vorangehenden Key
|
||||
(define (parse-map items)
|
||||
(let ((map-indent (item-indent (car items))))
|
||||
(let loop ((items items) (result '()))
|
||||
(if (null? items)
|
||||
(reverse result)
|
||||
(let* ((content (item-content (car items)))
|
||||
(split (key-split-index content)))
|
||||
(if (not split)
|
||||
(begin
|
||||
(format (current-error-port)
|
||||
"YAML-Syntaxfehler: Ungültige Zeile: ~a\n" content)
|
||||
(loop (cdr items) result))
|
||||
(let ((key (parse-key (string-take content split)))
|
||||
(value-str (effective-value
|
||||
(string-drop content (+ split 1)))))
|
||||
(if (string-null? value-str)
|
||||
;; Wert steht im eingerückten Block darunter
|
||||
(receive (children rest)
|
||||
(span (lambda (it) (> (item-indent it) map-indent))
|
||||
(cdr items))
|
||||
(loop rest
|
||||
(cons (cons key (parse-block children))
|
||||
result)))
|
||||
(loop (cdr items)
|
||||
(cons (cons key (parse-scalar value-str))
|
||||
result))))))))))
|
||||
|
||||
;; Hauptparser für Key-Value oder Listen
|
||||
(define (parse-lines lines current-indent)
|
||||
(let loop ((ls lines) (result '()))
|
||||
(if (null? ls)
|
||||
(reverse result)
|
||||
(let* ((raw-line (car ls))
|
||||
(line (clean-line raw-line)))
|
||||
(cond
|
||||
;; Kommentar oder leere Zeile
|
||||
((blank-or-comment? raw-line)
|
||||
(loop (cdr ls) result))
|
||||
;; Liste: "- wert", "- key: value" (Inline-Mapping) oder "-" mit
|
||||
;; eingerücktem Block darunter
|
||||
(define (parse-list items)
|
||||
(let ((list-indent (item-indent (car items))))
|
||||
(let loop ((items items) (result '()))
|
||||
(if (null? items)
|
||||
(reverse result)
|
||||
(let ((content (item-content (car items))))
|
||||
(if (not (dash-line? content))
|
||||
(begin
|
||||
(format (current-error-port)
|
||||
"YAML-Syntaxfehler: Ungültiges Listenelement: ~a\n"
|
||||
content)
|
||||
(loop (cdr items) result))
|
||||
(receive (children rest)
|
||||
(span (lambda (it) (> (item-indent it) list-indent))
|
||||
(cdr items))
|
||||
(let* ((after-dash (string-drop content 1))
|
||||
(inline (effective-value after-dash)))
|
||||
(cond
|
||||
((and (string-null? inline) (null? children))
|
||||
(loop rest (cons '() result)))
|
||||
((string-null? inline)
|
||||
(loop rest (cons (parse-block children) result)))
|
||||
(else
|
||||
;; Inline-Inhalt: als synthetisches Item mit der
|
||||
;; Spalte des Inhalts vor die Kinder stellen, so
|
||||
;; funktioniert auch "- key: value" mit weiteren
|
||||
;; Keys auf den Folgezeilen
|
||||
(let* ((offset (- (string-length content)
|
||||
(string-length
|
||||
(string-trim after-dash))))
|
||||
(synth (cons (+ list-indent offset) inline)))
|
||||
(loop rest
|
||||
(cons (parse-block (cons synth children))
|
||||
result)))))))))))))
|
||||
|
||||
;; Liste
|
||||
((string-match "^- " line)
|
||||
(let ((list-lines (take-indented ls current-indent)))
|
||||
(let ((parsed-list (parse-list list-lines current-indent)))
|
||||
(loop (drop ls (length list-lines))
|
||||
(cons parsed-list result)))))
|
||||
|
||||
;; Key: Value
|
||||
((string-match "^[^:]+:" line)
|
||||
(let* ((kv (string-split line #\:))
|
||||
(key (string-trim (car kv)))
|
||||
(value-str (string-trim (string-join (cdr kv) ":")))
|
||||
(next-lines (cdr ls)))
|
||||
(if (string-null? value-str)
|
||||
;; Wert auf nachfolgender Einrückungsebene
|
||||
(let* ((sub (take-indented next-lines (+ current-indent 2)))
|
||||
(parsed (parse-lines sub (+ current-indent 2)))
|
||||
(remaining (drop next-lines (length sub))))
|
||||
(loop remaining
|
||||
(cons (cons key parsed) result)))
|
||||
;; Einfacher Key:Value
|
||||
(loop next-lines
|
||||
(cons (cons key (parse-scalar value-str)) result)))))
|
||||
|
||||
;; Fehlerhafte Zeile
|
||||
(else
|
||||
;; Vermeide Fehlermeldung für Leerzeilen oder leere Objekte
|
||||
(if (or (string-null? (string-trim line))
|
||||
(member line '("{}" "[]")))
|
||||
(loop (cdr ls) result)
|
||||
(begin
|
||||
(format (current-error-port)
|
||||
"Syntaxfehler: Ungültige Zeile: ~a\n" raw-line)
|
||||
(loop (cdr ls) result))))
|
||||
)))))
|
||||
|
||||
(let ((lines (read-lines filename)))
|
||||
(parse-lines lines 0)))
|
||||
(parse-block (read-items filename)))
|
||||
|
||||
(define (parse-yml-file filename) (resolve-inherits (yml-file->scm filename)))
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
(use-modules (ice-9 regex) (srfi srfi-1))
|
||||
|
||||
;; YAML-Writer: Gegenstück zu yaml_parser.scm.
|
||||
;; Schreibt verschachtelte alist/list-Strukturen als YAML, so dass
|
||||
;; yml-file->scm sie wieder einlesen kann.
|
||||
;;
|
||||
;; Datenmodell (wie vom Parser erzeugt):
|
||||
;; - Mapping: Liste von Paaren (key . value), key als String oder Symbol
|
||||
;; - Liste: Liste von beliebigen Werten
|
||||
;; - Skalare: String, Zahl, #t/#f, '() (leer/null, wird als [] geschrieben)
|
||||
;;
|
||||
;; Andere Werte werden vorab automatisch umgewandelt: Markups zu Strings,
|
||||
;; alles Unbekannte über ~a formatiert (siehe yml-sanitize).
|
||||
|
||||
;; Steuert, ob layout_bottom.ily die Header-Daten des Liedes als
|
||||
;; YAML-Datei exportiert; aktivierbar per (set! yaml-export-enabled #t)
|
||||
;; oder durch Definieren der Variable vor dem Laden der Includes.
|
||||
(define yaml-export-enabled
|
||||
(if (defined? 'yaml-export-enabled) yaml-export-enabled #f))
|
||||
|
||||
;; Beliebige Werte in YAML-taugliche Daten umwandeln.
|
||||
;; (Benötigt die LilyPond-Umgebung für markup? und markup->string.)
|
||||
(define (yml-sanitize v)
|
||||
(cond
|
||||
((or (string? v) (number? v) (boolean? v) (symbol? v) (null? v)) v)
|
||||
((markup? v) (markup->string v))
|
||||
((list? v) (map yml-sanitize v))
|
||||
((pair? v) (cons (yml-sanitize (car v)) (yml-sanitize (cdr v))))
|
||||
(else (format #f "~a" v))))
|
||||
|
||||
(define (scm->yml-string data)
|
||||
|
||||
;; Ist data ein Mapping (alist)? Nicht-leere Liste, deren Elemente
|
||||
;; alle Paare mit String- oder Symbol-Key sind.
|
||||
;; Achtung: eine Liste von Listen, deren erste Elemente Strings sind,
|
||||
;; ist davon nicht unterscheidbar und wird als Mapping interpretiert.
|
||||
(define (mapping? data)
|
||||
(and (pair? data)
|
||||
(list? data)
|
||||
(every (lambda (entry)
|
||||
(and (pair? entry)
|
||||
(or (string? (car entry))
|
||||
(symbol? (car entry)))))
|
||||
data)))
|
||||
|
||||
(define (scalar? v)
|
||||
(or (string? v) (symbol? v) (number? v) (boolean? v) (null? v)))
|
||||
|
||||
(define (key->string k)
|
||||
(if (symbol? k) (symbol->string k) k))
|
||||
|
||||
;; Muss der String gequotet werden, damit er beim Einlesen wieder
|
||||
;; als derselbe String erkannt wird?
|
||||
(define (needs-quotes? s)
|
||||
(or (string-null? s)
|
||||
(member s '("true" "false" "null" "{}" "[]"))
|
||||
(string->number s) ;; zahlartig ("1981", "1.", "-5") würde zur Zahl
|
||||
(string-index s #\#) ;; würde als Kommentar abgeschnitten
|
||||
(string-index s #\:) ;; würde als Key: Value gelesen
|
||||
(string-index s #\newline) ;; mehrzeilig
|
||||
(string-index s #\")
|
||||
(string-prefix? "-" s) ;; würde als Listenelement gelesen
|
||||
(string-prefix? "'" s)
|
||||
(not (string=? s (string-trim-both s))))) ;; führende/folgende Leerzeichen
|
||||
|
||||
;; Doppelt gequoteter YAML-String; Backslash, Anführungszeichen und
|
||||
;; Zeilenumbrüche werden escaped, damit alles auf einer Zeile bleibt.
|
||||
(define (quote-string s)
|
||||
(string-append
|
||||
"\""
|
||||
(string-concatenate
|
||||
(map (lambda (c)
|
||||
(cond
|
||||
((char=? c #\\) "\\\\")
|
||||
((char=? c #\") "\\\"")
|
||||
((char=? c #\newline) "\\n")
|
||||
(else (string c))))
|
||||
(string->list s)))
|
||||
"\""))
|
||||
|
||||
(define (scalar->string v)
|
||||
(cond
|
||||
((eq? v #t) "true")
|
||||
((eq? v #f) "false")
|
||||
((null? v) "[]")
|
||||
((number? v) (number->string v))
|
||||
((symbol? v) (scalar->string (symbol->string v)))
|
||||
((string? v) (if (needs-quotes? v) (quote-string v) v))
|
||||
(else (error "scm->yml-string: nicht unterstützter Wert" v))))
|
||||
|
||||
(define (indent-string n) (make-string n #\space))
|
||||
|
||||
;; Key ggf. quoten (leere oder zahlartige Keys, Sonderzeichen)
|
||||
(define (key->yml-string k)
|
||||
(let ((s (key->string k)))
|
||||
(if (needs-quotes? s) (quote-string s) s)))
|
||||
|
||||
;; Doppelte Keys sind in YAML nicht erlaubt: Werte gleicher Keys werden
|
||||
;; zu einer Liste zusammengefasst, z.B. authors mit (voice 2) (voice 3)
|
||||
;; -> voice: [2, 3].
|
||||
(define (merge-duplicate-keys data)
|
||||
(define (value->list v)
|
||||
(if (and (list? v) (not (mapping? v))) v (list v)))
|
||||
(let loop ((entries data) (result '()))
|
||||
(if (null? entries)
|
||||
(reverse result)
|
||||
(let* ((entry (car entries))
|
||||
(existing (find (lambda (e) (equal? (car e) (car entry)))
|
||||
result)))
|
||||
(if existing
|
||||
(begin
|
||||
(set-cdr! existing (append (value->list (cdr existing))
|
||||
(value->list (cdr entry))))
|
||||
(loop (cdr entries) result))
|
||||
(loop (cdr entries)
|
||||
(cons (cons (car entry) (cdr entry)) result)))))))
|
||||
|
||||
;; Mapping schreiben: "key: skalar" oder "key:" mit eingerücktem Inhalt
|
||||
(define (write-mapping data indent port)
|
||||
(for-each
|
||||
(lambda (entry)
|
||||
(let ((key (key->yml-string (car entry)))
|
||||
(value (cdr entry)))
|
||||
(if (scalar? value)
|
||||
(format port "~a~a: ~a\n"
|
||||
(indent-string indent) key (scalar->string value))
|
||||
(begin
|
||||
(format port "~a~a:\n" (indent-string indent) key)
|
||||
(write-node value (+ indent 2) port)))))
|
||||
(merge-duplicate-keys data)))
|
||||
|
||||
;; Liste schreiben: "- skalar" oder "-" mit eingerücktem Inhalt
|
||||
;; (Verschachtelter Inhalt darf NICHT auf der "-"-Zeile beginnen,
|
||||
;; weil parse-list den Inhalt der "-"-Zeile verwirft, sobald
|
||||
;; eingerückte Folgezeilen existieren.)
|
||||
(define (write-list data indent port)
|
||||
(for-each
|
||||
(lambda (item)
|
||||
(if (scalar? item)
|
||||
(format port "~a- ~a\n"
|
||||
(indent-string indent) (scalar->string item))
|
||||
(begin
|
||||
(format port "~a-\n" (indent-string indent))
|
||||
(write-node item (+ indent 2) port))))
|
||||
data))
|
||||
|
||||
(define (write-node data indent port)
|
||||
(cond
|
||||
((mapping? data) (write-mapping data indent port))
|
||||
((and (list? data) (not (null? data))) (write-list data indent port))
|
||||
(else (format port "~a~a\n" (indent-string indent) (scalar->string data)))))
|
||||
|
||||
(call-with-output-string
|
||||
(lambda (port) (write-node (yml-sanitize data) 0 port))))
|
||||
|
||||
;; Daten als YAML-Datei schreiben
|
||||
(define (scm->yml-file filename data)
|
||||
(call-with-output-file filename
|
||||
(lambda (port)
|
||||
(display "---\n" port)
|
||||
(display (scm->yml-string data) port))))
|
||||
|
||||
Reference in New Issue
Block a user