Wrapper around the Haskell library cassava for processing CSV data in constant space via io-streams.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Tutorial.hs 5.4KB

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