cassava 0.1.0.0 → 0.1.0.1
raw patch · 5 files changed
+74/−17 lines, 5 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Data.Csv.Parser: field :: Parser Field
+ Data.Csv.Parser: field :: Word8 -> Parser Field
- Data.Csv.Parser: name :: Parser Field
+ Data.Csv.Parser: name :: Word8 -> Parser Field
Files
- Data/Csv/Parser.hs +13/−14
- cassava.cabal +3/−2
- examples/IndexBasedDecode.hs +13/−0
- examples/NamedBasedDecode.hs +22/−0
- tests/UnitTests.hs +23/−1
Data/Csv/Parser.hs view
@@ -80,12 +80,12 @@ -- | Parse a header, including the terminating line separator. header :: Word8 -- ^ Field delimiter -> AL.Parser Header-header delim = V.fromList <$> name `sepBy1` (A.word8 delim) <* endOfLine+header !delim = V.fromList <$> name delim `sepBy1` (A.word8 delim) <* endOfLine -- | Parse a header name. Header names have the same format as regular -- 'field's.-name :: AL.Parser Field -- TODO: Create Name type alias-name = field+name :: Word8 -> AL.Parser Field -- TODO: Create Name type alias+name !delim = field delim removeBlankLines :: [Record] -> [Record] removeBlankLines = filter (not . blankLine)@@ -98,19 +98,19 @@ -- this parser. record :: Word8 -- ^ Field delimiter -> AL.Parser Record-record !delim = V.fromList <$> field `sepBy1` (A.word8 delim)+record !delim = V.fromList <$> field delim `sepBy1` (A.word8 delim) {-# INLINE record #-} -- | Parse a field. The field may be in either the escaped or -- non-escaped format. The return value is unescaped.-field :: AL.Parser Field-field = do+field :: Word8 -> AL.Parser Field+field !delim = do mb <- A.peekWord8 -- We purposely don't use <|> as we want to commit to the first -- choice if we see a double quote. case mb of Just b | b == doubleQuote -> escapedField- _ -> unescapedField+ _ -> unescapedField delim escapedField :: AL.Parser S.ByteString escapedField = do@@ -127,11 +127,11 @@ Left err -> fail err else return s -unescapedField :: AL.Parser S.ByteString-unescapedField = A.takeWhile (\ c -> c /= doubleQuote &&- c /= newline &&- c /= commaB &&- c /= cr)+unescapedField :: Word8 -> AL.Parser S.ByteString+unescapedField !delim = A.takeWhile (\ c -> c /= doubleQuote &&+ c /= newline &&+ c /= delim &&+ c /= cr) dquote :: AL.Parser Char dquote = char '"'@@ -151,8 +151,7 @@ then return (acc `mappend` fromByteString h) else rest -doubleQuote, newline, commaB, cr :: Word8+doubleQuote, newline, cr :: Word8 doubleQuote = 34 newline = 10-commaB = 44 cr = 13
cassava.cabal view
@@ -1,10 +1,10 @@ Name: cassava-Version: 0.1.0.0+Version: 0.1.0.1 Synopsis: A CSV parsing and encoding library Description: A CSV parsing and encoding library optimized for ease of use and high performance.-Homepage: https://github.com/tibbe/sea+Homepage: https://github.com/tibbe/cassava License: BSD3 License-file: LICENSE Bug-reports: https://github.com/tibbe/cassava/issues@@ -16,6 +16,7 @@ Category: Text, Web, CSV Build-type: Simple Cabal-version: >=1.8+Extra-source-files: examples/*.hs Library
+ examples/IndexBasedDecode.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE ScopedTypeVariables #-}++import qualified Data.ByteString.Lazy as BL+import Data.Csv+import qualified Data.Vector as V++main :: IO ()+main = do+ csvData <- BL.readFile "salaries.csv"+ case decode csvData of+ Left err -> putStrLn err+ Right v -> V.forM_ v $ \ (name, salary :: Int) ->+ putStrLn $ name ++ " earns " ++ show salary ++ " dollars"
+ examples/NamedBasedDecode.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}++import Control.Applicative+import qualified Data.ByteString.Lazy as BL+import Data.Csv+import qualified Data.Vector as V++data Person = Person+ { name :: String+ , salary :: Int+ }++instance FromNamedRecord Person where+ parseNamedRecord r = Person <$> r .: "name" <*> r .: "salary"++main :: IO ()+main = do+ csvData <- BL.readFile "salaries.csv"+ case decodeByName csvData of+ Left err -> putStrLn err+ Right (_, v) -> V.forM_ v $ \ p ->+ putStrLn $ name p ++ " earns " ++ show (salary p) ++ " dollars"
tests/UnitTests.hs view
@@ -27,7 +27,15 @@ -- Parse tests decodesAs :: BL.ByteString -> [[B.ByteString]] -> Assertion-decodesAs input expected = case decode input of+decodesAs input expected = assertResult input expected $ decode input++decodesWithAs :: DecodeOptions -> BL.ByteString -> [[B.ByteString]] -> Assertion+decodesWithAs opts input expected =+ assertResult input expected $ decodeWith opts input++assertResult :: BL.ByteString -> [[B.ByteString]]+ -> Either String (V.Vector (V.Vector B.ByteString)) -> Assertion+assertResult input expected res = case res of Right r -> V.fromList (map V.fromList expected) @=? r Left err -> assertFailure $ " input: " ++ show (BL8.unpack input) ++ "\n" ++@@ -37,6 +45,10 @@ encodesAs input expected = encode (V.fromList (map V.fromList input)) @?= expected +encodesWithAs :: EncodeOptions -> [[B.ByteString]] -> BL.ByteString -> Assertion+encodesWithAs opts input expected =+ encodeWith opts (V.fromList (map V.fromList input)) @?= expected+ namedEncodesAs :: [B.ByteString] -> [[(B.ByteString, B.ByteString)]] -> BL.ByteString -> Assertion namedEncodesAs hdr input expected =@@ -79,6 +91,10 @@ , ("twoRecords", [["abc"], ["def"]], "abc\r\ndef\r\n") , ("newline", [["abc\ndef"]], "\"abc\ndef\"\r\n") ]+ , testGroup "encodeWith"+ [ testCase "tab-delim" $ encodesWithAs (defEnc { encDelimiter = 9 })+ [["1", "2"]] "1\t2\r\n"+ ] , testGroup "decode" $ map decodeTest [ ("simple", "a,b,c\n", [["a", "b", "c"]]) , ("crlf", "a,b\r\nc,d\r\n", [["a", "b"], ["c", "d"]])@@ -87,12 +103,18 @@ [["a", "b", "c"], ["d", "e", "f"]]) , ("leadingSpace", " a, b, c\n", [[" a", " b", " c"]]) ] ++ [testCase "rfc4180" testRfc4180]+ , testGroup "decodeWith"+ [ testCase "tab-delim" $ decodesWithAs (defDec { decDelimiter = 9 })+ "1\t2" [["1", "2"]]+ ] ] where encodeTest (name, input, expected) = testCase name $ input `encodesAs` expected decodeTest (name, input, expected) = testCase name $ input `decodesAs` expected+ defEnc = defaultEncodeOptions+ defDec = defaultDecodeOptions nameBasedTests :: [TF.Test] nameBasedTests =