racket/collects/2htdp/batch-io.rkt
Eli Barzilay 5aca765989 racket' -> racket/base' switches in htdp' and 2htdp'.
All of these look safe.  Also, see comment in `2htdp/batch-io' about the
splitting thing (which should probably be revised with the extensions to
`racket/string').
2012-11-06 13:29:09 -05:00

181 lines
6.0 KiB
Racket

#lang racket/base
(require racket/function
racket/file
racket/string
racket/local
(for-syntax racket/base
syntax/parse)
htdp/error
(rename-in lang/prim (first-order->higher-order f2h))
"private/csv/csv.rkt")
;; todo?
;; -- export tokenization?
;; -----------------------------------------------------------------------------
(provide simulate-file) ;; syntax (simulate-file reader string ...)
(provide
;; all reader functions consume the name of a file f:
;; -- f must be a file name (string) in the same folder as the program
read-file ;; String -> String
;; read the specified file as a string
read-1strings ;; String -> [Listof 1String]
;; read the specified file as a list of 1strings (characters)
read-lines ;; String -> [Listof String]
;; read the specified file as a list of strings, one per line
read-words ;; String -> [Listof String]
;; read the specified file as a list of white-space separated tokens
read-words/line ;; String -> [Listof [Listof String]]
;; read the specified file as a list of lines, each line as a list of words
read-csv-file ;; String -> [Listof [Listof (U Any)]]
;; -- f must be formated as a a file with comma-separated values (Any)
;; read the specified file as a list of lists---one per line---of values (Any)
read-csv-file/rows ;; String ([Listof Any] -> X) -> [Listof X]
;; -- f must be formated as a a file with comma-separated values (Any)
;; read the specified file as a file of comma-separated values, apply the second
;; argument to each row, i.e., list of CSV on one line
write-file ;; String String -> String
;; (write-file filename str) writes str to filename;
;; produces the file name as a confirmation that the write succeeded
)
;; -----------------------------------------------------------------------------
(define-syntax-rule
(def-reader (name f s ...) body ...)
(define (name f s ...)
(check-file f 'name)
(let ()
body ...)))
;; --- exported functions --
(def-reader (read-file f)
(list->string (read-chunks f read-char drop-last-newline)))
(def-reader (read-1strings f)
(map string (read-chunks f read-char drop-last-newline)))
(def-reader (read-lines f)
(read-chunks f *read-line reverse))
(def-reader (read-words f)
(read-words/line/internal f append))
(def-reader (read-words/line f)
;; String -> [Listof [Listof String]]
;; read the specified file as a list of lines, each line as a list of words
(read-words/line/internal f cons))
(define (read-words/line/internal f combine)
(define lines (read-chunks f *read-line (lambda (x) x)))
(foldl (lambda (f r)
(define fst (filter (compose not (curry string=? "")) (split f)))
(combine fst r))
'() lines))
(def-reader (read-csv-file f)
(read-csv-file/func f))
(def-reader (read-csv-file/rows f row)
(check-proc 'read-csv-file row 1 "one argument" "row")
(read-csv-file/func f row))
(define (*read-line)
(read-line (current-input-port) 'any))
;; -----------------------------------------------------------------------------
;; tester
(define-syntax (simulate-file stx)
(syntax-case stx ()
[(simulate-file)
(raise-syntax-error #f "expects at least one sub-expression" stx)]
[(simulate-file reader str ...) #'(simulate-file/proc (f2h reader) str ...)]))
(define (simulate-file/proc reader . los)
(define _1 (check-proc "simulate-file" reader 1 "reader" "one argument"))
(define _2
(andmap
(lambda (f)
(check-arg "simulate-file" (string? f) "sequence of strings" "" f))
los))
(define t (make-temporary-file "drracket-temporary-file-~a"))
(dynamic-wind
(lambda ()
(with-output-to-file t
(lambda () (for-each displayln los))
#:mode 'text
#:exists 'replace))
(lambda ()
(reader (path->string t)))
(lambda ()
(delete-file t))))
;; -----------------------------------------------------------------------------
;; writer
(define (write-file f str)
(check-arg 'write-file (string? f) "string (name of file)" "first" f)
(check-arg 'write-file (string? str) "string" "second" str)
(let ([result (not (file-exists? f))])
(with-output-to-file f
(lambda () (printf "~a" str))
#:mode 'text
#:exists 'replace)
f))
;; -----------------------------------------------------------------------------
;; auxiliaries
;; String [([Listof X] -> Y)] -> [Listof Y]
(define (read-csv-file/func f [row (lambda (x) x)])
(local ((define (reader o)
(make-csv-reader o '((strip-leading-whitespace? . #t)
(strip-trailing-whitespace? . #t)))))
(map row (call-with-input-file f (compose csv->list reader)))))
;; String (-> X) ([Listof X] -> [Listof X]) -> [Listof X]
;; read a file as a list of X where process-accu is applied to accu when eof
(define (read-chunks f read-chunk process-accu)
(with-input-from-file f
#:mode 'text
(lambda ()
(let loop ([accu '()])
(define nxt (read-chunk))
(if (eof-object? nxt) (process-accu accu) (loop (cons nxt accu)))))))
;; [Listof Char] -> [Listof Char]
(define (drop-last-newline accu)
(reverse (if (and (pair? accu) (char=? (car accu) #\newline)) (cdr accu) accu)))
;; String[file name] Symbol -> Void
;; effect: ensure that f is a file in current directory or report error for t
(define (check-file f t)
(check-arg t (string? f) "string" "first" f)
(check-arg t (file-exists? f) "name of file in program's folder" "first" f))
;; split : String [Regexp] -> [Listof String]
;; splits a string into a list of substrings using the given delimiter
;; (white space by default)
;;ELI: This shouldn't be needed now, it can use `string-split' as is
;; (also, the trimming doesn't make sense if the pattern is not a
;; space--?)
(define (split str [ptn #rx"[ ]+"])
(regexp-split ptn (string-trim str)))
;; split-lines : String -> Listof[String]
;; splits a string with newlines into a list of lines
(define (split-lines str)
(map string-trim (split str "\r*\n")))