Skip to content

Instantly share code, notes, and snippets.

@sleibrock
Last active December 7, 2021 21:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sleibrock/eb2778ad0b46ca8946928a69dfe2e10f to your computer and use it in GitHub Desktop.
Save sleibrock/eb2778ad0b46ca8946928a69dfe2e10f to your computer and use it in GitHub Desktop.
A WIP CSV Macro in Racket
#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