-
Notifications
You must be signed in to change notification settings - Fork 5
/
Tutorial.hs
140 lines (121 loc) · 5.36 KB
/
Tutorial.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
{-# 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