diff --git a/collects/2htdp/batch-io.rkt b/collects/2htdp/batch-io.rkt index d80d4df9cb..1cdf88ef1f 100644 --- a/collects/2htdp/batch-io.rkt +++ b/collects/2htdp/batch-io.rkt @@ -13,6 +13,9 @@ ;; todo? ;; -- export tokenization? +(define *input-devices* `((standard-in ,current-input-port))) +(define *output-devices* `((standard-out ,current-output-port))) + ;; ----------------------------------------------------------------------------- (provide simulate-file) ;; syntax (simulate-file reader string ...) @@ -50,6 +53,9 @@ write-file ;; String String -> String ;; (write-file filename str) writes str to filename; ;; produces the file name as a confirmation that the write succeeded + + ;; *input-devives*: symbols that redirect input from an input-port + ;; *output-devives*: symbols that redirect output from a output-port ) ;; ----------------------------------------------------------------------------- @@ -57,7 +63,7 @@ (define-syntax-rule (def-reader (name f s ...) body ...) (define (name f s ...) - (check-file f 'name) + (check-input-file f 'name) (let () body ...))) @@ -135,44 +141,57 @@ ;; writer (define (write-file f str) - (check-arg 'write-file (string? f) "string (name of file)" "first" f) + (check-output-file f 'write-file) (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)) + (define (wt) (printf "~a" str)) + (define device (assq f *output-devices*)) + (if device + (parameterize ((current-output-port [(cadr device)])) (wt)) + (with-output-to-file f wt #: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 (rd) + (let loop ([accu '()]) (define nxt (read-chunk)) - (if (eof-object? nxt) (process-accu accu) (loop (cons nxt accu))))))) + (if (eof-object? nxt) (process-accu accu) (loop (cons nxt accu))))) + (define device (assq f *input-devices*)) + (if device + (parameterize ((current-input-port [(cadr device)])) (rd)) + (with-input-from-file f #:mode 'text rd))) + +(define (read-csv-file/func f [row (lambda (x) x)]) + (define (reader o) + (csv->list + (make-csv-reader o '((strip-leading-whitespace? . #t) + (strip-trailing-whitespace? . #t))))) + (define device (assq f *input-devices*)) + (map row + (if device + (reader [(cadr device)]) + (call-with-input-file f #:mode 'text reader)))) ;; [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)) +(define (check-input-file f t) + (define d? (assq f *input-devices*)) + (check-arg t (or (string? f) d?) (format "string or one of: ~a" (map car *input-devices*)) "first" f) + (check-arg t (or d? (file-exists? f)) "name of file in program's folder" "first" f)) + +;; effect: ensure that f is a file in current directory or report error for t +(define (check-output-file f t) + (define d? (assq f *output-devices*)) + (check-arg t (or (string? f) d?) (format "string or one of: ~a" (map car *output-devices*)) "first" f)) ;; split : String [Regexp] -> [Listof String] ;; splits a string into a list of substrings using the given delimiter diff --git a/collects/2htdp/tests/batch-io.rkt b/collects/2htdp/tests/batch-io.rkt index 6a7fa1148c..5407442ef5 100644 --- a/collects/2htdp/tests/batch-io.rkt +++ b/collects/2htdp/tests/batch-io.rkt @@ -1,8 +1,11 @@ -#lang scheme/load +#lang racket (require rackunit) (require 2htdp/batch-io) +;; --------------------------------------------------------------------------------------------------- +;; use simulate file to test the I/O functions + (check-equal? (simulate-file read-file "hello world" @@ -33,77 +36,84 @@ (check-equal? (simulate-file read-file) "") -(define file "batch-io.txt") +;; --------------------------------------------------------------------------------------------------- +;; manipulate an actual file on disk -(define test1 #<list test1))) -(write-file file test1) -(check-equal? (read-1strings file) as-1strings1 "as-1strings 1") - -(define as-1strings2 - (map string - (apply append - (map string->list - (cdr - (foldr (lambda (f r) (cons "\n" (cons f r))) '() - test2-as-list)))))) - -(write-file file test2) -(check-equal? (read-1strings file) as-1strings2 "as-lines 2") - -(define test2-a-as-list '("test1" "" "test2")) - -(define test2-a - (apply string-append - (list (first test2-as-list) - (string #\newline) - (string #\newline) - (second test2-as-list)))) - -(write-file file test2-a) -(check-equal? (read-lines file) test2-a-as-list "as-lines 2-a") -(check-equal? (read-words file) '("test1" "test2") "as-words 2-a") - -(define test3 #<< eos + ) + + (define test2-as-list '("test1" "test2")) + + (define test2 + (apply string-append + (list (first test2-as-list) + (string #\newline) + (second test2-as-list)))) + + (write-file file test1) + (check-true (string=? (simulate-file read-file test1) test1) "read-file 1") + + (write-file file test2) + (check-true (string=? (simulate-file read-file test2) test2) "read-file 2") + + (write-file file test1) + (check-equal? (read-lines file) (list test1) "as-lines 1") + + (write-file file test2) + (check-equal? (read-lines file) test2-as-list "as-lines 2") + + (define as-1strings1 (map string (string->list test1))) + (write-file file test1) + (check-equal? (read-1strings file) as-1strings1 "as-1strings 1") + + (define as-1strings2 + (map string + (apply append + (map string->list + (cdr + (foldr (lambda (f r) (cons "\n" (cons f r))) '() + test2-as-list)))))) + + (write-file file test2) + (check-equal? (read-1strings file) as-1strings2 "as-lines 2") + + (define test2-a-as-list '("test1" "" "test2")) + + (define test2-a + (apply string-append + (list (first test2-as-list) + (string #\newline) + (string #\newline) + (second test2-as-list)))) + + (write-file file test2-a) + (check-equal? (read-lines file) test2-a-as-list "as-lines 2-a") + (check-equal? (read-words file) '("test1" "test2") "as-words 2-a") + + (define test3 #<< eos word1, word2 word3, word4 eos - ) - -(write-file file test3) -(check-equal? (read-words file) '("word1," "word2" "word3," "word4") - "as-words") -(check-equal? (read-words/line file) '(("word1," "word2") ("word3," "word4")) - "as-words") -(check-equal? (read-csv-file file) '(("word1" "word2") ("word3" "word4")) - "as-cvs 1") -(check-equal? (read-csv-file/rows file length) '(2 2) - "as-csv/rows") + ) + + (write-file file test3) + (check-equal? (read-words file) '("word1," "word2" "word3," "word4") + "as-words") + (check-equal? (read-words/line file) '(("word1," "word2") ("word3," "word4")) + "as-words") + (check-equal? (read-csv-file file) '(("word1" "word2") ("word3" "word4")) + "as-cvs 1") + (check-equal? (read-csv-file/rows file length) '(2 2) + "as-csv/rows") + (delete-file file)) +;; --------------------------------------------------------------------------------------------------- +;; check failure more of functions. (check-exn exn:fail:contract? (lambda () (simulate-file cons))) ;; (check-exn exn:fail:contract? (lambda () (simulate-file))) ;; <--- figure this out @@ -120,3 +130,79 @@ eos (check-exn exn:fail:contract? (lambda () (read-1strings 0))) (check-exn exn:fail:contract? (lambda () (read-1strings '("test")))) + +;; --------------------------------------------------------------------------------------------------- +;; read from stdio + +(let () + (define *standard-out* "") + (define-syntax-rule + (catch-stdo e) + (set! *standard-out* (with-output-to-string (lambda () e)))) + (define-syntax-rule + (pipe-stdi e) + (with-input-from-string *standard-out* (lambda () e))) + + + (define test1 #<list test1))) + (catch-stdo (write-file 'standard-out test1)) + (check-equal? (pipe-stdi (read-1strings 'standard-in)) as-1strings1 "as-1strings 1") + + (define as-1strings2 + (map string + (apply append + (map string->list + (cdr + (foldr (lambda (f r) (cons "\n" (cons f r))) '() + test2-as-list)))))) + + (catch-stdo (write-file 'standard-out test2)) + (check-equal? (pipe-stdi (read-1strings 'standard-in)) as-1strings2 "as-lines 2") + + (define test2-a-as-list '("test1" "" "test2")) + + (define test2-a + (apply string-append + (list (first test2-as-list) + (string #\newline) + (string #\newline) + (second test2-as-list)))) + + (catch-stdo (write-file 'standard-out test2-a)) + (check-equal? (pipe-stdi (read-lines 'standard-in)) test2-a-as-list "as-lines 2-a") + (check-equal? (pipe-stdi (read-words 'standard-in)) '("test1" "test2") "as-words 2-a") + + (define test3 #<< eos + word1, word2 + word3, word4 + eos + ) + + (catch-stdo (write-file 'standard-out test3)) + (check-equal? (pipe-stdi (read-words 'standard-in)) '("word1," "word2" "word3," "word4") + "as-words") + (check-equal? (pipe-stdi (read-words/line 'standard-in)) '(("word1," "word2") ("word3," "word4")) + "as-words") + (check-equal? (pipe-stdi (read-csv-file 'standard-in)) '(("word1" "word2") ("word3" "word4")) + "as-cvs 1") + (check-equal? (pipe-stdi (read-csv-file/rows 'standard-in length)) '(2 2) + "as-csv/rows")) \ No newline at end of file diff --git a/collects/2htdp/tests/batch-io2.rkt b/collects/2htdp/tests/batch-io2.rkt index 6401558b1a..1d5dfb9d23 100644 --- a/collects/2htdp/tests/batch-io2.rkt +++ b/collects/2htdp/tests/batch-io2.rkt @@ -4,14 +4,17 @@ (require 2htdp/batch-io) -(with-output-to-file "batch-io2.txt" +(define file "batch-io2.txt") + +(with-output-to-file file (lambda () (display "hello") (display #\return) (display #\linefeed)) #:exists 'replace) -(read-lines "batch-io2.txt") - - +(require rackunit) +(check-equal? (read-lines "batch-io2.txt") '("hello")) +(when (file-exists? file) + (delete-file file)) \ No newline at end of file diff --git a/collects/2htdp/tests/batch-io2.txt b/collects/2htdp/tests/batch-io2.txt deleted file mode 100644 index ef0493b275..0000000000 --- a/collects/2htdp/tests/batch-io2.txt +++ /dev/null @@ -1 +0,0 @@ -hello diff --git a/collects/teachpack/2htdp/scribblings/batch-io.scrbl b/collects/teachpack/2htdp/scribblings/batch-io.scrbl index a0b248f717..03f54e9bb4 100644 --- a/collects/teachpack/2htdp/scribblings/batch-io.scrbl +++ b/collects/teachpack/2htdp/scribblings/batch-io.scrbl @@ -32,9 +32,16 @@ @; ----------------------------------------------------------------------------- -@(define-syntax-rule (reading name ctc s) - @defproc[(@name [f (and/c string? file-exists?)]) @ctc ]{ - reads the content of file @racket[f] and produces it as @s .} ) +@(define-syntax reading + (syntax-rules () + [(_ name ctc s) + @defproc[(@name [f (or/c 'standard-out (and/c string? file-exists?))]) @ctc ]{ + reads the standard input device (until closed) or the content of file + @racket[f] and produces it as @list[s].}] + [(_ name ctc [x ctc2] s ...) + @defproc[(@name [f (or/c 'standard-out (and/c string? file-exists?))] [@x @ctc2]) @ctc ]{ + reads the standard input device (until closed) or the content of file + @racket[f] and produces it as @list[s ...].}])) @teachpack["batch-io"]{Batch Input/Output} @@ -119,8 +126,7 @@ length. Here the third line of the file turns into a row of three elements. } -@item{@defproc[(@read-csv-file/rows [f (and/c string? exists?)][s - (-> (listof any/c) X?)]) (listof X?)]{reads the content of file @racket[f] and +@item{@reading[read-csv-file/rows (listof X?) [s (-> (listof any/c) X?)]]{reads the content of file @racket[f] and produces it as list of rows, each constructed via @racket[s]} @examples[#:eval (examples-batch-io) @@ -132,12 +138,14 @@ elements. number of separated tokens and the result is just a list of numbers. In many cases, the function argument is used to construct a structure from a row.} + ] There is only one writer function at the moment: @itemlist[ -@item{@defproc[(write-file [f string?] [cntnt string?]) string?]{ +@item{@defproc[(write-file [f (or/c 'standard-out string?)] [cntnt string?]) string?]{ + sends @racket[cntnt] to the standard output device or turns @racket[cntnt] into the content of file @racket[f], located in the same folder (directory) as the program. If the write succeeds, the function produces the name of the file (@racket[f]); otherwise it signals