Skip to content
This repository was archived by the owner on Aug 27, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions stylesheets/tutorials.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,21 @@ body {
max-width: 730px;
}
}

table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre
{ margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }
td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }
td.sourceCode { padding-left: 5px; }
.sourceCode span.kw { color: #007020; font-weight: bold; }
.sourceCode span.dt { color: #902000; }
.sourceCode span.dv { color: #40a070; }
.sourceCode span.bn { color: #40a070; }
.sourceCode span.fl { color: #40a070; }
.sourceCode span.ch { color: #4070a0; }
.sourceCode span.st { color: #4070a0; }
.sourceCode span.co { color: #60a0b0; font-style: italic; }
.sourceCode span.ot { color: #007020; }
.sourceCode span.al { color: red; font-weight: bold; }
.sourceCode span.fu { color: #06287e; }
.sourceCode span.re { }
.sourceCode span.er { color: red; font-weight: bold; }
34 changes: 34 additions & 0 deletions tutorials/haskell/csv-encoding-decoding/code/app/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module Main
( main
)
where

-- open-data
import OpenData

-- base
import qualified Control.Monad as Monad
import qualified System.Exit as Exit


-- |
--
--

main :: IO ()
main = do
putStrLn "Open data!"

eitherCountryItems <-
fmap filterCountryItems
<$> decodeItemsFromFile "items.csv"

case eitherCountryItems of
Left reason ->
Exit.die reason

Right countryItems -> do
putStr "Number of country items: "
print (length countryItems)

-- Monad.void (encodeItemsToFile "countries.csv" countryItems)
26 changes: 26 additions & 0 deletions tutorials/haskell/csv-encoding-decoding/code/open-data.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: open-data
version: 0.1.0
build-type: Simple
cabal-version: >= 1.10

library
hs-source-dirs: src
exposed-modules:
OpenData
build-depends:
base >= 4.8 && < 4.9
, bytestring
, cassava >= 0.4 && < 0.5
, text >= 1.2 && < 1.3
, vector >= 0.11 && < 0.12
ghc-options: -Wall
default-language: Haskell2010

executable open-data-exe
hs-source-dirs: app
main-is: Main.hs
ghc-options: -Wall
build-depends:
base
, open-data
default-language: Haskell2010
259 changes: 259 additions & 0 deletions tutorials/haskell/csv-encoding-decoding/code/src/OpenData.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

----------------------------------------------------------------------
-- |
-- Module : OpenData
--
--
--
----------------------------------------------------------------------

module OpenData
( Item(..)
, ItemType(..)
, decodeItems
, decodeItemsFromFile
, encodeItems
, encodeItemsToFile
, filterCountryItems
, itemHeader
, japanItem
, japanRecord
)
where

-- base
import Control.Exception (IOException)
import qualified Control.Exception as Exception
import qualified Data.Foldable as Foldable

-- bytestring
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as ByteString

-- cassava
import Data.Csv
( DefaultOrdered(headerOrder)
, FromField(parseField)
, FromNamedRecord(parseNamedRecord)
, Header
, ToField(toField)
, ToNamedRecord(toNamedRecord)
, (.:)
, (.=)
)
import qualified Data.Csv as Cassava

-- text
import Data.Text (Text)
import qualified Data.Text.Encoding as Text

-- vector
import Data.Vector (Vector)
import qualified Data.Vector as Vector


-- |
--
-- An item type.

data ItemType
= Country
| Other Text
deriving (Eq, Show)


-- |
--
-- Parse an item type from a CSV field.

instance FromField ItemType where
parseField "International Country" =
pure Country

parseField otherType =
Other <$> parseField otherType


-- |
--
-- Convert an item type to a CSV field.

instance ToField ItemType where
toField Country =
"International Country"

toField (Other otherType) =
toField otherType


-- |
--
-- An item.

data Item =
Item
{ itemName :: Text
, itemLink :: Text
, itemType :: ItemType
}
deriving (Eq, Show)


-- |
--
--

isCountryItem
:: Item
-> Bool
isCountryItem =
(==) Country . itemType


-- |
--
--

filterCountryItems
:: Vector Item
-> Vector Item
filterCountryItems =
Vector.filter isCountryItem


-- |
--
--

japanItem :: Item
japanItem =
Item
{ itemName = "Japan"
, itemLink = "http://www.data.go.jp/"
, itemType = Country
}


-- |
--
--

itemHeader :: Header
itemHeader =
Vector.fromList
[ "Item"
, "Link"
, "Type"
]

-- |
--
--

japanRecord :: ByteString
japanRecord =
"Japan,http://www.data.go.jp/,International Country"


-- |
--
--

instance DefaultOrdered Item where
headerOrder _ =
Cassava.header
[ "Item"
, "Link"
, "Type"
]


-- |
--
-- Parse an item from a CSV record.

instance FromNamedRecord Item where
parseNamedRecord m =
Item
<$> fmap Text.decodeLatin1 (m .: "Item")
<*> m .: "Link"
<*> m .: "Type"


-- |
--
-- Convert an item to a CSV record.

instance ToNamedRecord Item where
toNamedRecord Item{..} =
Cassava.namedRecord
[ "Item" .= itemName
, "Link" .= itemLink
, "Type" .= itemType
]


-- |
--
-- Decode a vector of items from CSV.

decodeItems
:: ByteString
-> Either String (Vector Item)
decodeItems =
fmap snd . Cassava.decodeByName


-- |
--
-- Encode a vector of items to CSV.

encodeItems
:: Vector Item
-> ByteString
encodeItems =
Cassava.encodeDefaultOrderedByName . Foldable.toList


-- |
--
-- Decode a vector of items from a CSV file.

decodeItemsFromFile
:: FilePath
-> IO (Either String (Vector Item))
decodeItemsFromFile filePath = do
catchShowIO (ByteString.readFile filePath)
>>= return . either Left decodeItems


-- |
--
-- Encode a vector of items to a CSV file.

encodeItemsToFile
:: FilePath
-> Vector Item
-> IO (Either String ())
encodeItemsToFile filePath =
catchShowIO . ByteString.writeFile filePath . encodeItems


-- |
--
-- Run an action. If an IO exception is raised, show it.

catchShowIO
:: IO a
-> IO (Either String a)
catchShowIO action =
fmap Right action
`Exception.catch` handleIOException
where
handleIOException
:: IOException
-> IO (Either String a)
handleIOException =
return . Left . show
1 change: 1 addition & 0 deletions tutorials/haskell/csv-encoding-decoding/code/stack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
resolver: lts-5.15
Loading