box-csv 0.0.3 → 0.2.0
raw patch · 3 files changed
Files
- box-csv.cabal +28/−58
- src/Box/Csv.hs +35/−58
- test/test.hs +0/−19
box-csv.cabal view
@@ -1,67 +1,37 @@ cabal-version: 2.4-name: box-csv-version: 0.0.3-synopsis: See readme.md-description: See readme.md for description.-category: project-author: Tony Day-maintainer: tonyday567@gmail.com-copyright: Tony Day (c) AfterTimes-license: BSD-3-Clause-homepage: https://github.com/tonyday567/box-csv#readme-bug-reports: https://github.com/tonyday567/box-csv/issues-build-type: Simple+name: box-csv+version: 0.2.0+synopsis: CSV parsing in a box.+description: CSV parsing using attoparsec and the box library.+category: project+author: Tony Day+maintainer: tonyday567@gmail.com+copyright: Tony Day (c) 2018-2022+license: BSD-3-Clause+homepage: https://github.com/tonyday567/box-csv#readme+bug-reports: https://github.com/tonyday567/box-csv/issues+build-type: Simple+tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.2.1+ source-repository head- type: git+ type: git location: https://github.com/tonyday567/box-csv library- hs-source-dirs:- src+ hs-source-dirs: src default-extensions:- NoImplicitPrelude- NegativeLiterals- OverloadedStrings- UnicodeSyntax ghc-options:- -Wall- -Wcompat- -Wincomplete-record-updates- -Wincomplete-uni-patterns- -Wredundant-constraints- build-depends:- attoparsec >= 0.13,- base >=4.7 && <5,- box >= 0.6 && < 0.7,- generic-lens,- lens >= 4.19,- numhask >= 0.6 && < 0.7,- scientific >= 0.3,- text >= 1.2,- time >= 1.9,- exposed-modules:- Box.Csv- other-modules:- default-language: Haskell2010+ -Wall -Wcompat -Wincomplete-record-updates+ -Wincomplete-uni-patterns -Wredundant-constraints -fwrite-ide-info+ -hiedir=.hie -Wunused-packages -test-suite test- type: exitcode-stdio-1.0- main-is: test.hs- hs-source-dirs:- test- default-extensions:- NoImplicitPrelude- NegativeLiterals- OverloadedStrings- UnicodeSyntax- ghc-options:- -Wall- -Wcompat- -Wincomplete-record-updates- -Wincomplete-uni-patterns- -Wredundant-constraints build-depends:- base >=4.7 && <5,- doctest >= 0.16,- numhask >= 0.6 && < 0.7- default-language: Haskell2010+ , attoparsec >=0.13 && <0.16+ , base >=4.7 && <5+ , box ^>=0.8+ , text ^>=1.2+ , time ^>=1.9++ exposed-modules: Box.Csv+ other-modules:+ default-language: Haskell2010
src/Box/Csv.hs view
@@ -1,16 +1,13 @@ {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StrictData #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE NoImplicitPrelude #-} -- | A csv process based on attoparsec and the box library--- module Box.Csv ( CsvConfig (..), defaultCsvConfig,- file, Header (..), rowEmitter, rowCommitter,@@ -27,7 +24,6 @@ A.double, double', fields,- scis, ints, doubles, day',@@ -37,81 +33,70 @@ where import Box-import Control.Lens+import Control.Monad import qualified Data.Attoparsec.Text as A-import Data.Generics.Labels ()-import Data.Scientific+import Data.Functor.Contravariant+import Data.Text (Text, unpack) import qualified Data.Text as Text-import NumHask.Prelude import Data.Time+import GHC.Generics -- $setup -- >>> :set -XOverloadedStrings+-- >>> import Box+-- >>> import Box.Csv+-- >>> import qualified Data.Text as Text+-- >>> import qualified Data.Attoparsec.Text as A -- | csv file configuration-data CsvConfig- = CsvConfig- { -- | file name stem- name :: Text,- -- | file suffix- suffix :: Text,- -- | directory- dir :: Text,- -- | field separator- fsep :: Char,- -- | nature of header row(s)- header :: Header- }+data CsvConfig = CsvConfig+ { -- | file name+ file :: FilePath,+ -- | field separator+ fsep :: Char,+ -- | nature of header row(s)+ header :: Header+ } deriving (Show, Generic, Eq) -- | default csv file details -- -- >>> defaultCsvConfig--- CsvConfig {name = "time_series_covid19_deaths_global_narrow", suffix = ".csv", dir = "./other", fsep = ',', header = HasHXL}+-- CsvConfig {file = "./other/time_series_covid19_deaths_global_narrow.csv", fsep = ',', header = HasHXL} -- -- test data from https://data.humdata.org/dataset/novel-coronavirus-2019-ncov-cases defaultCsvConfig :: CsvConfig defaultCsvConfig = CsvConfig- "time_series_covid19_deaths_global_narrow"- ".csv"- "./other"+ "./other/time_series_covid19_deaths_global_narrow.csv" ',' HasHXL --- | filepath for the config.------ >>> file defaultCsvConfig--- "./other/time_series_covid19_deaths_global_narrow.csv"-file :: CsvConfig -> FilePath-file cfg =- cfg ^. #dir- <> "/"- <> cfg ^. #name- <> cfg ^. #suffix- & unpack- -- | Type of header rows. Note the modern propensity for multiple header rows. data Header = HasHeader | HasHXL | NoHeader deriving (Show, Eq) +-- | attoparsec parse emitter which returns the original text on failure+parseE :: (Functor m) => A.Parser a -> Emitter m Text -> Emitter m (Either Text a)+parseE parser e = (\t -> either (const $ Left t) Right (A.parseOnly parser t)) <$> e+ -- | A continuation emitter of parsed csv rows from a CsvConfig, returning the original text on failure--- >>> rowEmitter defaultCsvConfig fields `with` emit+-- >>> emit <$|> rowEmitter defaultCsvConfig fields -- Just (Right ["Province/State","Country/Region","Lat","Long","Date","Value","ISO 3166-1 Alpha 3-Codes","Region Code","Sub-region Code","Intermediate Region Code\r"])-rowEmitter :: CsvConfig -> (Char -> A.Parser a) -> Cont IO (Emitter IO (Either Text a))-rowEmitter cfg p = parseE (p (view #fsep cfg)) <$> fileE (file cfg)+rowEmitter :: CsvConfig -> (Char -> A.Parser a) -> CoEmitter IO (Either Text a)+rowEmitter cfg p = parseE (p (fsep cfg)) <$> fileE (file cfg) -- | commits printed csv rows ----- >>> let testConfig = CsvConfig "test" ".csv" "./test" ',' NoHeader--- >>> let ctest = rowCommitter testConfig (fmap (Text.intercalate "," . fmap show))--- >>> ctest `with` (\c -> commit c [[1..10::Int]])+-- >>> let testConfig = CsvConfig "./test/test.csv" ',' NoHeader+-- >>> let ctest = rowCommitter testConfig (fmap (Text.intercalate "," . fmap (Text.pack . show)))+--+-- >>> (\c -> commit c [[1..10::Int]]) <$|> ctest -- True ----- >>> rowEmitter testConfig ints `with` emit+-- >>> emit <$|> rowEmitter testConfig ints -- Just (Right [1,2,3,4,5,6,7,8,9,10])----rowCommitter :: CsvConfig -> (a -> [Text]) -> Cont IO (Committer IO a)-rowCommitter cfg f = contramap (Text.intercalate (Text.singleton $ view #fsep cfg) . f) <$> fileWriteC (file cfg)+rowCommitter :: CsvConfig -> (a -> [Text]) -> CoCommitter IO a+rowCommitter cfg f = contramap (Text.intercalate (Text.singleton $ fsep cfg) . f) <$> fileWriteC (file cfg) -- | Run a parser across all lines of a file. --@@ -125,7 +110,7 @@ -- >>> take 2 $ drop 2 [x | (Right x) <- r1] -- [["","Afghanistan","33.0","65.0","2020-06-29","733","AFG","142","34","\r"],["","Afghanistan","33.0","65.0","2020-06-28","721","AFG","142","34","\r"]] runCsv :: CsvConfig -> (Char -> A.Parser a) -> IO [Either Text a]-runCsv cfg p = with (rowEmitter cfg p) toListE+runCsv cfg p = toListM <$|> rowEmitter cfg p -- * low-level generic csv parser helpers @@ -223,22 +208,14 @@ pure d' -- * Block list parsers+ -- | Parser for a csv row of [Text].--- TODO: deal with potential for an extra '\r' -- -- >>> A.parseOnly (fields ',') "field1,field2\r" -- Right ["field1","field2\r"]--- fields :: Char -> A.Parser [Text] fields c = field_ c `A.sepBy1` sep c---- | parser for a csv row of [Scientific]------ >>> A.parseOnly (scis ',') "1,2.2,3.3"--- Right [1.0,2.2,3.3]-scis :: Char -> A.Parser [Scientific]-scis c = A.scientific `A.sepBy1` sep c -- | parser for a csv row of [Double] --
− test/test.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedLabels #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE NoImplicitPrelude #-}-{-# OPTIONS_GHC -Wall #-}--module Main where--import NumHask.Prelude-import Test.DocTest--main :: IO ()-main =- doctest- [ "src/Box/Csv.hs"- ]