Last active
December 7, 2021 21:55
-
-
Save sleibrock/eb2778ad0b46ca8946928a69dfe2e10f to your computer and use it in GitHub Desktop.
A WIP CSV Macro in Racket
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket/base | |
; for-syntax means importing bindings for the macro-level generation phase | |
(require (for-syntax racket/syntax racket/base racket/string) | |
(only-in racket/string string-join string-split) | |
(only-in racket/port port->lines)) | |
(define-syntax (defrec stx) | |
(syntax-case stx () | |
[(_ id col-headers (fields ...) delim) | |
; Check for free identifiers first | |
(for-each (lambda (x) | |
(unless (identifier? x) | |
(raise-syntax-error #f "not an identifier" stx x))) | |
(cons #'id (syntax->list #'(fields ...)))) | |
(with-syntax ([pred-id (format-id #'id "~a?" #'id)] | |
[file->things (format-id #'id "file->~as" #'id)] | |
[thing->csv (format-id #'id "~a->csv" #'id)] | |
[list->thing (format-id #'id "list->~a" #'id)] | |
[thing=? (format-id #'id "~a=?" #'id)] | |
[delimiter (syntax->datum #'delim)] | |
[headers-lst (syntax->datum #'col-headers)]) | |
#`(begin | |
; Encapsulate our data into a Vector storage class | |
; Pack the ID, the headers, the delim-type and the actual data | |
; items into the vector for use in the other functions | |
(define (id fields ...) | |
(unless (= (length headers-lst) (length (list fields ...))) | |
(error 'id "Whoops! Incorrect amount of data!")) | |
(apply vector `(id ,headers-lst ,delimiter ,@(list fields ...)))) | |
; Create the predicate here using whatever means | |
(define (pred-id v) | |
(and (vector? v) | |
(eq? (vector-ref v 0) 'id))) | |
; Create a constant access for the record's headers (no need to vec-ref) | |
(define (headers v) headers-lst) | |
(define (list->thing listof-vals) | |
(apply id listof-vals)) | |
; Convert a generic file port into a list of things (incomplete) | |
(define (file->things fpath #:skip-fn [sfn (λ (x) x)]) | |
(call-with-input-file (build-path (string->path fpath)) | |
(λ (input-f) | |
(map | |
(λ (row) | |
(list->thing (string-split row delimiter))) | |
(sfn (port->lines input-f)))))) | |
; Convert a list of things into a CSV formatted file using the delimiter | |
(define (thing->csv fpath listof-v) | |
(call-with-output-file #:exists 'replace | |
fpath | |
(λ (output) | |
(parameterize ([current-output-port output]) | |
(displayln (string-join headers-lst delimiter)) | |
(for-each | |
(λ (v) | |
(displayln | |
(string-join | |
(for/list ([f headers-lst] [n (in-naturals 3)]) | |
(vector-ref v n)) | |
delimiter))) | |
listof-v))))) | |
; Equality check to determine if things are equal | |
(define (thing=? v1 v2) | |
(and (= (pred-id v1) (pred-id v2)))) | |
; Accessor funcs for each field | |
; The for/list generates a pairing of (Field * Num) | |
; where Num is used to access the storage vector | |
#,@(for/list ([x (syntax->list #'(fields ...))] | |
[n (in-naturals 3)]) | |
(with-syntax ([acc-id (format-id #'id "~a-~a" #'id x)] | |
[ix n]) | |
#`(define (acc-id v) | |
(unless (pred-id v) | |
(error 'acc-id "~a is not a ~a struct" v 'id)) | |
(vector-ref v ix))))))])) | |
; An example of defining a CSV record with read/writers | |
(defrec MyRecord | |
(list "ProductID" "ProductName") | |
[pid name] | |
",") | |
; Try to write the records | |
(define my-records | |
(list | |
(MyRecord "300" "hi") | |
(MyRecord "400" "Hello") | |
(list->MyRecord '("500" "Poop")))) | |
; this works | |
(MyRecord->csv "MyRecords.csv" my-records) | |
; test reading in a file | |
(define read-back (file->MyRecords "MyRecords.csv" #:skip-fn cdr)) | |
(for-each displayln read-back) | |
; end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment