packages feed

colonnade 0.3 → 0.4

raw patch · 10 files changed

+222/−74 lines, 10 filesdep +bytestringdep +textPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, text

API changes (from Hackage documentation)

- Colonnade.Internal.Ap: Ap :: f a -> Ap f (a -> b) -> Ap f b
- Colonnade.Internal.Ap: Pure :: a -> Ap f a
- Colonnade.Internal.Ap: data Ap f a
- Colonnade.Internal.Ap: hoistAp :: (forall a. f a -> g a) -> Ap f b -> Ap g b
- Colonnade.Internal.Ap: instance GHC.Base.Applicative (Colonnade.Internal.Ap.Ap f)
- Colonnade.Internal.Ap: instance GHC.Base.Functor (Colonnade.Internal.Ap.Ap f)
- Colonnade.Internal.Ap: liftAp :: f a -> Ap f a
- Colonnade.Internal.Ap: retractAp :: Applicative f => Ap f a -> f a
- Colonnade.Internal.Ap: runAp :: Applicative g => (forall x. f x -> g x) -> Ap f a -> g a
- Colonnade.Internal.Ap: runAp_ :: Monoid m => (forall a. f a -> m) -> Ap f b -> m
- Colonnade.Types: DecodingAp :: !(f content) -> !(content -> Either String a) -> !(Decoding f content (a -> b)) -> Decoding f content b
- Colonnade.Types: DecodingPure :: !a -> Decoding f content a
+ Colonnade.Types: [DecodingAp] :: !(f content) -> !(content -> Either String a) -> !(Decoding f content (a -> b)) -> Decoding f content b
+ Colonnade.Types: [DecodingPure] :: !a -> Decoding f content a
- Colonnade.Decoding: contramapContent :: Contravariant f => (c2 -> c1) -> Decoding f c1 a -> Decoding f c2 a
+ Colonnade.Decoding: contramapContent :: forall c1 c2 f a. Contravariant f => (c2 -> c1) -> Decoding f c1 a -> Decoding f c2 a
- Colonnade.Decoding: headedToIndexed :: Eq content => Vector content -> Decoding Headed content a -> Either (HeadingErrors content) (Decoding (Indexed Headed) content a)
+ Colonnade.Decoding: headedToIndexed :: forall content a. Eq content => Vector content -> Decoding Headed content a -> Either (HeadingErrors content) (Decoding (Indexed Headed) content a)
- Colonnade.Decoding: headlessToIndexed :: Decoding Headless c a -> Decoding (Indexed Headless) c a
+ Colonnade.Decoding: headlessToIndexed :: forall c a. Decoding Headless c a -> Decoding (Indexed Headless) c a
- Colonnade.Decoding: length :: Decoding f c a -> Int
+ Colonnade.Decoding: length :: forall f c a. Decoding f c a -> Int
- Colonnade.Decoding: maxIndex :: Decoding (Indexed f) c a -> Int
+ Colonnade.Decoding: maxIndex :: forall f c a. Decoding (Indexed f) c a -> Int
- Colonnade.Decoding: uncheckedRun :: Decoding (Indexed f) content a -> Vector content -> Either (DecodingCellErrors f content) a
+ Colonnade.Decoding: uncheckedRun :: forall content a f. Decoding (Indexed f) content a -> Vector content -> Either (DecodingCellErrors f content) a

Files

colonnade.cabal view
@@ -1,5 +1,5 @@ name:                colonnade-version:             0.3+version:             0.4 synopsis:            Generic types and functions for columnar encoding and decoding description:         Please see README.md homepage:            https://github.com/andrewthad/colonnade#readme@@ -17,13 +17,18 @@   exposed-modules:     Colonnade.Types     Colonnade.Encoding+    Colonnade.Encoding.Text+    Colonnade.Encoding.ByteString.Char8     Colonnade.Decoding+    Colonnade.Decoding.Text+    Colonnade.Decoding.ByteString.Char8     Colonnade.Internal-    Colonnade.Internal.Ap   build-depends:       base >= 4.7 && < 5     , contravariant     , vector+    , text+    , bytestring   default-language:    Haskell2010  source-repository head
src/Colonnade/Decoding.hs view
@@ -3,11 +3,12 @@ {-# LANGUAGE BangPatterns        #-} module Colonnade.Decoding where -import Colonnade.Internal (EitherWrap(..))+import Colonnade.Internal (EitherWrap(..),mapLeft) import Colonnade.Types import Data.Functor.Contravariant import Data.Vector (Vector) import qualified Data.Vector as Vector+import Data.Char (chr)  -- | Converts the content type of a 'Decoding'. The @'Contravariant' f@ -- constraint means that @f@ can be 'Headless' but not 'Headed'.@@ -101,14 +102,59 @@        <$> EitherWrap rcurrent        <*> rnext -eitherMonoidAp :: Monoid a => Either a (b -> c) -> Either a b -> Either a c-eitherMonoidAp = go where-  go (Left a1) (Left a2) = Left (mappend a1 a2)-  go (Left a1) (Right _) = Left a1-  go (Right _) (Left a2) = Left a2-  go (Right f) (Right b) = Right (f b)+-- | This adds one to the index because text editors consider+--   line number to be one-based, not zero-based.+prettyError :: (c -> String) -> DecodingRowError f c -> String+prettyError toStr (DecodingRowError ix e) = unlines+  $ ("Decoding error on line " ++ show (ix + 1) ++ " of file.")+  : ("Error Category: " ++ descr)+  : map ("  " ++) errDescrs+  where (descr,errDescrs) = prettyRowError toStr e -mapLeft :: (a -> b) -> Either a c -> Either b c-mapLeft _ (Right a) = Right a-mapLeft f (Left a) = Left (f a)+prettyRowError :: (content -> String) -> RowError f content -> (String, [String])+prettyRowError toStr x = case x of+  RowErrorParse err -> (,) "CSV Parsing"+    [ "The line could not be parsed into cells correctly."+    , "Original parser error: " ++ err+    ]+  RowErrorSize reqLen actualLen -> (,) "Row Length"+    [ "Expected the row to have exactly " ++ show reqLen ++ " cells."+    , "The row only has " ++ show actualLen ++ " cells."+    ]+  RowErrorMinSize reqLen actualLen -> (,) "Row Min Length"+    [ "Expected the row to have at least " ++ show reqLen ++ " cells."+    , "The row only has " ++ show actualLen ++ " cells."+    ]+  RowErrorMalformed enc -> (,) "Text Decoding"+    [ "Tried to decode the input as " ++ enc ++ " text"+    , "There is a mistake in the encoding of the text."+    ]+  RowErrorHeading errs -> (,) "Header" (prettyHeadingErrors toStr errs)+  RowErrorDecode errs -> (,) "Cell Decoding" (prettyCellErrors toStr errs)++prettyCellErrors :: (c -> String) -> DecodingCellErrors f c -> [String]+prettyCellErrors toStr (DecodingCellErrors errs) = drop 1 $+  flip concatMap errs $ \(DecodingCellError content (Indexed ix _) msg) ->+    let str = toStr content in+    [ "-----------"+    , "Column " ++ columnNumToLetters ix+    , "Original parse error: " ++ msg+    , "Cell Content Length: " ++ show (Prelude.length str)+    , "Cell Content: " ++ if null str+        then "[empty cell]"+        else str+    ]++prettyHeadingErrors :: (c -> String) -> HeadingErrors c -> [String]+prettyHeadingErrors conv (HeadingErrors missing duplicates) = concat+  [ concatMap (\h -> ["The header " ++ conv h ++ " was missing."]) missing+  , concatMap (\(h,n) -> ["The header " ++ conv h ++ " occurred " ++ show n ++ " times."]) duplicates+  ]++columnNumToLetters :: Int -> String+columnNumToLetters i+  | i >= 0 && i < 25 = [chr (i + 65)]+  | otherwise = "Beyond Z. Fix this."++ 
+ src/Colonnade/Decoding/ByteString/Char8.hs view
@@ -0,0 +1,26 @@+module Colonnade.Decoding.ByteString.Char8 where++import Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as BC8++char :: ByteString -> Either String Char+char b = case BC8.length b of+  1 -> Right (BC8.head b)+  0 -> Left "cannot decode Char from empty bytestring"+  _ -> Left "cannot decode Char from multi-character bytestring"++int :: ByteString -> Either String Int+int b = do+  (a,bsRem) <- maybe (Left "could not parse int") Right (BC8.readInt b)+  if ByteString.null bsRem+    then Right a+    else Left "found extra characters after int"++bool :: ByteString -> Either String Bool+bool b+  | b == BC8.pack "true" = Right True+  | b == BC8.pack "false" = Right False+  | otherwise = Left "must be true or false"++
+ src/Colonnade/Decoding/Text.hs view
@@ -0,0 +1,47 @@+module Colonnade.Decoding.Text where++import Prelude hiding (map)+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Read as TextRead++char :: Text -> Either String Char+char t = case Text.length t of+  1 -> Right (Text.head t)+  0 -> Left "cannot decode Char from empty text"+  _ -> Left "cannot decode Char from multi-character text"++text :: Text -> Either String Text+text = Right++int :: Text -> Either String Int+int t = do+  (a,tRem) <- TextRead.decimal t+  if Text.null tRem+    then Right a+    else Left "found extra characters after int"++trueFalse :: Text -> Text -> Text -> Either String Bool+trueFalse t f txt+  | txt == t = Right True+  | txt == f = Right False+  | otherwise = Left $ concat+      ["must be [", Text.unpack t, "] or [", Text.unpack f, "]"]++-- | This refers to the 'TextRead.Reader' from @Data.Text.Read@, not+--   to the @Reader@ monad.+fromReader :: TextRead.Reader a -> Text -> Either String a+fromReader f t = do+  (a,tRem) <- f t+  if Text.null tRem+    then Right a+    else Left "found extra characters at end of text"++optional :: (Text -> Either String a) -> Text -> Either String (Maybe a)+optional f t = if Text.null t+  then Right Nothing+  else fmap Just (f t)++map :: (a -> b) -> (Text -> Either String a) -> Text -> Either String b+map f g t = fmap f (g t)+
src/Colonnade/Encoding.hs view
@@ -29,8 +29,9 @@               -> (content -> m b)               -> a               -> m b-runRowMonadic (Encoding v) g a = fmap (mconcat . Vector.toList) $ Vector.forM v $ \e ->-  g (oneEncodingEncode e a)+runRowMonadic (Encoding v) g a = fmap (mconcat . Vector.toList) +  $ Vector.forM v +  $ \e -> g (oneEncodingEncode e a)  runHeader :: (c1 -> c2) -> Encoding Headed c1 a -> Vector c2 runHeader g (Encoding v) =@@ -43,5 +44,14 @@ runHeaderMonadic (Encoding v) g =   fmap (mconcat . Vector.toList) $ Vector.mapM (g . getHeaded . oneEncodingHead) v +fromMaybe :: c -> Encoding f c a -> Encoding f c (Maybe a)+fromMaybe c (Encoding v) = Encoding $ flip Vector.map v $+  \(OneEncoding h encode) -> OneEncoding h (maybe c encode) +columns :: (b -> a -> c)+        -> (b -> f c) +        -> Vector b +        -> Encoding f c a+columns getCell getHeader bs =+  Encoding $ Vector.map (\b -> OneEncoding (getHeader b) (getCell b)) bs 
+ src/Colonnade/Encoding/ByteString/Char8.hs view
@@ -0,0 +1,24 @@+module Colonnade.Encoding.ByteString.Char8 where++import Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as BC8+import qualified Data.ByteString.Builder    as Builder+import qualified Data.ByteString.Lazy       as LByteString++char :: Char -> ByteString+char = BC8.singleton++int :: Int -> ByteString+int = LByteString.toStrict+    . Builder.toLazyByteString +    . Builder.intDec++bool :: Bool -> ByteString+bool x = case x of+  True -> BC8.pack "true"+  False -> BC8.pack "false"++byteString :: ByteString -> ByteString+byteString = id+
+ src/Colonnade/Encoding/Text.hs view
@@ -0,0 +1,24 @@+module Colonnade.Encoding.Text where++import Data.Text+import qualified Data.Text as Text+import qualified Data.Text.Lazy as LText+import qualified Data.Text.Lazy.Builder as Builder+import qualified Data.Text.Lazy.Builder.Int as Builder++char :: Char -> Text+char = Text.singleton++int :: Int -> Text+int = LText.toStrict+    . Builder.toLazyText+    . Builder.decimal++text :: Text -> Text+text = id++bool :: Bool -> Text+bool x = case x of+  True -> Text.pack "true"+  False -> Text.pack "false"+
src/Colonnade/Internal.hs view
@@ -12,3 +12,6 @@   EitherWrap (Right _) <*> EitherWrap (Left a2) = EitherWrap (Left a2)   EitherWrap (Right f) <*> EitherWrap (Right b) = EitherWrap (Right (f b)) +mapLeft :: (a -> b) -> Either a c -> Either b c+mapLeft _ (Right a) = Right a+mapLeft f (Left a) = Left (f a)
− src/Colonnade/Internal/Ap.hs
@@ -1,47 +0,0 @@-{-# LANGUAGE Rank2Types #-}-{-# LANGUAGE GADTs #-}-{-# OPTIONS_GHC -Wall #-}--module Colonnade.Internal.Ap-  ( Ap(..)-  , runAp-  , runAp_-  , liftAp-  , hoistAp-  , retractAp-  ) where--import Control.Applicative---- | The free 'Applicative' for a 'Functor' @f@.-data Ap f a where-  Pure :: a -> Ap f a-  Ap   :: f a -> Ap f (a -> b) -> Ap f b--runAp :: Applicative g => (forall x. f x -> g x) -> Ap f a -> g a-runAp _ (Pure x) = pure x-runAp u (Ap f x) = flip id <$> u f <*> runAp u x--runAp_ :: Monoid m => (forall a. f a -> m) -> Ap f b -> m-runAp_ f = getConst . runAp (Const . f)--instance Functor (Ap f) where-  fmap f (Pure a)   = Pure (f a)-  fmap f (Ap x y)   = Ap x ((f .) <$> y)--instance Applicative (Ap f) where-  pure = Pure-  Pure f <*> y = fmap f y-  Ap x y <*> z = Ap x (flip <$> y <*> z)--liftAp :: f a -> Ap f a-liftAp x = Ap x (Pure id)-{-# INLINE liftAp #-}--hoistAp :: (forall a. f a -> g a) -> Ap f b -> Ap g b-hoistAp _ (Pure a) = Pure a-hoistAp f (Ap x y) = Ap (f x) (hoistAp f y)--retractAp :: Applicative f => Ap f a -> f a-retractAp (Pure a) = pure a-retractAp (Ap x y) = x <**> retractAp y
src/Colonnade/Types.hs view
@@ -1,6 +1,7 @@-{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveFunctor              #-}+{-# LANGUAGE DeriveFoldable             #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE GADTs #-}+{-# LANGUAGE GADTs                      #-} module Colonnade.Types   ( Encoding(..)   , Decoding(..)@@ -24,11 +25,11 @@  -- | Isomorphic to 'Identity' newtype Headed a = Headed { getHeaded :: a }-  deriving (Eq,Ord,Functor,Show,Read)+  deriving (Eq,Ord,Functor,Show,Read,Foldable)  -- | Isomorphic to 'Proxy' data Headless a = Headless-  deriving (Eq,Ord,Functor,Show,Read)+  deriving (Eq,Ord,Functor,Show,Read,Foldable)  data Indexed f a = Indexed   { indexedIndex :: !Int@@ -38,7 +39,7 @@ data HeadingErrors content = HeadingErrors   { headingErrorsMissing   :: Vector content       -- ^ headers that were missing   , headingErrorsDuplicate :: Vector (content,Int) -- ^ headers that occurred more than once-  } deriving (Show,Read)+  } deriving (Show,Read,Eq)  instance (Show content, Typeable content) => Exception (HeadingErrors content) @@ -51,27 +52,33 @@   { decodingCellErrorContent :: !content   , decodingCellErrorHeader  :: !(Indexed f content)   , decodingCellErrorMessage :: !String-  } deriving (Show,Read)+  } deriving (Show,Read,Eq)  -- instance (Show (f content), Typeable content) => Exception (DecodingError f content)  newtype DecodingCellErrors f content = DecodingCellErrors   { getDecodingCellErrors :: Vector (DecodingCellError f content)-  } deriving (Monoid,Show,Read)+  } deriving (Monoid,Show,Read,Eq)  -- newtype ParseRowError = ParseRowError String +-- TODO: rewrite the instances for this by hand. They+-- currently use FlexibleContexts. data DecodingRowError f content = DecodingRowError   { decodingRowErrorRow   :: !Int   , decodingRowErrorError :: !(RowError f content)-  }+  } deriving (Show,Read,Eq) +-- TODO: rewrite the instances for this by hand. They+-- currently use FlexibleContexts. data RowError f content   = RowErrorParse !String -- ^ Error occurred parsing the document into cells   | RowErrorDecode !(DecodingCellErrors f content) -- ^ Error decoding the content   | RowErrorSize !Int !Int -- ^ Wrong number of cells in the row   | RowErrorHeading !(HeadingErrors content)   | RowErrorMinSize !Int !Int+  | RowErrorMalformed !String -- ^ Error decoding unicode content+  deriving (Show,Read,Eq)  -- instance (Show (f content), Typeable content) => Exception (DecodingErrors f content) @@ -80,13 +87,15 @@  -- | This just actually a specialization of the free applicative. --   Check out @Control.Applicative.Free@ in the @free@ library to---   learn more about this.+--   learn more about this. The meanings of the fields are documented+--   slightly more in the source code. Unfortunately, haddock does not+--   play nicely with GADTs. data Decoding f content a where-  DecodingPure :: !a -- ^ function+  DecodingPure :: !a -- function                -> Decoding f content a-  DecodingAp :: !(f content) -- ^ header-             -> !(content -> Either String a) -- ^ decoding function-             -> !(Decoding f content (a -> b)) -- ^ next decoding+  DecodingAp :: !(f content) -- header+             -> !(content -> Either String a) -- decoding function+             -> !(Decoding f content (a -> b)) -- next decoding              -> Decoding f content b  instance Functor (Decoding f content) where@@ -98,6 +107,7 @@   DecodingPure f <*> y = fmap f y   DecodingAp h c y <*> z = DecodingAp h c (flip <$> y <*> z) +-- | Encodes a header and a cell. data OneEncoding f content a = OneEncoding   { oneEncodingHead   :: !(f content)   , oneEncodingEncode :: !(a -> content)