diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 William Casarin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Pipes/Csv.hs b/Pipes/Csv.hs
new file mode 100644
--- /dev/null
+++ b/Pipes/Csv.hs
@@ -0,0 +1,105 @@
+{-| This module allows constant-space CSV parsing.
+
+    It feeds 'ByteString's into cassavas incremental CSV parser to attain true
+    constant-space record streaming.
+-}
+
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Pipes.Csv (
+  feedParser,
+  feedHeaderParser,
+  decode,
+  decodeWith,
+  decodeByName,
+  decodeByNameWith
+) where
+
+import qualified Data.Csv.Incremental as CI
+import qualified Data.ByteString as B
+
+import Data.Csv.Incremental (Parser(..), HeaderParser(..))
+import Data.Csv (DecodeOptions, FromRecord,
+                 FromNamedRecord, defaultDecodeOptions)
+import Data.ByteString (ByteString)
+import Pipes
+
+-- | Create a Record 'Producer' by feeding 'ByteString's into a 'Parser'
+feedParser :: Monad m
+           => Parser a
+           -> Producer ByteString m ()
+           -> Producer (Either String a) m ()
+feedParser parser source = case parser of
+    Fail _ e  -> yield (Left e)
+    Done es   -> each es
+    Some es k -> each es >> cont k source
+    Partial k -> cont k source
+  where
+    cont = continue feedParser
+
+
+-- | Create a NamedRecord 'Producer' by feeding 'ByteString's into a 'Parser'
+feedHeaderParser :: (Monad m, FromNamedRecord a)
+                 => HeaderParser (Parser a)
+                 -> Producer ByteString m ()
+                 -> Producer (Either String a) m ()
+feedHeaderParser headerParser source = case headerParser of
+    FailH bs e -> yield (Left e)
+    PartialH k -> cont k source
+    DoneH _ p  -> feedParser p source
+  where
+    cont = continue feedHeaderParser
+
+
+-- | Handle continuations properly within a Producer
+continue :: (Monad (t m), Monad m, MonadTrans t)
+         => (a -> Producer ByteString m () -> t m b)
+         -> (ByteString -> a)
+         -> Producer ByteString m ()
+         -> t m b
+continue feed k producer = do
+  x <- lift (next producer)
+  case x of
+    Left () -> feed (k B.empty) (return ())
+    Right (bs, producer') ->
+      if (B.null bs)
+      then continue feed k producer'
+      else feed (k bs) producer'
+
+
+
+-- | Equivalent to @'decodeWith' 'defaultDecodeOptions'@.
+decode :: (Monad m, FromRecord a)
+       => Bool
+       -> Producer ByteString m ()
+       -> Producer (Either String a) m ()
+decode = decodeWith defaultDecodeOptions
+
+
+-- | Create a 'Producer' that takes a 'ByteString' 'Producer' as input,
+-- producing either errors or 'FromRecord's.
+decodeWith :: (Monad m, FromRecord a)
+           => DecodeOptions
+           -> Bool
+           -> Producer ByteString m ()
+           -> Producer (Either String a) m ()
+decodeWith opts skipHeader src = feedParser (CI.decodeWith opts skipHeader) src
+
+
+-- | Equivalent to @'decodeByNameWith' 'defaultDecodeOptions'@.
+decodeByName :: (Monad m, FromNamedRecord a)
+             => Producer ByteString m ()
+             => Producer (Either String a) m ()
+decodeByName = decodeByNameWith defaultDecodeOptions
+
+
+-- | Create a 'Producer' that takes a 'ByteString' 'Producer' as input,
+-- producing either errors or 'FromNamedRecord's.
+decodeByNameWith :: (Monad m, FromNamedRecord a)
+                 => DecodeOptions
+                 -> Producer ByteString m ()
+                 -> Producer (Either String a) m ()
+decodeByNameWith opts src = feedHeaderParser (CI.decodeByNameWith opts) src
+
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/pipes-csv.cabal b/pipes-csv.cabal
new file mode 100644
--- /dev/null
+++ b/pipes-csv.cabal
@@ -0,0 +1,27 @@
+name:                pipes-csv
+version:             1.0.0
+synopsis: Fast, streaming csv parser
+license:             MIT
+license-file:        LICENSE
+author:              William Casarin
+maintainer:          will@casarin.me
+category:            Pipes, CSV
+build-type:          Simple
+cabal-version:       >=1.10
+Description:
+  `pipes-csv` is a streaming csv parser built on top of `cassava` and `pipes`
+
+source-repository head
+  type: git
+  location: https://github.com/jb55/pipes-csv
+
+library
+  ghc-options: -Wall -O2
+  exposed-modules:   Pipes.Csv
+  build-depends: base       >= 4.6  && < 4.7
+               , text       >= 0.11 && < 0.12
+               , bytestring >= 0.10 && < 0.11
+               , cassava    >= 0.2  && < 0.3
+               , pipes      >= 4    && < 5
+  default-language:    Haskell2010
+
