packages feed

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 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)