sv-cassava (empty) → 0.1
raw patch · 6 files changed
+241/−0 lines, 6 filesdep +HUnitdep +attoparsecdep +basesetup-changed
Dependencies added: HUnit, attoparsec, base, bytestring, cassava, sv, sv-cassava, text, utf8-string, validation, vector
Files
- LICENCE +31/−0
- Setup.hs +2/−0
- changelog.md +5/−0
- src/Data/Sv/Cassava.hs +73/−0
- sv-cassava.cabal +58/−0
- tests/tests.hs +72/−0
+ LICENCE view
@@ -0,0 +1,31 @@+Copyright (c) 2017-2018, Commonwealth Scientific and Industrial Research Organisation+(CSIRO) ABN 41 687 119 230.++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 QFPL 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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ changelog.md view
@@ -0,0 +1,5 @@+# Revision history for sv-cassava++## 0.1 -- 2018-03-14++* First version. Released on an unsuspecting world.
+ src/Data/Sv/Cassava.hs view
@@ -0,0 +1,73 @@+{-|+Module : Data.Sv.Cassava+Copyright : (C) CSIRO 2017-2018+License : BSD3+Maintainer : George Wilson <george.wilson@data61.csiro.au>+Stability : experimental+Portability : non-portable++This module provides integration between sv and cassava.+It lets you plug cassava's (very fast!) parser into sv's decoding layer.++Our benchmarking indicates that parsing is very expensive,+while decoding is comparatively less performance-sensitive.+So if performance matters to you, and cassava's parser is sufficient for+your needs, you might as well use it!++sv's own parser is much slower than cassava's right now, but aims to have+better error messages and keep more information for you to work with, such as+spacing, quoting, and newline information.+-}++module Data.Sv.Cassava (+ parseDecodeFromCassava+, parseCassava+, decodeFromCassava+) where++import Data.Attoparsec.ByteString (parseOnly)+import Data.ByteString (ByteString)+import Data.ByteString.UTF8 as UTF8+import qualified Data.Csv as Cassava+import qualified Data.Csv.Parser as Cassava+import Data.Maybe (mapMaybe)+import Data.Sv+import qualified Data.Sv.Decode as D+import Data.Vector (Vector, (!?))+import Data.Vector.NonEmpty (NonEmptyVector (NonEmptyVector))+import qualified Data.Vector as V+import Text.Escape (Unescaped (Unescaped))+import Text.Space (unspaced)+import Text.Quote (Quote (DoubleQuote))++-- | Use an sv 'Decode' to decode from cassava's 'Cassava.Csv' type.+decodeFromCassava :: Decode' ByteString a -> Cassava.Csv -> DecodeValidation ByteString [a]+decodeFromCassava d =+ traverse (D.promote d) . fs2r . V.toList+ where+ fs2r :: [Vector Cassava.Field] -> [Record ByteString]+ fs2r = mapMaybe (fmap (Record . fmap (unspaced . Quoted DoubleQuote . Unescaped)) . vec2nev)+ vec2nev :: Vector b -> Maybe (NonEmptyVector b)+ vec2nev v = NonEmptyVector <$> v !? 0 <*> pure (V.drop 1 v)++-- | Parse a 'Cassava.Csv' from a 'ByteString' using cassava's parser+--+-- This returns its result in a 'DecodeValidation', so that it's compatible+-- with the rest of sv.+parseCassava :: Cassava.DecodeOptions -> ByteString -> DecodeValidation ByteString Cassava.Csv+parseCassava opts =+ D.validateEither' (BadParse . UTF8.fromString) . parseOnly (Cassava.csv opts)++-- | Parse a 'Cassava.Csv' from a 'ByteString' using cassava's parser, then+-- decode it using the given 'Decode'.+--+-- This has the benefit of letting you use cassava's parser, which is very fast,+-- with sv's decoding.+parseDecodeFromCassava :: Decode' ByteString a -> Headedness -> Cassava.DecodeOptions -> ByteString -> DecodeValidation ByteString [a]+parseDecodeFromCassava d h opts bs =+ (chompFirst h <$> parseCassava opts bs) `bindValidation` decodeFromCassava d+ where+ -- The csv returned from cassava's parser may include a header row.+ -- If it does, we want to skip that row.+ chompFirst Headed = V.drop 1+ chompFirst Unheaded = id
+ sv-cassava.cabal view
@@ -0,0 +1,58 @@+-- Initial sv-cassava.cabal generated by cabal init. For further +-- documentation, see http://haskell.org/cabal/users-guide/++name: sv-cassava+version: 0.1+synopsis: Integration to use sv with cassava's parser+description:+ This package provides functions allowing you to use cassava's parser+ together with sv's decoding. You might prefer this to using sv's own+ parser if speed is a significant concern.+homepage: https://github.com/qfpl/sv+license: BSD3+license-file: LICENCE+author: George Wilson+maintainer: george@wils.online+copyright: Copyright (c) 2017-2018, Commonwealth Scientific and Industrial Research Organisation (CSIRO) ABN 41 687 119 230.+category: CSV, Text, Web+build-type: Simple+extra-source-files: changelog.md+cabal-version: >=1.10++library+ exposed-modules:+ Data.Sv.Cassava+ build-depends:+ attoparsec >= 0.12.1.4 && < 0.14+ , base >=4.8 && <5+ , bytestring >= 0.9.1.10 && < 0.11+ , cassava >= 0.4.1 && < 0.6+ , sv >= 0.1 && < 0.2+ , utf8-string >= 1 && < 1.1+ , vector >= 0.10 && < 0.13+ hs-source-dirs:+ src+ default-language:+ Haskell2010++test-suite hunit+ type:+ exitcode-stdio-1.0+ main-is:+ tests.hs+ default-language:+ Haskell2010+ build-depends:+ base >=4.8 && <5+ , bytestring >= 0.9.1.10 && < 0.11+ , cassava >= 0.4.1 && < 0.6+ , sv >= 0.1 && < 0.2+ , sv-cassava+ , HUnit >= 1.4 && < 1.7+ , text >= 1.0 && < 1.3+ , validation >= 1 && < 1.1+ , vector >= 0.10 && < 0.13+ ghc-options:+ -Wall+ hs-source-dirs:+ tests
+ tests/tests.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE OverloadedStrings #-}++module Main (main) where++import Control.Monad (when)+import Data.ByteString (ByteString)+import qualified Data.Csv as Cassava+import Data.Sv+import Data.Sv.Cassava+import qualified Data.Sv.Decode as D+import Data.Text (Text)+import qualified Data.Vector as V+import System.Exit (exitFailure)+import Test.HUnit++data TestRow = TestRow Int Double Text deriving (Eq, Show)++testRowDec :: Decode' ByteString TestRow+testRowDec = TestRow <$> D.int <*> D.double <*> D.utf8++bs1 :: ByteString+bs1 = mconcat+ [ "1,5.5,hello\n"+ , "2,-3.54,\n"+ , "3,42.0,\"Text in quotes\"\n"+ ]++bs2 :: ByteString+bs2 = hdr `mappend` bs1+ where+ hdr = "id, number, name\n"++csv1 :: Cassava.Csv+csv1 = V.fromList . fmap V.fromList $+ [ ["1","5.5","hello"]+ , ["2","-3.54", ""]+ , ["3", "42.0", "Text in quotes"]+ ]++decoded1 :: [TestRow]+decoded1 =+ [ TestRow 1 5.5 "hello"+ , TestRow 2 (-3.54) ""+ , TestRow 3 42 "Text in quotes"+ ]++parseTest :: Test+parseTest = TestLabel "parse" . TestCase $+ parseCassava Cassava.defaultDecodeOptions bs1 @?= pure csv1++decodeTest :: Test+decodeTest = TestLabel "decode" . TestCase $+ decodeFromCassava testRowDec csv1 @?= pure decoded1++parseDecodeTest :: Test+parseDecodeTest = TestLabel "parse+decode" . TestList $+ [ TestLabel "unheaded" . TestCase $+ parseDecodeFromCassava testRowDec Unheaded Cassava.defaultDecodeOptions bs1 @?=+ pure decoded1+ , TestLabel "headed" . TestCase $+ parseDecodeFromCassava testRowDec Headed Cassava.defaultDecodeOptions bs2 @?=+ pure decoded1+ ]++tests :: Test+tests = TestList [parseTest, decodeTest, parseDecodeTest]++main :: IO ()+main = do+ cs <- runTestTT tests+ when (errors cs > 0 || failures cs > 0)+ exitFailure