#lang racket/base
(require racket/contract
racket/file
racket/match
racket/path
file/gzip
file/gunzip)
(define VERSION "1")
(define (pack-current-directory)
(let ([bytes-output-port (open-output-bytes)])
(pack-current-directory-to-port bytes-output-port)
(get-output-bytes bytes-output-port)))
(define (pack-current-directory-to-port an-output-port)
(display VERSION an-output-port)
(newline an-output-port)
(let-values ([(ip op) (make-pipe)])
(write (for/list ([a-path (pathlist-closure
(list (build-path 'same)))])
(cond [(directory-exists? a-path)
(list (munge-path a-path))]
[(file-exists? a-path)
(list (munge-path a-path)
(file->bytes a-path))]
[else
(error 'pack-current-directory)]))
op)
(close-output-port op)
(gzip-through-ports ip an-output-port #f (current-seconds))))
(define (unpack-into-current-directory directory-bytes
#:exists (exists-flag 'error))
(unpack-port-into-current-directory (open-input-bytes directory-bytes)
#:exists exists-flag))
(define (unpack-port-into-current-directory an-input-port
#:exists (exists-flag 'error))
(let ([version (read-line an-input-port)])
(unless (string=? version VERSION)
(error 'unpack-port-into-current-directory
"Expected version ~s, got ~s" VERSION version)))
(make-directory* (current-directory))
(let-values ([(inp outp) (make-pipe)])
(gunzip-through-ports an-input-port outp)
(let ([s-exp (read inp)])
(for ([elt s-exp])
(match elt
[(list (and a-munged-path (? all-munged-path-component?)))
(make-directory* (unmunge-path a-munged-path))]
[(list (and a-munged-path (? all-munged-path-component?))
(and some-bytes (? bytes?)))
(call-with-output-file (unmunge-path a-munged-path)
(lambda (op)
(write-bytes some-bytes op))
#:exists exists-flag)])))))
(define (munge-path a-path)
(map (lambda (component)
(cond
[(eq? component 'same) 'same]
[(eq? component 'up)
(error 'munge-path)]
[else
(path->string component)]))
(explode-path a-path)))
(define (munged-path-component? x)
(or (eq? x 'same)
(string? x)))
(define (all-munged-path-component? x)
(and (list? x)
(andmap munged-path-component? x)))
(define (unmunge-path x)
(apply build-path x))
(provide/contract
[pack-current-directory (-> bytes?)]
[pack-current-directory-to-port (output-port? . -> . any)]
[unpack-into-current-directory ((bytes?)
(#:exists (or/c 'error
'append
'update
'replace
'truncate
'truncate/replace))
. ->* . any)]
[unpack-port-into-current-directory ((input-port?)
(#:exists (or/c 'error
'append
'update
'replace
'truncate
'truncate/replace))
. ->* . any)])