@@ -0,0 +1,132 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+{-
4+
5+This file is part of the Haskell package cassava-streams. It is
6+subject to the license terms in the LICENSE file found in the
7+top-level directory of this distribution and at
8+git://pmade.com/cassava-streams/LICENSE. No part of cassava-streams
9+package, including this file, may be copied, modified, propagated, or
10+distributed except according to the terms contained in the LICENSE
11+file.
12+
13+-}
14+
15+--------------------------------------------------------------------------------
16+-- | A simple tutorial on using the cassava-streams library to glue
17+-- together cassava and io-streams.
18+--
19+-- Note: if you're reading this on Hackage or in Haddock then you
20+-- should switch to source view with the \"Source\" link at the top of
21+-- this page or open this file in your favorite text editor.
22+module System.IO.Streams.Csv.Tutorial
23+ ( -- * Types representing to-do items and their state
24+ Item (..)
25+ , TState (..)
26+
27+ -- * Functions which use cassava-streams functions
28+ , onlyTodo
29+ , markDone
30+ ) where
31+
32+--------------------------------------------------------------------------------
33+import Control.Applicative
34+import Control.Monad
35+import Data.Csv
36+import qualified Data.Vector as V
37+import System.IO
38+import qualified System.IO.Streams as Streams
39+import System.IO.Streams.Csv
40+
41+--------------------------------------------------------------------------------
42+-- | A to-do item.
43+data Item = Item
44+ { title :: String
45+ , state :: TState
46+ } deriving (Show, Eq)
47+
48+instance FromNamedRecord Item where
49+ parseNamedRecord m = Item <$> m .: "Title"
50+ <*> m .: "State"
51+
52+instance ToNamedRecord Item where
53+ toNamedRecord (Item t s) =
54+ namedRecord [ "Title" .= t
55+ , "State" .= s
56+ ]
57+
58+--------------------------------------------------------------------------------
59+-- | Possible states for a to-do item.
60+data TState = Todo -- ^ Item needs to be completed.
61+ | Done -- ^ Item has been finished.
62+ deriving (Show, Eq)
63+
64+instance FromField TState where
65+ parseField "TODO" = return Todo
66+ parseField "DONE" = return Done
67+ parseField _ = mzero
68+
69+instance ToField TState where
70+ toField Todo = "TODO"
71+ toField Done = "DONE"
72+
73+--------------------------------------------------------------------------------
74+-- | The @onlyTodo@ function reads to-do 'Item's from the given input
75+-- handle (in CSV format) and writes them back to the output handle
76+-- (also in CSV format), but only if the items are in the @Todo@
77+-- state. In another words, the CSV data is filtered so that the
78+-- output handle only receives to-do 'Item's which haven't been
79+-- completed.
80+--
81+-- The io-streams @handleToInputStream@ function is used to create an
82+-- @InputStream ByteString@ stream from the given input handle.
83+--
84+-- That stream is then given to the cassava-streams function
85+-- 'decodeStreamByName' which converts the @InputStream ByteString@
86+-- stream into an @InputStream Item@ stream.
87+--
88+-- Finally the io-streams @filter@ function is used to filter the
89+-- input stream so that it only produces to-do items which haven't
90+-- been completed.
91+onlyTodo :: Handle -- ^ Input handle where CSV data can be read.
92+ -> Handle -- ^ Output handle where CSV data can be written.
93+ -> IO ()
94+onlyTodo inH outH = do
95+ -- A stream which produces items which are not 'Done'.
96+ input <- Streams.handleToInputStream inH >>=
97+ decodeStreamByName >>=
98+ Streams.filter (\item -> state item == Done)
99+
100+ -- A stream to write items into. They will be converted to CSV.
101+ output <- Streams.handleToOutputStream outH >>=
102+ encodeStreamByName (V.fromList ["State", "Title"])
103+
104+ -- Connect the input and output streams.
105+ Streams.connect input output
106+
107+--------------------------------------------------------------------------------
108+-- | The @markDone@ function will read to-do items from the given
109+-- input handle and mark any matching items as @Done@. All to-do
110+-- items are written to the given output handle.
111+markDone :: String -- ^ Items with this title are marked as @Done@.
112+ -> Handle -- ^ Input handle where CSV data can be read.
113+ -> Handle -- ^ Output handle where CSV data can be written.
114+ -> IO ()
115+markDone titleOfItem inH outH = do
116+ -- Change matching items to the 'Done' state.
117+ let markDone' item = if title item == titleOfItem
118+ then item {state = Done}
119+ else item
120+
121+ -- A stream which produces items and converts matching items to the
122+ -- 'Done' state.
123+ input <- Streams.handleToInputStream inH >>=
124+ decodeStreamByName >>=
125+ Streams.map markDone'
126+
127+ -- A stream to write items into. They will be converted to CSV.
128+ output <- Streams.handleToOutputStream outH >>=
129+ encodeStreamByName (V.fromList ["State", "Title"])
130+
131+ -- Connect the input and output streams.
132+ Streams.connect input output