tee-pipe.rkt
#lang racket
(module+ test
  (require rackunit))

(require racket/async-channel)

(provide (contract-out
          [tee-pipe (->* (input-port?) #:rest (listof (-> input-port? any)) list?)]))

(define (tee-pipe in . handlers)
  (define-values (r-outputs r-channels)
    (for/fold ([outputs '()]
               [channels '()])
        ([h (in-list handlers)])
      (define-values (i o) (make-pipe))
      (define c (make-async-channel))
      (define t (thread (thunk
                         (with-handlers [(exn:fail?
                                          (lambda (e)
                                            (async-channel-put c e)))]
                           (async-channel-put c (h i)))
                         (close-input-port i))))
      (values (cons o outputs) (cons c channels))))
  (define outputs (reverse r-outputs))
  (define channels (reverse r-channels))
  (thread (thunk (apply copy-port in outputs)
                 (for ([o (in-list outputs)])
                   (close-output-port o))))
  (for/list ([c (in-list channels)]) (async-channel-get c)))


(module+ test
  (check-equal? '("123\nabc\nABC" ("123" "abc" "ABC"))
                (tee-pipe (open-input-string "123\nabc\nABC")
                          port->string
                          port->lines))

  (check-equal? '("123\nabc\nABC" ("123" "abc" "ABC") (#"123" #"abc" #"ABC"))
                (tee-pipe (open-input-string "123\nabc\nABC")
                          port->string
                          port->lines
                          port->bytes-lines)))