{-# LANGUAGE OverloadedStrings #-} {- This file is part of the Haskell package cassava-streams. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at git://pmade.com/cassava-streams/LICENSE. No part of cassava-streams package, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. -} -------------------------------------------------------------------------------- -- | A simple tutorial on using the cassava-streams library to glue -- together cassava and io-streams. -- -- Note: if you're reading this on Hackage or in Haddock then you -- should switch to source view with the \"Source\" link at the top of -- this page or open this file in your favorite text editor. module System.IO.Streams.Csv.Tutorial ( -- * Types representing to-do items and their state Item (..) , TState (..) -- * Functions which use cassava-streams functions , onlyTodo , markDone ) where -------------------------------------------------------------------------------- import Control.Monad import Data.Csv import qualified Data.Vector as V import System.IO import qualified System.IO.Streams as Streams import System.IO.Streams.Csv -------------------------------------------------------------------------------- -- | A to-do item. data Item = Item { title :: String -- ^ Title. , state :: TState -- ^ State. , time :: Maybe Double -- ^ Seconds taken to complete. } deriving (Show, Eq) instance FromNamedRecord Item where parseNamedRecord m = Item <$> m .: "Title" <*> m .: "State" <*> m .: "Time" instance ToNamedRecord Item where toNamedRecord (Item t s tm) = namedRecord [ "Title" .= t , "State" .= s , "Time" .= tm ] -------------------------------------------------------------------------------- -- | Possible states for a to-do item. data TState = Todo -- ^ Item needs to be completed. | Done -- ^ Item has been finished. deriving (Show, Eq) instance FromField TState where parseField "TODO" = return Todo parseField "DONE" = return Done parseField _ = mzero instance ToField TState where toField Todo = "TODO" toField Done = "DONE" -------------------------------------------------------------------------------- -- | The @onlyTodo@ function reads to-do 'Item's from the given input -- handle (in CSV format) and writes them back to the output handle -- (also in CSV format), but only if the items are in the @Todo@ -- state. In another words, the CSV data is filtered so that the -- output handle only receives to-do 'Item's which haven't been -- completed. -- -- The io-streams @handleToInputStream@ function is used to create an -- @InputStream ByteString@ stream from the given input handle. -- -- That stream is then given to the cassava-streams function -- 'decodeStreamByName' which converts the @InputStream ByteString@ -- stream into an @InputStream Item@ stream. -- -- Notice that the cassava-streams function 'onlyValidRecords' is used -- to transform the decoding stream into one that only produces valid -- records. Any records which fail type conversion (via -- @FromNamedRecord@ or @FromRecord@) will not escape from -- 'onlyValidRecords' but instead will throw an exception. -- -- Finally the io-streams @filter@ function is used to filter the -- input stream so that it only produces to-do items which haven't -- been completed. onlyTodo :: Handle -- ^ Input handle where CSV data can be read. -> Handle -- ^ Output handle where CSV data can be written. -> IO () onlyTodo inH outH = do -- A stream which produces items which are not 'Done'. input <- Streams.handleToInputStream inH >>= decodeStreamByName >>= onlyValidRecords >>= Streams.filter (\item -> state item /= Done) -- A stream to write items into. They will be converted to CSV. output <- Streams.handleToOutputStream outH >>= encodeStreamByName (V.fromList ["State", "Time", "Title"]) -- Connect the input and output streams. Streams.connect input output -------------------------------------------------------------------------------- -- | The @markDone@ function will read to-do items from the given -- input handle and mark any matching items as @Done@. All to-do -- items are written to the given output handle. markDone :: String -- ^ Items with this title are marked as @Done@. -> Handle -- ^ Input handle where CSV data can be read. -> Handle -- ^ Output handle where CSV data can be written. -> IO () markDone titleOfItem inH outH = do -- Change matching items to the 'Done' state. let markDone' item = if title item == titleOfItem then item {state = Done} else item -- A stream which produces items and converts matching items to the -- 'Done' state. input <- Streams.handleToInputStream inH >>= decodeStreamByName >>= onlyValidRecords >>= Streams.map markDone' -- A stream to write items into. They will be converted to CSV. output <- Streams.handleToOutputStream outH >>= encodeStreamByName (V.fromList ["State", "Time", "Title"]) -- Connect the input and output streams. Streams.connect input output