colonnade 0.5 → 1.0.0
raw patch · 8 files changed
+544/−678 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Colonnade.Decoding: columnNumToLetters :: Int -> String
- Colonnade.Decoding: contramapContent :: forall c1 c2 f a. Contravariant f => (c2 -> c1) -> Decolonnade f c1 a -> Decolonnade f c2 a
- Colonnade.Decoding: headed :: content -> (content -> Either String a) -> Decolonnade Headed content a
- Colonnade.Decoding: headedToIndexed :: forall content a. Eq content => Vector content -> Decolonnade Headed content a -> Either (HeadingErrors content) (Decolonnade (Indexed Headed) content a)
- Colonnade.Decoding: headless :: (content -> Either String a) -> Decolonnade Headless content a
- Colonnade.Decoding: headlessToIndexed :: forall c a. Decolonnade Headless c a -> Decolonnade (Indexed Headless) c a
- Colonnade.Decoding: indexed :: Int -> (content -> Either String a) -> Decolonnade (Indexed Headless) content a
- Colonnade.Decoding: length :: forall f c a. Decolonnade f c a -> Int
- Colonnade.Decoding: maxIndex :: forall f c a. Decolonnade (Indexed f) c a -> Int
- Colonnade.Decoding: prettyCellErrors :: (c -> String) -> DecolonnadeCellErrors f c -> [String]
- Colonnade.Decoding: prettyError :: (c -> String) -> DecolonnadeRowError f c -> String
- Colonnade.Decoding: prettyHeadingErrors :: (c -> String) -> HeadingErrors c -> [String]
- Colonnade.Decoding: prettyRowError :: (content -> String) -> RowError f content -> (String, [String])
- Colonnade.Decoding: uncheckedRun :: forall content a f. Decolonnade (Indexed f) content a -> Vector content -> Either (DecolonnadeCellErrors f content) a
- Colonnade.Decoding: uncheckedRunWithRow :: Int -> Decolonnade (Indexed f) content a -> Vector content -> Either (DecolonnadeRowError f content) a
- Colonnade.Encoding: ascii :: Foldable f => Colonnade Headed String a -> f a -> String
- Colonnade.Encoding: bool :: f c -> (a -> Bool) -> (a -> c) -> (a -> c) -> Colonnade f c a
- Colonnade.Encoding: columns :: Foldable g => (b -> a -> c) -> (b -> f c) -> g b -> Colonnade f c a
- Colonnade.Encoding: fromMaybe :: c -> Colonnade f c a -> Colonnade f c (Maybe a)
- Colonnade.Encoding: headed :: c -> (a -> c) -> Colonnade Headed c a
- Colonnade.Encoding: headless :: (a -> c) -> Colonnade Headless c a
- Colonnade.Encoding: mapContent :: Functor f => (c1 -> c2) -> Colonnade f c1 a -> Colonnade f c2 a
- Colonnade.Encoding: replaceWhen :: c -> (a -> Bool) -> Colonnade f c a -> Colonnade f c a
- Colonnade.Encoding: runBothMonadic_ :: Monad m => Colonnade Headed content a -> (content -> content -> m b) -> a -> m ()
- Colonnade.Encoding: runHeader :: (c1 -> c2) -> Colonnade Headed c1 a -> Vector c2
- Colonnade.Encoding: runHeaderMonadic :: (Monad m, Monoid b) => Colonnade Headed content a -> (content -> m b) -> m b
- Colonnade.Encoding: runHeaderMonadicGeneral :: (Monad m, Monoid b, Foldable h) => Colonnade h content a -> (content -> m b) -> m b
- Colonnade.Encoding: runHeaderMonadicGeneral_ :: (Monad m, Monoid b, Foldable h) => Colonnade h content a -> (content -> m b) -> m ()
- Colonnade.Encoding: runHeaderMonadic_ :: (Monad m) => Colonnade Headed content a -> (content -> m b) -> m ()
- Colonnade.Encoding: runRow :: (c1 -> c2) -> Colonnade f c1 a -> a -> Vector c2
- Colonnade.Encoding: runRowMonadic :: (Monad m, Monoid b) => Colonnade f content a -> (content -> m b) -> a -> m b
- Colonnade.Encoding: runRowMonadicWith :: (Monad m) => b -> (b -> b -> b) -> Colonnade f content a -> (content -> m b) -> a -> m b
- Colonnade.Encoding: runRowMonadic_ :: Monad m => Colonnade f content a -> (content -> m b) -> a -> m ()
- Colonnade.Encoding: singleton :: f c -> (a -> c) -> Colonnade f c a
- Colonnade.Internal: EitherWrap :: Either a b -> EitherWrap a b
- Colonnade.Internal: [getEitherWrap] :: EitherWrap a b -> Either a b
- Colonnade.Internal: foldlMapM :: (Foldable t, Monoid b, Monad m) => (a -> m b) -> t a -> m b
- Colonnade.Internal: instance GHC.Base.Functor (Colonnade.Internal.EitherWrap a)
- Colonnade.Internal: instance GHC.Base.Monoid a => GHC.Base.Applicative (Colonnade.Internal.EitherWrap a)
- Colonnade.Internal: mapLeft :: (a -> b) -> Either a c -> Either b c
- Colonnade.Internal: newtype EitherWrap a b
- Colonnade.Types: Colonnade :: Vector (OneColonnade f c a) -> Colonnade f c a
- Colonnade.Types: DecolonnadeCellError :: !content -> !(Indexed f content) -> !String -> DecolonnadeCellError f content
- Colonnade.Types: DecolonnadeCellErrors :: Vector (DecolonnadeCellError f content) -> DecolonnadeCellErrors f content
- Colonnade.Types: DecolonnadeRowError :: !Int -> !(RowError f content) -> DecolonnadeRowError f content
- Colonnade.Types: Headed :: a -> Headed a
- Colonnade.Types: HeadingErrors :: Vector content -> Vector (content, Int) -> HeadingErrors content
- Colonnade.Types: Headless :: Headless a
- Colonnade.Types: Indexed :: !Int -> !(f a) -> Indexed f a
- Colonnade.Types: OneColonnade :: !(f content) -> !(a -> content) -> OneColonnade f content a
- Colonnade.Types: RowErrorDecode :: !(DecolonnadeCellErrors f content) -> RowError f content
- Colonnade.Types: RowErrorHeading :: !(HeadingErrors content) -> RowError f content
- Colonnade.Types: RowErrorMalformed :: !String -> RowError f content
- Colonnade.Types: RowErrorMinSize :: !Int -> !Int -> RowError f content
- Colonnade.Types: RowErrorParse :: !String -> RowError f content
- Colonnade.Types: RowErrorSize :: !Int -> !Int -> RowError f content
- Colonnade.Types: [DecolonnadeAp] :: !(f content) -> !(content -> Either String a) -> !(Decolonnade f content (a -> b)) -> Decolonnade f content b
- Colonnade.Types: [DecolonnadePure] :: !a -> Decolonnade f content a
- Colonnade.Types: [decodingCellErrorContent] :: DecolonnadeCellError f content -> !content
- Colonnade.Types: [decodingCellErrorHeader] :: DecolonnadeCellError f content -> !(Indexed f content)
- Colonnade.Types: [decodingCellErrorMessage] :: DecolonnadeCellError f content -> !String
- Colonnade.Types: [decodingRowErrorError] :: DecolonnadeRowError f content -> !(RowError f content)
- Colonnade.Types: [decodingRowErrorRow] :: DecolonnadeRowError f content -> !Int
- Colonnade.Types: [getColonnade] :: Colonnade f c a -> Vector (OneColonnade f c a)
- Colonnade.Types: [getDecolonnadeCellErrors] :: DecolonnadeCellErrors f content -> Vector (DecolonnadeCellError f content)
- Colonnade.Types: [getHeaded] :: Headed a -> a
- Colonnade.Types: [headingErrorsDuplicate] :: HeadingErrors content -> Vector (content, Int)
- Colonnade.Types: [headingErrorsMissing] :: HeadingErrors content -> Vector content
- Colonnade.Types: [indexedHeading] :: Indexed f a -> !(f a)
- Colonnade.Types: [indexedIndex] :: Indexed f a -> !Int
- Colonnade.Types: [oneColonnadeEncode] :: OneColonnade f content a -> !(a -> content)
- Colonnade.Types: [oneColonnadeHead] :: OneColonnade f content a -> !(f content)
- Colonnade.Types: data Decolonnade f content a
- Colonnade.Types: data DecolonnadeCellError f content
- Colonnade.Types: data DecolonnadeRowError f content
- Colonnade.Types: data HeadingErrors content
- Colonnade.Types: data Headless a
- Colonnade.Types: data Indexed f a
- Colonnade.Types: data OneColonnade f content a
- Colonnade.Types: data RowError f content
- Colonnade.Types: instance (GHC.Classes.Eq (f content), GHC.Classes.Eq content) => GHC.Classes.Eq (Colonnade.Types.DecolonnadeCellError f content)
- Colonnade.Types: instance (GHC.Classes.Eq (f content), GHC.Classes.Eq content) => GHC.Classes.Eq (Colonnade.Types.DecolonnadeCellErrors f content)
- Colonnade.Types: instance (GHC.Classes.Eq (f content), GHC.Classes.Eq content) => GHC.Classes.Eq (Colonnade.Types.DecolonnadeRowError f content)
- Colonnade.Types: instance (GHC.Classes.Eq (f content), GHC.Classes.Eq content) => GHC.Classes.Eq (Colonnade.Types.RowError f content)
- Colonnade.Types: instance (GHC.Read.Read (f content), GHC.Read.Read content) => GHC.Read.Read (Colonnade.Types.DecolonnadeCellError f content)
- Colonnade.Types: instance (GHC.Read.Read (f content), GHC.Read.Read content) => GHC.Read.Read (Colonnade.Types.DecolonnadeCellErrors f content)
- Colonnade.Types: instance (GHC.Read.Read (f content), GHC.Read.Read content) => GHC.Read.Read (Colonnade.Types.DecolonnadeRowError f content)
- Colonnade.Types: instance (GHC.Read.Read (f content), GHC.Read.Read content) => GHC.Read.Read (Colonnade.Types.RowError f content)
- Colonnade.Types: instance (GHC.Show.Show (f content), GHC.Show.Show content) => GHC.Show.Show (Colonnade.Types.DecolonnadeCellError f content)
- Colonnade.Types: instance (GHC.Show.Show (f content), GHC.Show.Show content) => GHC.Show.Show (Colonnade.Types.DecolonnadeCellErrors f content)
- Colonnade.Types: instance (GHC.Show.Show (f content), GHC.Show.Show content) => GHC.Show.Show (Colonnade.Types.DecolonnadeRowError f content)
- Colonnade.Types: instance (GHC.Show.Show (f content), GHC.Show.Show content) => GHC.Show.Show (Colonnade.Types.RowError f content)
- Colonnade.Types: instance (GHC.Show.Show content, Data.Typeable.Internal.Typeable content) => GHC.Exception.Exception (Colonnade.Types.HeadingErrors content)
- Colonnade.Types: instance Data.Foldable.Foldable Colonnade.Types.Headed
- Colonnade.Types: instance Data.Foldable.Foldable Colonnade.Types.Headless
- Colonnade.Types: instance Data.Functor.Contravariant.Contravariant (Colonnade.Types.Colonnade f content)
- Colonnade.Types: instance Data.Functor.Contravariant.Contravariant (Colonnade.Types.OneColonnade f content)
- Colonnade.Types: instance Data.Functor.Contravariant.Contravariant Colonnade.Types.Headless
- Colonnade.Types: instance Data.Functor.Contravariant.Divisible.Divisible (Colonnade.Types.Colonnade f content)
- Colonnade.Types: instance GHC.Base.Applicative (Colonnade.Types.Decolonnade f content)
- Colonnade.Types: instance GHC.Base.Functor (Colonnade.Types.Decolonnade f content)
- Colonnade.Types: instance GHC.Base.Functor Colonnade.Types.Headed
- Colonnade.Types: instance GHC.Base.Functor Colonnade.Types.Headless
- Colonnade.Types: instance GHC.Base.Functor f => GHC.Base.Functor (Colonnade.Types.Indexed f)
- Colonnade.Types: instance GHC.Base.Monoid (Colonnade.Types.Colonnade f c a)
- Colonnade.Types: instance GHC.Base.Monoid (Colonnade.Types.DecolonnadeCellErrors f content)
- Colonnade.Types: instance GHC.Base.Monoid (Colonnade.Types.HeadingErrors content)
- Colonnade.Types: instance GHC.Classes.Eq (Colonnade.Types.Headless a)
- Colonnade.Types: instance GHC.Classes.Eq (f a) => GHC.Classes.Eq (Colonnade.Types.Indexed f a)
- Colonnade.Types: instance GHC.Classes.Eq a => GHC.Classes.Eq (Colonnade.Types.Headed a)
- Colonnade.Types: instance GHC.Classes.Eq content => GHC.Classes.Eq (Colonnade.Types.HeadingErrors content)
- Colonnade.Types: instance GHC.Classes.Ord (Colonnade.Types.Headless a)
- Colonnade.Types: instance GHC.Classes.Ord (f a) => GHC.Classes.Ord (Colonnade.Types.Indexed f a)
- Colonnade.Types: instance GHC.Classes.Ord a => GHC.Classes.Ord (Colonnade.Types.Headed a)
- Colonnade.Types: instance GHC.Read.Read (Colonnade.Types.Headless a)
- Colonnade.Types: instance GHC.Read.Read (f a) => GHC.Read.Read (Colonnade.Types.Indexed f a)
- Colonnade.Types: instance GHC.Read.Read a => GHC.Read.Read (Colonnade.Types.Headed a)
- Colonnade.Types: instance GHC.Read.Read content => GHC.Read.Read (Colonnade.Types.HeadingErrors content)
- Colonnade.Types: instance GHC.Show.Show (Colonnade.Types.Headless a)
- Colonnade.Types: instance GHC.Show.Show (f a) => GHC.Show.Show (Colonnade.Types.Indexed f a)
- Colonnade.Types: instance GHC.Show.Show a => GHC.Show.Show (Colonnade.Types.Headed a)
- Colonnade.Types: instance GHC.Show.Show content => GHC.Show.Show (Colonnade.Types.HeadingErrors content)
- Colonnade.Types: newtype Colonnade f c a
- Colonnade.Types: newtype DecolonnadeCellErrors f content
- Colonnade.Types: newtype Headed a
+ Colonnade: ascii :: Foldable f => Colonnade Headed String a -> f a -> String
+ Colonnade: bool :: f c -> (a -> Bool) -> (a -> c) -> (a -> c) -> Colonnade f c a
+ Colonnade: columns :: Foldable g => (b -> a -> c) -> (b -> f c) -> g b -> Colonnade f c a
+ Colonnade: data Colonnade h c a
+ Colonnade: data Headed a
+ Colonnade: data Headless a
+ Colonnade: fromMaybe :: c -> Colonnade f c a -> Colonnade f c (Maybe a)
+ Colonnade: headed :: c -> (a -> c) -> Colonnade Headed c a
+ Colonnade: headless :: (a -> c) -> Colonnade Headless c a
+ Colonnade: mapContent :: Functor f => (c1 -> c2) -> Colonnade f c1 a -> Colonnade f c2 a
+ Colonnade: modifyWhen :: (c -> c) -> (a -> Bool) -> Colonnade f c a -> Colonnade f c a
+ Colonnade: replaceWhen :: c -> (a -> Bool) -> Colonnade f c a -> Colonnade f c a
+ Colonnade: singleton :: f c -> (a -> c) -> Colonnade f c a
+ Colonnade.Encode: bothMonadic_ :: Monad m => Colonnade Headed content a -> (content -> content -> m b) -> a -> m ()
+ Colonnade.Encode: header :: (c1 -> c2) -> Colonnade Headed c1 a -> Vector c2
+ Colonnade.Encode: headerMonadic :: (Monad m, Monoid b) => Colonnade Headed content a -> (content -> m b) -> m b
+ Colonnade.Encode: headerMonadicGeneral :: (Monad m, Monoid b, Foldable h) => Colonnade h content a -> (content -> m b) -> m b
+ Colonnade.Encode: headerMonadicGeneral_ :: (Monad m, Foldable h) => Colonnade h content a -> (content -> m b) -> m ()
+ Colonnade.Encode: headerMonadic_ :: (Monad m) => Colonnade Headed content a -> (content -> m b) -> m ()
+ Colonnade.Encode: headerMonoidalGeneral :: (Monoid m, Foldable h) => Colonnade h c a -> (c -> m) -> m
+ Colonnade.Encode: row :: (c1 -> c2) -> Colonnade f c1 a -> a -> Vector c2
+ Colonnade.Encode: rowMonadic :: (Monad m, Monoid b) => Colonnade f content a -> (content -> m b) -> a -> m b
+ Colonnade.Encode: rowMonadicWith :: (Monad m) => b -> (b -> b -> b) -> Colonnade f content a -> (content -> m b) -> a -> m b
+ Colonnade.Encode: rowMonadic_ :: Monad m => Colonnade f content a -> (content -> m b) -> a -> m ()
+ Colonnade.Encode: rowMonoidal :: Monoid m => Colonnade h c a -> (c -> m) -> a -> m
+ Colonnade.Internal: Colonnade :: Vector (OneColonnade h c a) -> Colonnade h c a
+ Colonnade.Internal: Headed :: a -> Headed a
+ Colonnade.Internal: Headless :: Headless a
+ Colonnade.Internal: OneColonnade :: !(h content) -> !(a -> content) -> OneColonnade h content a
+ Colonnade.Internal: [getColonnade] :: Colonnade h c a -> Vector (OneColonnade h c a)
+ Colonnade.Internal: [getHeaded] :: Headed a -> a
+ Colonnade.Internal: [oneColonnadeEncode] :: OneColonnade h content a -> !(a -> content)
+ Colonnade.Internal: [oneColonnadeHead] :: OneColonnade h content a -> !(h content)
+ Colonnade.Internal: data Headless a
+ Colonnade.Internal: data OneColonnade h content a
+ Colonnade.Internal: instance Data.Foldable.Foldable Colonnade.Internal.Headed
+ Colonnade.Internal: instance Data.Foldable.Foldable Colonnade.Internal.Headless
+ Colonnade.Internal: instance Data.Functor.Contravariant.Contravariant (Colonnade.Internal.Colonnade h content)
+ Colonnade.Internal: instance Data.Functor.Contravariant.Contravariant (Colonnade.Internal.OneColonnade h content)
+ Colonnade.Internal: instance Data.Functor.Contravariant.Contravariant Colonnade.Internal.Headless
+ Colonnade.Internal: instance Data.Functor.Contravariant.Divisible.Divisible (Colonnade.Internal.Colonnade h content)
+ Colonnade.Internal: instance GHC.Base.Functor Colonnade.Internal.Headed
+ Colonnade.Internal: instance GHC.Base.Functor Colonnade.Internal.Headless
+ Colonnade.Internal: instance GHC.Base.Monoid (Colonnade.Internal.Colonnade h c a)
+ Colonnade.Internal: instance GHC.Classes.Eq (Colonnade.Internal.Headless a)
+ Colonnade.Internal: instance GHC.Classes.Eq a => GHC.Classes.Eq (Colonnade.Internal.Headed a)
+ Colonnade.Internal: instance GHC.Classes.Ord (Colonnade.Internal.Headless a)
+ Colonnade.Internal: instance GHC.Classes.Ord a => GHC.Classes.Ord (Colonnade.Internal.Headed a)
+ Colonnade.Internal: instance GHC.Read.Read (Colonnade.Internal.Headless a)
+ Colonnade.Internal: instance GHC.Read.Read a => GHC.Read.Read (Colonnade.Internal.Headed a)
+ Colonnade.Internal: instance GHC.Show.Show (Colonnade.Internal.Headless a)
+ Colonnade.Internal: instance GHC.Show.Show a => GHC.Show.Show (Colonnade.Internal.Headed a)
+ Colonnade.Internal: newtype Colonnade h c a
+ Colonnade.Internal: newtype Headed a
Files
- colonnade.cabal +3/−4
- src/Colonnade.hs +296/−0
- src/Colonnade/Decoding.hs +0/−160
- src/Colonnade/Encode.hs +152/−0
- src/Colonnade/Encoding.hs +0/−344
- src/Colonnade/Internal.hs +92/−17
- src/Colonnade/Types.hs +0/−152
- test/Main.hs +1/−1
colonnade.cabal view
@@ -1,5 +1,5 @@ name: colonnade-version: 0.5+version: 1.0.0 synopsis: Generic types and functions for columnar encoding and decoding description: The `colonnade` package provides a way to to talk about@@ -28,9 +28,8 @@ library hs-source-dirs: src exposed-modules:- Colonnade.Types- Colonnade.Encoding- Colonnade.Decoding+ Colonnade+ Colonnade.Encode Colonnade.Internal build-depends: base >= 4.7 && < 5
+ src/Colonnade.hs view
@@ -0,0 +1,296 @@+-- | Build backend-agnostic columnar encodings that can be +-- used to visualize tabular data.+module Colonnade+ ( -- * Example+ -- $setup+ -- * Types+ Colonnade+ , Headed+ , Headless+ -- * Create+ , headed+ , headless+ , singleton+ -- * Transform+ , fromMaybe+ , columns+ , bool+ , replaceWhen+ , modifyWhen+ , mapContent+ -- * Ascii Table+ , ascii+ ) where++import Colonnade.Internal+import qualified Colonnade.Encode as Encode+import Data.Vector (Vector)+import Data.Foldable+import Data.Monoid (Endo(..))+import Control.Monad+import Data.Functor.Contravariant+import qualified Data.Bool+import qualified Data.Maybe+import qualified Data.List as List+import qualified Data.Vector as Vector++-- $setup+--+-- First, let\'s bring in some neccessary imports that will be+-- used for the remainder of the examples in the docs:+--+-- >>> import Data.Monoid (mconcat,(<>))+-- >>> import Data.Functor.Contravariant (contramap)+--+-- The data types we wish to encode are:+--+-- >>> data Color = Red | Green | Blue deriving (Show,Eq)+-- >>> data Person = Person { name :: String, age :: Int }+-- >>> data House = House { color :: Color, price :: Int }+--+-- One potential columnar encoding of a @Person@ would be:+--+-- >>> :{+-- let colPerson :: Colonnade Headed String Person+-- colPerson = mconcat+-- [ headed "Name" name+-- , headed "Age" (show . age)+-- ]+-- :}+--+-- The type signature on @colPerson@ is not neccessary+-- but is included for clarity. We can feed data into this encoding+-- to build a table:+--+-- >>> let people = [Person "David" 63, Person "Ava" 34, Person "Sonia" 12]+-- >>> putStr (ascii colPerson people)+-- +-------+-----++-- | Name | Age |+-- +-------+-----++-- | David | 63 |+-- | Ava | 34 |+-- | Sonia | 12 |+-- +-------+-----++--+-- Similarly, we can build a table of houses with:+--+-- >>> let showDollar = (('$':) . show) :: Int -> String+-- >>> :{+-- let encodingHouse :: Colonnade Headed String House+-- encodingHouse = mconcat+-- [ headed "Color" (show . color)+-- , headed "Price" (showDollar . price)+-- ]+-- :}+--+-- >>> let houses = [House Green 170000, House Blue 115000, House Green 150000]+-- >>> putStr (ascii encodingHouse houses)+-- +-------+---------++-- | Color | Price |+-- +-------+---------++-- | Green | $170000 |+-- | Blue | $115000 |+-- | Green | $150000 |+-- +-------+---------++++-- | A single column with a header.+headed :: c -> (a -> c) -> Colonnade Headed c a+headed h = singleton (Headed h)++-- | A single column without a header.+headless :: (a -> c) -> Colonnade Headless c a+headless = singleton Headless++-- | A single column with any kind of header. This is not typically needed.+singleton :: f c -> (a -> c) -> Colonnade f c a+singleton h = Colonnade . Vector.singleton . OneColonnade h++-- | Lift a column over a 'Maybe'. For example, if some people+-- have houses and some do not, the data that pairs them together+-- could be represented as:+--+-- >>> :{+-- let owners :: [(Person,Maybe House)]+-- owners =+-- [ (Person "Jordan" 18, Nothing)+-- , (Person "Ruth" 25, Just (House Red 125000))+-- , (Person "Sonia" 12, Just (House Green 145000))+-- ]+-- :}+--+-- The column encodings defined earlier can be reused with+-- the help of 'fromMaybe':+--+-- >>> :{+-- let colOwners :: Colonnade Headed String (Person,Maybe House)+-- colOwners = mconcat+-- [ contramap fst colPerson+-- , contramap snd (fromMaybe "" encodingHouse)+-- ]+-- :}+--+-- >>> putStr (ascii colOwners owners)+-- +--------+-----+-------+---------++-- | Name | Age | Color | Price |+-- +--------+-----+-------+---------++-- | Jordan | 18 | | |+-- | Ruth | 25 | Red | $125000 |+-- | Sonia | 12 | Green | $145000 |+-- +--------+-----+-------+---------++fromMaybe :: c -> Colonnade f c a -> Colonnade f c (Maybe a)+fromMaybe c (Colonnade v) = Colonnade $ flip Vector.map v $+ \(OneColonnade h encode) -> OneColonnade h (maybe c encode)++-- | Convert a collection of @b@ values into a columnar encoding of+-- the same size. Suppose we decide to show a house\'s color+-- by putting a check mark in the column corresponding to+-- the color instead of by writing out the name of the color:+--+-- >>> let allColors = [Red,Green,Blue]+-- >>> let encColor = columns (\c1 c2 -> if c1 == c2 then "✓" else "") (Headed . show) allColors+-- >>> :t encColor+-- encColor :: Colonnade Headed [Char] Color+-- >>> let encHouse = headed "Price" (showDollar . price) <> contramap color encColor+-- >>> :t encHouse+-- encHouse :: Colonnade Headed [Char] House+-- >>> putStr (ascii encHouse houses)+-- +---------+-----+-------+------++-- | Price | Red | Green | Blue |+-- +---------+-----+-------+------++-- | $170000 | | ✓ | |+-- | $115000 | | | ✓ |+-- | $150000 | | ✓ | |+-- +---------+-----+-------+------++columns :: Foldable g+ => (b -> a -> c) -- ^ Cell content function+ -> (b -> f c) -- ^ Header content function+ -> g b -- ^ Basis for column encodings+ -> Colonnade f c a+columns getCell getHeader = id+ . Colonnade+ . Vector.map (\b -> OneColonnade (getHeader b) (getCell b))+ . Vector.fromList+ . toList++bool ::+ f c -- ^ Heading+ -> (a -> Bool) -- ^ Predicate+ -> (a -> c) -- ^ Contents when predicate is false+ -> (a -> c) -- ^ Contents when predicate is true+ -> Colonnade f c a+bool h p onTrue onFalse = singleton h (Data.Bool.bool <$> onFalse <*> onTrue <*> p)++-- | Modify the contents of cells in rows whose values satisfy the+-- given predicate. Header content is unaffected. With an HTML backend, +-- this can be used to strikethrough the contents of cells with data that is+-- considered invalid.+modifyWhen ::+ (c -> c) -- ^ Content change+ -> (a -> Bool) -- ^ Row predicate+ -> Colonnade f c a -- ^ Original 'Colonnade'+ -> Colonnade f c a+modifyWhen changeContent p (Colonnade v) = Colonnade+ ( Vector.map+ (\(OneColonnade h encode) -> OneColonnade h $ \a ->+ if p a then changeContent (encode a) else encode a+ ) v+ )++-- | Replace the contents of cells in rows whose values satisfy the+-- given predicate. Header content is unaffected.+replaceWhen ::+ c -- ^ New content+ -> (a -> Bool) -- ^ Row predicate+ -> Colonnade f c a -- ^ Original 'Colonnade'+ -> Colonnade f c a+replaceWhen newContent p (Colonnade v) = Colonnade+ ( Vector.map+ (\(OneColonnade h encode) -> OneColonnade h $ \a ->+ if p a then newContent else encode a+ ) v+ )++-- | 'Colonnade' is covariant in its content type. Consequently, it can be+-- mapped over. There is no standard typeclass for types that are covariant+-- in their second-to-last argument, so this function is provided for+-- situations that require this.+mapContent :: Functor f => (c1 -> c2) -> Colonnade f c1 a -> Colonnade f c2 a+mapContent f (Colonnade v) = Colonnade+ $ Vector.map (\(OneColonnade h c) -> (OneColonnade (fmap f h) (f . c))) v++-- | Render a collection of rows as an ascii table. The table\'s columns are+-- specified by the given 'Colonnade'. This implementation is inefficient and+-- does not provide any wrapping behavior. It is provided so that users can+-- try out @colonnade@ in ghci and so that @doctest@ can verify examples+-- code in the haddocks.+ascii :: Foldable f+ => Colonnade Headed String a -- ^ columnar encoding+ -> f a -- ^ rows+ -> String+ascii enc xs =+ let theHeader :: [(Int,String)]+ theHeader = (zip (enumFrom 0) . map (\s -> " " ++ s ++ " ")) (toList (Encode.header id enc))+ theBody :: [[(Int,String)]]+ theBody = map (zip (enumFrom 0) . map (\s -> " " ++ s ++ " ") . toList . Encode.row id enc) (toList xs)+ sizes :: [Int]+ sizes = ($ replicate (length theHeader) 1) $ appEndo $ mconcat+ [ foldMap (\(i,str) -> Endo (replaceAt i (length str))) theHeader+ , (foldMap . foldMap) (\(i,str) -> Endo (replaceAt i (length str))) theBody+ ]+ paddedHeader :: [String]+ paddedHeader = map (\(i,str) -> rightPad (atDef 1 sizes i) ' ' str) theHeader+ paddedBody :: [[String]]+ paddedBody = (map . map) (\(i,str) -> rightPad (atDef 1 sizes i) ' ' str) theBody+ divider :: String+ divider = "+" ++ join (List.intersperse "+" (map (\i -> replicate i '-') sizes)) ++ "+"+ headerStr :: String+ headerStr = "|" ++ join (List.intersperse "|" paddedHeader) ++ "|"+ bodyStr :: String+ bodyStr = List.unlines (map ((\s -> "|" ++ s ++ "|") . join . List.intersperse "|") paddedBody)+ in divider ++ "\n" ++ headerStr+ ++ "\n" ++ divider+ ++ "\n" ++ bodyStr ++ divider ++ "\n"+++-- this has no effect if the index is out of bounds+replaceAt :: Ord a => Int -> a -> [a] -> [a]+replaceAt _ _ [] = []+replaceAt n v (a:as) = if n > 0+ then a : replaceAt (n - 1) v as+ else (max v a) : as++rightPad :: Int -> a -> [a] -> [a]+rightPad m a xs = take m $ xs ++ repeat a++atDef :: a -> [a] -> Int -> a+atDef def = Data.Maybe.fromMaybe def .^ atMay where+ (.^) f g x1 x2 = f (g x1 x2)+ atMay = eitherToMaybe .^ at_+ eitherToMaybe = either (const Nothing) Just+ at_ xs o | o < 0 = Left $ "index must not be negative, index=" ++ show o+ | otherwise = f o xs+ where f 0 (z:_) = Right z+ f i (_:zs) = f (i-1) zs+ f i [] = Left $ "index too large, index=" ++ show o ++ ", length=" ++ show (o-i)++-- data Company = Company String String Int+-- +-- data Company = Company+-- { companyName :: String+-- , companyCountry :: String+-- , companyValue :: Int+-- } deriving (Show)+-- +-- myCompanies :: [Company]+-- myCompanies =+-- [ Company "eCommHub" "United States" 50+-- , Company "Layer 3 Communications" "United States" 10000000+-- , Company "Microsoft" "England" 500000000+-- ]++++++
− src/Colonnade/Decoding.hs
@@ -1,160 +0,0 @@-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE BangPatterns #-}-module Colonnade.Decoding where--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 'Decolonnade'. The @'Contravariant' f@--- constraint means that @f@ can be 'Headless' but not 'Headed'.-contramapContent :: forall c1 c2 f a. Contravariant f => (c2 -> c1) -> Decolonnade f c1 a -> Decolonnade f c2 a-contramapContent f = go- where- go :: forall b. Decolonnade f c1 b -> Decolonnade f c2 b- go (DecolonnadePure x) = DecolonnadePure x- go (DecolonnadeAp h decode apNext) =- DecolonnadeAp (contramap f h) (decode . f) (go apNext)--headless :: (content -> Either String a) -> Decolonnade Headless content a-headless f = DecolonnadeAp Headless f (DecolonnadePure id)--headed :: content -> (content -> Either String a) -> Decolonnade Headed content a-headed h f = DecolonnadeAp (Headed h) f (DecolonnadePure id)--indexed :: Int -> (content -> Either String a) -> Decolonnade (Indexed Headless) content a-indexed ix f = DecolonnadeAp (Indexed ix Headless) f (DecolonnadePure id)--maxIndex :: forall f c a. Decolonnade (Indexed f) c a -> Int-maxIndex = go 0 where- go :: forall b. Int -> Decolonnade (Indexed f) c b -> Int- go !ix (DecolonnadePure _) = ix- go !ix1 (DecolonnadeAp (Indexed ix2 _) decode apNext) =- go (max ix1 ix2) apNext---- | This function uses 'unsafeIndex' to access--- elements of the 'Vector'.-uncheckedRunWithRow ::- Int- -> Decolonnade (Indexed f) content a- -> Vector content- -> Either (DecolonnadeRowError f content) a-uncheckedRunWithRow i d v = mapLeft (DecolonnadeRowError i . RowErrorDecode) (uncheckedRun d v)---- | This function does not check to make sure that the indicies in--- the 'Decolonnade' are in the 'Vector'.-uncheckedRun :: forall content a f.- Decolonnade (Indexed f) content a- -> Vector content- -> Either (DecolonnadeCellErrors f content) a-uncheckedRun dc v = getEitherWrap (go dc)- where- go :: forall b.- Decolonnade (Indexed f) content b- -> EitherWrap (DecolonnadeCellErrors f content) b- go (DecolonnadePure b) = EitherWrap (Right b)- go (DecolonnadeAp ixed@(Indexed ix h) decode apNext) =- let rnext = go apNext- content = Vector.unsafeIndex v ix- rcurrent = mapLeft (DecolonnadeCellErrors . Vector.singleton . DecolonnadeCellError content ixed) (decode content)- in rnext <*> (EitherWrap rcurrent)--headlessToIndexed :: forall c a.- Decolonnade Headless c a -> Decolonnade (Indexed Headless) c a-headlessToIndexed = go 0 where- go :: forall b. Int -> Decolonnade Headless c b -> Decolonnade (Indexed Headless) c b- go !ix (DecolonnadePure a) = DecolonnadePure a- go !ix (DecolonnadeAp Headless decode apNext) =- DecolonnadeAp (Indexed ix Headless) decode (go (ix + 1) apNext)--length :: forall f c a. Decolonnade f c a -> Int-length = go 0 where- go :: forall b. Int -> Decolonnade f c b -> Int- go !a (DecolonnadePure _) = a- go !a (DecolonnadeAp _ _ apNext) = go (a + 1) apNext---- | Maps over a 'Decolonnade' that expects headers, converting these--- expected headers into the indices of the columns that they--- correspond to.-headedToIndexed :: forall content a. Eq content- => Vector content -- ^ Headers in the source document- -> Decolonnade Headed content a -- ^ Decolonnade that contains expected headers- -> Either (HeadingErrors content) (Decolonnade (Indexed Headed) content a)-headedToIndexed v = getEitherWrap . go- where- go :: forall b. Eq content- => Decolonnade Headed content b- -> EitherWrap (HeadingErrors content) (Decolonnade (Indexed Headed) content b)- go (DecolonnadePure b) = EitherWrap (Right (DecolonnadePure b))- go (DecolonnadeAp hd@(Headed h) decode apNext) =- let rnext = go apNext- ixs = Vector.elemIndices h v- ixsLen = Vector.length ixs- rcurrent- | ixsLen == 1 = Right (Vector.unsafeIndex ixs 0)- | ixsLen == 0 = Left (HeadingErrors (Vector.singleton h) Vector.empty)- | otherwise = Left (HeadingErrors Vector.empty (Vector.singleton (h,ixsLen)))- in (\ix ap -> DecolonnadeAp (Indexed ix hd) decode ap)- <$> EitherWrap rcurrent- <*> rnext---- | This adds one to the index because text editors consider--- line number to be one-based, not zero-based.-prettyError :: (c -> String) -> DecolonnadeRowError f c -> String-prettyError toStr (DecolonnadeRowError ix e) = unlines- $ ("Decolonnade error on line " ++ show (ix + 1) ++ " of file.")- : ("Error Category: " ++ descr)- : map (" " ++) errDescrs- where (descr,errDescrs) = prettyRowError toStr e--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 Decolonnade"- [ "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 Decolonnade" (prettyCellErrors toStr errs)--prettyCellErrors :: (c -> String) -> DecolonnadeCellErrors f c -> [String]-prettyCellErrors toStr (DecolonnadeCellErrors errs) = drop 1 $- flip concatMap errs $ \(DecolonnadeCellError 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/Encode.hs view
@@ -0,0 +1,152 @@+-- | Most users of this library do not need this module. The functions+-- here are used to build functions that apply a 'Colonnade'+-- to a collection of values, building a table from them. Ultimately, +-- a function that applies a @Colonnade Headed MyCell a@ +-- to data will have roughly the following type:+--+-- > myTableRenderer :: Foldable g => Colonnade Headed MyCell a -> g a -> MyContent+--+-- In the companion packages @yesod-colonnade@ and+-- @reflex-dom-colonnade@, functions with+-- similar type signatures are readily available.+-- These packages use the functions provided here+-- in the implementations of their rendering functions.+-- It is recommended that users who believe they may need+-- this module look at the source of the companion packages +-- to see an example of how this module\'s functions are used.+-- Other backends are encouraged to use these functions+-- to build monadic or monoidal content from a 'Colonnade'.+--+-- The functions exported here take a 'Colonnade' and +-- convert it to a fragment of content. The functions whose+-- names start with @row@ take at least a @Colonnade f c a@ and an @a@+-- value to generate a row of content. The functions whose names+-- start with @header@ need the @Colonnade f c a@ but not+-- an @a@ value since a value is not needed to build a header.+-- +module Colonnade.Encode+ ( row+ , rowMonadic+ , rowMonadic_+ , rowMonadicWith+ , rowMonoidal+ , header+ , headerMonadic+ , headerMonadic_+ , headerMonadicGeneral+ , headerMonadicGeneral_+ , headerMonoidalGeneral+ , bothMonadic_+ ) where++import Colonnade.Internal+import Data.Vector (Vector)+import Data.Foldable+import qualified Data.Vector as Vector++-- | Consider providing a variant the produces a list+-- instead. It may allow more things to get inlined+-- in to a loop.+row :: (c1 -> c2) -> Colonnade f c1 a -> a -> Vector c2+row g (Colonnade v) a = flip Vector.map v $+ \(OneColonnade _ encode) -> g (encode a)++bothMonadic_ :: Monad m+ => Colonnade Headed content a+ -> (content -> content -> m b)+ -> a+ -> m ()+bothMonadic_ (Colonnade v) g a =+ forM_ v $ \(OneColonnade (Headed h) encode) -> g h (encode a)++rowMonadic :: + (Monad m, Monoid b)+ => Colonnade f content a+ -> (content -> m b)+ -> a+ -> m b+rowMonadic (Colonnade v) g a =+ flip foldlMapM v+ $ \e -> g (oneColonnadeEncode e a)++rowMonadic_ :: + Monad m+ => Colonnade f content a+ -> (content -> m b)+ -> a+ -> m ()+rowMonadic_ (Colonnade v) g a =+ forM_ v $ \e -> g (oneColonnadeEncode e a)++rowMonoidal ::+ Monoid m+ => Colonnade h c a+ -> (c -> m)+ -> a+ -> m+rowMonoidal (Colonnade v) g a =+ foldMap (\e -> g (oneColonnadeEncode e a)) v++rowMonadicWith :: + (Monad m)+ => b+ -> (b -> b -> b)+ -> Colonnade f content a+ -> (content -> m b)+ -> a+ -> m b+rowMonadicWith bempty bappend (Colonnade v) g a =+ foldlM (\bl e -> do+ br <- g (oneColonnadeEncode e a)+ return (bappend bl br)+ ) bempty v++header :: (c1 -> c2) -> Colonnade Headed c1 a -> Vector c2+header g (Colonnade v) =+ Vector.map (g . getHeaded . oneColonnadeHead) v++-- | This function is a helper for abusing 'Foldable' to optionally+-- render a header. Its future is uncertain.+headerMonadicGeneral :: (Monad m, Monoid b, Foldable h)+ => Colonnade h content a+ -> (content -> m b)+ -> m b+headerMonadicGeneral (Colonnade v) g = id+ $ fmap (mconcat . Vector.toList)+ $ Vector.mapM (foldlMapM g . oneColonnadeHead) v++headerMonadic :: + (Monad m, Monoid b)+ => Colonnade Headed content a+ -> (content -> m b)+ -> m b+headerMonadic (Colonnade v) g =+ fmap (mconcat . Vector.toList) $ Vector.mapM (g . getHeaded . oneColonnadeHead) v++headerMonadicGeneral_ :: + (Monad m, Foldable h)+ => Colonnade h content a+ -> (content -> m b)+ -> m ()+headerMonadicGeneral_ (Colonnade v) g =+ Vector.mapM_ (mapM_ g . oneColonnadeHead) v++headerMonoidalGeneral ::+ (Monoid m, Foldable h)+ => Colonnade h c a+ -> (c -> m)+ -> m+headerMonoidalGeneral (Colonnade v) g =+ foldMap (foldMap g . oneColonnadeHead) v+ ++headerMonadic_ ::+ (Monad m)+ => Colonnade Headed content a+ -> (content -> m b)+ -> m ()+headerMonadic_ (Colonnade v) g = Vector.mapM_ (g . getHeaded . oneColonnadeHead) v++foldlMapM :: (Foldable t, Monoid b, Monad m) => (a -> m b) -> t a -> m b+foldlMapM f = foldlM (\b a -> fmap (mappend b) (f a)) mempty+
− src/Colonnade/Encoding.hs
@@ -1,344 +0,0 @@--- | Build backend-agnostic columnar encodings that can be used to visualize data.--module Colonnade.Encoding- ( -- * Example- -- $setup- -- * Create- headed- , headless- , singleton- -- * Transform- , fromMaybe- , columns- , bool- , replaceWhen- , mapContent- -- * Render- , runRow- , runRowMonadic- , runRowMonadic_- , runRowMonadicWith- , runHeader- , runHeaderMonadic- , runHeaderMonadic_- , runHeaderMonadicGeneral- , runHeaderMonadicGeneral_- , runBothMonadic_- -- * Ascii Table- , ascii- ) where--import Colonnade.Types-import Data.Vector (Vector)-import Data.Foldable-import Data.Monoid (Endo(..))-import Control.Monad-import Data.Functor.Contravariant-import qualified Data.Bool-import qualified Data.Maybe-import qualified Data.List as List-import qualified Data.Vector as Vector-import qualified Colonnade.Internal as Internal---- $setup------ First, let\'s bring in some neccessary imports that will be--- used for the remainder of the examples in the docs:------ >>> import Data.Monoid (mconcat,(<>))--- >>> import Data.Functor.Contravariant (contramap)------ Assume that the data we wish to encode is:------ >>> data Color = Red | Green | Blue deriving (Show,Eq)--- >>> data Person = Person { name :: String, age :: Int }--- >>> data House = House { color :: Color, price :: Int }------ One potential columnar encoding of a @Person@ would be:------ >>> :{--- let encodingPerson :: Colonnade Headed String Person--- encodingPerson = mconcat--- [ headed "Name" name--- , headed "Age" (show . age)--- ]--- :}------ The type signature on @encodingPerson@ is not neccessary--- but is included for clarity. We can feed data into this encoding--- to build a table:------ >>> let people = [Person "David" 63, Person "Ava" 34, Person "Sonia" 12]--- >>> putStr (ascii encodingPerson people)--- +-------+-----+--- | Name | Age |--- +-------+-----+--- | David | 63 |--- | Ava | 34 |--- | Sonia | 12 |--- +-------+-----+------ Similarly, we can build a table of houses with:------ >>> let showDollar = (('$':) . show) :: Int -> String--- >>> :{--- let encodingHouse :: Colonnade Headed String House--- encodingHouse = mconcat--- [ headed "Color" (show . color)--- , headed "Price" (showDollar . price)--- ]--- :}------ >>> let houses = [House Green 170000, House Blue 115000, House Green 150000]--- >>> putStr (ascii encodingHouse houses)--- +-------+---------+--- | Color | Price |--- +-------+---------+--- | Green | $170000 |--- | Blue | $115000 |--- | Green | $150000 |--- +-------+---------+----- | A single column with a header.-headed :: c -> (a -> c) -> Colonnade Headed c a-headed h = singleton (Headed h)---- | A single column without a header.-headless :: (a -> c) -> Colonnade Headless c a-headless = singleton Headless---- | A single column with any kind of header. This is not typically needed.-singleton :: f c -> (a -> c) -> Colonnade f c a-singleton h = Colonnade . Vector.singleton . OneColonnade h---- | Lift a column over a 'Maybe'. For example, if some people--- have houses and some do not, the data that pairs them together--- could be represented as:------ >>> :{--- >>> let owners :: [(Person,Maybe House)]--- >>> owners =--- >>> [ (Person "Jordan" 18, Nothing)--- >>> , (Person "Ruth" 25, Just (House Red 125000))--- >>> , (Person "Sonia" 12, Just (House Green 145000))--- >>> ]--- >>> :}------ The column encodings defined earlier can be reused with--- the help of 'fromMaybe':------ >>> :{--- >>> let encodingOwners :: Colonnade Headed String (Person,Maybe House)--- >>> encodingOwners = mconcat--- >>> [ contramap fst encodingPerson--- >>> , contramap snd (fromMaybe "" encodingHouse)--- >>> ]--- >>> :}------ >>> putStr (ascii encodingOwners owners)--- +--------+-----+-------+---------+--- | Name | Age | Color | Price |--- +--------+-----+-------+---------+--- | Jordan | 18 | | |--- | Ruth | 25 | Red | $125000 |--- | Sonia | 12 | Green | $145000 |--- +--------+-----+-------+---------+-fromMaybe :: c -> Colonnade f c a -> Colonnade f c (Maybe a)-fromMaybe c (Colonnade v) = Colonnade $ flip Vector.map v $- \(OneColonnade h encode) -> OneColonnade h (maybe c encode)---- | Convert a collection of @b@ values into a columnar encoding of--- the same size. Suppose we decide to show a house\'s color--- by putting a check mark in the column corresponding to--- the color instead of by writing out the name of the color:------ >>> let allColors = [Red,Green,Blue]--- >>> let encColor = columns (\c1 c2 -> if c1 == c2 then "✓" else "") (Headed . show) allColors--- >>> :t encColor--- encColor :: Colonnade Headed [Char] Color--- >>> let encHouse = headed "Price" (showDollar . price) <> contramap color encColor--- >>> :t encHouse--- encHouse :: Colonnade Headed [Char] House--- >>> putStr (ascii encHouse houses)--- +---------+-----+-------+------+--- | Price | Red | Green | Blue |--- +---------+-----+-------+------+--- | $170000 | | ✓ | |--- | $115000 | | | ✓ |--- | $150000 | | ✓ | |--- +---------+-----+-------+------+-columns :: Foldable g- => (b -> a -> c) -- ^ Cell content function- -> (b -> f c) -- ^ Header content function- -> g b -- ^ Basis for column encodings- -> Colonnade f c a-columns getCell getHeader = id- . Colonnade- . Vector.map (\b -> OneColonnade (getHeader b) (getCell b))- . Vector.fromList- . toList--bool ::- f c -- ^ Heading- -> (a -> Bool) -- ^ Predicate- -> (a -> c) -- ^ Contents when predicate is false- -> (a -> c) -- ^ Contents when predicate is true- -> Colonnade f c a-bool h p onTrue onFalse = singleton h (Data.Bool.bool <$> onFalse <*> onTrue <*> p)--replaceWhen ::- c- -> (a -> Bool)- -> Colonnade f c a- -> Colonnade f c a-replaceWhen newContent p (Colonnade v) = Colonnade- ( Vector.map- (\(OneColonnade h encode) -> OneColonnade h $ \a ->- if p a then newContent else encode a- ) v- )---- | 'Colonnade' is covariant in its content type. Consequently, it can be--- mapped over. There is no standard typeclass for types that are covariant--- in their second-to-last argument, so this function is provided for--- situations that require this.-mapContent :: Functor f => (c1 -> c2) -> Colonnade f c1 a -> Colonnade f c2 a-mapContent f (Colonnade v) = Colonnade- $ Vector.map (\(OneColonnade h c) -> (OneColonnade (fmap f h) (f . c))) v---- | Consider providing a variant the produces a list--- instead. It may allow more things to get inlined--- in to a loop.-runRow :: (c1 -> c2) -> Colonnade f c1 a -> a -> Vector c2-runRow g (Colonnade v) a = flip Vector.map v $- \(OneColonnade _ encode) -> g (encode a)--runBothMonadic_ :: Monad m- => Colonnade Headed content a- -> (content -> content -> m b)- -> a- -> m ()-runBothMonadic_ (Colonnade v) g a =- forM_ v $ \(OneColonnade (Headed h) encode) -> g h (encode a)--runRowMonadic :: (Monad m, Monoid b)- => Colonnade f content a- -> (content -> m b)- -> a- -> m b-runRowMonadic (Colonnade v) g a =- flip Internal.foldlMapM v- $ \e -> g (oneColonnadeEncode e a)--runRowMonadic_ :: Monad m- => Colonnade f content a- -> (content -> m b)- -> a- -> m ()-runRowMonadic_ (Colonnade v) g a =- forM_ v $ \e -> g (oneColonnadeEncode e a)--runRowMonadicWith :: (Monad m)- => b- -> (b -> b -> b)- -> Colonnade f content a- -> (content -> m b)- -> a- -> m b-runRowMonadicWith bempty bappend (Colonnade v) g a =- foldlM (\bl e -> do- br <- g (oneColonnadeEncode e a)- return (bappend bl br)- ) bempty v--runHeader :: (c1 -> c2) -> Colonnade Headed c1 a -> Vector c2-runHeader g (Colonnade v) =- Vector.map (g . getHeaded . oneColonnadeHead) v---- | This function is a helper for abusing 'Foldable' to optionally--- render a header. Its future is uncertain.-runHeaderMonadicGeneral :: (Monad m, Monoid b, Foldable h)- => Colonnade h content a- -> (content -> m b)- -> m b-runHeaderMonadicGeneral (Colonnade v) g = id- $ fmap (mconcat . Vector.toList)- $ Vector.mapM (Internal.foldlMapM g . oneColonnadeHead) v--runHeaderMonadic :: (Monad m, Monoid b)- => Colonnade Headed content a- -> (content -> m b)- -> m b-runHeaderMonadic (Colonnade v) g =- fmap (mconcat . Vector.toList) $ Vector.mapM (g . getHeaded . oneColonnadeHead) v--runHeaderMonadicGeneral_ :: (Monad m, Monoid b, Foldable h)- => Colonnade h content a- -> (content -> m b)- -> m ()-runHeaderMonadicGeneral_ (Colonnade v) g =- Vector.mapM_ (Internal.foldlMapM g . oneColonnadeHead) v--runHeaderMonadic_ ::- (Monad m)- => Colonnade Headed content a- -> (content -> m b)- -> m ()-runHeaderMonadic_ (Colonnade v) g = Vector.mapM_ (g . getHeaded . oneColonnadeHead) v---- | Render a collection of rows as an ascii table. The table\'s columns are--- specified by the given 'Colonnade'. This implementation is inefficient and--- does not provide any wrapping behavior. It is provided so that users can--- try out @colonnade@ in ghci and so that @doctest@ can verify examples--- code in the haddocks.-ascii :: Foldable f- => Colonnade Headed String a -- ^ columnar encoding- -> f a -- ^ rows- -> String-ascii enc xs =- let theHeader :: [(Int,String)]- theHeader = (zip (enumFrom 0) . map (\s -> " " ++ s ++ " ")) (toList (runHeader id enc))- theBody :: [[(Int,String)]]- theBody = map (zip (enumFrom 0) . map (\s -> " " ++ s ++ " ") . toList . runRow id enc) (toList xs)- sizes :: [Int]- sizes = ($ replicate (length theHeader) 1) $ appEndo $ mconcat- [ foldMap (\(i,str) -> Endo (replaceAt i (length str))) theHeader- , (foldMap . foldMap) (\(i,str) -> Endo (replaceAt i (length str))) theBody- ]- paddedHeader :: [String]- paddedHeader = map (\(i,str) -> rightPad (atDef 1 sizes i) ' ' str) theHeader- paddedBody :: [[String]]- paddedBody = (map . map) (\(i,str) -> rightPad (atDef 1 sizes i) ' ' str) theBody- divider :: String- divider = "+" ++ join (List.intersperse "+" (map (\i -> replicate i '-') sizes)) ++ "+"- headerStr :: String- headerStr = "|" ++ join (List.intersperse "|" paddedHeader) ++ "|"- bodyStr :: String- bodyStr = List.unlines (map ((\s -> "|" ++ s ++ "|") . join . List.intersperse "|") paddedBody)- in divider ++ "\n" ++ headerStr- ++ "\n" ++ divider- ++ "\n" ++ bodyStr ++ divider ++ "\n"----- this has no effect if the index is out of bounds-replaceAt :: Ord a => Int -> a -> [a] -> [a]-replaceAt _ _ [] = []-replaceAt n v (a:as) = if n > 0- then a : replaceAt (n - 1) v as- else (max v a) : as--rightPad :: Int -> a -> [a] -> [a]-rightPad m a xs = take m $ xs ++ repeat a--atDef :: a -> [a] -> Int -> a-atDef def = Data.Maybe.fromMaybe def .^ atMay where- (.^) f g x1 x2 = f (g x1 x2)- atMay = eitherToMaybe .^ at_- eitherToMaybe = either (const Nothing) Just- at_ xs o | o < 0 = Left $ "index must not be negative, index=" ++ show o- | otherwise = f o xs- where f 0 (z:_) = Right z- f i (_:zs) = f (i-1) zs- f i [] = Left $ "index too large, index=" ++ show o ++ ", length=" ++ show (o-i)-
src/Colonnade/Internal.hs view
@@ -1,23 +1,98 @@-{-# LANGUAGE DeriveFunctor #-}-module Colonnade.Internal where+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} -import Data.Foldable (foldrM,foldlM)+{-# OPTIONS_HADDOCK not-home #-} -newtype EitherWrap a b = EitherWrap- { getEitherWrap :: Either a b- } deriving (Functor)+module Colonnade.Internal+ ( Colonnade(..)+ , OneColonnade(..)+ , Headed(..)+ , Headless(..)+ ) where -instance Monoid a => Applicative (EitherWrap a) where- pure = EitherWrap . Right- EitherWrap (Left a1) <*> EitherWrap (Left a2) = EitherWrap (Left (mappend a1 a2))- EitherWrap (Left a1) <*> EitherWrap (Right _) = EitherWrap (Left a1)- EitherWrap (Right _) <*> EitherWrap (Left a2) = EitherWrap (Left a2)- EitherWrap (Right f) <*> EitherWrap (Right b) = EitherWrap (Right (f b))+import Data.Vector (Vector)+import Data.Functor.Contravariant (Contravariant(..))+import Data.Functor.Contravariant.Divisible (Divisible(..))+import Control.Exception (Exception)+import Data.Typeable (Typeable)+import qualified Data.Vector as Vector -mapLeft :: (a -> b) -> Either a c -> Either b c-mapLeft _ (Right a) = Right a-mapLeft f (Left a) = Left (f a)+-- | As the first argument to the 'Colonnade' type +-- constructor, this indictates that the columnar encoding has +-- a header. This type is isomorphic to 'Identity' but is +-- given a new name to clarify its intent:+--+-- > example :: Colonnade Headed Text Foo+--+-- The term @example@ represents a columnar encoding of @Foo@+-- in which the columns have headings.+newtype Headed a = Headed { getHeaded :: a }+ deriving (Eq,Ord,Functor,Show,Read,Foldable) -foldlMapM :: (Foldable t, Monoid b, Monad m) => (a -> m b) -> t a -> m b-foldlMapM f = foldlM (\b a -> fmap (mappend b) (f a)) mempty+-- | As the first argument to the 'Colonnade' type +-- constructor, this indictates that the columnar encoding does not have +-- a header. This type is isomorphic to 'Proxy' but is +-- given a new name to clarify its intent:+--+-- > example :: Colonnade Headless Text Foo+--+-- The term @example@ represents a columnar encoding of @Foo@+-- in which the columns do not have headings.+data Headless a = Headless+ deriving (Eq,Ord,Functor,Show,Read,Foldable)++instance Contravariant Headless where+ contramap _ Headless = Headless++-- | Encodes a header and a cell.+data OneColonnade h content a = OneColonnade+ { oneColonnadeHead :: !(h content)+ , oneColonnadeEncode :: !(a -> content)+ }++instance Contravariant (OneColonnade h content) where+ contramap f (OneColonnade h e) = OneColonnade h (e . f)++-- | An columnar encoding of @a@. The type variable @h@ determines what+-- is present in each column in the header row. It is typically instantiated+-- to 'Headed' and occasionally to 'Headless'. There is nothing that+-- restricts it to these two types, although they satisfy the majority+-- of use cases. The type variable @c@ is the content type. This can+-- be @Text@, @String@, or @ByteString@. In the companion libraries+-- @reflex-dom-colonnade@ and @yesod-colonnade@, additional types+-- that represent HTML with element attributes are provided that serve+-- as the content type. Presented more visually:+--+-- > +---- Content (Text, ByteString, Html, etc.)+-- > |+-- > v+-- > Colonnade h c a+-- > ^ ^+-- > | |+-- > | +-- Value consumed to build a row+-- > |+-- > +------ Headedness (Headed or Headless)+--+-- Internally, a 'Colonnade' is represented as a 'Vector' of individual+-- column encodings. It is possible to use any collection type with+-- 'Alternative' and 'Foldable' instances. However, 'Vector' was chosen to+-- optimize the data structure for the use case of building the structure+-- once and then folding over it many times. It is recommended that+-- 'Colonnade's are defined at the top-level so that GHC avoids reconstructing+-- them every time they are used.+newtype Colonnade h c a = Colonnade+ { getColonnade :: Vector (OneColonnade h c a)+ } deriving (Monoid)++instance Contravariant (Colonnade h content) where+ contramap f (Colonnade v) = Colonnade+ (Vector.map (contramap f) v)++instance Divisible (Colonnade h content) where+ conquer = Colonnade Vector.empty+ divide f (Colonnade a) (Colonnade b) =+ Colonnade $ (Vector.++)+ (Vector.map (contramap (fst . f)) a)+ (Vector.map (contramap (snd . f)) b)
− src/Colonnade/Types.hs
@@ -1,152 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE GADTs #-}-module Colonnade.Types- ( Colonnade(..)- , Decolonnade(..)- , OneColonnade(..)- , Headed(..)- , Headless(..)- , Indexed(..)- , HeadingErrors(..)- , DecolonnadeCellError(..)- , DecolonnadeRowError(..)- , DecolonnadeCellErrors(..)- , RowError(..)- ) where--import Data.Vector (Vector)-import Data.Functor.Contravariant (Contravariant(..))-import Data.Functor.Contravariant.Divisible (Divisible(..))-import Control.Exception (Exception)-import Data.Typeable (Typeable)-import qualified Data.Vector as Vector---- | This type is isomorphic to 'Identity'.-newtype Headed a = Headed { getHeaded :: a }- deriving (Eq,Ord,Functor,Show,Read,Foldable)---- | This type is isomorphic to 'Proxy'-data Headless a = Headless- deriving (Eq,Ord,Functor,Show,Read,Foldable)--data Indexed f a = Indexed- { indexedIndex :: !Int- , indexedHeading :: !(f a)- } deriving (Eq,Ord,Functor,Show,Read)--data HeadingErrors content = HeadingErrors- { headingErrorsMissing :: Vector content -- ^ headers that were missing- , headingErrorsDuplicate :: Vector (content,Int) -- ^ headers that occurred more than once- } deriving (Show,Read,Eq)--instance (Show content, Typeable content) => Exception (HeadingErrors content)--instance Monoid (HeadingErrors content) where- mempty = HeadingErrors Vector.empty Vector.empty- mappend (HeadingErrors a1 b1) (HeadingErrors a2 b2) = HeadingErrors- (a1 Vector.++ a2) (b1 Vector.++ b2)--data DecolonnadeCellError f content = DecolonnadeCellError- { decodingCellErrorContent :: !content- , decodingCellErrorHeader :: !(Indexed f content)- , decodingCellErrorMessage :: !String- } deriving (Show,Read,Eq)---- instance (Show (f content), Typeable content) => Exception (DecolonnadeError f content)--newtype DecolonnadeCellErrors f content = DecolonnadeCellErrors- { getDecolonnadeCellErrors :: Vector (DecolonnadeCellError f content)- } deriving (Monoid,Show,Read,Eq)---- newtype ParseRowError = ParseRowError String---- TODO: rewrite the instances for this by hand. They--- currently use FlexibleContexts.-data DecolonnadeRowError f content = DecolonnadeRowError- { 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 !(DecolonnadeCellErrors 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 (DecolonnadeErrors f content)--instance Contravariant Headless where- contramap _ Headless = Headless---- | This just actually a specialization of the free applicative.--- Check out @Control.Applicative.Free@ in the @free@ library to--- 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 Decolonnade f content a where- DecolonnadePure :: !a -- function- -> Decolonnade f content a- DecolonnadeAp :: !(f content) -- header- -> !(content -> Either String a) -- decoding function- -> !(Decolonnade f content (a -> b)) -- next decoding- -> Decolonnade f content b--instance Functor (Decolonnade f content) where- fmap f (DecolonnadePure a) = DecolonnadePure (f a)- fmap f (DecolonnadeAp h c apNext) = DecolonnadeAp h c ((f .) <$> apNext)--instance Applicative (Decolonnade f content) where- pure = DecolonnadePure- DecolonnadePure f <*> y = fmap f y- DecolonnadeAp h c y <*> z = DecolonnadeAp h c (flip <$> y <*> z)---- | Encodes a header and a cell.-data OneColonnade f content a = OneColonnade- { oneColonnadeHead :: !(f content)- , oneColonnadeEncode :: !(a -> content)- }--instance Contravariant (OneColonnade f content) where- contramap f (OneColonnade h e) = OneColonnade h (e . f)---- | An columnar encoding of @a@. The type variable @f@ determines what--- is present in each column in the header row. It is typically instantiated--- to 'Headed' and occasionally to 'Headless'. There is nothing that--- restricts it to these two types, although they satisfy the majority--- of use cases. The type variable @c@ is the content type. This can--- be @Text@, @String@, or @ByteString@. In the companion libraries--- @reflex-dom-colonnade@ and @yesod-colonnade@, additional types--- that represent HTML with element attributes are provided that serve--- as the content type.------ Internally, a 'Colonnade' is represented as a 'Vector' of individual--- column encodings. It is possible to use any collection type with--- 'Alternative' and 'Foldable' instances. However, 'Vector' was chosen to--- optimize the data structure for the use case of building the structure--- once and then folding over it many times. It is recommended that--- 'Colonnade's are defined at the top-level so that GHC avoid reconstructing--- them every time they are used.-newtype Colonnade f c a = Colonnade- { getColonnade :: Vector (OneColonnade f c a)- } deriving (Monoid)--instance Contravariant (Colonnade f content) where- contramap f (Colonnade v) = Colonnade- (Vector.map (contramap f) v)--instance Divisible (Colonnade f content) where- conquer = Colonnade Vector.empty- divide f (Colonnade a) (Colonnade b) =- Colonnade $ (Vector.++)- (Vector.map (contramap (fst . f)) a)- (Vector.map (contramap (snd . f)) b)- -- (Vector.map (\(OneEncoding h c) -> (h,c . fst . f)) a)- -- (Vector.map (\(OneEncoding h c) -> (h,c . snd . f)) b)-
test/Main.hs view
@@ -2,5 +2,5 @@ main :: IO () main = doctest- [ "src/Colonnade/Encoding.hs"+ [ "src/Colonnade.hs" ]