colonnade 1.0.0 → 1.1.0
raw patch · 4 files changed
+694/−217 lines, 4 filesdep +profunctorsPVP ok
version bump matches the API change (PVP)
Dependencies added: profunctors
API changes (from Hackage documentation)
- Colonnade: data Headed a
- Colonnade: mapContent :: Functor f => (c1 -> c2) -> Colonnade f c1 a -> Colonnade f c2 a
- 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
+ Colonnade: Base :: Pillar
+ Colonnade: Cap :: !Pillar -> Pillar
+ Colonnade: Headed :: a -> Headed a
+ Colonnade: Headless :: Headless a
+ Colonnade: [FasciaBase] :: !r -> Fascia Base r
+ Colonnade: [FasciaCap] :: !r -> Fascia p r -> Fascia (Cap p) r
+ Colonnade: [getHeaded] :: Headed a -> a
+ Colonnade: asciiCapped :: Foldable f => Cornice p a String -> f a -> String
+ Colonnade: cap :: c -> Colonnade Headed a c -> Cornice (Cap Base) a c
+ Colonnade: data Cornice (p :: Pillar) a c
+ Colonnade: data Fascia (p :: Pillar) r
+ Colonnade: data Pillar
+ Colonnade: mapHeaderContent :: Functor h => (c -> c) -> Colonnade h a c -> Colonnade h a c
+ Colonnade: newtype Headed a
+ Colonnade: recap :: c -> Cornice p a c -> Cornice (Cap p) a c
+ Colonnade.Encode: Base :: Pillar
+ Colonnade.Encode: Cap :: !Pillar -> Pillar
+ Colonnade.Encode: Colonnade :: Vector (OneColonnade h a c) -> Colonnade h a c
+ Colonnade.Encode: Headed :: a -> Headed a
+ Colonnade.Encode: Headless :: Headless a
+ Colonnade.Encode: OneColonnade :: !(h c) -> !(a -> c) -> OneColonnade h a c
+ Colonnade.Encode: OneCornice :: !c -> !(k p a c) -> OneCornice k a c
+ Colonnade.Encode: Sized :: {-# UNPACK #-} !Int -> !(f a) -> Sized f a
+ Colonnade.Encode: [AnnotatedCorniceBase] :: !(Maybe Int) -> !(Colonnade (Sized Headed) a c) -> AnnotatedCornice Base a c
+ Colonnade.Encode: [AnnotatedCorniceCap] :: !(Maybe Int) -> {-# UNPACK #-} !(Vector (OneCornice AnnotatedCornice p a c)) -> AnnotatedCornice (Cap p) a c
+ Colonnade.Encode: [CorniceBase] :: !(Colonnade Headed a c) -> Cornice Base a c
+ Colonnade.Encode: [CorniceCap] :: {-# UNPACK #-} !(Vector (OneCornice Cornice p a c)) -> Cornice (Cap p) a c
+ Colonnade.Encode: [FasciaBase] :: !r -> Fascia Base r
+ Colonnade.Encode: [FasciaCap] :: !r -> Fascia p r -> Fascia (Cap p) r
+ Colonnade.Encode: [getColonnade] :: Colonnade h a c -> Vector (OneColonnade h a c)
+ Colonnade.Encode: [getHeaded] :: Headed a -> a
+ Colonnade.Encode: [oneColonnadeEncode] :: OneColonnade h a c -> !(a -> c)
+ Colonnade.Encode: [oneColonnadeHead] :: OneColonnade h a c -> !(h c)
+ Colonnade.Encode: [oneCorniceBody] :: OneCornice k a c -> !(k p a c)
+ Colonnade.Encode: [oneCorniceHead] :: OneCornice k a c -> !c
+ Colonnade.Encode: [sizedContent] :: Sized f a -> !(f a)
+ Colonnade.Encode: [sizedSize] :: Sized f a -> {-# UNPACK #-} !Int
+ Colonnade.Encode: annotate :: Cornice p a c -> AnnotatedCornice p a c
+ Colonnade.Encode: annotateFinely :: Foldable f => (Int -> Int -> Int) -> (Int -> Int) -> (c -> Int) -> f a -> Cornice p a c -> AnnotatedCornice p a c
+ Colonnade.Encode: class ToEmptyCornice (p :: Pillar)
+ Colonnade.Encode: data AnnotatedCornice (p :: Pillar) a c
+ Colonnade.Encode: data Cornice (p :: Pillar) a c
+ Colonnade.Encode: data Fascia (p :: Pillar) r
+ Colonnade.Encode: data Headless a
+ Colonnade.Encode: data OneColonnade h a c
+ Colonnade.Encode: data OneCornice k (p :: Pillar) a c
+ Colonnade.Encode: data Pillar
+ Colonnade.Encode: data Sized f a
+ Colonnade.Encode: discard :: Cornice p a c -> Colonnade Headed a c
+ Colonnade.Encode: endow :: forall p a c. (c -> c -> c) -> Cornice p a c -> Colonnade Headed a c
+ Colonnade.Encode: headerMonoidalFull :: Monoid m => Colonnade h a c -> (h c -> m) -> m
+ Colonnade.Encode: headersMonoidal :: forall r m c p a. Monoid m => Maybe (Fascia p r, r -> m -> m) -> [(Int -> c -> m, m -> m)] -> AnnotatedCornice p a c -> m
+ Colonnade.Encode: instance Colonnade.Encode.ToEmptyCornice 'Colonnade.Encode.Base
+ Colonnade.Encode: instance Colonnade.Encode.ToEmptyCornice ('Colonnade.Encode.Cap p)
+ Colonnade.Encode: instance Colonnade.Encode.ToEmptyCornice p => GHC.Base.Monoid (Colonnade.Encode.Cornice p a c)
+ Colonnade.Encode: instance Data.Foldable.Foldable Colonnade.Encode.Headed
+ Colonnade.Encode: instance Data.Foldable.Foldable Colonnade.Encode.Headless
+ Colonnade.Encode: instance Data.Foldable.Foldable f => Data.Foldable.Foldable (Colonnade.Encode.Sized f)
+ Colonnade.Encode: instance Data.Functor.Contravariant.Contravariant Colonnade.Encode.Headless
+ Colonnade.Encode: instance Data.Semigroup.Semigroup (Colonnade.Encode.Colonnade h a c)
+ Colonnade.Encode: instance Data.Semigroup.Semigroup (Colonnade.Encode.Cornice p a c)
+ Colonnade.Encode: instance GHC.Base.Functor Colonnade.Encode.Headed
+ Colonnade.Encode: instance GHC.Base.Functor Colonnade.Encode.Headless
+ Colonnade.Encode: instance GHC.Base.Functor f => GHC.Base.Functor (Colonnade.Encode.Sized f)
+ Colonnade.Encode: instance GHC.Base.Functor h => Data.Profunctor.Unsafe.Profunctor (Colonnade.Encode.Colonnade h)
+ Colonnade.Encode: instance GHC.Base.Functor h => Data.Profunctor.Unsafe.Profunctor (Colonnade.Encode.OneColonnade h)
+ Colonnade.Encode: instance GHC.Base.Functor h => GHC.Base.Functor (Colonnade.Encode.Colonnade h a)
+ Colonnade.Encode: instance GHC.Base.Functor h => GHC.Base.Functor (Colonnade.Encode.OneColonnade h a)
+ Colonnade.Encode: instance GHC.Base.Monoid (Colonnade.Encode.Colonnade h a c)
+ Colonnade.Encode: instance GHC.Classes.Eq (Colonnade.Encode.Headless a)
+ Colonnade.Encode: instance GHC.Classes.Eq a => GHC.Classes.Eq (Colonnade.Encode.Headed a)
+ Colonnade.Encode: instance GHC.Classes.Ord (Colonnade.Encode.Headless a)
+ Colonnade.Encode: instance GHC.Classes.Ord a => GHC.Classes.Ord (Colonnade.Encode.Headed a)
+ Colonnade.Encode: instance GHC.Read.Read (Colonnade.Encode.Headless a)
+ Colonnade.Encode: instance GHC.Read.Read a => GHC.Read.Read (Colonnade.Encode.Headed a)
+ Colonnade.Encode: instance GHC.Show.Show (Colonnade.Encode.Headless a)
+ Colonnade.Encode: instance GHC.Show.Show a => GHC.Show.Show (Colonnade.Encode.Headed a)
+ Colonnade.Encode: newtype Colonnade h a c
+ Colonnade.Encode: newtype Headed a
+ Colonnade.Encode: rowMonoidalHeader :: Monoid m => Colonnade h a c -> (h c -> c -> m) -> a -> m
+ Colonnade.Encode: size :: AnnotatedCornice p a c -> Maybe Int
+ Colonnade.Encode: sizeColumns :: (Foldable f, Foldable h) => (c -> Int) -> f a -> Colonnade h a c -> Colonnade (Sized h) a c
+ Colonnade.Encode: toEmptyCornice :: ToEmptyCornice p => Cornice p a c
+ Colonnade.Encode: uncapAnnotated :: forall p a c. AnnotatedCornice p a c -> Colonnade (Sized Headed) a c
- Colonnade: ascii :: Foldable f => Colonnade Headed String a -> f a -> String
+ Colonnade: ascii :: Foldable f => Colonnade Headed a String -> f a -> String
- Colonnade: bool :: f c -> (a -> Bool) -> (a -> c) -> (a -> c) -> Colonnade f c a
+ Colonnade: bool :: f c -> (a -> Bool) -> (a -> c) -> (a -> c) -> Colonnade f a c
- Colonnade: columns :: Foldable g => (b -> a -> c) -> (b -> f c) -> g b -> Colonnade f c a
+ Colonnade: columns :: Foldable g => (b -> a -> c) -> (b -> f c) -> g b -> Colonnade f a c
- Colonnade: data Colonnade h c a
+ Colonnade: data Colonnade h a c
- Colonnade: fromMaybe :: c -> Colonnade f c a -> Colonnade f c (Maybe a)
+ Colonnade: fromMaybe :: c -> Colonnade f a c -> Colonnade f (Maybe a) c
- Colonnade: headed :: c -> (a -> c) -> Colonnade Headed c a
+ Colonnade: headed :: c -> (a -> c) -> Colonnade Headed a c
- Colonnade: headless :: (a -> c) -> Colonnade Headless c a
+ Colonnade: headless :: (a -> c) -> Colonnade Headless a c
- Colonnade: modifyWhen :: (c -> c) -> (a -> Bool) -> Colonnade f c a -> Colonnade f c a
+ Colonnade: modifyWhen :: (c -> c) -> (a -> Bool) -> Colonnade f a c -> Colonnade f a c
- Colonnade: replaceWhen :: c -> (a -> Bool) -> Colonnade f c a -> Colonnade f c a
+ Colonnade: replaceWhen :: c -> (a -> Bool) -> Colonnade f a c -> Colonnade f a c
- Colonnade: singleton :: f c -> (a -> c) -> Colonnade f c a
+ Colonnade: singleton :: h c -> (a -> c) -> Colonnade h a c
- Colonnade.Encode: bothMonadic_ :: Monad m => Colonnade Headed content a -> (content -> content -> m b) -> a -> m ()
+ Colonnade.Encode: bothMonadic_ :: Monad m => Colonnade Headed a c -> (c -> c -> m b) -> a -> m ()
- Colonnade.Encode: header :: (c1 -> c2) -> Colonnade Headed c1 a -> Vector c2
+ Colonnade.Encode: header :: (c1 -> c2) -> Colonnade Headed a c1 -> Vector c2
- Colonnade.Encode: headerMonadic :: (Monad m, Monoid b) => Colonnade Headed content a -> (content -> m b) -> m b
+ Colonnade.Encode: headerMonadic :: (Monad m, Monoid b) => Colonnade Headed a c -> (c -> 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, Monoid b, Foldable h) => Colonnade h a c -> (c -> m b) -> m b
- Colonnade.Encode: headerMonadicGeneral_ :: (Monad m, Foldable h) => Colonnade h content a -> (content -> m b) -> m ()
+ Colonnade.Encode: headerMonadicGeneral_ :: (Monad m, Foldable h) => Colonnade h a c -> (c -> m b) -> m ()
- Colonnade.Encode: headerMonadic_ :: (Monad m) => Colonnade Headed content a -> (content -> m b) -> m ()
+ Colonnade.Encode: headerMonadic_ :: (Monad m) => Colonnade Headed a c -> (c -> m b) -> m ()
- Colonnade.Encode: headerMonoidalGeneral :: (Monoid m, Foldable h) => Colonnade h c a -> (c -> m) -> m
+ Colonnade.Encode: headerMonoidalGeneral :: (Monoid m, Foldable h) => Colonnade h a c -> (c -> m) -> m
- Colonnade.Encode: row :: (c1 -> c2) -> Colonnade f c1 a -> a -> Vector c2
+ Colonnade.Encode: row :: (c1 -> c2) -> Colonnade f a c1 -> a -> Vector c2
- Colonnade.Encode: rowMonadic :: (Monad m, Monoid b) => Colonnade f content a -> (content -> m b) -> a -> m b
+ Colonnade.Encode: rowMonadic :: (Monad m, Monoid b) => Colonnade f a c -> (c -> 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: rowMonadicWith :: (Monad m) => b -> (b -> b -> b) -> Colonnade f a c -> (c -> m b) -> a -> m b
- Colonnade.Encode: rowMonadic_ :: Monad m => Colonnade f content a -> (content -> m b) -> a -> m ()
+ Colonnade.Encode: rowMonadic_ :: Monad m => Colonnade f a c -> (c -> m b) -> a -> m ()
- Colonnade.Encode: rowMonoidal :: Monoid m => Colonnade h c a -> (c -> m) -> a -> m
+ Colonnade.Encode: rowMonoidal :: Monoid m => Colonnade h a c -> (c -> m) -> a -> m
Files
- colonnade.cabal +4/−2
- src/Colonnade.hs +207/−93
- src/Colonnade/Encode.hs +483/−24
- src/Colonnade/Internal.hs +0/−98
colonnade.cabal view
@@ -1,5 +1,5 @@ name: colonnade-version: 1.0.0+version: 1.1.0 synopsis: Generic types and functions for columnar encoding and decoding description: The `colonnade` package provides a way to to talk about@@ -10,6 +10,8 @@ that provides (1) a content type and (2) functions for feeding data into a columnar encoding: .+ * <https://hackage.haskell.org/package/blaze-colonnade blaze-colonnade> for `blaze` html tables+ . * <https://hackage.haskell.org/package/reflex-dom-colonnade reflex-dom-colonnade> for reactive `reflex-dom` tables . * <https://hackage.haskell.org/package/yesod-colonnade yesod-colonnade> for `yesod` widgets@@ -30,13 +32,13 @@ exposed-modules: Colonnade Colonnade.Encode- Colonnade.Internal build-depends: base >= 4.7 && < 5 , contravariant >= 1.2 && < 1.5 , vector >= 0.10 && < 0.13 , text >= 1.0 && < 1.3 , bytestring >= 0.10 && < 0.11+ , profunctors >= 4.0 && < 5.3 default-language: Haskell2010 ghc-options: -Wall
src/Colonnade.hs view
@@ -1,3 +1,7 @@+{-# LANGUAGE DataKinds #-}++{-# OPTIONS_GHC -Wall -fno-warn-unused-imports -fno-warn-unticked-promoted-constructors -Werror #-}+ -- | Build backend-agnostic columnar encodings that can be -- used to visualize tabular data. module Colonnade@@ -5,32 +9,39 @@ -- $setup -- * Types Colonnade- , Headed- , Headless+ , Headed(..)+ , Headless(..) -- * Create , headed , headless , singleton -- * Transform+ , mapHeaderContent , fromMaybe , columns , bool , replaceWhen , modifyWhen- , mapContent+ -- * Cornice+ -- ** Types+ , Cornice+ , Pillar(..)+ , Fascia(..)+ -- ** Create+ , cap+ , recap -- * Ascii Table , ascii+ , asciiCapped ) where -import Colonnade.Internal-import qualified Colonnade.Encode as Encode-import Data.Vector (Vector)+import Colonnade.Encode (Colonnade,Cornice,+ Pillar(..),Fascia(..),Headed(..),Headless(..)) import Data.Foldable-import Data.Monoid (Endo(..)) import Control.Monad-import Data.Functor.Contravariant import qualified Data.Bool import qualified Data.Maybe+import qualified Colonnade.Encode as E import qualified Data.List as List import qualified Data.Vector as Vector @@ -40,7 +51,7 @@ -- used for the remainder of the examples in the docs: -- -- >>> import Data.Monoid (mconcat,(<>))--- >>> import Data.Functor.Contravariant (contramap)+-- >>> import Data.Profunctor (lmap) -- -- The data types we wish to encode are: --@@ -51,7 +62,7 @@ -- One potential columnar encoding of a @Person@ would be: -- -- >>> :{--- let colPerson :: Colonnade Headed String Person+-- let colPerson :: Colonnade Headed Person String -- colPerson = mconcat -- [ headed "Name" name -- , headed "Age" (show . age)@@ -75,16 +86,11 @@ -- 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)--- ]--- :}---+-- >>> colHouse = mconcat [headed "Color" (show . color), headed "Price" (showDollar . price)]+-- >>> :t colHouse+-- colHouse :: Colonnade Headed House [Char] -- >>> let houses = [House Green 170000, House Blue 115000, House Green 150000]--- >>> putStr (ascii encodingHouse houses)+-- >>> putStr (ascii colHouse houses) -- +-------+---------+ -- | Color | Price | -- +-------+---------+@@ -95,17 +101,23 @@ -- | A single column with a header.-headed :: c -> (a -> c) -> Colonnade Headed c a+headed :: c -> (a -> c) -> Colonnade Headed a c headed h = singleton (Headed h) -- | A single column without a header.-headless :: (a -> c) -> Colonnade Headless c a+headless :: (a -> c) -> Colonnade Headless a c 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+singleton :: h c -> (a -> c) -> Colonnade h a c+singleton h = E.Colonnade . Vector.singleton . E.OneColonnade h +-- | Map over the content in the header. This is similar performing 'fmap'+-- on a 'Colonnade' except that the body content is unaffected.+mapHeaderContent :: Functor h => (c -> c) -> Colonnade h a c -> Colonnade h a c+mapHeaderContent f (E.Colonnade v) = + E.Colonnade (Vector.map (\(E.OneColonnade h e) -> E.OneColonnade (fmap f h) e) v)+ -- | 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:@@ -123,10 +135,10 @@ -- the help of 'fromMaybe': -- -- >>> :{--- let colOwners :: Colonnade Headed String (Person,Maybe House)+-- let colOwners :: Colonnade Headed (Person,Maybe House) String -- colOwners = mconcat--- [ contramap fst colPerson--- , contramap snd (fromMaybe "" encodingHouse)+-- [ lmap fst colPerson+-- , lmap snd (fromMaybe "" colHouse) -- ] -- :} --@@ -138,9 +150,9 @@ -- | 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)+fromMaybe :: c -> Colonnade f a c -> Colonnade f (Maybe a) c+fromMaybe c (E.Colonnade v) = E.Colonnade $ flip Vector.map v $+ \(E.OneColonnade h encode) -> E.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@@ -150,10 +162,10 @@ -- >>> 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+-- encColor :: Colonnade Headed Color [Char]+-- >>> let encHouse = headed "Price" (showDollar . price) <> lmap color encColor -- >>> :t encHouse--- encHouse :: Colonnade Headed [Char] House+-- encHouse :: Colonnade Headed House [Char] -- >>> putStr (ascii encHouse houses) -- +---------+-----+-------+------+ -- | Price | Red | Green | Blue |@@ -166,10 +178,10 @@ => (b -> a -> c) -- ^ Cell content function -> (b -> f c) -- ^ Header content function -> g b -- ^ Basis for column encodings- -> Colonnade f c a+ -> Colonnade f a c columns getCell getHeader = id- . Colonnade- . Vector.map (\b -> OneColonnade (getHeader b) (getCell b))+ . E.Colonnade+ . Vector.map (\b -> E.OneColonnade (getHeader b) (getCell b)) . Vector.fromList . toList @@ -178,7 +190,7 @@ -> (a -> Bool) -- ^ Predicate -> (a -> c) -- ^ Contents when predicate is false -> (a -> c) -- ^ Contents when predicate is true- -> Colonnade f c a+ -> Colonnade f a c bool h p onTrue onFalse = singleton h (Data.Bool.bool <$> onFalse <*> onTrue <*> p) -- | Modify the contents of cells in rows whose values satisfy the@@ -188,11 +200,11 @@ 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+ -> Colonnade f a c -- ^ Original 'Colonnade'+ -> Colonnade f a c+modifyWhen changeContent p (E.Colonnade v) = E.Colonnade ( Vector.map- (\(OneColonnade h encode) -> OneColonnade h $ \a ->+ (\(E.OneColonnade h encode) -> E.OneColonnade h $ \a -> if p a then changeContent (encode a) else encode a ) v )@@ -202,77 +214,179 @@ replaceWhen :: c -- ^ New content -> (a -> Bool) -- ^ Row predicate- -> Colonnade f c a -- ^ Original 'Colonnade'- -> Colonnade f c a-replaceWhen newContent p (Colonnade v) = Colonnade+ -> Colonnade f a c -- ^ Original 'Colonnade'+ -> Colonnade f a c+replaceWhen newContent p (E.Colonnade v) = E.Colonnade ( Vector.map- (\(OneColonnade h encode) -> OneColonnade h $ \a ->+ (\(E.OneColonnade h encode) -> E.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+-- | Augment a 'Colonnade' with a header spans over all of the+-- existing headers. This is best demonstrated by example. +-- Let\'s consider how we might encode a pairing of the people +-- and houses from the initial example:+-- +-- >>> let personHomePairs = zip people houses+-- >>> let colPersonFst = lmap fst colPerson+-- >>> let colHouseSnd = lmap snd colHouse+-- >>> putStr (ascii (colPersonFst <> colHouseSnd) personHomePairs)+-- +-------+-----+-------+---------++-- | Name | Age | Color | Price |+-- +-------+-----+-------+---------++-- | David | 63 | Green | $170000 |+-- | Ava | 34 | Blue | $115000 |+-- | Sonia | 12 | Green | $150000 |+-- +-------+-----+-------+---------++-- +-- This tabular encoding leaves something to be desired. The heading+-- not indicate that the name and age refer to a person and that+-- the color and price refer to a house. Without reaching for 'Cornice',+-- we can still improve this situation with 'mapHeaderContent':+--+-- >>> let colPersonFst' = mapHeaderContent ("Person " ++) colPersonFst+-- >>> let colHouseSnd' = mapHeaderContent ("House " ++) colHouseSnd+-- >>> putStr (ascii (colPersonFst' <> colHouseSnd') personHomePairs)+-- +-------------+------------+-------------+-------------++-- | Person Name | Person Age | House Color | House Price |+-- +-------------+------------+-------------+-------------++-- | David | 63 | Green | $170000 |+-- | Ava | 34 | Blue | $115000 |+-- | Sonia | 12 | Green | $150000 |+-- +-------------+------------+-------------+-------------++--+-- This is much better, but for longer tables, the redundancy+-- of prefixing many column headers can become annoying. The solution+-- that a 'Cornice' offers is to nest headers:+-- +-- >>> let cor = mconcat [cap "Person" colPersonFst, cap "House" colHouseSnd]+-- >>> :t cor+-- cor :: Cornice ('Cap 'Base) (Person, House) [Char]+-- >>> putStr (asciiCapped cor personHomePairs)+-- +-------------+-----------------++-- | Person | House |+-- +-------+-----+-------+---------++-- | Name | Age | Color | Price |+-- +-------+-----+-------+---------++-- | David | 63 | Green | $170000 |+-- | Ava | 34 | Blue | $115000 |+-- | Sonia | 12 | Green | $150000 |+-- +-------+-----+-------+---------++-- +cap :: c -> Colonnade Headed a c -> Cornice (Cap Base) a c+cap h = E.CorniceCap . Vector.singleton . E.OneCornice h . E.CorniceBase +-- | Add another cap to a cornice. There is no limit to how many times+-- this can be applied:+-- +-- >>> data Day = Weekday | Weekend deriving (Show)+-- >>> :{+-- let cost :: Int -> Day -> String+-- cost base w = case w of+-- Weekday -> showDollar base+-- Weekend -> showDollar (base + 1)+-- colStandard = foldMap (\c -> headed c (cost 8)) ["Yt","Ad","Sr"]+-- colSpecial = mconcat [headed "Stud" (cost 6), headed "Mltry" (cost 7)]+-- corStatus = mconcat+-- [ cap "Standard" colStandard+-- , cap "Special" colSpecial+-- ] +-- corShowtime = mconcat+-- [ recap "" (cap "" (headed "Day" show))+-- , foldMap (\c -> recap c corStatus) ["Matinee","Evening"]+-- ]+-- :}+--+-- >>> putStr (asciiCapped corShowtime [Weekday,Weekend])+-- +---------+-----------------------------+-----------------------------++-- | | Matinee | Evening |+-- +---------+--------------+--------------+--------------+--------------++-- | | Standard | Special | Standard | Special |+-- +---------+----+----+----+------+-------+----+----+----+------+-------++-- | Day | Yt | Ad | Sr | Stud | Mltry | Yt | Ad | Sr | Stud | Mltry |+-- +---------+----+----+----+------+-------+----+----+----+------+-------++-- | Weekday | $8 | $8 | $8 | $6 | $7 | $8 | $8 | $8 | $6 | $7 |+-- | Weekend | $9 | $9 | $9 | $7 | $8 | $9 | $9 | $9 | $7 | $8 |+-- +---------+----+----+----+------+-------+----+----+----+------+-------++recap :: c -> Cornice p a c -> Cornice (Cap p) a c+recap h cor = E.CorniceCap (Vector.singleton (E.OneCornice h cor))++asciiCapped :: Foldable f+ => Cornice p a String -- ^ columnar encoding+ -> f a -- ^ rows+ -> String+asciiCapped cor xs =+ let annCor = E.annotateFinely (\x y -> x + y + 3) id + List.length xs cor+ sizedCol = E.uncapAnnotated annCor+ in E.headersMonoidal+ Nothing + [ (\sz _ -> hyphens (sz + 2) ++ "+", \s -> "+" ++ s ++ "\n")+ , (\sz c -> " " ++ rightPad sz ' ' c ++ " |", \s -> "|" ++ s ++ "\n")+ ] annCor ++ asciiBody sizedCol xs+ + -- | 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+-- try out @colonnade@ in ghci and so that @doctest@ can verify example -- code in the haddocks. ascii :: Foldable f- => Colonnade Headed String a -- ^ columnar encoding+ => Colonnade Headed a String -- ^ 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+ascii col xs = + let sizedCol = E.sizeColumns List.length xs col+ divider = concat+ [ "+" + , E.headerMonoidalFull sizedCol + (\(E.Sized sz _) -> hyphens (sz + 2) ++ "+")+ , "\n" ]- 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"-+ in List.concat+ [ divider+ , concat+ [ "|"+ , E.headerMonoidalFull sizedCol+ (\(E.Sized s (Headed h)) -> " " ++ rightPad s ' ' h ++ " |")+ , "\n"+ ]+ , asciiBody sizedCol xs+ ] --- 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+asciiBody :: Foldable f+ => Colonnade (E.Sized Headed) a String+ -> f a+ -> String+asciiBody sizedCol xs =+ let divider = concat+ [ "+" + , E.headerMonoidalFull sizedCol + (\(E.Sized sz _) -> hyphens (sz + 2) ++ "+")+ , "\n"+ ]+ rowContents = foldMap+ (\x -> concat+ [ "|"+ , E.rowMonoidalHeader + sizedCol+ (\(E.Sized sz _) c -> " " ++ rightPad sz ' ' c ++ " |")+ x+ , "\n"+ ]+ ) xs+ in List.concat+ [ divider+ , rowContents+ , divider+ ]+ +hyphens :: Int -> String+hyphens n = List.replicate n '-' 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 --
src/Colonnade/Encode.hs view
@@ -1,3 +1,15 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}++{-# OPTIONS_HADDOCK not-home #-}+{-# OPTIONS_GHC -Wall -fno-warn-unused-imports -fno-warn-unticked-promoted-constructors -Werror #-}+ -- | 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, @@ -25,35 +37,77 @@ -- an @a@ value since a value is not needed to build a header. -- module Colonnade.Encode- ( row+ ( -- * Colonnade+ -- ** Types+ Colonnade(..)+ , OneColonnade(..)+ , Headed(..)+ , Headless(..)+ , Sized(..)+ -- ** Row+ , row , rowMonadic , rowMonadic_ , rowMonadicWith , rowMonoidal+ , rowMonoidalHeader+ -- ** Header , header , headerMonadic , headerMonadic_ , headerMonadicGeneral , headerMonadicGeneral_ , headerMonoidalGeneral+ , headerMonoidalFull+ -- ** Other , bothMonadic_+ , sizeColumns+ -- * Cornice+ -- ** Types+ , Cornice(..)+ , AnnotatedCornice(..)+ , OneCornice(..)+ , Pillar(..)+ , ToEmptyCornice(..)+ , Fascia(..)+ -- ** Encoding+ , annotate+ , annotateFinely+ , size+ , endow+ , discard+ , headersMonoidal+ , uncapAnnotated ) where -import Colonnade.Internal import Data.Vector (Vector) import Data.Foldable+import Control.Monad.ST (ST,runST)+import Data.Monoid+import Data.Functor.Contravariant (Contravariant(..))+import Data.Profunctor (Profunctor(..))+import Data.Semigroup (Semigroup)+import Data.List.NonEmpty (NonEmpty((:|)))+import Data.Foldable (toList)+import qualified Data.Semigroup as Semigroup import qualified Data.Vector as Vector+import qualified Data.Vector as V+import qualified Data.Vector.Unboxed.Mutable as MVU+import qualified Data.Vector.Unboxed as VU+import qualified Data.Vector as V+import qualified Data.Vector as Vector+import qualified Data.Vector.Generic as GV -- | 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 :: (c1 -> c2) -> Colonnade f a c1 -> 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)+ => Colonnade Headed a c+ -> (c -> c -> m b) -> a -> m () bothMonadic_ (Colonnade v) g a =@@ -61,8 +115,8 @@ rowMonadic :: (Monad m, Monoid b)- => Colonnade f content a- -> (content -> m b)+ => Colonnade f a c+ -> (c -> m b) -> a -> m b rowMonadic (Colonnade v) g a =@@ -71,8 +125,8 @@ rowMonadic_ :: Monad m- => Colonnade f content a- -> (content -> m b)+ => Colonnade f a c+ -> (c -> m b) -> a -> m () rowMonadic_ (Colonnade v) g a =@@ -80,19 +134,75 @@ rowMonoidal :: Monoid m- => Colonnade h c a+ => Colonnade h a c -> (c -> m) -> a -> m rowMonoidal (Colonnade v) g a =- foldMap (\e -> g (oneColonnadeEncode e a)) v+ foldMap (\(OneColonnade _ encode) -> g (encode a)) v +rowMonoidalHeader ::+ Monoid m+ => Colonnade h a c+ -> (h c -> c -> m)+ -> a+ -> m+rowMonoidalHeader (Colonnade v) g a =+ foldMap (\(OneColonnade h encode) -> g h (encode a)) v++rowUpdateSize ::+ (c -> Int) -- ^ Get size from content+ -> MutableSizedColonnade s h a c+ -> a+ -> ST s ()+rowUpdateSize toSize (MutableSizedColonnade v mv) a = if MVU.length mv /= V.length v+ then error "rowMonoidalSize: vector sizes mismatched"+ else V.imapM_ (\ix (OneColonnade _ encode) ->+ MVU.modify mv (\oldSize -> max oldSize (toSize (encode a))) ix+ ) v++headerUpdateSize :: Foldable h+ => (c -> Int) -- ^ Get size from content+ -> MutableSizedColonnade s h a c+ -> ST s ()+headerUpdateSize toSize (MutableSizedColonnade v mv) = if MVU.length mv /= V.length v+ then error "rowMonoidalSize: vector sizes mismatched"+ else V.imapM_ (\ix (OneColonnade h _) -> + MVU.modify mv (\oldSize -> max oldSize (foldl' (\sz c -> max sz (toSize c)) 0 h)) ix+ ) v++sizeColumns :: (Foldable f, Foldable h)+ => (c -> Int) -- ^ Get size from content+ -> f a+ -> Colonnade h a c+ -> Colonnade (Sized h) a c+sizeColumns toSize rows colonnade = runST $ do+ mcol <- newMutableSizedColonnade colonnade+ headerUpdateSize toSize mcol + mapM_ (rowUpdateSize toSize mcol) rows+ freezeMutableSizedColonnade mcol++newMutableSizedColonnade :: Colonnade h a c -> ST s (MutableSizedColonnade s h a c)+newMutableSizedColonnade (Colonnade v) = do+ mv <- MVU.replicate (V.length v) 0+ return (MutableSizedColonnade v mv)++freezeMutableSizedColonnade :: MutableSizedColonnade s h a c -> ST s (Colonnade (Sized h) a c)+freezeMutableSizedColonnade (MutableSizedColonnade v mv) =+ if MVU.length mv /= V.length v+ then error "rowMonoidalSize: vector sizes mismatched"+ else do+ sizeVec <- VU.freeze mv+ return $ Colonnade+ $ V.map (\(OneColonnade h enc,sz) -> OneColonnade (Sized sz h) enc)+ $ V.zip v (GV.convert sizeVec)+ rowMonadicWith :: (Monad m) => b -> (b -> b -> b)- -> Colonnade f content a- -> (content -> m b)+ -> Colonnade f a c+ -> (c -> m b) -> a -> m b rowMonadicWith bempty bappend (Colonnade v) g a =@@ -101,15 +211,15 @@ return (bappend bl br) ) bempty v -header :: (c1 -> c2) -> Colonnade Headed c1 a -> Vector c2+header :: (c1 -> c2) -> Colonnade Headed a c1 -> 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)+ => Colonnade h a c+ -> (c -> m b) -> m b headerMonadicGeneral (Colonnade v) g = id $ fmap (mconcat . Vector.toList)@@ -117,36 +227,385 @@ headerMonadic :: (Monad m, Monoid b)- => Colonnade Headed content a- -> (content -> m b)+ => Colonnade Headed a c+ -> (c -> 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)+ => Colonnade h a c+ -> (c -> m b) -> m () headerMonadicGeneral_ (Colonnade v) g = Vector.mapM_ (mapM_ g . oneColonnadeHead) v headerMonoidalGeneral :: (Monoid m, Foldable h)- => Colonnade h c a+ => Colonnade h a c -> (c -> m) -> m headerMonoidalGeneral (Colonnade v) g = foldMap (foldMap g . oneColonnadeHead) v- +headerMonoidalFull ::+ Monoid m+ => Colonnade h a c+ -> (h c -> m)+ -> m+headerMonoidalFull (Colonnade v) g = foldMap (g . oneColonnadeHead) v+ headerMonadic_ :: (Monad m)- => Colonnade Headed content a- -> (content -> m b)+ => Colonnade Headed a c+ -> (c -> 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++discard :: Cornice p a c -> Colonnade Headed a c+discard = go where+ go :: forall p a c. Cornice p a c -> Colonnade Headed a c+ go (CorniceBase c) = c+ go (CorniceCap children) = Colonnade (getColonnade . go . oneCorniceBody =<< children)++endow :: forall p a c. (c -> c -> c) -> Cornice p a c -> Colonnade Headed a c+endow f x = case x of+ CorniceBase colonnade -> colonnade+ CorniceCap v -> Colonnade (V.concatMap (\(OneCornice h b) -> go h b) v)+ where+ go :: forall p'. c -> Cornice p' a c -> Vector (OneColonnade Headed a c)+ go c (CorniceBase (Colonnade v)) = V.map (mapOneColonnadeHeader (f c)) v+ go c (CorniceCap v) = V.concatMap (\(OneCornice h b) -> go (f c h) b) v++uncapAnnotated :: forall p a c. AnnotatedCornice p a c -> Colonnade (Sized Headed) a c+uncapAnnotated x = case x of+ AnnotatedCorniceBase _ colonnade -> colonnade+ AnnotatedCorniceCap _ v -> Colonnade (V.concatMap (\(OneCornice _ b) -> go b) v)+ where+ go :: forall p'. AnnotatedCornice p' a c -> Vector (OneColonnade (Sized Headed) a c)+ go (AnnotatedCorniceBase _ (Colonnade v)) = v+ go (AnnotatedCorniceCap _ v) = V.concatMap (\(OneCornice _ b) -> go b) v++annotate :: Cornice p a c -> AnnotatedCornice p a c+annotate = go where+ go :: forall p a c. Cornice p a c -> AnnotatedCornice p a c+ go (CorniceBase c) = let len = V.length (getColonnade c) in+ AnnotatedCorniceBase+ (if len > 0 then (Just len) else Nothing)+ (mapHeadedness (Sized 1) c)+ go (CorniceCap children) =+ let annChildren = fmap (mapOneCorniceBody go) children+ in AnnotatedCorniceCap + ( ( ( V.foldl' (combineJustInt (+))+ ) Nothing . V.map (size . oneCorniceBody)+ ) annChildren+ )+ annChildren++combineJustInt :: (Int -> Int -> Int) -> Maybe Int -> Maybe Int -> Maybe Int+combineJustInt f acc el = case acc of+ Nothing -> case el of + Nothing -> Nothing+ Just i -> Just i+ Just i -> case el of+ Nothing -> Just i+ Just j -> Just (f i j)++mapJustInt :: (Int -> Int) -> Maybe Int -> Maybe Int+mapJustInt _ Nothing = Nothing+mapJustInt f (Just i) = Just (f i)++annotateFinely :: Foldable f+ => (Int -> Int -> Int) -- ^ fold function+ -> (Int -> Int) -- ^ finalize+ -> (c -> Int) -- ^ Get size from content+ -> f a+ -> Cornice p a c + -> AnnotatedCornice p a c+annotateFinely g finish toSize xs cornice = runST $ do+ m <- newMutableSizedCornice cornice+ sizeColonnades toSize xs m+ freezeMutableSizedCornice g finish m++sizeColonnades :: forall f s p a c.+ Foldable f+ => (c -> Int) -- ^ Get size from content+ -> f a+ -> MutableSizedCornice s p a c + -> ST s ()+sizeColonnades toSize xs cornice = do+ goHeader cornice+ mapM_ (goRow cornice) xs + where+ goRow :: forall p'. MutableSizedCornice s p' a c -> a -> ST s ()+ goRow (MutableSizedCorniceBase c) a = rowUpdateSize toSize c a+ goRow (MutableSizedCorniceCap children) a = mapM_ (flip goRow a . oneCorniceBody) children+ goHeader :: forall p'. MutableSizedCornice s p' a c -> ST s ()+ goHeader (MutableSizedCorniceBase c) = headerUpdateSize toSize c+ goHeader (MutableSizedCorniceCap children) = mapM_ (goHeader . oneCorniceBody) children+ +freezeMutableSizedCornice :: forall s p a c.+ (Int -> Int -> Int) -- ^ fold function+ -> (Int -> Int) -- ^ finalize+ -> MutableSizedCornice s p a c + -> ST s (AnnotatedCornice p a c)+freezeMutableSizedCornice step finish = go+ where+ go :: forall p' a' c'. MutableSizedCornice s p' a' c' -> ST s (AnnotatedCornice p' a' c')+ go (MutableSizedCorniceBase msc) = do+ szCol <- freezeMutableSizedColonnade msc+ let sz = + ( mapJustInt finish + . V.foldl' (combineJustInt step) Nothing + . V.map (Just . sizedSize . oneColonnadeHead)+ ) (getColonnade szCol)+ return (AnnotatedCorniceBase sz szCol)+ go (MutableSizedCorniceCap v1) = do+ v2 <- V.mapM (traverseOneCorniceBody go) v1+ let sz = + ( mapJustInt finish + . V.foldl' (combineJustInt step) Nothing + . V.map (size . oneCorniceBody)+ ) v2+ return $ AnnotatedCorniceCap sz v2++newMutableSizedCornice :: forall s p a c.+ Cornice p a c + -> ST s (MutableSizedCornice s p a c)+newMutableSizedCornice = go where+ go :: forall p'. Cornice p' a c -> ST s (MutableSizedCornice s p' a c)+ go (CorniceBase c) = fmap MutableSizedCorniceBase (newMutableSizedColonnade c)+ go (CorniceCap v) = fmap MutableSizedCorniceCap (V.mapM (traverseOneCorniceBody go) v)+ +traverseOneCorniceBody :: Monad m => (k p a c -> m (j p a c)) -> OneCornice k p a c -> m (OneCornice j p a c)+traverseOneCorniceBody f (OneCornice h b) = fmap (OneCornice h) (f b)++mapHeadedness :: (forall x. h x -> h' x) -> Colonnade h a c -> Colonnade h' a c+mapHeadedness f (Colonnade v) = + Colonnade (V.map (\(OneColonnade h c) -> OneColonnade (f h) c) v)+++-- | This is an O(1) operation, sort of+size :: AnnotatedCornice p a c -> Maybe Int+size x = case x of+ AnnotatedCorniceBase m _ -> m+ AnnotatedCorniceCap sz _ -> sz++mapOneCorniceBody :: (forall p' a' c'. k p' a' c' -> j p' a' c') -> OneCornice k p a c -> OneCornice j p a c+mapOneCorniceBody f (OneCornice h b) = OneCornice h (f b)++mapOneColonnadeHeader :: Functor h => (c -> c) -> OneColonnade h a c -> OneColonnade h a c+mapOneColonnadeHeader f (OneColonnade h b) = OneColonnade (fmap f h) b++headersMonoidal :: forall r m c p a.+ Monoid m+ => Maybe (Fascia p r, r -> m -> m) -- ^ Apply the Fascia header row content+ -> [(Int -> c -> m, m -> m)] -- ^ Build content from cell content and size+ -> AnnotatedCornice p a c+ -> m+headersMonoidal wrapRow fromContentList = go wrapRow+ where+ go :: forall p'. Maybe (Fascia p' r, r -> m -> m) -> AnnotatedCornice p' a c -> m+ go ef (AnnotatedCorniceBase _ (Colonnade v)) = + let g :: m -> m+ g m = case ef of+ Nothing -> m+ Just (FasciaBase r, f) -> f r m+ in g $ foldMap (\(fromContent,wrap) -> wrap + (foldMap (\(OneColonnade (Sized sz (Headed h)) _) -> + (fromContent sz h)) v)) fromContentList+ go ef (AnnotatedCorniceCap _ v) = + let g :: m -> m+ g m = case ef of+ Nothing -> m+ Just (FasciaCap r _, f) -> f r m+ in g (foldMap (\(fromContent,wrap) -> wrap (foldMap (\(OneCornice h b) -> + (case size b of+ Nothing -> mempty+ Just sz -> fromContent sz h)+ ) v)) fromContentList)+ <> case ef of+ Nothing -> case flattenAnnotated v of+ Nothing -> mempty+ Just annCoreNext -> go Nothing annCoreNext+ Just (FasciaCap _ fn, f) -> case flattenAnnotated v of+ Nothing -> mempty+ Just annCoreNext -> go (Just (fn,f)) annCoreNext++flattenAnnotated :: Vector (OneCornice AnnotatedCornice p a c) -> Maybe (AnnotatedCornice p a c)+flattenAnnotated v = case v V.!? 0 of + Nothing -> Nothing+ Just (OneCornice _ x) -> Just $ case x of+ AnnotatedCorniceBase m _ -> flattenAnnotatedBase m v+ AnnotatedCorniceCap m _ -> flattenAnnotatedCap m v++flattenAnnotatedBase :: Maybe Int -> Vector (OneCornice AnnotatedCornice Base a c) -> AnnotatedCornice Base a c+flattenAnnotatedBase msz = AnnotatedCorniceBase msz+ . Colonnade + . V.concatMap + (\(OneCornice _ (AnnotatedCorniceBase _ (Colonnade v))) -> v)++flattenAnnotatedCap :: Maybe Int -> Vector (OneCornice AnnotatedCornice (Cap p) a c) -> AnnotatedCornice (Cap p) a c+flattenAnnotatedCap m = AnnotatedCorniceCap m . V.concatMap getTheVector++getTheVector :: OneCornice AnnotatedCornice (Cap p) a c -> Vector (OneCornice AnnotatedCornice p a c)+getTheVector (OneCornice _ (AnnotatedCorniceCap _ v)) = v++data MutableSizedCornice s (p :: Pillar) a c where+ MutableSizedCorniceBase :: + {-# UNPACK #-} !(MutableSizedColonnade s Headed a c) + -> MutableSizedCornice s Base a c+ MutableSizedCorniceCap :: + {-# UNPACK #-} !(Vector (OneCornice (MutableSizedCornice s) p a c))+ -> MutableSizedCornice s (Cap p) a c++data MutableSizedColonnade s h a c = MutableSizedColonnade+ { _mutableSizedColonnadeColumns :: {-# UNPACK #-} !(Vector (OneColonnade h a c))+ , _mutableSizedColonnadeSizes :: {-# UNPACK #-} !(MVU.STVector s Int)+ }++-- | 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 Foo Text+--+-- 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)++-- | 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 Foo Text+--+-- 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)++data Sized f a = Sized+ { sizedSize :: {-# UNPACK #-} !Int+ , sizedContent :: !(f a)+ } deriving (Functor, Foldable)++instance Contravariant Headless where+ contramap _ Headless = Headless++-- | Encodes a header and a cell.+data OneColonnade h a c = OneColonnade+ { oneColonnadeHead :: !(h c)+ , oneColonnadeEncode :: !(a -> c)+ } deriving (Functor)++instance Functor h => Profunctor (OneColonnade h) where+ rmap = fmap+ lmap 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:+--+-- > +---- Value consumed to build a row+-- > |+-- > v+-- > Colonnade h a c+-- > ^ ^+-- > | |+-- > | +-- Content (Text, ByteString, Html, etc.)+-- > |+-- > +------ 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 a c = Colonnade+ { getColonnade :: Vector (OneColonnade h a c)+ } deriving (Monoid,Functor)++instance Functor h => Profunctor (Colonnade h) where+ rmap = fmap+ lmap f (Colonnade v) = Colonnade (Vector.map (lmap f) v)++instance Semigroup (Colonnade h a c) where+ Colonnade a <> Colonnade b = Colonnade (a Vector.++ b)+ sconcat xs = Colonnade (vectorConcatNE (fmap getColonnade xs))++-- | Isomorphic to the natural numbers. Only the promoted version of+-- this type is used.+data Pillar = Cap !Pillar | Base++class ToEmptyCornice (p :: Pillar) where+ toEmptyCornice :: Cornice p a c++instance ToEmptyCornice Base where+ toEmptyCornice = CorniceBase mempty++instance ToEmptyCornice (Cap p) where+ toEmptyCornice = CorniceCap Vector.empty++data Fascia (p :: Pillar) r where+ FasciaBase :: !r -> Fascia Base r+ FasciaCap :: !r -> Fascia p r -> Fascia (Cap p) r++data OneCornice k (p :: Pillar) a c = OneCornice+ { oneCorniceHead :: !c+ , oneCorniceBody :: !(k p a c)+ }++data Cornice (p :: Pillar) a c where+ CorniceBase :: !(Colonnade Headed a c) -> Cornice Base a c+ CorniceCap :: {-# UNPACK #-} !(Vector (OneCornice Cornice p a c)) -> Cornice (Cap p) a c++instance Semigroup (Cornice p a c) where+ CorniceBase a <> CorniceBase b = CorniceBase (mappend a b)+ CorniceCap a <> CorniceCap b = CorniceCap (a Vector.++ b)+ sconcat xs@(x :| _) = case x of+ CorniceBase _ -> CorniceBase (Colonnade (vectorConcatNE (fmap (getColonnade . getCorniceBase) xs)))+ CorniceCap _ -> CorniceCap (vectorConcatNE (fmap getCorniceCap xs))++instance ToEmptyCornice p => Monoid (Cornice p a c) where+ mempty = toEmptyCornice+ mappend = (Semigroup.<>)+ mconcat xs1 = case xs1 of+ [] -> toEmptyCornice+ x : xs2 -> Semigroup.sconcat (x :| xs2)++getCorniceBase :: Cornice Base a c -> Colonnade Headed a c+getCorniceBase (CorniceBase c) = c++getCorniceCap :: Cornice (Cap p) a c -> Vector (OneCornice Cornice p a c)+getCorniceCap (CorniceCap c) = c++data AnnotatedCornice (p :: Pillar) a c where+ AnnotatedCorniceBase :: !(Maybe Int) -> !(Colonnade (Sized Headed) a c) -> AnnotatedCornice Base a c+ AnnotatedCorniceCap :: + !(Maybe Int)+ -> {-# UNPACK #-} !(Vector (OneCornice AnnotatedCornice p a c))+ -> AnnotatedCornice (Cap p) a c++-- data MaybeInt = JustInt {-# UNPACK #-} !Int | NothingInt++-- | This is provided with vector-0.12, but we include a copy here +-- for compatibility.+vectorConcatNE :: NonEmpty (Vector a) -> Vector a+vectorConcatNE = Vector.concat . toList
− src/Colonnade/Internal.hs
@@ -1,98 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--{-# OPTIONS_HADDOCK not-home #-}--module Colonnade.Internal- ( Colonnade(..)- , OneColonnade(..)- , Headed(..)- , Headless(..)- ) 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---- | 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)---- | 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)-