First draft parsing medicines and events in CSV files
This commit is contained in:
commit
1eb6df9261
9 changed files with 190 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
dist*
|
5
ChangeLog.md
Normal file
5
ChangeLog.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Revision history for pilu
|
||||
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
30
LICENSE
Normal file
30
LICENSE
Normal file
|
@ -0,0 +1,30 @@
|
|||
Copyright (c) 2019, Tissevert
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of Tissevert nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
2
Setup.hs
Normal file
2
Setup.hs
Normal file
|
@ -0,0 +1,2 @@
|
|||
import Distribution.Simple
|
||||
main = defaultMain
|
29
pilu.cabal
Normal file
29
pilu.cabal
Normal file
|
@ -0,0 +1,29 @@
|
|||
-- Initial pilu.cabal generated by cabal init. For further documentation,
|
||||
-- see http://haskell.org/cabal/users-guide/
|
||||
|
||||
name: pilu
|
||||
version: 0.1.0.0
|
||||
-- synopsis:
|
||||
-- description:
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Tissevert
|
||||
maintainer: tissevert+devel@ens-lyon.fr
|
||||
-- copyright:
|
||||
category: Health
|
||||
build-type: Simple
|
||||
extra-source-files: ChangeLog.md
|
||||
cabal-version: >=1.10
|
||||
|
||||
executable pilu
|
||||
main-is: Main.hs
|
||||
other-modules: CSV
|
||||
, Event
|
||||
, Medicine
|
||||
-- other-extensions:
|
||||
build-depends: base >=4.9 && <4.10
|
||||
, containers
|
||||
, parsec
|
||||
, time
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
43
src/CSV.hs
Normal file
43
src/CSV.hs
Normal file
|
@ -0,0 +1,43 @@
|
|||
{-# LANGUAGE FlexibleInstances #-}
|
||||
module CSV (
|
||||
Row(..)
|
||||
, parse
|
||||
) where
|
||||
|
||||
import Control.Applicative ((<|>))
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map (fromList, keys)
|
||||
import Text.Parsec.Pos (newPos)
|
||||
import Text.ParserCombinators.Parsec ((<?>), Parser, ParseError, char, endBy, many, noneOf, sepBy, string, try)
|
||||
import Text.ParserCombinators.Parsec.Error (Message(..), newErrorMessage)
|
||||
import qualified Text.ParserCombinators.Parsec as Parsec (parse)
|
||||
|
||||
class Row a where
|
||||
fromRow :: Map String String -> Either Message a
|
||||
|
||||
instance Row [String] where
|
||||
fromRow = return . Map.keys
|
||||
|
||||
csvFile :: Char -> Parser [[String]]
|
||||
csvFile separator = line `endBy` eol
|
||||
where
|
||||
line = cell `sepBy` (char separator)
|
||||
cell = quoted <|> many (noneOf $ separator:"\r\n")
|
||||
eol = try (string "\r\n") <|> try (string "\r") <|> try (string "\n") <?> "end-of-line"
|
||||
|
||||
quoted :: Parser String
|
||||
quoted = char '"' *> many quotedChar <* char '"' <?> "quote ending the cell"
|
||||
where
|
||||
quotedChar = noneOf "\"" <|> try (string "\"\"" *> return '"')
|
||||
|
||||
parse :: Row a => String -> Char -> String -> Either ParseError [a]
|
||||
parse name separator input = do
|
||||
lines <- Parsec.parse (csvFile separator) name input
|
||||
case lines of
|
||||
[] -> Right []
|
||||
(header:rows) ->
|
||||
let assocs = Map.fromList . zipWith (,) header <$> rows in
|
||||
sequence $ zipWith parseRow [1..] assocs
|
||||
where
|
||||
parseRow n row = either (errorAt n) return $ fromRow row
|
||||
errorAt n message = Left $ newErrorMessage message (newPos name n 0)
|
45
src/Event.hs
Normal file
45
src/Event.hs
Normal file
|
@ -0,0 +1,45 @@
|
|||
module Event (
|
||||
) where
|
||||
|
||||
import CSV (Row(..))
|
||||
import Control.Monad (foldM)
|
||||
import Data.List (isSuffixOf, takeWhile, sortOn)
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map (delete, empty, insert, lookup, toList)
|
||||
import Data.Time (Day)
|
||||
import Text.ParserCombinators.Parsec.Error (Message(..))
|
||||
|
||||
data EventType = Prescription | Provisioning deriving (Eq, Show)
|
||||
|
||||
data Event = Event {
|
||||
date :: Day
|
||||
, amounts :: Map String Int
|
||||
, eventtype :: EventType
|
||||
} deriving (Show)
|
||||
|
||||
instance Row Event where
|
||||
fromRow assoc = do
|
||||
date <- read <$> get "date"
|
||||
(kind, ints) <- foldM addAmount (Nothing, Map.empty) . Map.toList $ Map.delete "date" assoc
|
||||
maybe
|
||||
(Left $ Expect "A prescription or provisioning for a medicine")
|
||||
(Right . Event date ints)
|
||||
kind
|
||||
where
|
||||
get key = maybe (Left $ Expect key) Right $ Map.lookup key assoc
|
||||
unexpected = Left . UnExpect . show
|
||||
addAmount (evType, ints) (key, val) = do
|
||||
(newEvType, amount) <- readAmount val
|
||||
let newInts = Map.insert key amount ints
|
||||
case evType of
|
||||
Nothing -> Right (Just newEvType, newInts)
|
||||
Just currentEvType ->
|
||||
if currentEvType == newEvType then Right (evType, newInts) else unexpected newEvType
|
||||
|
||||
readAmount :: String -> Either Message (EventType, Int)
|
||||
readAmount [] = Left $ Expect "prescription or provisioning"
|
||||
readAmount ('+':n) = Right (Provisioning, read n)
|
||||
readAmount s =
|
||||
if "/j" `isSuffixOf` s
|
||||
then Right (Prescription, read $ takeWhile (/= '/') s)
|
||||
else Left $ Expect "rate"
|
4
src/Main.hs
Normal file
4
src/Main.hs
Normal file
|
@ -0,0 +1,4 @@
|
|||
module Main where
|
||||
|
||||
main :: IO ()
|
||||
main = putStrLn "Hello, Haskell!"
|
31
src/Medicine.hs
Normal file
31
src/Medicine.hs
Normal file
|
@ -0,0 +1,31 @@
|
|||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
module Medicine (
|
||||
Medicine(..)
|
||||
, Pharmacy
|
||||
, pharmacy
|
||||
) where
|
||||
|
||||
import CSV (Row(..))
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map (empty, insert, lookup)
|
||||
import Text.ParserCombinators.Parsec.Error (Message(..))
|
||||
|
||||
data Medicine = Medicine {
|
||||
name :: String
|
||||
, content :: Int
|
||||
, minStock :: Int
|
||||
} deriving (Show)
|
||||
|
||||
instance Row Medicine where
|
||||
fromRow assoc = Medicine <$>
|
||||
get "name"
|
||||
<*> (read <$> get "content")
|
||||
<*> (read <$> get "minStock")
|
||||
where
|
||||
get key = maybe (Left $ Expect key) Right $ Map.lookup key assoc
|
||||
|
||||
type Pharmacy = Map String Medicine
|
||||
|
||||
pharmacy :: [Medicine] -> Pharmacy
|
||||
pharmacy [] = Map.empty
|
||||
pharmacy (medicine:medicines) = Map.insert (name medicine) medicine $ pharmacy medicines
|
Loading…
Reference in a new issue