comma (empty) → 1.0.0
raw patch · 5 files changed
+150/−0 lines, 5 filesdep +QuickCheckdep +attoparsecdep +basesetup-changed
Dependencies added: QuickCheck, attoparsec, base, comma, text
Files
- LICENSE +23/−0
- Setup.hs +2/−0
- comma.cabal +37/−0
- src/Text/Comma.hs +50/−0
- test/Prop.hs +38/−0
+ LICENSE view
@@ -0,0 +1,23 @@+Copyright (c) 2017 Daniel Lovasko+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.++THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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
+ comma.cabal view
@@ -0,0 +1,37 @@+name: comma+version: 1.0.0+synopsis: CSV Parser & Producer+description: Comma is a simple CSV format parser and producer that+ closely follows the RFC4810 document.+homepage: https://github.com/lovasko/comma+license: OtherLicense+license-file: LICENSE+author: Daniel Lovasko <daniel.lovasko@gmail.com>+maintainer: Daniel Lovasko <daniel.lovasko@gmail.com>+copyright: 2017 Daniel Lovasko+category: Text+build-type: Simple+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Text.Comma+ build-depends: base >= 4.7 && < 5+ , attoparsec+ , text+ default-language: Haskell2010++test-suite comma-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Prop.hs+ build-depends: base >= 4.7 && < 5+ , QuickCheck+ , comma+ , text+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/lovasko/comma
+ src/Text/Comma.hs view
@@ -0,0 +1,50 @@+{- |+Module : Text.Comma+Description : CSV Parser & Producer+Copyright : (c) Daniel Lovasko, 2017+License : BSD2++Maintainer : Daniel Lovasko <daniel.lovasko@gmail.com>+Stability : stable+Portability : portable+-}++{-# LANGUAGE OverloadedStrings #-}++module Text.Comma+( comma+, uncomma+) where++import Control.Applicative+import Data.List+import qualified Data.Attoparsec.Text as A+import qualified Data.Text as T+++-- | Parse a field of a record.+field :: A.Parser T.Text -- ^ parser+field = fmap T.concat quoted <|> normal A.<?> "field"+ where+ normal = A.takeWhile (A.notInClass "\n\r,\"") A.<?> "normal field"+ quoted = A.char '"' *> many between <* A.char '"' A.<?> "quoted field"+ between = A.takeWhile1 (/= '"') <|> (A.string "\"\"" *> pure "\"")++-- | Parse a block of text into a CSV table.+comma :: T.Text -- ^ CSV text+ -> Either String [[T.Text]] -- ^ error | table+comma text = A.parseOnly table (T.stripEnd text)+ where+ table = A.sepBy1 record A.endOfLine A.<?> "table"+ record = A.sepBy1 field (A.char ',') A.<?> "record"++-- | Render a table of texts into a valid CSV output.+uncomma :: [[T.Text]] -- ^ table+ -> T.Text -- ^ CSV text+uncomma = T.unlines . map (\r -> T.concat $ intersperse "," (map conv r))+ where+ isQuoted = T.any (`elem` ['"', '\n', '\r'])+ enquote x = T.concat ["\"", x, "\""]+ conv f+ | isQuoted f = enquote (T.replace "\"" "\"\"" f)+ | otherwise = f
+ test/Prop.hs view
@@ -0,0 +1,38 @@+import Control.Monad+import Text.Comma+import Test.QuickCheck+import qualified Data.Text as T+++newtype CSV = CSV [[T.Text]] deriving (Show)++-- | Generate the content of a field.+genField :: Gen T.Text+genField = fmap T.pack (replicateM 5 $ elements alphabet)+ where alphabet = ['a'..'z'] ++ ['A'..'Z'] ++ "\"\n"++-- | Random-generated instances of CSV.+instance Arbitrary CSV where+ arbitrary = do+ nRows <- elements [1..10]+ nCols <- elements [1..3]+ cells <- replicateM (nRows * nCols) genField+ return $ CSV (init $ chunksOf nCols cells)++-- | Split a list into chunks of a specified length.+chunksOf :: Int -- ^ chunk length+ -> [a] -- ^ list+ -> [[a]] -- ^ list of chunks+chunksOf _ [] = [[]]+chunksOf n xs = take n xs : chunksOf n (drop n xs)++-- | The 'comma' and 'uncomma' function have to form identity when composed.+test :: CSV -- ^ CSV table+ -> Bool -- ^ result+test (CSV csv) = case comma (uncomma csv) of+ Left _ -> False+ Right csv' -> csv == csv'++-- | Run the identity property test.+main :: IO ()+main = quickCheckWith stdArgs {maxSuccess=10000} (property test)