rainbox 0.4.0.4 → 0.26.0.0
raw patch · 33 files changed
Files
- LICENSE +1/−1
- README.md +8/−31
- changelog +4/−8
- current-versions.txt +0/−60
- lib/Rainbox.hs +72/−144
- lib/Rainbox/Array2d.hs +0/−216
- lib/Rainbox/BicolorTable.hs +185/−0
- lib/Rainbox/Box.hs +0/−341
- lib/Rainbox/Box/Primitives.hs +0/−559
- lib/Rainbox/Core.hs +733/−0
- lib/Rainbox/Reader.hs +0/−293
- lib/Rainbox/Tutorial.hs +570/−0
- lib/Rainbox/Tutorial.lhs +0/−174
- minimum-versions.txt +0/−60
- package.yaml +56/−0
- rainbox.cabal +85/−137
- stack.yaml +3/−0
- sunlight-test.hs +0/−15
- test/Rainbow/Instances.hs +104/−0
- test/Rainbox/Array2dTests.hs +0/−389
- test/Rainbox/Box/PrimitivesTests.hs +0/−222
- test/Rainbox/BoxDir.hs +0/−7
- test/Rainbox/BoxTests.hs +0/−207
- test/Rainbox/Instances.hs +65/−0
- test/Rainbox/ReaderTests.hs +0/−133
- test/RainboxDir.hs +0/−13
- test/RainboxTests.hs +0/−7
- test/Test/Rainbow/Generators.hs +0/−82
- test/Visual.hs +0/−154
- test/rainbox-mosaic.hs +0/−20
- test/rainbox-properties.hs +157/−0
- test/rainbox-test.hs +0/−11
- test/rainbox-visual.hs +87/−5
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2014, Omari Norman+Copyright (c) 2014 - 2020 Omari Norman All rights reserved.
README.md view
@@ -4,46 +4,23 @@ Provides pretty printing of boxes in two dimensions. Rainbox is useful for console programs that need to format tabular data. +On Hackage+==========++https://hackage.haskell.org/package/rainbox+ Documentation ============= In addition to the Haddock documentation, a tutorial is available in-[the Rainbox.Tutorial module](lib/Rainbox/Tutorial.lhs). This-module is best read in your text editor or through the Github web-interface, as it is written in literate Haskell, which HsColour does-not fare so well with.+[the Rainbox.Tutorial module](lib/Rainbox/Tutorial.hs). Portability =========== There's nothing unportable in Rainbox; however, it does use-[Rainbow](http://hackage.haskell.org/package/rainbow) which works-only on UNIX-like systems because it uses the UNIX terminfo library.-I only develop for UNIX-like systems because they are the only ones-I use.--Tests-=====--You can simply use "cabal test". However, I recommend that you do:-- cabal configure --enable-tests- cabal build- dist/build/rainbox-test/rainbox-test- dist/build/rainbox-visual/rainbox-visual--The last test, `rainbox-visual`, relies on you to examine the output-and make sure it looks correct.--Tests are also run on Travis:--[](https://travis-ci.org/massysett/rainbox)--and although you can see the output of `rainbox-visual` there, it's-not formatted quite right on Travis.--At this time, Rainbox is verified to work with GHC versions 7.4.1,-7.6.3, and 7.8.2.+[Rainbow](http://hackage.haskell.org/package/rainbow) which is only+tested on UNIX-like systems. License =======
changelog view
@@ -1,11 +1,7 @@-0.4.0.2-- * lower array dependendy to 0.4.0.0; this is the version that shipped- with GHC 7.4.1+v0.22.0.0 -0.4.0.0+* Changes to conform to rainbow-0.32.0.0 - * update for newer version of rainbow, where each Chunk has a list of Text- rather than a single Text+v0.20.0.0 - * test with GHC 7.8.2+* Made updates to monoid for base-4.11
− current-versions.txt
@@ -1,60 +0,0 @@-This package was tested to work with these dependency-versions and compiler version.-These are the default versions fetched by cabal install.-Tested as of: 2014-06-14 02:31:26.107979 UTC-Path to compiler: ghc-7.8.2-Compiler description: 7.8.2--/opt/ghc/7.8.2/lib/ghc-7.8.2/package.conf.d:- Cabal-1.18.1.3- array-0.5.0.0- base-4.7.0.0- bin-package-db-0.0.0.0- binary-0.7.1.0- rts-1.0- bytestring-0.10.4.0- containers-0.5.5.1- deepseq-1.3.0.2- directory-1.2.1.0- filepath-1.3.0.2- (ghc-7.8.2)- ghc-prim-0.3.1.0- (haskell2010-1.1.2.0)- (haskell98-2.0.0.3)- hoopl-3.10.0.1- hpc-0.6.0.1- integer-gmp-0.5.1.0- old-locale-1.0.0.6- old-time-1.1.0.2- pretty-1.1.1.1- process-1.2.0.0- template-haskell-2.9.0.0- time-1.4.2- transformers-0.3.0.0- unix-2.7.0.1--/home/massysett/rainbox/sunlight-8257/db:- QuickCheck-2.7.5- ansi-terminal-0.6.1.1- ansi-wl-pprint-0.6.7.1- async-2.0.1.5- mtl-2.2.1- optparse-applicative-0.9.0- parsec-3.1.5- primitive-0.5.3.0- rainbow-0.14.0.2- rainbox-0.4.0.4- random-1.0.1.1- regex-base-0.93.2- regex-tdfa-1.2.0- stm-2.4.3- tagged-0.7.2- tasty-0.8.1.1- tasty-quickcheck-0.8.1- terminfo-0.4.0.0- text-1.1.1.3- tf-random-0.5- transformers-0.4.1.0- transformers-compat-0.3.3.4- unbounded-delays-0.1.0.7-
lib/Rainbox.hs view
@@ -1,162 +1,90 @@--- | Create grids of (possibly) colorful boxes.+-- | Typically to use Rainbox you will want these @import@s: ----- For an introduction, see "Rainbox.Tutorial". That file is--- written in literate Haskell, so you will want to look at the--- source itself. HsColour does not do very well with literate--- Haskell, so you will want to view the file in your text editor or--- on Github:+-- @+-- import qualified Data.Sequence as Seq+-- import Rainbow+-- import Rainbox ----- <https://github.com/massysett/rainbox/blob/master/lib/Rainbox/Tutorial.lhs>+-- -- and, for GHC before 7.10:+-- import Data.Monoid+-- @ ----- This module only helps you create simple grids of cells, rather--- like a spreadsheet that does not allow you to merge or split--- cells. If your needs are more complicated, use "Rainbox.Box",--- which allows you to build 'Box'es of arbitrary complexity by--- pasting simpler 'Box'es together. (You can of course use this--- module together with "Rainbox.Box" to create very complex--- layouts.)+-- Rainbox does not re-export anything from "Data.Sequence" or+-- "Rainbow" because I don't know if you want all those things dumped+-- into the same namespace.+--+-- "Rainbox.Tutorial" wil get you started. "Rainbox.Core" contains+-- the implementation details, which you should not need to pay+-- attention to (if you do need to use "Rainbox.Core" for ordinary+-- usage of the library, that's a bug; please report it.) module Rainbox- ( -- * Backgrounds- Background(..)- , defaultBackground- , backgroundFromChunk- , same-- -- * Alignment- , Align- , Horiz- , Vert- , top- , bottom+ ( -- * Alignment and Boxes+ Alignment+ , Horizontal+ , Vertical+ , center , left , right- , center-- -- * Bar- , Bar(..)-- -- * Cell- , Cell(..)-- -- * Creating Box and gluing them together+ , top+ , bottom+ , centerH+ , centerV+ , Box+ , Orientation ( spacer, spreader ) - -- | For simple needs you will only need 'gridByRows' or- -- 'gridByCols'; 'boxCells' and 'glueBoxes' are provided for more- -- complex needs.- , gridByRows- , gridByCols- , checkGrid- , boxCells- , glueBoxes+ -- * Box construction+ , fromChunk+ , blank+ , wrap -- * Rendering , render- , printBox- ) where--import Rainbox.Box-import Rainbox.Array2d-import Data.Array-import Data.String---- | A 'Cell' consists of multiple screen lines; each screen line is--- a 'Bar'.-data Cell = Cell- { bars :: [Bar]- -- ^ Each Bar is one line on the screen.-- , horiz :: Align Horiz- -- ^ How this Cell aligns compared to the other Cell in its- -- column; use 'left', 'center', or 'right'.-- , vert :: Align Vert- -- ^ How this Cell aligns compared to other Cell in its row; use- -- 'top', 'center', or 'bottom'.-- , background :: Background- -- ^ Background color for necessary padding that is added to the- -- Cell to make it the correct width and height. Does not affect- -- the 'Chunk' contained in the 'bars'; these will use the colors- -- that are designated in the 'Chunk' itself.- } deriving (Eq, Show)---- | Creates a Cell with a 'left' horizontal alignment, a 'top'--- vertical alignment, and a 'defaultBackground'. The cell will be--- one 'Bar' tall and contain the text given in the string.-instance IsString Cell where- fromString s = Cell [(fromString s)] left top defaultBackground+ , putBox+ , hPutBox --- | Returns the width of each 'Bar' in the 'Cell'.-cellWidths :: Cell -> [Int]-cellWidths = map width . bars+ -- * Tables+ --+ -- | Types and functions to build a simple spreadsheet-like grid.+ -- You create a nested 'Seq' of 'Cell', and then use 'tableByRows'+ -- or 'tableByColumns' to create a 'Box', which you can then+ -- render using "Rainbow" functions. Each column is as wide as+ -- necessary to accomodate the widest cell in the column, but no+ -- wider, which means the columns will tend to meld together. To+ -- add separators you'll have to add separator cells in yourself. --- | Transforms a grid of 'Cell' to a grid of 'Box' by adding--- necessary padding to each 'Cell'. In every row of the array, all--- the 'Box' will have equal height; in every column of the array,--- all the 'Box' will have equal width.-boxCells- :: (Ix col, Ix row)- => Array (col, row) Cell- -> Array (col, row) Box-boxCells ay = cells $ mapTable conv tbl- where- tbl = table getWidth getHeight ay- where- getWidth _ = maximum . (0:) . concat . map cellWidths . map snd- getHeight _ = maximum . (0:) . map (length . bars . snd)- conv lCol lRow _ _ c = grow bk (Height lRow) (Width lCol) av ah bx- where- Cell bs ah av bk = c- bx = barsToBox bk ah bs+ -- ** Cell type+ , Cell(..)+ , separator --- | Use 'catH' and 'catV' to fuse an array of 'Box' into a single--- 'Box'. For example, if the 'bounds' of the array are--- @((0,0),(3,5))@, then the array has the number of cells given by--- @rangeSize ((0,0), (3,5))@ (that is, 24). The upper left corner--- is @(0,0)@ and the lower right corner is @(3,5)@; the upper right--- and lower left corners are @(3,0)@ and @(0,5)@, respectively.-glueBoxes- :: (Ix col, Ix row)- => Array (col, row) Box- -> Box-glueBoxes- = catH defaultBackground top- . map (catV defaultBackground left)- . cols+ -- ** van Laarhoven lenses+ , rows+ , horizontal+ , vertical+ , background --- | Creates a single 'Box' from a list of rows of 'Cell'. Each--- list is a row of 'Cell'. The list of rows is from top to bottom;--- within each row, the cells are given from left to right.------ /This function is partial./ Each list of 'Cell' must be--- the same length; otherwise, your program will crash. Since you--- will typically generate the list of rows using 'map' or list--- comprehensions or the like, this isn't typically a problem; if it--- is a problem, then check the inputs to this function with--- 'checkGrid' before you apply it.+ -- ** Table builders+ , tableByRows+ , tableByColumns -gridByRows :: [[Cell]] -> Box-gridByRows = glueBoxes . boxCells . arrayByRows+ -- * Bi-color tables+ --+ -- | Build tables with rows of alternating background colors.+ , BicolorTableCellLine+ , BicolorTableCell+ , BicolorTableRow+ , BicolorTable(..)+ , bicolorTableToBox+ , hPutBicolorTable+ , putBicolorTable --- | Creates a single 'Box' from a list of columns of 'Cell'. Each--- list is a column of 'Cell'. The list of columns is from left to--- right; within each column, the cells are given from top to--- bottom.------ /This function is partial./ Each list of 'Cell' must be--- the same length; otherwise, your program will crash. Since you--- will typically generate the list of columns using 'map' or list--- comprehensions or the like, this isn't typically a problem; if it--- is a problem, then check the inputs to this function with--- 'checkGrid' before you apply it.+ -- ** van Laarhoven lenses+ , bctEvenBackground+ , bctOddBackground+ , bctSpacerWidth+ , bctAlignments+ , bctRows -gridByCols :: [[Cell]] -> Box-gridByCols = glueBoxes . boxCells . arrayByCols+ ) where --- | Checks the input to 'gridByRows' or 'gridByCols' to ensure that--- it is safe. True if the list of list of 'Cell' is safe for--- either of these functions; False if not.-checkGrid :: [[Cell]] -> Bool-checkGrid ls = case ls of- [] -> True- x:xs -> let len = length x in all ((== len) . length) xs+import Rainbox.Core+import Rainbox.BicolorTable
− lib/Rainbox/Array2d.hs
@@ -1,216 +0,0 @@--- | Helpers for two-dimensional arrays.-module Rainbox.Array2d- (- -- * Tables- Table- , lCols- , lRows- , cells- , table- , labelCols- , labelRows- , mapTable- , mapColLabels- , mapRowLabels-- -- * Two-dimensional arrays- , cols- , rows- , arrayByRows- , arrayByCols- ) where--import Data.Array---- * Tables---- | A Table is a two-dimensional array with two associated--- one-dimensional arrays: an array of labels for each column, and--- an array of labels for each row.-data Table lCol lRow col row a = Table- { lCols :: Array col lCol- -- ^ One label for each column- , lRows :: Array row lRow- -- ^ One label for each row- , cells :: Array (col, row) a- -- ^ Two-dimensional array of cells- } deriving (Eq, Show)--instance (Ix col, Ix row) => Functor (Table lCol lRow col row) where- fmap f t = t { cells = fmap f . cells $ t }---- | Make a new Table.-table- :: (Ix col, Ix row)- => (col -> [(row, a)] -> lCol)- -- ^ Function to generate the column labels. It is applied to the- -- column index and the full contents of the column.- - -> (row -> [(col, a)] -> lRow)- -- ^ Function to generate the row labels. It is applied to the- -- row index and the full contents of the row.-- -> Array (col, row) a- -- ^ Cells of the table-- -> Table lCol lRow col row a-table fCol fRow ay = Table ayc ayr ay- where- ayc = labelCols fCol ay- ayr = labelRows fRow ay---- | Given a two-dimensional array and a function that generates--- labels, return an array of column labels.-labelCols- :: (Ix col, Ix row)- => (col -> [(row, a)] -> lCol)- -- ^ Function to generate the column labels. It is applied to the- -- column index and the full contents of the column.- -> Array (col, row) a- -> Array col lCol-labelCols f a = listArray (minCol, maxCol) es- where- ((minCol, minRow), (maxCol, maxRow)) = bounds a- es = zipWith f ixsCols . map mkRow $ ixsCols- where- ixsCols = range (minCol, maxCol)- mkRow col = zip ixsRows (map (\rw -> a ! (col, rw)) ixsRows)- where- ixsRows = range (minRow, maxRow)---- | Given a two-dimensional array and a function that generates--- labels, return an array of row labels.-labelRows- :: (Ix col, Ix row)- => (row -> [(col, a)] -> lRow)- -- ^ Function to generate the row labels. It is applied to the- -- row index and the full contents of the row.- -> Array (col, row) a- -> Array row lRow-labelRows f a = listArray (minRow, maxRow) es- where- ((minCol, minRow), (maxCol, maxRow)) = bounds a- es = zipWith f ixsRows . map mkCol $ ixsRows- where- ixsRows = range (minRow, maxRow)- mkCol row = zip ixsCols (map (\cl -> a ! (cl, row)) ixsCols)- where- ixsCols = range (minCol, maxCol)---- | Transform the cells of the table. Similar to the Functor--- instance, but the mapping function has access to the label and--- index of each cell in the 'Table'.-mapTable- :: (Ix col, Ix row)- => (lCol -> lRow -> col -> row -> a -> b)- -- ^ Function is passed the label for the column, the label for- -- the row, the column index, the row index, and the contents of- -- the cell. It returns a new cell.- -> Table lCol lRow col row a- -> Table lCol lRow col row b-mapTable f (Table cs rs ls) = Table cs rs ls'- where- ls' = listArray (bounds ls) . map g . assocs $ ls- where- g ((col, row), e) = f (cs ! col) (rs ! row) col row e---- | Transform the column labels.-mapColLabels- :: (Ix col, Ix row)- => (lCol -> col -> [(lRow, row, a)] -> lCol')- -- ^ The function is passed the column label, column index, and- -- the full contents of the column.- -> Table lCol lRow col row a- -> Table lCol' lRow col row a-mapColLabels f (Table cs rs ls) = Table cs' rs ls- where- ((colMin, rowMin), (colMax, rowMax)) = bounds ls- cs' = listArray (colMin, colMax) es- where- es = zipWith3 f (elems cs) (indices cs) rws- where- rws = map mkRow . indices $ cs- where- mkRow idx = zipWith3 (,,) (elems rs)- (indices rs)- (map (ls !) (range ((idx, rowMin), (idx, rowMax))))---- | Transform the row labels.-mapRowLabels- :: (Ix col, Ix row)- => (lRow -> row -> [(lCol, col, a)] -> lRow')- -- ^ The function is passed the row label, the row index, and the- -- full contents of the row.- -> Table lCol lRow col row a- -> Table lCol lRow' col row a-mapRowLabels f (Table cs rs ls) = Table cs rs' ls- where- ((colMin, rowMin), (colMax, rowMax)) = bounds ls- rs' = listArray (rowMin, rowMax) es- where- es = zipWith3 f (elems rs) (indices rs) cls- where- cls = map mkCol . indices $ rs- where- mkCol idx = zipWith3 (,,) (elems cs)- (indices cs)- (map (ls !) (range ((colMin, idx), (colMax, idx))))---- * Two-dimensional arrays---- | Given a two-dimensional array, return a list of columns in--- order.-cols- :: (Ix col, Ix row)- => Array (col, row) a- -> [[a]]-cols ay = map getCol $ range (minCol, maxCol)- where- ((minCol, minRow), (maxCol, maxRow)) = bounds ay- ixsRows = range (minRow, maxRow)- getCol ixCol = map (\rw -> ay ! (ixCol, rw)) ixsRows---- | Given a two-dimensional array, return a list of rows in order.-rows- :: (Ix col, Ix row)- => Array (col, row) a- -> [[a]]-rows ay = map getRow $ range (minRow, maxRow)- where- ((minCol, minRow), (maxCol, maxRow)) = bounds ay- ixsCols = range (minCol, maxCol)- getRow ixRow = map (\cl -> ay ! (cl, ixRow)) ixsCols---- | Generate a two-dimensional array from a list of rows. Each row--- must be of equal length; otherwise, the generated array will have--- undefined elements.-arrayByRows- :: [[a]]- -> Array (Int, Int) a-arrayByRows ls = array ((0,0), (colMax, rowMax)) $ indexRows ls- where- rowMax = length ls - 1- colMax = case ls of- [] -> -1- x:_ -> length x - 1--indexRows :: [[a]] -> [((Int, Int),a)]-indexRows = concat . map f . zip [0 ..]- where- f (rw, ls) = map g $ zip [0 ..] ls- where- g (cl, a) = ((cl, rw), a)---- | Generate a two-dimensional array from a list of columns. Each--- column must be of equal length; otherwise, the generated array--- will have undefined elements.-arrayByCols- :: [[a]]- -> Array (Int, Int) a-arrayByCols ls = listArray ((0,0), (colMax, rowMax)) . concat $ ls- where- colMax = length ls - 1- rowMax = case ls of- [] -> -1- x:_ -> length x - 1-
+ lib/Rainbox/BicolorTable.hs view
@@ -0,0 +1,185 @@+{-# LANGUAGE TemplateHaskell #-}+-- | Functions and types to build 'BicolorTable's. Everything you should+-- typically need is exported from "Rainbox".+module Rainbox.BicolorTable where++import Control.Lens+import Data.Foldable (foldl')+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import Rainbow+import qualified Rainbow.Types as R+import Rainbox.Core+import System.IO++-- | A single line within a cell in a 'BicolorTable'. For each+-- 'Chunk', leave the 'back' as the default if you want the 'Chunk'+-- background to match '_bctEvenBackground' or '_bctOddBackground'.+-- If you specify a background color for any 'Chunk', it will for+-- that 'Chunk' override the table's background color.+type BicolorTableCellLine = Seq Chunk++-- | The set of all lines within a cell in a 'BicolorTable'.+type BicolorTableCell = Seq BicolorTableCellLine++-- | The set of all columns in a single row. If any single row is+-- narrower than the widest row in the table, it will be padded with+-- empty cells so that it is the same width as the widest row in the+-- table.+type BicolorTableRow = Seq BicolorTableCell++-- | Description for a table with rows of alternating background colors. For+-- instance, if designed for a terminal with a white background, the+-- row backgrounds might alternate between white and light grey.+-- The different backgrounds help with readability.+--+-- For the 'Chunk' that are in the table, simply leave the 'back'+-- color blank if you wish to use the row's background color. Upon+-- rendering, 'bicolorTable' will render the 'Chunk' with a+-- background color that matches that of the row. If you specify a+-- background color for a 'Chunk', it will override the background+-- color for the row.+--+-- Note that a row may contain more than one line of text.+--+-- Unlike tables built with 'tableByRows' or 'tableByColumns', all+-- tables built with 'bicolorTable' will have separator colums+-- between each column.+data BicolorTable = BicolorTable+ { _bctEvenBackground :: Radiant+ -- ^ Background color for all even-numbered rows. Row numbering+ -- starts with zero. To use the terminal's default background color, use+ -- 'mempty'.++ , _bctOddBackground :: Radiant+ -- ^ Background color for all odd-colored rows. Row numbering+ -- starts with zero. To use the terminal's default background color, use+ -- 'mempty'.++ , _bctSpacerWidth :: Int+ -- ^ The width of each column of spacer cells.++ , _bctAlignments :: Seq (Alignment Vertical)+ -- ^ Specifies the alignment for each column in the table. If any+ -- row in '_bctRows' is longer than this 'Seq', each extra column+ -- is assumed to have an alignment 'left'.++ , _bctRows :: Seq BicolorTableRow+ -- ^ Specifies all the textual and color data for the+ -- BicolorTable. The outermost 'Seq' is the set of all rows.+ -- These will alternate in background color bewteen+ -- '_bctEvenBackground' and '_bctOddBackground'. This is a+ -- Russian doll of nested 'Seq'; the type synonyms help explain+ -- the types.+ } deriving Show++makeLenses ''BicolorTable++-- | Transforms a 'BicolorTable' to a 'Box'.+bicolorTableToBox :: BicolorTable -> Box Vertical+bicolorTableToBox = tableByRows . bicolorToPlainTable++-- | Creates a bi-color table and renders it to the given 'Handle'+-- using 'bicolorTable' and 'hPutBox'.+hPutBicolorTable :: Handle -> BicolorTable -> IO ()+hPutBicolorTable h = hPutBox h . bicolorTableToBox++-- | Creates a bi-color table and renders it to standard output+-- using 'hPutBicolorTable'.+putBicolorTable :: BicolorTable -> IO ()+putBicolorTable = hPutBicolorTable stdout++-- | Convert a 'Chunk' for rendering by substituting the table's+-- row background for the chunk's row background if applicable.+convertChunkForRendering+ :: Radiant+ -- ^ Background for this row+ -> Chunk+ -> Chunk+convertChunkForRendering rad chk+ = chk+ & over (R.scheme . R.style8 . R.back) newBack8+ & over (R.scheme . R.style256 . R.back) newBack256+ where+ newBack8 (R.Color Nothing) = R._color8 rad+ newBack8 x = x+ newBack256 (R.Color Nothing) = R._color256 rad+ newBack256 x = x++-- | Converts a 'BicolorTableCellLine' for rendering.+convertBicolorTableCellLineForRendering+ :: Radiant+ -> BicolorTableCellLine+ -> BicolorTableCellLine+convertBicolorTableCellLineForRendering rad = fmap (convertChunkForRendering rad)++-- | Converts a 'BicolorTableCell' for rendering.+convertBicolorTableCellForRendering+ :: Radiant+ -> BicolorTableCell+ -> BicolorTableCell+convertBicolorTableCellForRendering rad+ = fmap (convertBicolorTableCellLineForRendering rad)+++-- | Convert a BicolorTable cell to a plain Cell. Does all necessary Chunk+-- conversions.+bicolorToPlainCell+ :: Radiant+ -- ^ Appropriate background color+ -> Alignment Vertical+ -- ^ Column alignment+ -> BicolorTableCell+ -> Cell+bicolorToPlainCell rad align bic = Cell rws top align rad+ where+ rws = convertBicolorTableCellForRendering rad bic++-- | Convert a BicolorTable row to a plain Row. Does all necessary Chunk conversions.+-- Includes spacer cells.+bicolorToPlainRow+ :: Radiant+ -- ^ Background color for even rows+ -> Radiant+ -- ^ Background color for odd rows+ -> Int+ -- ^ Width of spacer cells+ -> Int+ -- ^ Number for this row+ -> Seq (Alignment Vertical)+ -- ^ Column alignments+ -> BicolorTableRow+ -> Seq Cell+bicolorToPlainRow bkgdEven bkgdOdd sepWidth colNum aligns+ = Seq.intersperse spcr+ . Seq.zipWith (bicolorToPlainCell bkgd) aligns+ where+ bkgd | even colNum = bkgdEven+ | otherwise = bkgdOdd+ spcr = separator bkgd sepWidth++-- | Converts a BicolorTable table to a plain table with 'Cell'. Does all+-- necessary Chunk conversions, and includes spacer cells.+bicolorToPlainTable+ :: BicolorTable+ -> Seq (Seq Cell)+bicolorToPlainTable bct = Seq.mapWithIndex f rws+ where+ (BicolorTable bkgdEven bkgdOdd sepWidth aligns rws) = padBicolorTable bct+ f rowIdx = bicolorToPlainRow bkgdEven bkgdOdd sepWidth rowIdx aligns++-- | Pads out '_bctAlignments' so that it is as long as the longest+-- row in the table, and pads out each row in '_bctRows' so that it+-- is as long as the longest row in the table.+padBicolorTable :: BicolorTable -> BicolorTable+padBicolorTable bct+ = bct & over bctRows padRows+ & over bctAlignments padAligns+ where+ maxLen = foldl' max 0 . fmap Seq.length . _bctRows $ bct+ padRows = fmap pad+ where+ pad row = row <>+ Seq.replicate (max 0 (maxLen - Seq.length row)) Seq.empty+ padAligns aligns = aligns+ <> Seq.replicate (max 0 (maxLen - Seq.length aligns)) left
− lib/Rainbox/Box.hs
@@ -1,341 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--- | Working with 'Box'.------ A 'Box' is a rectangular block of text. You can paste 'Box'--- together to create new rectangles, and you can grow or reduce--- existing 'Box' to create new 'Box'es.------ There are only six primitive functions that make a 'Box':------ * 'B.blank' - formats a blank box with nothing but a (possibly)--- colorful background. Useful to paste to other 'Box' to provide--- white space.------ * 'B.chunks' - Makes a box out of Rainbow 'Chunk'.------ * 'B.catH' - paste 'Box' together horizontally------ * 'B.catV' - paste 'Box' together vertically------ * 'B.viewH' - view a 'Box', keeping the same height but possibly--- trimming the width------ * 'B.viewV' - view a 'Box', keeping the same width but possibly--- trimming the height------ The other functions use these building blocks to do other useful--- things.------ There are many crude diagrams in the Haddock documentation. A--- dash means a character with data; a period means a blank--- character. When you print your 'Box', the blank characters will--- have the appropriate background color.-module Rainbox.Box- ( -- * Backgrounds- Background(..)- , defaultBackground- , backgroundFromChunk- , backgroundToTextSpec- , same- - -- * Height and columns- , Height(..)- , B.height- , Width(..)- , B.HasWidth(..)-- -- * Alignment- , Align- , Vert- , Horiz- , B.center- , B.top- , B.bottom- , B.left- , B.right-- -- * Box properties- , B.Bar(..)- , B.barToBox- , B.barsToBox- , B.Box- , B.unBox-- -- * Making Boxes- , B.blank- , blankH- , blankV- , B.chunks- , chunk-- -- * Pasting Boxes together- , B.catH- , B.catV- , sepH- , sepV- , punctuateH- , punctuateV-- -- * Viewing Boxes- , view- , B.viewH- , B.viewV-- -- * Growing Boxes- , grow- , growH- , growV- , column-- -- * Resizing- , resize- , resizeH- , resizeV-- -- * Printing Boxes- , render- , printBox- ) where--import Data.Monoid-import Data.List (intersperse)-import qualified Data.Text as X-import System.Console.Rainbow-import System.Console.Rainbow.Types-import System.Console.Rainbow.Colors-import qualified Rainbox.Box.Primitives as B-import Rainbox.Box.Primitives- ( Box- , Align- , Horiz- , Vert- , Height(..)- , Width(..)- , Background- , unBox- )-import qualified System.IO as IO--backgroundFromChunk :: Chunk -> B.Background-backgroundFromChunk (Chunk ts _) = B.Background bk8 bk256- where- bk8 = case getLast . background8 . style8 $ ts of- Nothing -> c8_default- Just c -> c- bk256 = case getLast . background256 . style256 $ ts of- Nothing -> c256_default- Just c -> c--backgroundToTextSpec :: B.Background -> TextSpec-backgroundToTextSpec (B.Background bk8 bk256) = TextSpec- { style8 = mempty { background8 = Last . Just $ bk8 }- , style256 = mempty { background256 = Last . Just $ bk256 } }---- | Use the default background colors of the current terminal.-defaultBackground :: B.Background-defaultBackground = B.Background c8_default c256_default---- | Use the same color for 8 and 256-color backgrounds.-same :: Color8 -> B.Background-same c = B.Background c (to256 c)------- # Box making------- | A blank horizontal box with a given width and no height.-blankH :: Background -> Int -> Box-blankH bk i = B.blank bk (Height 0) (Width i)---- | A blank vertical box with a given length.-blankV :: Background -> Int -> Box-blankV bk i = B.blank bk (Height i) (Width 0)---- | A Box made of a single 'Chunk'.-chunk :: Chunk -> Box-chunk = B.chunks . (:[])---- | Grow a box. Each dimension of the result 'Box' is never smaller--- than the corresponding dimension of the input 'Box'. Analogous to--- 'view', so you give the resulting dimensions that you want. The--- alignment is analogous to 'view'; for instance, if you specify--- that the alignment is 'top' and 'left', the extra padding is--- added to the right and bottom sides of the resulting 'Box'.--grow- :: Background- -> Height- -> Width- -> Align Vert- -> Align Horiz- -> Box- -> Box-grow bk (B.Height h) (B.Width w) av ah- = growH bk w ah- . growV bk h av---- | Grow a 'Box' horizontally.--growH- :: Background- -> Int- -- ^ Resulting width- -> Align Horiz- -> Box- -> Box-growH bk tgtW a b- | tgtW < w = b- | otherwise = B.catH bk B.top [lft, b, rt]- where- w = B.width b- diff = tgtW - w- (lft, rt) = (blankH bk wl, blankH bk wr)- (wl, wr)- | a == B.center = B.split diff- | a == B.left = (0, diff)- | otherwise = (diff, 0)---- | Grow a 'Box' vertically.-growV- :: Background- -> Int- -- ^ Resulting height- -> Align Vert- -> Box- -> Box-growV bk tgtH a b- | tgtH < h = b- | otherwise = B.catV bk B.left [tp, b, bt]- where- h = B.height b- diff = tgtH - h- (tp, bt) = (blankV bk ht, blankV bk hb)- (ht, hb)- | a == B.center = B.split diff- | a == B.top = (0, diff)- | otherwise = (diff, 0)---- | Returns a list of 'Box', each being exactly as wide as the--- widest 'Box' in the input list.-column- :: Background- -> Align Horiz- -> [Box]- -> [Box]-column bk ah bs = map (growH bk w ah) bs- where- w = maximum . (0:) . map B.width $ bs--view- :: Height- -> Width- -> Align Vert- -> Align Horiz- -> Box- -> Box-view h w av ah- = B.viewH (B.unWidth w) ah- . B.viewV (B.unHeight h) av------- # Resizing------- | Resize a 'Box'. Will grow or trim it as necessary in order to--- reach the resulting size. Returns an empty 'Box' if either--- 'Height' or 'Width' is less than 1.--resize- :: Background- -> Height- -> Width- -> Align Vert- -> Align Horiz- -> Box- -> Box-resize bk h w av ah- = resizeH bk (unWidth w) ah- . resizeV bk (unHeight h) av---- | Resize horizontally.-resizeH- :: Background- -> Int- -- ^ Resulting width- -> Align Horiz- -> Box- -> Box-resizeH bk w a b- | bw < w = growH bk w a b- | bw > w = B.viewH w a b- | otherwise = b- where- bw = B.width b---- | Resize vertically.-resizeV- :: Background- -> Int- -- ^ Resulting height- -> Align Vert- -> Box- -> Box-resizeV bk h a b- | bh < h = growV bk h a b- | bh > h = B.viewV h a b- | otherwise = b- where- bh = B.height b------- # Glueing------- | @sepH sep a bs@ lays out @bs@ horizontally with alignment @a@,--- with @sep@ amount of space in between each.-sepH :: Background -> Int -> Align Vert -> [Box] -> Box-sepH bk sep a = punctuateH bk a bl- where- bl = blankH bk sep---- | @sepV sep a bs@ lays out @bs@ vertically with alignment @a@,--- with @sep@ amount of space in between each.-sepV :: Background -> Int -> Align Horiz -> [Box] -> Box-sepV bk sep a = punctuateV bk a bl- where- bl = blankV bk sep---- | @punctuateH a p bs@ horizontally lays out the boxes @bs@ with a--- copy of @p@ interspersed between each.-punctuateH :: Background -> Align Vert -> Box -> [Box] -> Box-punctuateH bk a sep = B.catH bk a . intersperse sep---- | A vertical version of 'punctuateH'.-punctuateV :: Background -> Align Horiz -> Box -> [Box] -> Box-punctuateV bk a sep = B.catV bk a . intersperse sep---- | Convert a 'Box' to Rainbow 'Chunk's. You can then print it--- using 'putChunks' or the like.-render :: Box -> [Chunk]-render bx = case unBox bx of- B.NoHeight _ -> []- B.WithHeight rw ->- concat . concat . map (: [["\n"]])- . map renderRod $ rw--renderRod :: B.Rod -> [Chunk]-renderRod = map toChunk . B.unRod- where- toChunk = either spcToChunk id . B.unNibble- spcToChunk ss =- Chunk (backgroundToTextSpec (B.spcBackground ss))- [X.replicate (B.numSpaces ss) (X.singleton ' ')]---- | Prints a Box to standard output. If standard output is not a--- terminal, no colors are used. Otherwise, colors are used if your--- TERM environment variable suggests they are available.-printBox :: Box -> IO ()-printBox b = do- t <- smartTermFromEnv IO.stdout- hPutChunks IO.stdout t . render $ b-
− lib/Rainbox/Box/Primitives.hs
@@ -1,559 +0,0 @@--- | Box primitives.------ This module provides all functions that have access to the--- internals of a 'Box'. There are only six functions that make a--- 'Box':------ * 'blank' - formats a blank box with nothing but a (possibly)--- colorful background. Useful to paste to other 'Box' to provide--- white space.------ * 'chunks' - Makes a box out of Rainbow 'Chunk'.------ * 'catH' - paste 'Box' together horizontally------ * 'catV' - paste 'Box' together vertically------ * 'viewH' - view a 'Box', keeping the same height but possibly--- trimming the width------ * 'viewV' - view a 'Box', keeping the same width but possibly--- trimming the height------ There are many crude diagrams in the Haddock documentation. A--- dash means a character with data; a period means a blank--- character. When you print your 'Box', the blank characters will--- have the appropriate background color.-module Rainbox.Box.Primitives- ( -- * Background- Background(..)-- -- * Alignment- , Align- , Vert- , Horiz- , center- , top- , bottom- , left- , right-- -- * Box- , Bar(..)- , Rod(..)- , barToBox- , barsToBox- , Nibble- , unNibble- , Spaces- , numSpaces- , spcBackground- , BoxP(..)- , Box- , unBox-- -- * Height and Width- , Height(..)- , height- , Width(..)- , HasWidth(..)-- -- * Making Boxes- , blank- , chunks- , catH- , catV- , viewH- , viewV-- -- * Helpers- , split-- ) where--import qualified Data.Foldable as F-import System.Console.Rainbow.Types-import Data.Monoid-import qualified Data.Text as X-import Data.String-import System.Console.Rainbow.Colors---- # Background---- | Background colors to use when inserting necessary padding.-data Background = Background- { boxBackground8 :: Color8- , boxBackground256 :: Color256- } deriving (Eq, Show)---- # Box--data Spaces = Spaces- { numSpaces :: Int- , spcBackground :: Background- } deriving (Eq, Show)--instance HasWidth Spaces where- width = numSpaces--newtype Nibble = Nibble { unNibble :: Either Spaces Chunk }- deriving (Eq, Show)--instance IsString Nibble where- fromString = Nibble . Right . fromString--instance HasWidth Nibble where- width = either width width . unNibble---- | Occupies a single row on screen. The 'Chunk' you place in a--- 'Bar' should not have any control characters such as newlines or--- tabs, as rainbox assumes that each character in a 'Bar' takes up--- one screen column and that each character does not create--- newlines. Leave newline handling up to rainbox. However,--- rainbox will /not/ check to make sure that your inputs do not--- contain newlines, tabs, or other spurious characters. Similarly, use of--- combining characters will create unexpected results, as Rainbox--- will see something that takes up (for instance) two characters--- and think it takes up two screen columns, when in reality it will--- take up only one screen column. So, if you need accented--- characters, use a single Unicode code point, not two code points.--- For example, for é, use U+00E9, not U+0065 and U+0301.-newtype Bar = Bar { unBar :: [Chunk] }- deriving (Eq, Show)--barToBox :: Bar -> Box-barToBox = chunks . unBar--barsToBox :: Background -> Align Horiz -> [Bar] -> Box-barsToBox bk ah = catV bk ah . map barToBox--instance IsString Bar where- fromString = Bar . (:[]) . fromString--instance Monoid Bar where- mempty = Bar []- mappend (Bar l) (Bar r) = Bar $ l ++ r---- | A 'Box' has a width in columns and a height in rows. Its--- height and width both are always at least zero. It can have--- positive height even if its width is zero, and it can have--- positive width even if its height is zero.------ Each row in a 'Box' always has the same number of characters; a--- 'Box' with zero height has no characters but still has a certain--- width.--newtype Box = Box { unBox :: BoxP }- deriving (Eq, Show)--newtype Rod = Rod { unRod :: [Nibble] }- deriving (Eq, Show)--instance IsString Rod where- fromString = Rod . (:[]) . fromString--instance HasWidth Rod where- width = sum . map width . unRod---- | Box payload. Has the data of the box.-data BoxP- = NoHeight Int- -- ^ A Box with width but no height. The Int must be at least- -- zero. If it is zero, the Box has no height and no width.- | WithHeight [Rod]- -- ^ A Box that has height of at least one. It must have at least- -- one component Bar.- deriving (Eq, Show)--instance HasWidth BoxP where- width b = case b of- NoHeight w -> w- WithHeight ns -> sum . map width $ ns--instance IsString Box where- fromString = Box . WithHeight . (:[]) . fromString---- # Height and Width---- | A count of rows-newtype Height = Height { unHeight :: Int }- deriving (Eq, Ord, Show)---- | How many 'Rod' are in this 'Box'?-height :: Box -> Int-height b = case unBox b of- NoHeight _ -> 0- WithHeight rs -> length rs---- | A count of columns-newtype Width = Width { unWidth :: Int }- deriving (Eq, Ord, Show)---- | How many columns are in this thing? A column is one character--- wide. Every 'Bar' in a 'Box' always has the same number of--- columns.------ This is for things that have a single, solitary width, not things--- like columns that might have different widths at different--- points.-class HasWidth a where- width :: a -> Int--instance HasWidth Bar where- width = sum . map (sum . map X.length . text) . unBar--instance HasWidth Box where- width b = case unBox b of- NoHeight i -> i- WithHeight rs -> case rs of- [] -> error "cols: error"- x:_ -> width x--instance HasWidth Chunk where- width = sum . map X.length . text---- # Making Boxes---- | A blank 'Box'. Useful for aligning other 'Box'.-blank- :: Background- -> Height- -> Width- -> Box-blank bk r c- | unHeight r < 1 = Box $ NoHeight (max 0 (unWidth c))- | otherwise = Box . WithHeight $ replicate (unHeight r) row- where- row | unWidth c < 1 = Rod []- | otherwise = Rod [ blanks bk (unWidth c) ]---- | A 'Box' made of 'Chunk'. Always one Bar tall, and has as many--- columns as there are characters in the 'Chunk'.-chunks :: [Chunk] -> Box-chunks = Box . WithHeight . (:[]) . Rod . map (Nibble . Right)---- | Alignment.-data Align a = Center | NonCenter a- deriving (Eq, Show)---- | Vertical alignment.-data Vert = ATop | ABottom- deriving (Eq, Show)---- | Horizontal alignment.-data Horiz = ALeft | ARight- deriving (Eq, Show)--center :: Align a-center = Center--top :: Align Vert-top = NonCenter ATop--bottom :: Align Vert-bottom = NonCenter ABottom--left :: Align Horiz-left = NonCenter ALeft--right :: Align Horiz-right = NonCenter ARight---- | Merge several Box horizontally into one Box. That is, with--- alignment set to ATop:------ > --- ------- ------- > --- ---------- > --------- becomes------ > ----------------- > ----------....--- > ---...........------ With alignment set to ABottom, becomes------ > ---...........--- > ----------....--- > ----------------catH :: Background -> Align Vert -> [Box] -> Box-catH bk al bs- | null bs = Box $ NoHeight 0- | hght == 0 = Box . NoHeight . sum . map width $ bs- | otherwise = Box . WithHeight . mergeHoriz . map (pad . unBox) $ bs- where- pad = padHoriz bk al hght- hght = F.maximum . (0:) . map height $ bs---- | Merge several Box vertically into one Box. That is, with--- alignment set to 'left':------ > ---------- > ---------- >--- > ------ > ------ >--- > ------- > ---------- becomes------ > ---------- > ---------- > ---....--- > ---....--- > ---....--- > ----...--- > ----...------ With alignment set to 'right', becomes------ > ---------- > ---------- > ....------ > ....------ > ...------- > ...------catV :: Background -> Align Horiz -> [Box] -> Box-catV bk al bs- | null bs = Box $ NoHeight 0- | otherwise = Box . foldr f (NoHeight w)- . concat . map (flatten . unBox) $ bs- where- w = F.maximum . (0:) . map width $ bs- f mayR bp = case mayR of- Nothing -> bp- Just rw -> case bp of- WithHeight wh -> WithHeight $ padded : wh- _ -> WithHeight [padded]- where- padded = padVert bk al w rw- flatten bp = case bp of- NoHeight _ -> [Nothing]- WithHeight rs -> map Just rs----- | Given the resulting height, pad a list of Height. So, when given--- a height of 3 and an alignment of 'top':------ > ----------- > -------------- becomes------ > ----------- > ----------- > ........------ where dashes is a 'Bar' with data, and dots is a blank 'Bar'.--padHoriz :: Background -> Align Vert -> Int -> BoxP -> [Rod]-padHoriz bk a hght bp = case bp of- NoHeight w -> map (Rod . (:[])) . replicate h $ blanks bk w- WithHeight rs -> concat [tp, rs, bot]- where- nPad = max 0 $ h - length rs- (nATop, nBot) = case a of- Center -> split nPad- NonCenter ATop -> (0, nPad)- NonCenter ABottom -> (nPad, 0)- pad = Rod [blanks bk len]- where- len = case rs of- [] -> 0- x:_ -> width x- (tp, bot) = (replicate nATop pad, replicate nBot pad)- where- h = max 0 hght---- | Given the resulting width, pad a 'Bar'. So, when given--- a width of 10 and an alignment of 'right',------ > ------------- becomes------ > ...---------padVert- :: Background- -> Align Horiz- -> Int- -> Rod- -> Rod-padVert bk a wdth rw@(Rod cs) = Rod . concat $ [lft, cs, rght]- where- nPad = max 0 $ w - width rw- (nLeft, nRight) = case a of- Center -> split nPad- NonCenter ALeft -> (0, nPad)- NonCenter ARight -> (nPad, 0)- (lft, rght) = (mkPad nLeft, mkPad nRight)- mkPad n- | n == 0 = []- | otherwise = [blanks bk n]- w = max 0 wdth----- | Merge several horizontal Height into one set of horizontal 'Bar'.--- That is:------ > ----- ----- -------- > ----- ----- -------- > ----- ----- ----------- into------ > ------------------ > ------------------ > --------------------- Strange behavior will result if each input list is not exactly--- the same length.--mergeHoriz :: [[Rod]] -> [Rod]-mergeHoriz = foldr (zipWith merge) (repeat (Rod []))- where- merge (Rod r1) (Rod r2) = Rod $ r1 ++ r2---- # Viewing---- | View a 'Box', possibly shrinking it. You set the size of your--- viewport and how it is oriented relative to the 'Box' as a whole.--- The 'Box' returned may be smaller than the argument 'Box', but it--- will never be bigger.------ Examples:------ >>> :set -XOverloadedStrings--- >>> let box = catV defaultBackground top [ "ab", "cd" ]--- >>> printBox . view (Height 1) (Width 1) left top $ box--- a------ >>> printBox . view (Height 1) (Width 1) right bottom $ box--- d--viewV :: Int -> Align Vert -> Box -> Box-viewV hght a (Box b) = Box $ case b of- WithHeight rs- | h == 0 -> NoHeight . width . head $ rs- | otherwise -> WithHeight $ case a of- NonCenter ATop -> take h rs- NonCenter ABottom -> drop extra rs- Center -> drop nDrop . take nTake $ rs- where- (trimL, trimR) = split extra- nTake = length rs - trimR- nDrop = trimL- where- extra = max 0 $ length rs - h- x -> x- where- h = max 0 hght--viewH :: Int -> Align Horiz -> Box -> Box-viewH wdth a (Box b) = Box $ case b of- NoHeight nh -> NoHeight (min w nh)- WithHeight rs -> WithHeight $ map f rs- where- f rw = case a of- NonCenter ALeft -> takeChars w rw- NonCenter ARight -> dropChars extra rw- Center -> dropChars nDrop . takeChars nTake $ rw- where- (trimL, trimR) = split extra- nTake = max 0 $ width rw - trimR- nDrop = trimL- where- extra = max 0 $ width rw - w- where- w = max 0 wdth---dropChars :: Int -> Rod -> Rod-dropChars colsIn = Rod . go colsIn . unRod- where- go n cs- | n <= 0 = cs- | otherwise = case cs of- [] -> []- x:xs- | lenX <= n -> go (n - lenX) xs- | otherwise -> x' : xs- where- lenX = case unNibble x of- Left blnk -> numSpaces blnk- Right chk -> width chk- x' = case unNibble x of- Left blnk -> Nibble . Left $- blnk { numSpaces = numSpaces blnk - n }- Right chk -> Nibble . Right . dropChunkChars n $ chk---- | Drops the given number of characters from a Chunk.-dropChunkChars :: Int -> Chunk -> Chunk-dropChunkChars n c = c { text = go n (text c) }- where- go nLeft ls = case ls of- [] -> []- t:ts- | len < nLeft -> go (nLeft - len) ts- | len == nLeft -> ts- | otherwise -> X.drop nLeft t : ts- where- len = X.length t--takeChars :: Int -> Rod -> Rod-takeChars colsIn = Rod . go colsIn . unRod- where- go n cs- | n <= 0 = []- | otherwise = case cs of- [] -> []- x:xs- | lenX <= n -> x : go (n - lenX) xs- | otherwise -> [x']- where- (lenX, x') = case unNibble x of- Left blnk ->- ( numSpaces blnk,- Nibble . Left $ blnk { numSpaces = n } )- Right chk ->- ( width chk,- Nibble . Right . takeChunkChars n $ chk)--takeChunkChars :: Int -> Chunk -> Chunk-takeChunkChars n c = c { text = go n (text c) }- where- go nLeft ls = case ls of- [] -> []- t:ts- | len < nLeft -> t : go (nLeft - len) ts- | len == nLeft -> [t]- | otherwise -> [X.take nLeft t]- where- len = X.length t------- # Helpers------- | Generate spaces.-blanks- :: Background- -- ^ Background colors- -> Int- -- ^ Number of blanks- -> Nibble-blanks bk c = Nibble (Left (Spaces c bk))---- | Split a number into two parts, so that the sum of the two parts--- is equal to the original number.-split :: Int -> (Int, Int)-split i = (r, r + rm)- where- (r, rm) = i `quotRem` 2-
+ lib/Rainbox/Core.hs view
@@ -0,0 +1,733 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE TemplateHaskell #-}+-- lens-simple makeLenses will not make type signatures+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# OPTIONS_HADDOCK not-home #-}+-- | Contains the innards of 'Rainbox'. You shouldn't need anything+-- in here. Some functions here are partial or have undefined results+-- if their inputs don't respect particular invariants.+module Rainbox.Core where++import Control.Monad (join)+import qualified Data.Foldable as F+import Data.Function ((&))+import qualified Data.Map as M+import Data.Monoid ((<>))+import Data.Sequence (Seq, ViewL (EmptyL, (:<)), viewl, (|>))+import qualified Data.Sequence as Seq+import qualified Data.Text as X+import qualified Data.Traversable as T+import Control.Lens (Lens', lens)+import Rainbow ( Chunk , Radiant , chunk , back, hPutChunks)+import Rainbow.Types (Chunk (_yarn))+import System.IO++-- # Alignment++-- | Alignment. Used in conjunction with 'Horizontal' and 'Vertical',+-- this determines how a payload aligns with the axis of a 'Box'.+data Alignment a = Center | NonCenter a+ deriving (Eq, Ord, Show, Functor, F.Foldable, T.Traversable)++instance Semigroup (Alignment a) where+ x <> y = case x of+ Center -> y+ NonCenter a -> case y of+ Center -> NonCenter a+ NonCenter b -> NonCenter b++-- | 'mempty' is 'center'. 'mappend' takes the rightmost non-'center'+-- value.++instance Monoid (Alignment a) where+ mempty = Center++-- # Horizontal and vertical++-- | Determines how a payload aligns with a horizontal axis.+data Horizontal = Top | Bottom+ deriving (Eq, Ord, Show)++-- | Determines how a payload aligns with a vertical axis.+data Vertical = Port | Starboard+ deriving (Eq, Ord, Show)++-- | Place this payload so that it is centered on the vertical axis or+-- horizontal axis.+center :: Alignment a+center = Center++-- | Center horizontally; like 'center', but monomorphic.+centerH :: Alignment Horizontal+centerH = center++-- | Center vertically; like 'center', but monomorphic.+centerV :: Alignment Vertical+centerV = center++-- | Place this payload's left edge on the vertical axis.+left :: Alignment Vertical+left = NonCenter Port++-- | Place this payload's right edge on the vertical axis.+right :: Alignment Vertical+right = NonCenter Starboard++-- | Place this payload's top edge on the horizontal axis.+top :: Alignment Horizontal+top = NonCenter Top++-- | Place this payload's bottom edge on the horizontal axis.+bottom :: Alignment Horizontal+bottom = NonCenter Bottom+++-- # Width and height++-- | A count of rows.+newtype Height = Height Int+ deriving (Eq, Ord, Show)++-- | A count of columns.+newtype Width = Width Int+ deriving (Eq, Ord, Show)++class HasHeight a where+ height :: a -> Int++instance HasHeight Height where+ height (Height a) = max 0 a++instance HasHeight Chunk where+ height _ = 1++instance (HasHeight a, HasHeight b) => HasHeight (Either a b) where+ height = either height height++class HasWidth a where+ width :: a -> Int++instance HasWidth Width where+ width (Width a) = max 0 a++instance HasWidth Chunk where+ width ck = X.length . _yarn $ ck++instance (HasWidth a, HasWidth b) => HasWidth (Either a b) where+ width = either width width++-- # Core++-- | A 'Core' is either a single 'Chunk' or, if the box is blank, is+-- merely a height and a width.+newtype Core = Core (Either Chunk (Height, Width))+ deriving (Eq, Ord, Show)++instance HasWidth Core where+ width (Core ei) = either width (width . snd) ei++instance HasHeight Core where+ height (Core ei) = either height (height . fst) ei++-- # Rods++-- | An intermediate type used in rendering; it consists either of+-- text 'Chunk' or of a number of spaces coupled with a background color.+newtype Rod = Rod (Either (Int, Radiant) Chunk)+ deriving (Eq, Ord, Show)++instance HasWidth Rod where+ width (Rod ei) = case ei of+ Left (i, _) -> max 0 i+ Right c -> width c++-- # RodRows++-- | A list of screen rows; each screen row is a 'Seq' of 'Rod'.+--+-- A 'RodRows' with width but no height does nothing if rendered+-- alone, but it can affect the width of other 'RodRows' if combined+-- with them.+data RodRows+ = RodRowsWithHeight (Seq (Seq Rod))+ -- ^ Each outer 'Seq' represents a single screen row. Each 'Seq'+ -- has a height of 1.+ --+ -- The outer 'Seq' must have a length of at least 1, even if the+ -- inner 'Seq' is empty. If the outer 'Seq' has a length of zero,+ -- undefined behavior occurs. For a 'RodRows' with no height and no+ -- width, use 'RodRowsNoHeight'.++ | RodRowsNoHeight Int+ -- ^ A 'RodRows' that has no height. If the 'Int' is less than 1,+ -- the 'RodRows' has no width and no height. Otherwise, the+ -- 'RodRows' has no height but has the given width.+ deriving (Eq, Ord, Show)++instance HasHeight RodRows where+ height (RodRowsWithHeight sq) = Seq.length sq+ height (RodRowsNoHeight _) = 0++instance HasWidth RodRows where+ width (RodRowsWithHeight sq) = F.foldl' max 0 . fmap (F.sum . fmap width) $ sq+ width (RodRowsNoHeight i) = max 0 i++-- | Convert a 'Core' to a 'Seq' of 'Rod' for rendering.+rodRowsFromCore :: Radiant -> Core -> RodRows+rodRowsFromCore bk (Core ei) = case ei of+ Left ck -> RodRowsWithHeight . Seq.singleton+ . Seq.singleton . Rod . Right $ ck+ Right (Height h, Width w)+ | h < 1 -> RodRowsNoHeight w+ | otherwise -> RodRowsWithHeight . Seq.replicate h . Seq.singleton+ . Rod . Left $ (w, bk)++-- | Converts a 'RodRows' to a nested 'Seq' of 'Chunk' in+-- preparation for rendering. Newlines are added to the end of each+-- line.+chunksFromRodRows :: RodRows -> Seq (Seq Chunk)+chunksFromRodRows rr = case rr of+ RodRowsWithHeight sq -> fmap (|> chunk "\n") . fmap (fmap chunkFromRod) $ sq+ where+ chunkFromRod (Rod ei) = case ei of+ Left (i, r) -> (chunk . X.replicate i $ " ") & back r+ Right c -> c+ RodRowsNoHeight _ -> Seq.empty+++-- # Payload++-- | A 'Payload' holds a 'RodRows', which determines the number+-- and content of the screen rows. The 'Payload' also has an+-- 'Alignment', which specifies how the payload aligns with the axis.+-- Whether the 'Alignment' is 'Horizontal' or 'Vertical' determines+-- the orientation of the 'Payload'. The 'Payload' also contains a+-- background color, which is type 'Radiant'. The background color+-- extends continuously from the 'Payload' in both directions that are+-- perpendicular to the axis.++data Payload a = Payload (Alignment a) Radiant (Either RodRows Core)+ deriving (Eq, Ord, Show)++instance HasWidth (Payload a) where+ width (Payload _ _ ei) = width ei++instance HasHeight (Payload a) where+ height (Payload _ _ ei) = height ei++-- # Padding and merging++-- | Adds padding to the top and bottom of each Payload. A Payload+-- with a Core is converted to a RodRows and has padding added; a+-- Payload with a RodRows has necessary padding added to the top and+-- bottom. The number of elements in the resulting Seq is the same as+-- the number of elements in the input Seq; no merging is performed.++addVerticalPadding+ :: Box Horizontal+ -> Seq RodRows+addVerticalPadding bx@(Box sqnce) = fmap eqlize sqnce+ where+ maxTop = above bx+ maxBot = below bx+ eqlize bhp@(Payload _ rd ei) = case ei of+ Left rr -> eqlzeRodRows rr+ Right cre -> eqlzeRodRows (rodRowsFromCore rd cre)+ where+ eqlzeRodRows rr = case rr of+ RodRowsWithHeight sq -> RodRowsWithHeight $ tp w <> sq <> bot w+ RodRowsNoHeight i+ | maxTop + maxBot == 0 -> RodRowsNoHeight i+ | otherwise -> RodRowsWithHeight $ tp w <> bot w+ where+ w = width rr+ tp w = Seq.replicate (max 0 (maxTop - above bhp)) (pad w)+ bot w = Seq.replicate (max 0 (maxBot - below bhp)) (pad w)+ pad w = Seq.singleton . Rod . Left $ (w, rd)++-- | Merges multiple horizontal RodRows into a single RodRows. All+-- RodRows must already have been the same height; if they are not the+-- same height, undefined behavior occurs.++horizontalMerge :: Seq RodRows -> RodRows+horizontalMerge sqn = case viewl sqn of+ EmptyL -> RodRowsNoHeight 0+ x :< xs -> case x of+ RodRowsNoHeight i -> RodRowsNoHeight $ F.foldl' comb i xs+ where+ comb acc x' = case x' of+ RodRowsNoHeight i' -> acc + i'+ RodRowsWithHeight _ -> error "horizontalMerge: error 1"+ RodRowsWithHeight sq -> RodRowsWithHeight $ F.foldl' comb sq xs+ where+ comb acc rr = case rr of+ RodRowsWithHeight sq' -> Seq.zipWith (<>) acc sq'+ RodRowsNoHeight _ -> error "horizontalMerge: error 2"++-- | Split a number into two parts, so that the sum of the two parts+-- is equal to the original number.+split :: Int -> (Int, Int)+split i = (r, r + rm)+ where+ (r, rm) = i `quotRem` 2++-- | Adds padding to the left and right of each Payload.+-- A Payload with a Core is converted to a RodRows and has padding+-- added; a Payload with a RodRows has necessary padding added to the+-- left and right. The number of elements in the resulting Seq is+-- the same as the number of elements in the input Seq; no merging is+-- performed.++addHorizontalPadding+ :: Box Vertical+ -> Seq RodRows+addHorizontalPadding bx@(Box sqnce) = fmap eqlize sqnce+ where+ maxLeft = port bx+ maxRight = starboard bx+ eqlize (Payload a rd ei) = case ei of+ Left rr -> addLeftRight rr+ Right cre -> addLeftRight $ rodRowsFromCore rd cre+ where+ addLeftRight (RodRowsNoHeight _) = RodRowsNoHeight $ maxLeft + maxRight+ addLeftRight (RodRowsWithHeight sq) = RodRowsWithHeight $+ fmap addLeftRightToLine sq+ addLeftRightToLine lin = padder lenLft <> lin <> padder lenRgt+ where+ lenLin = F.sum . fmap width $ lin+ lenLft = case a of+ Center -> maxLeft - (fst . split $ lenLin)+ NonCenter Port -> maxLeft+ NonCenter Starboard -> maxLeft - lenLin+ lenRgt = case a of+ Center -> maxRight - (snd . split $ lenLin)+ NonCenter Port -> maxRight - lenLin+ NonCenter Starboard -> maxRight+ padder len+ | len < 1 = Seq.empty+ | otherwise = Seq.singleton . Rod . Left $ (len, rd)+++-- | Merge multiple vertical RodRows into a single RodRows. Each+-- RodRows should already be the same width.++verticalMerge :: Seq RodRows -> RodRows+verticalMerge sqnce = case viewl sqnce of+ EmptyL -> RodRowsNoHeight 0+ x :< xs -> F.foldl' comb x xs+ where+ comb acc rr = case (acc, rr) of+ (RodRowsNoHeight w, RodRowsNoHeight _) -> RodRowsNoHeight w+ (RodRowsNoHeight _, RodRowsWithHeight sq) -> RodRowsWithHeight sq+ (RodRowsWithHeight sq, RodRowsNoHeight _) -> RodRowsWithHeight sq+ (RodRowsWithHeight sq1, RodRowsWithHeight sq2) ->+ RodRowsWithHeight $ sq1 <> sq2++-- # Box++-- | A 'Box' is the central building block. It consists of zero or+-- more payloads; each payload has the same orientation, which is either+-- 'Horizontal' or 'Vertical'. This orientation also determines+-- the orientation of the entire 'Box'.+--+-- A 'Box' is a 'Monoid' so you can combine them using the usual+-- monoid functions. For a 'Box' 'Vertical', the leftmost values+-- added with 'mappend' are at the top of the 'Box'; for a 'Box'+-- 'Horizontal', the leftmost values added with 'mappend' are on the+-- left side of the 'Box'.+newtype Box a = Box (Seq (Payload a))+ deriving (Eq, Ord, Show)++instance Semigroup (Box a) where+ (Box x) <> (Box y) = Box (x <> y)++instance Monoid (Box a) where+ mempty = Box Seq.empty++-- # Orientation++-- | This typeclass is responsible for transforming a 'Box' into+-- Rainbow 'Chunk' so they can be printed to your screen. This+-- requires adding appropriate whitespace with the right colors, as+-- well as adding newlines in the right places.+class Orientation a where+ rodRows :: Box a -> RodRows++ spacer :: Radiant -> Int -> Box a+ -- ^ Builds a one-dimensional box of the given size; its single+ -- dimension is parallel to the axis. When added to a+ -- box, it will insert blank space of the given length. For a 'Box'+ -- 'Horizontal', this produces a horizontal line; for a 'Box'+ -- 'Vertical', a vertical line.++ spreader :: Alignment a -> Int -> Box a+ -- ^ Builds a one-dimensional box of the given size; its single+ -- dimension is perpendicular to the axis. This can be used to make+ -- a 'Box' 'Vertical' wider or a 'Box' 'Horizontal' taller.++instance Orientation Vertical where+ rodRows = verticalMerge . addHorizontalPadding++ spacer r i = Box . Seq.singleton $+ Payload (NonCenter Port) r (Right . Core . Right $+ (Height (max 0 i), Width 0))+ spreader a i = Box . Seq.singleton $+ Payload a mempty (Right . Core . Right $+ (Height 0, Width (max 0 i)))++instance Orientation Horizontal where+ rodRows = horizontalMerge . addVerticalPadding++ spacer r i = Box . Seq.singleton $+ Payload (NonCenter Top) r (Right . Core . Right $+ (Height 0, Width (max 0 i)))+ spreader a i = Box . Seq.singleton $+ Payload a mempty (Right . Core . Right $+ (Height (max 0 i), Width 0))++-- # port, starboard, above, below+++-- | Things that are oriented around a vertical axis.+class LeftRight a where+ -- | Length to the left of the vertical axis.+ port :: a -> Int++ -- | Length to the right of the vertical axis.+ starboard :: a -> Int++-- | Things that are oriented around a horizontal axis.+class UpDown a where+ -- | Number of lines above the horizontal axis.+ above :: a -> Int+ -- | Number of lines below the horizontal axis.+ below :: a -> Int+++instance LeftRight (Payload Vertical) where+ port (Payload a _ ei) = case a of+ NonCenter Port -> 0+ NonCenter Starboard -> width ei+ Center -> fst . split . width $ ei++ starboard (Payload a _ s3) = case a of+ NonCenter Port -> width s3+ NonCenter Starboard -> 0+ Center -> snd . split . width $ s3++instance UpDown (Payload Horizontal) where+ above (Payload a _ s3) = case a of+ NonCenter Top -> 0+ NonCenter Bottom -> height s3+ Center -> fst . split . height $ s3++ below (Payload a _ s3) = case a of+ NonCenter Top -> height s3+ NonCenter Bottom -> 0+ Center -> snd . split . height $ s3++instance LeftRight (Box Vertical) where+ port (Box sq) = F.foldl' max 0 . fmap port $ sq+ starboard (Box sq) = F.foldl' max 0 . fmap starboard $ sq++instance HasWidth (Box Vertical) where+ width b = port b + starboard b++instance HasHeight (Box Vertical) where+ height (Box sq) = F.sum . fmap height $ sq++instance UpDown (Box Horizontal) where+ above (Box sq) = F.foldl' max 0 . fmap above $ sq+ below (Box sq) = F.foldl' max 0 . fmap below $ sq++instance HasHeight (Box Horizontal) where+ height b = above b + below b++instance HasWidth (Box Horizontal) where+ width (Box sq) = F.sum . fmap width $ sq++-- # Box construction++-- | Construct a box from a single 'Chunk'.+fromChunk+ :: Alignment a+ -> Radiant+ -- ^ Background color. The background color in the 'Chunk' is not+ -- changed; this background is used if the 'Payload' must be padded+ -- later on.+ -> Chunk+ -> Box a+fromChunk a r = Box . Seq.singleton . Payload a r . Right . Core . Left++-- | Construct a blank box. Useful for adding in background spacers.+-- For functions that build one-dimensional boxes, see 'spacer' and+-- 'spreader'.+blank+ :: Alignment a+ -> Radiant+ -- ^ Color for the blank area.+ -> Height+ -> Width+ -> Box a+blank a r h w =+ Box . Seq.singleton . Payload a r . Right . Core . Right $ (h, w)++-- | Wrap a 'Box' in another 'Box'. Useful for changing a+-- 'Horizontal' 'Box' to a 'Vertical' one, or simply for putting a+-- 'Box' inside another one to control size and background color.+wrap+ :: Orientation a+ => Alignment b+ -- ^ Alignment for new 'Box'. This also determines whether the new+ -- 'Box' is 'Horizontal' or 'Vertical'.+ -> Radiant+ -- ^ Background color for new box+ -> Box a+ -> Box b+wrap a r = Box . Seq.singleton . Payload a r . Left . rodRows++-- # Box rendering++-- | Convert a box to a 'Seq' of 'Chunk' in preparation for rendering.+-- Use 'F.toList' to convert the 'Seq' of 'Chunk' to a list so that+-- you can print it using the functions in "Rainbow".+render :: Orientation a => Box a -> Seq Chunk+render = join . chunksFromRodRows . rodRows++-- | Renders a 'Box' to the given 'Handle'. This uses 'hPutChunks' so consult+-- that function for more details on how this works; generally it is going to+-- use the maximum number of colors possible for your terminal.+hPutBox :: Orientation a => Handle -> Box a -> IO ()+hPutBox h b = hPutChunks h (F.toList . render $ b)++-- | Uses 'hPutBox' to render the given 'Box' to standard output.+putBox :: Orientation a => Box a -> IO ()+putBox = hPutBox stdout+++-- # Tables++-- | A single cell in a spreadsheet-like grid.+data Cell = Cell+ { _rows :: Seq (Seq Chunk)+ -- ^ The cell can have multiple rows of text; there is one 'Seq' for+ -- each row of text.+ , _horizontal :: Alignment Horizontal+ -- ^ How this 'Cell' should align compared to other 'Cell' in its+ -- row.+ , _vertical :: Alignment Vertical+ -- ^ How this 'Cell' should align compared to other 'Cell' in its column.+ , _background :: Radiant+ -- ^ Background color for this cell. The background in the+ -- individual 'Chunk' in the 'cellRows' are not affected by+ -- 'cellBackground'; instead, 'cellBackground' determines the color+ -- of necessary padding that will be added so that the cells make a+ -- uniform table.+ } deriving (Eq, Ord, Show)++rows :: Lens' Cell (Seq (Seq Chunk))+rows = lens _rows (\cel fld -> cel { _rows = fld })++horizontal :: Lens' Cell (Alignment Horizontal)+horizontal = lens _horizontal (\cel fld -> cel { _horizontal = fld })++vertical :: Lens' Cell (Alignment Vertical)+vertical = lens _vertical (\cel fld -> cel { _vertical = fld })++background :: Lens' Cell Radiant+background = lens _background (\cel fld -> cel { _background = fld })++instance Semigroup Cell where+ (Cell rx hx vx bx) <> (Cell ry hy vy by)+ = Cell (zipSeqs rx ry) (hx <> hy) (vx <> vy) (bx <> by)+ where+ zipSeqs x y = Seq.zipWith (<>) x' y'+ where+ x' = x <> Seq.replicate+ (max 0 (Seq.length y - Seq.length x)) Seq.empty+ y' = y <> Seq.replicate+ (max 0 (Seq.length x - Seq.length y)) Seq.empty+++-- | 'mappend' combines two 'Cell' horizontally so they are+-- side-by-side, left-to-right. The '_horizontal', '_vertical', and+-- '_background' fields are combined using their respective 'Monoid'+-- instances. 'mempty' uses the respective 'mempty' value for each+-- field.+instance Monoid Cell where+ mempty = Cell mempty mempty mempty mempty++-- | Creates a blank 'Cell' with the given background color and width;+-- useful for adding separators between columns.+separator :: Radiant -> Int -> Cell+separator rd i = Cell (Seq.singleton (Seq.singleton ck)) top left rd+ where+ ck = (chunk $ X.replicate (max 0 i) " ") & back rd++-- Cells by row:+-- 0. Ensure each row is equal length+-- 1. Create one BoxV for each cell+-- 2. Create widest cell map+-- 3. Pad each BoxV to appropriate width, using cellVert alignment+-- 4. Convert each BoxV to BoxH, using cellHoriz and cellBackground+-- 5. mconcatSeq each row+-- 6. Convert each row to BoxV; use default background+-- and center alignment+-- 7. mconcatSeq the rows++-- | Create a table where each inner 'Seq' is a row of cells,+-- from left to right. If necessary, blank cells are added to the end+-- of a row to ensure that each row has the same number of cells as+-- the longest row.+tableByRows :: Seq (Seq Cell) -> Box Vertical+tableByRows+ = mconcatSeq+ . fmap rowToBoxV+ . fmap mconcatSeq+ . fmap (fmap toBoxH)+ . uncurry padBoxV+ . addWidthMap+ . fmap (fmap cellToBoxV)+ . equalize mempty++rowToBoxV :: Box Horizontal -> Box Vertical+rowToBoxV = wrap center mempty++cellToBoxV :: Cell -> (Box Vertical, Alignment Horizontal, Radiant)+cellToBoxV (Cell rs ah av rd) = (bx, ah, rd)+ where+ bx = mconcatSeq+ . fmap (wrap av rd)+ . fmap (mconcatSeq . fmap (fromChunk top rd))+ $ rs++toBoxH+ :: (Box Vertical, Alignment Horizontal, Radiant)+ -> Box Horizontal+toBoxH (bv, ah, rd) = wrap ah rd bv++addWidthMap+ :: Seq (Seq (Box Vertical, b, c))+ -> (M.Map Int (Int, Int), Seq (Seq (Box Vertical, b, c)))+addWidthMap sqnce = (m, sqnce)+ where+ m = widestCellMap . fmap (fmap (\(a, _, _) -> a)) $ sqnce++padBoxV+ :: M.Map Int (Int, Int)+ -> Seq (Seq (Box Vertical, a, b))+ -> Seq (Seq (Box Vertical, a, b))+padBoxV mp = fmap (Seq.mapWithIndex f)+ where+ f idx (bx, a, b) = (bx <> padLeft <> padRight, a, b)+ where+ (lenL, lenR) = mp M.! idx+ padLeft = spreader right lenL+ padRight = spreader left lenR+++widestCellMap :: Seq (Seq (Box Vertical)) -> M.Map Int (Int, Int)+widestCellMap = F.foldl' outer M.empty+ where+ outer mpOuter = Seq.foldlWithIndex inner mpOuter+ where+ inner mpInner idx bx = case M.lookup idx mpInner of+ Nothing -> M.insert idx (port bx, starboard bx) mpInner+ Just (pOld, sOld) -> M.insert idx+ (max pOld (port bx), max sOld (starboard bx)) mpInner++-- Table by columns:+--+-- 0. Equalize columns+-- 1. Create one BoxH for each cell+-- 2. Create tallest cell map+-- 3. Pad each BoxH to appropriate height, using cellHeight alignment+-- 4. Convert each BoxH to BoxV, using cellVert and cellBackground+-- 5. mconcatSeq each column+-- 6. Convert each column to BoxH+-- 7. mconcatSeq the columns++-- | Create a table where each inner 'Seq' is a column of cells,+-- from top to bottom. If necessary, blank cells are added to the end+-- of a column to ensure that each column has the same number of cells+-- as the longest column.+tableByColumns :: Seq (Seq Cell) -> Box Horizontal+tableByColumns+ = mconcatSeq+ . fmap rowToBoxH+ . fmap mconcatSeq+ . fmap (fmap toBoxV)+ . uncurry padBoxH+ . addHeightMap+ . fmap (fmap cellToBoxH)+ . equalize mempty+++rowToBoxH :: Box Vertical -> Box Horizontal+rowToBoxH = wrap top mempty+++cellToBoxH :: Cell -> (Box Horizontal, Alignment Vertical, Radiant)+cellToBoxH (Cell rs ah av rd) = (bx, av, rd)+ where+ bx = wrap ah rd+ . mconcatSeq+ . fmap (wrap av rd)+ . fmap (mconcatSeq . fmap (fromChunk top rd))+ $ rs++addHeightMap+ :: Seq (Seq (Box Horizontal, b, c))+ -> (M.Map Int (Int, Int), Seq (Seq (Box Horizontal, b, c)))+addHeightMap sqnce = (m, sqnce)+ where+ m = tallestCellMap . fmap (fmap (\(a, _, _) -> a)) $ sqnce++tallestCellMap :: Seq (Seq (Box Horizontal)) -> M.Map Int (Int, Int)+tallestCellMap = F.foldl' outer M.empty+ where+ outer mpOuter = Seq.foldlWithIndex inner mpOuter+ where+ inner mpInner idx bx = case M.lookup idx mpInner of+ Nothing -> M.insert idx (above bx, below bx) mpInner+ Just (aOld, bOld) -> M.insert idx+ (max aOld (above bx), max bOld (below bx)) mpInner+++padBoxH+ :: M.Map Int (Int, Int)+ -> Seq (Seq (Box Horizontal, a, b))+ -> Seq (Seq (Box Horizontal, a, b))+padBoxH mp = fmap (Seq.mapWithIndex f)+ where+ f idx (bx, a, b) = (bx <> padTop <> padBot, a, b)+ where+ (lenT, lenB) = mp M.! idx+ padTop = spreader bottom lenT+ padBot = spreader top lenB+++toBoxV+ :: (Box Horizontal, Alignment Vertical, Radiant)+ -> Box Vertical+toBoxV (bh, av, rd) = wrap av rd bh+++-- | Ensures that each inner 'Seq' is the same length by adding the+-- given empty element where needed.+equalize :: a -> Seq (Seq a) -> Seq (Seq a)+equalize emp sqnce = fmap adder sqnce+ where+ maxLen = F.foldl' max 0 . fmap Seq.length $ sqnce+ adder sq = sq <> pad+ where+ pad = Seq.replicate (max 0 (maxLen - Seq.length sq)) emp++mconcatSeq :: Monoid a => Seq a -> a+mconcatSeq = F.foldl' (<>) mempty+
− lib/Rainbox/Reader.hs
@@ -1,293 +0,0 @@--- | 'Box' with many functions in a 'Reader' monad.------ The advantage of this module over "Rainbox" is that many of the--- functions have fewer arguments because they are instead carried--- in the 'Reader' monad. This also allows you to use four infix--- operators to easily join up 'Box'. The disadvantage is that--- using the 'Reader' monad adds a layer of indirection.-module Rainbox.Reader- ( -- * Backgrounds- B.Background(..)- , R.defaultBackground- , R.same-- -- * Box properties- , B.Bar(..)- , B.Box- , B.unBox-- -- * Height and columns- , B.Height(..)- , B.height- , B.Width(..)- , B.HasWidth(..)-- -- * Alignment- , B.Align- , B.Vert- , B.Horiz- , B.center- , B.top- , B.bottom- , B.left- , B.right-- -- * Reader monad- , Specs(..)- , Env- , runEnv-- -- * Making Boxes- , B.blank- , blankH- , blankV- , B.chunks- , R.chunk-- -- * Pasting Boxes together- , catH- , catV- , sepH- , sepV- , punctuateH- , punctuateV- , (<->)- , (<+>)- , (/-/)- , (/+/)-- -- * Viewing Boxes- , view- , viewH- , viewV-- -- * Growing Boxes- , grow- , growH- , growV- , column-- -- * Resizing- , resize- , resizeH- , resizeV-- -- * Printing Boxes- , R.render- , R.printBox- ) where---import Control.Monad.Trans.Reader-import Data.Functor.Identity-import qualified Rainbox.Box.Primitives as B-import qualified Rainbox.Box as R-import Rainbox.Box.Primitives- ( Box- , Height- , Width- , Align- , Horiz- , Vert- )--data Specs = Specs- { background :: B.Background- , alignH :: Align Horiz- , alignV :: Align Vert- , spaceH :: Int- -- ^ Amount of intervening space for horizontal joins- , spaceV :: Int- -- ^ Amount of intervening space for vertical joins- } deriving (Eq, Show)--type Env = ReaderT Specs--runEnv :: Specs -> Env Identity a -> a-runEnv s = runIdentity . ($ s) . runReaderT--blankH :: Monad m => Int -> Env m Box-blankH i = do- b <- asks background- return $ R.blankH b i--blankV :: Monad m => Int -> Env m Box-blankV i = do- b <- asks background- return $ R.blankV b i--catH :: Monad m => [Box] -> Env m Box-catH bxs = do- bk <- asks background- al <- asks alignV- return $ B.catH bk al bxs--catV :: Monad m => [Box] -> Env m Box-catV bxs = do- bk <- asks background- al <- asks alignH- return $ B.catV bk al bxs--grow :: Monad m => Height -> Width -> Box -> Env m Box-grow r c bx = do- b <- asks background- h <- asks alignH- v <- asks alignV- return $ R.grow b r c v h bx--growH :: Monad m => Int -> Box -> Env m Box-growH i bx = do- b <- asks background- h <- asks alignH- return $ R.growH b i h bx--growV :: Monad m => Int -> Box -> Env m Box-growV i bx = do- b <- asks background- v <- asks alignV- return $ R.growV b i v bx--column :: Monad m => [Box] -> Env m [Box]-column bs = do- bk <- asks background- ah <- asks alignH- return $ R.column bk ah bs--resize- :: Monad m => Height- -> Width- -> Box- -> Env m Box-resize r c bx = do- b <- asks background- h <- asks alignH- v <- asks alignV- return $ R.resize b r c v h bx--resizeH- :: Monad m => Int- -> Box- -> Env m Box-resizeH i bx = do- b <- asks background- h <- asks alignH- return $ R.resizeH b i h bx--resizeV- :: Monad m => Int- -> Box- -> Env m Box-resizeV i bx = do- b <- asks background- v <- asks alignV- return $ R.resizeV b i v bx--sepH- :: Monad m => Int- -> [Box]- -> Env m Box-sepH i bx = do- b <- asks background- v <- asks alignV- return $ R.sepH b i v bx--sepV- :: Monad m => Int- -> [Box]- -> Env m Box-sepV i bx = do- b <- asks background- h <- asks alignH- return $ R.sepV b i h bx--punctuateH- :: Monad m => Box- -> [Box]- -> Env m Box-punctuateH bx bxs = do- b <- asks background- v <- asks alignV- return $ R.punctuateH b v bx bxs--punctuateV- :: Monad m => Box- -> [Box]- -> Env m Box-punctuateV bx bxs = do- b <- asks background- h <- asks alignH- return $ R.punctuateV b h bx bxs--view- :: Monad m- => Height- -> Width- -> Box- -> Env m Box-view h w b = do- av <- asks alignV- ah <- asks alignH- return $ R.view h w av ah b--viewH- :: Monad m- => Int- -> Box- -> Env m Box-viewH h b = do- ah <- asks alignH- return $ B.viewH h ah b--viewV- :: Monad m- => Int- -> Box- -> Env m Box-viewV h b = do- av <- asks alignV- return $ B.viewV h av b---- | Paste two 'Box' together horizontally with no intervening--- space. Left fixity, precedence 5.-(<->) :: Monad m => Box -> Box -> Env m Box-(<->) l r = do- b <- asks background- a <- asks alignV- return $ B.catH b a [l, r]--infixl 5 <->---- | Paste two 'Box' together horizontally. Intervening space is--- determined by 'spaceH'.--- Left fixity, precedence 5.-(<+>) :: Monad m => Box -> Box -> Env m Box-(<+>) l r = do- bk <- asks background- a <- asks alignV- sp <- asks spaceH- bx <- blankH sp- return $ B.catH bk a [ l, bx, r ]--infixl 5 <+>---- | Paste two 'Box' together vertically with no intervening space.--- Left fixity, precedence 6.-(/-/) :: Monad m => Box -> Box -> Env m Box-(/-/) h l = do- b <- asks background- a <- asks alignH- return $ B.catV b a [ h, l ]--infixl 6 /-/---- | Paste two 'Box' together vertically. Intervening space is--- determined by 'spaceV'. Left fixity, precedence 6.-(/+/) :: Monad m => Box -> Box -> Env m Box-(/+/) h l = do- bk <- asks background- a <- asks alignH- sp <- asks spaceV- bx <- blankV sp- return $ B.catV bk a [ h, bx, l ]--infixl 6 /+/
+ lib/Rainbox/Tutorial.hs view
@@ -0,0 +1,570 @@+-- Modules might be imported solely so Haddock can hyperlink the+-- identifiers++{-# OPTIONS_GHC -fno-warn-unused-imports #-}+{-# LANGUAGE OverloadedStrings #-}++{-| The Rainbox tutorial++Rainbox helps you create arrangements of (possibly) colorful text+boxes. This module contains a tutorial. Typically the "Rainbox"+module contains all you need. There is also a "Rainbox.Core" module+which contains all the innards of Rainbox, but ordinarily you won't+need it.++The basic building block of Rainbox is known as a @core@. A core is+either a single 'Chunk' or a blank box of arbitrary size. A core made+of a single 'Chunk' should not contain any newlines. Leave newline+handling up to Rainbox. However, Rainbox will not check to ensure+that your 'Chunk' does not contain any newline characters. If it does+contain newlines, your boxes will not look right. Also, Rainbox needs+to know how wide your 'Chunk' are, in columns. To measure width,+Rainbox simply counts the number of characters in the 'Chunk'.+Therefore, if you need accented characters, be sure to use a single+character, not composed characters. That is, to get á, use U+00E1,+not U+00B4 and U+0061.++Many things in Rainbox have height and width. Both the height and+width of an object can be zero, but never less than zero. A @core@+made from a 'Chunk' always has a height of 1, and its width is equal+to the number of characters in the 'Text's that make up the 'Chunk'.+A @core@ made from a blank box has the height and width that you give+it, though neither its height nor its width is ever smaller than zero.++The next biggest building block after the @core@ is @payload@. There+are two different types of payloads: vertical payloads and horizontal+ones. A vertical payload aligns itself next to other vertical+payloads on a vertical axis, creating a chain of payloads. The+vertical payload also has an alignment. The alignment determines+whether the payload lines up along the axis on its left side, right+side, or in the center of the payload.++The vertical payload also has a background color, which has type+'Radiant'. Think of the background color as extending infinitely from+both the left and right side of the vertical payload. When the+vertical payload is combined with other vertical payloads into a 'Box'+'Vertical', this background color is used as necessary so that the+'Box' 'Vertical' forms a rectangle.++The horizontal payload is similar to the vertical payload, but the+axis is horizontal rather than vertical. The alignment determines+whether the payload aligns the axis along the top, center, or bottom+of the payload. A horizontal payload also contains a background+color; it extends infinitely from both the top and bottom of the+horizontal payload.++Finally, the biggest building block of Rainbox is the box. There are+two types of boxes: a 'Box' 'Horizontal', which holds zero or more horizontal+payloads, and a 'Box' 'Vertical', which holds zero or more vertical payloads.+Each kind of box is a 'Monoid', so you can combine it using the usual+monoid functions. So, to give a visual, a 'Box' 'Vertical' with five payloads+might look like this:++@++-- function: 'box1'++ V Vertical axis++ +----++ | v1 |+ | |+ +----+--------++ | v2 |+ | |+ +--------+-------------++ | |+ | v3 |+ | |+ | |+ +--------+----------++ | v4 |+ +----+----+-----++ | v5 |+ | |+ +---------+++@++Each payload is marked in the middle with @v1@, @v2@, etc. Note how+each payload has a different size. @v1@ and @v2@, and @v4@ have a+'left' alignment, as their left edge is lined up with the vertical+axis. @v3@ has a 'right' alignment. @v5@ has a 'center' alignment.+Think of each payload has having a background color extending+infinitely off of its left and right sides. These five payloads put+together make a 'Box' 'Vertical'. Since 'Box' 'Vertical' is a monoid,+you can combine various 'Box' 'Vertical'. Indeed, the pictured 'Box'+'Vertical' can be built only by combining smaller 'Box' 'Vertical', as+when you create payloads they are always given to you as a single+payload wrapped in a 'Box' 'Vertical'.++Now, you want to render your 'Box' 'Vertical'. You use the 'render' function,+which makes a sequence of Rainbow 'Chunk'. This turns your 'Box' 'Vertical'+into a nice rectangle for on-screen rendering:++@++-- function: 'renderBox1'++ +--------+----+--------++ | | v1 | |+ | | | |+ +--------+----+--------++ | | v2 |+ | | |+ +--------+-------------++ | | |+ | v3 | |+ | | |+ | | |+ +--------+----------+--++ | | v4 | |+ +---+----+----+-----+--++ | | v5 | |+ | | | |+ +---+---------+--------+++@++The spaces to the left and right of each payload are filled in with+the appropriate background color, which is the background+color of the adjoining payload.+++What if you want to place the 'Box' 'Vertical' alongside another box? If you+want to put it next to another 'Box' 'Vertical', just use 'mappend' or '<>'. But+what if you want to put it next to a 'Box' 'Horizontal'? Let's suppose you have a+'Box' 'Horizontal' that looks like this:++@++-- function: 'box2'++ +----++ | h1 |+ | |+Horizontal Axis > +----+----------++ | |+ | h2 |+ | |+ +----------+++@++The @h1@ payload has alignment 'bottom', because its bottom edge is+lined up along the horizontal axis. The @h2@ payload has alignment+'top'. You want to connect this 'Box' 'Horizontal' with the 'Box'+'Vertical' made above. You can't connect them directly because they+are different types. You can, however, take a complete 'Box' and wrap+it inside another 'Box'. This allows you to wrap a 'Box' 'Vertical'+inside of a 'Box' 'Horizontal', and vice versa, or even wrap a 'Box'+'Horizontal' inside of another 'Box' 'Horizontal'. You do this with+the 'wrap' function, which is applied to the alignment for the new+box, its background color, and the box you want to wrap. ao, let's+say you take the 'Box' 'Vertical' created above and wrap it inside a+'Box' 'Horizontal' with 'top' alignment and a background color, and+then you combine it with the 'Box' 'Horizontal' created above. The+result:++@++-- function: 'box3'++ +----++ | h1 |+ | |+ +--------+----+--------+----+----------++ | | v1 | | | |+ | | | | | h2 |+ +--------+----+--------+ | |+ | | v2 | +----------++ | | |+ +--------+-------------++ | | |+ | v3 | |+ | | |+ | | |+ +--------+----------+--++ | | v4 | |+ +---+----+----+-----+--++ | | v5 | |+ | | | |+ +---+---------+--------+++@++The old 'Box' 'Vertical', which is now wrapped in a 'Box'+'Horizontal', now has a background color which extends infinitely from+the top and bottom of the box. It is now just a payload inside of the+'Box' 'Horizontal'. The other two payloads in the 'Box' 'Horizontal',+@h1@ and @h2@, also have background colors extending from their tops+and bottoms.++So, when you render this 'Box' 'Horizontal' with 'render', you get this:+++@++-- function: 'renderBox3'++ +----------------------+----+----------++ | | h1 | |+ | | | |+ +--------+----+--------+----+----------++ | | v1 | | | |+ | | | | | h2 |+ +--------+----+--------+ | |+ | | v2 | +----------++ | | | | |+ +--------+-------------+ | |+ | | | | |+ | v3 | | | |+ | | | | |+ | | | | |+ +--------+----------+--+ | |+ | | v4 | | | |+ +---+----+----+-----+--+ | |+ | | v5 | | | |+ | | | | | |+ +---+---------+--------+----+----------+++@++The area above the old 'Box' 'Vertical' has the background color that we used in+the application of 'convert'. The area below the @h1@ payload has its+background color, and the area above and below the @h2@ payload has+its background color.++What if you just want to create an empty space? You can create entire+blank boxes with 'blank', but often it is enough to use 'spacer',+which gives you a one-dimensional 'Box' 'Horizontal' or 'Box'+'Vertical'. If you are creating a 'Box' 'Horizontal', 'spacer' gives+you a box with width, but no height; for a 'Box' 'Vertical', you get a+box with height, but no width. So, to return to the example 'Box'+'Horizontal', let's say you want to add a blank space a few columns+wide on the right side. You 'mappend' a 'Box' 'Horizontal' created+with 'spacer' to get this:++@++-- function: 'box4'++ +----++ | h1 |+ | |+Horizontal Axis > +----+----------+--++ | |+ | h2 |+ | |+ +----------++@++On the right side you now have a payload with width, but no height.+But it does have a background color. So when you 'render' the box, you+get this:+++@++-- function: 'renderBox4'++ +----+----------+--++ | h1 | | |+ | | | |+ +----+----------+ |+ | | | |+ | | h2 | |+ | | | |+ +----+----------+--+++@++You can also use 'spreader' to make a 'Box' 'Horizontal' taller or a+'Box' 'Vertical' wider. 'spreader' creates a one-dimensional 'Box'+that is perpendicular to the axis. Pay attention to the alignment of+the 'spreader' as this will determine how your box expands. Let's say+I want to take the 'Box' that contains the @h1@ and @h2@ payloads as+created above, but I also want to make sure the box is at least 12+rows tall. To do this, 'mappend' a 'spreader' that is 12 rows tall.+The result upon 'render'ing is:++@+ +-- functions: 'box5' 'renderBox5'++ +----+----------+--++ | | | |+ +----+ | |+ | h1 | | |+ | | | |+ +----+----------+ |+ | | | |+ | | h2 | |+ | | | |+ | +----------+ |+ | | | |+ +----+----------+--++@++Those are the basics of the Rainbox model, which should be enough to+get you started. Also helpful are the 'tableByRows' and+'tableByColumns' functions, which will help you build a simple grid+that resembles a spreadsheet; see its documentation for hints to get+started with that. You will also find an example using the+'tableByRows', as well as code to produce all of the examples shown+above, in the source code below.++== Why the use of 'Seq' everywhere, rather than lists?++Rainbox uses 'Seq' from "Data.Sequence" because lists can be+infinite. Practically every function in Rainbox will not accept+infinite inputs, because Rainbox needs to know exactly how long and+wide various payloads and boxes are in order to line them up+correctly. Use of the 'Seq' most accurately reflects the fact that+Rainbox does not work on infinite inputs.++-}+module Rainbox.Tutorial where++import Data.Foldable (toList)+import Data.Function ((&))+import Data.List (intersperse)+import Data.Monoid ((<>))+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import Data.Text (Text)+import qualified Data.Text as X+import qualified Rainbow+import qualified Rainbox++-- | Create a 'Box' for the given text. The default foreground and+-- background colors of the terminal are used for the 'Text'; the+-- given background is used as the background color for any added+-- padding.+textBox :: Rainbow.Radiant -> Text -> Rainbox.Box a+textBox r = Rainbox.fromChunk Rainbox.center r . Rainbow.chunk++-- | Centers the given 'Box' within a larger 'Box' that has the given+-- height and width and background color. The larger 'Box' has the+-- given 'Alignment'.+within+ :: Rainbox.Orientation a+ => Rainbox.Alignment a+ -> Int+ -- ^ Number of rows+ -> Int+ -- ^ Number of columns+ -> Rainbow.Radiant+ -- ^ Background color+ -> Rainbox.Box a+ -> Rainbox.Box a+within a r c b+ = Rainbox.wrap a b+ . mappend (Rainbox.spreader Rainbox.center r)+ . Rainbox.wrap Rainbox.centerH b+ . mappend (Rainbox.spreader Rainbox.center c)+ . Rainbox.wrap Rainbox.centerV b++-- | Puts the given text in the center of a box. The resulting box is+-- center aligned.+textWithin+ :: Rainbox.Orientation a+ => Rainbox.Alignment a+ -> Int+ -- ^ Number of rows+ -> Int+ -- ^ Number of columns+ -> Rainbow.Radiant+ -- ^ Background color for smaller box+ -> Rainbow.Radiant+ -- ^ Background color for larger box+ -> Text+ -> Rainbox.Box a+textWithin a r c bs bl = Rainbox.wrap a bl . within a r c bs . textBox bs++box1 :: Rainbox.Box Rainbox.Vertical+box1 = mconcat+ [ textWithin Rainbox.left 4 6 Rainbow.blue Rainbow.green "v1"+ , textWithin Rainbox.left 4 15 Rainbow.red Rainbow.magenta "v2"+ , textWithin Rainbox.right 6 10 Rainbow.yellow Rainbow.blue "v3"+ , textWithin Rainbox.left 3 12 Rainbow.green Rainbow.red "v4"+ , textWithin Rainbox.center 4 11 Rainbow.magenta Rainbow.blue "v5"+ ]++renderBox1 :: IO ()+renderBox1 = mapM_ Rainbow.putChunk . toList . Rainbox.render $ box1++box2 :: Rainbox.Box Rainbox.Horizontal+box2 = mconcat+ [ textWithin Rainbox.bottom 4 6 Rainbow.magenta Rainbow.green "h1"+ , textWithin Rainbox.top 5 12 Rainbow.blue Rainbow.yellow "h2"+ ]++renderBox2 :: IO ()+renderBox2 = mapM_ Rainbow.putChunk . toList . Rainbox.render $ box2++box3 :: Rainbox.Box Rainbox.Horizontal+box3 = mconcat+ [ Rainbox.wrap Rainbox.top Rainbow.yellow box1+ , box2+ ]++renderBox3 :: IO ()+renderBox3 = mapM_ Rainbow.putChunk . toList . Rainbox.render $ box3++box4 :: Rainbox.Box Rainbox.Horizontal+box4 = box2 <> Rainbox.spacer Rainbow.cyan 3++renderBox4 :: IO ()+renderBox4 = mapM_ Rainbow.putChunk . toList . Rainbox.render $ box4++box5 :: Rainbox.Box Rainbox.Horizontal+box5 = box4 <> Rainbox.spreader Rainbox.center 12++renderBox5 :: IO ()+renderBox5 = mapM_ Rainbow.putChunk . toList . Rainbox.render $ box5++-- Sample code for 'tableByRows'+--+-- Here is a simple data type representing stations in the+-- Washington DC Metrorail system.++data Line+ = Red+ | Blue+ | Orange+ | Green+ | Yellow+ | Silver+ deriving (Eq, Ord, Show, Enum)++data Station = Station+ { name :: Text+ , metroLines :: [Line]+ , address :: [Text]+ , underground :: Bool+ }++nameCell :: Rainbow.Radiant -> Text -> Rainbox.Cell+nameCell bk nm = Rainbox.Cell cks Rainbox.top Rainbox.center bk+ where+ cks = Seq.singleton . Seq.singleton $ (Rainbow.chunk nm & Rainbow.back bk)++linesCell :: Rainbow.Radiant -> [Line] -> Rainbox.Cell+linesCell bk lns = Rainbox.Cell cks Rainbox.top Rainbox.center bk+ where+ cks = Seq.fromList . fmap (lineRow bk) $ lns++lineRow :: Rainbow.Radiant -> Line -> Seq Rainbow.Chunk+lineRow bk li = Seq.singleton ck+ where+ ck = Rainbow.chunk (X.pack . show $ li) & Rainbow.fore clr & Rainbow.back bk+ clr = case li of+ Red -> Rainbow.red+ Blue -> Rainbow.blue+ Orange -> Rainbow.yellow <> Rainbow.color256 220+ Green -> Rainbow.green+ Yellow -> Rainbow.yellow+ Silver -> Rainbow.white <> Rainbow.grey+++addressCell :: Rainbow.Radiant -> [Text] -> Rainbox.Cell+addressCell bk lns = Rainbox.Cell cks Rainbox.top Rainbox.center bk+ where+ cks = Seq.fromList . fmap addrRow $ lns+ addrRow txt = Seq.singleton $ Rainbow.chunk txt & Rainbow.back bk++undergroundCell :: Rainbow.Radiant -> Bool -> Rainbox.Cell+undergroundCell bk bl+ = Rainbox.Cell (Seq.singleton . Seq.singleton $ ck) Rainbox.top Rainbox.left bk+ where+ ck = (Rainbow.chunk $ if bl then "Yes" else "No") & Rainbow.back bk++stations :: [Station]+stations =+ [ Station "Metro Center" [Red, Orange, Silver, Blue]+ ["607 13th St NW", "Washington, DC 20005"] True++ , Station "L'Enfant Plaza" [Orange, Silver, Blue, Green, Yellow]+ ["600 Maryland Ave SW", "Washington, DC 20024"] True++ , Station "Silver Spring" [Red]+ ["8400 Colesville Rd", "Silver Spring, MD 20910"] False++ , Station "Court House" [Silver, Orange]+ ["2100 Wilson Blvd", "Arlington, VA 22201"] True++ , Station "Prince George's Plaza" [Green, Yellow]+ ["3575 East-West Hwy", "Hyattsville, MD 20782"] True+ ]++coloredBack :: Rainbow.Radiant+coloredBack = Rainbow.color256 195++-- | Converts a 'Station' to a list of 'Cell'.++stationRow :: Rainbow.Radiant -> Station -> [Rainbox.Cell]+stationRow bk st =+ [ nameCell bk . name $ st+ , linesCell bk . metroLines $ st+ , addressCell bk . address $ st+ , undergroundCell bk . underground $ st+ ]++-- Building a table of stations on a row-by-row basis.++allStationRows :: [[Rainbox.Cell]]+allStationRows+ = zipWith stationRowWithSpacers (cycle [coloredBack, mempty]) stations+ where+ stationRowWithSpacers bk+ = intersperse (Rainbox.separator bk 1)+ . stationRow bk++verticalStationTable :: Rainbox.Box Rainbox.Vertical+verticalStationTable+ = Rainbox.tableByRows+ . Seq.fromList+ . fmap Seq.fromList+ $ allStationRows++renderVerticalStationTable :: IO ()+renderVerticalStationTable+ = mapM_ Rainbow.putChunk . toList . Rainbox.render $ verticalStationTable++-- Building a table of stations on a column-by-column basis.++stationColumn :: Station -> [Rainbox.Cell]+stationColumn st =+ [ nameCell coloredBack . name $ st+ , linesCell bland . metroLines $ st+ , addressCell coloredBack . address $ st+ , undergroundCell bland . underground $ st+ ]+ where+ bland = mempty++allStationColumns :: [[Rainbox.Cell]]+allStationColumns+ = intersperse spacerColumn+ . map stationColumn+ $ stations+ where+ spacerColumn = take 4 (cycle backgrounds)+ where+ backgrounds =+ [ Rainbox.separator coloredBack 1+ , Rainbox.separator mempty 1+ ]++horizontalStationTable :: Rainbox.Box Rainbox.Horizontal+horizontalStationTable+ = Rainbox.tableByColumns+ . Seq.fromList+ . fmap Seq.fromList+ $ allStationColumns+++renderHorizontalStationTable :: IO ()+renderHorizontalStationTable+ = mapM_ Rainbow.putChunk . toList . Rainbox.render $ horizontalStationTable
− lib/Rainbox/Tutorial.lhs
@@ -1,174 +0,0 @@-Rainbox tutorial - introduction-===============================--Rainbox (that is, *Rain*bow and *box*) helps you create colorful,-nicely formatted text boxes. It is based on the `rainbow` package,-which provides all the color support, so read the [documentation for-that](http://hackage.haskell.org/package/rainbow) before continuing.--[boxes](http://hackage.haskell.org/package/boxes) is a similar-package but without color support.--This file is written in literate Haskell, so it compile and run for-you. It also means that the compiler checks the examples, which-keeps them accurate. However, HsColour does not fare so well with-literate Haskell, so this file will not look good from the-hyperlinked source in Haddock. You're better off viewing it from a-text editor or through [the Gihub-website](http://www.github.com/massysett/rainbox).--A grid of boxes-===============--`Rainbox` is built on `Rainbox.Box`, which contains the building-blocks to create rectangular boxes of text. Each box is justified-as appropriate and is filled in with a background color to make it-rectangular.--If your needs are complex, use `Rainbox.Box`. Using it you can-patch boxes together into sort of a crazy quilt. For simpler needs-you can use `Rainbox`, which only allows you to create grids of-boxes, like a spreadsheet. This tutorial will show you how to use-the `Rainbox` module. Everything you need from this package will be-available from the `Rainbox` module. You will also need to import-packages from `rainbow`:--> {-# LANGUAGE OverloadedStrings #-}-> -- | If you are viewing this module in Haddock, note-> -- that the tutorial is contained in the source code of the-> -- module, which is written in literate Haskell.-> -- It is best viewed in your text editor or through-> -- Github at-> ---> -- <https://github.com/massysett/rainbox/blob/master/lib/Rainbox/Tutorial.lhs>--> module Rainbox.Tutorial where->-> import Data.List (intersperse)-> import Data.Monoid-> import Data.String-> import System.Console.Rainbow-> import Rainbox---Making a table of name data-===========================--For this example, we'll print a table of names, addresses, and-account balances. This type holds the data we're interested in:--> data Record = Record-> { firstName :: String-> , lastName :: String-> , address :: [String]-> , phone :: String-> , email :: String-> , balance :: String-> } deriving Show--And let's make a list of some sample data:--> records :: [Record]-> records =-> [ Record-> { firstName = "Nell"-> , lastName = "Langston"-> , address = [ "Owings Mills, MD 21117" ]-> , phone = "800-588-2300"-> , email = "NellJLangston@dayrep.com"-> , balance = "0"-> }-> -> , Record-> { firstName = "Sharon"-> , lastName = "Sutton"-> , address = [ "37 Church Street", "Flushing, NY 11354" ]-> , phone = "312-555-8100"-> , email = "SharonJSutton@teleworm.us"-> , balance = "1033.54"-> }-> -> , Record-> { firstName = "Barack"-> , lastName = "Obama"-> , address = [ "1600 Pennsylvania Ave NW", "Washington, DC" ]-> , phone = "877-CASH-NOW"-> , email = "president@whitehouse.gov"-> , balance = "23562.00"-> }-> -> , Record-> { firstName = "Bert and Ernie"-> , lastName = "Sesame"-> , address = [ "123 Sesame Street", "Lower Level",-> "Sesame, WN V6B432" ]-> , phone = "+45-123-4567"-> , email = "lower@rhyta.com"-> , balance = "100,451.05"-> }->-> , Record-> { firstName = "Vip"-> , lastName = "Vipperman"-> , address = [ "10000 Smiley Lane", "Denver, CO 80266" ]-> , phone = "303-555-1212"-> , email = "vipperman@rhyta.com"-> , balance = "301.00"-> }-> ]--Building each row of cells-==========================--`Rainbox` works with rows of `Cell`s. You build one list of `Cell`-for each row in your grid. Here we will make the last name bold,-and the rest of the text will be plain. We will also alternate each-row--every even row (starting with the first row) will be the-default color, and the odd rows will be yellow. We'll make a-function that takes a `Record` and returns another function that,-when applied to a `Chunk` that contains the color for the row,-returns a list of `Cell`. First let's make a small function that-will make a function that returns a `Cell` with our desired-defaults:--> cell :: [Chunk] -> Chunk -> Cell-> cell cks back = Cell brs left top (backgroundFromChunk back)-> where-> brs = map Bar . map ((:[]) . (<> back)) $ cks---> recordToCells :: Record -> Chunk -> [Cell]-> recordToCells r ck = map ($ ck) $-> [ cell . (:[]) . fromString . firstName $ r-> , cell . (:[]) $ (fromString (lastName r) <> bold)-> , cell . map fromString . address $ r-> , cell . (:[]) . fromString . phone $ r-> , cell . (:[]) . fromString . email $ r-> , cell . (:[]) . fromString . balance $ r-> ]--Zipping to get rows of cells-============================--> cellRows :: [[Cell]]-> cellRows = zipWith recordToCells records (cycle [mempty, f_yellow])--Adding white space between columns-==================================--If we print the table like it is now, there will be no whitespace,-as `Rainbox` does not add whitespace for you. Fortunately this is-easy to add. The string literal, " ", becomes a Cell due to the use-of the Overloaded Strings extension; the cell will have the default-background color.--> spacedOutCells :: [[Cell]]-> spacedOutCells = map (intersperse " ") cellRows--Printing the cells-==================--To see the result, run this function in ghci:--> printSampleBox :: IO ()-> printSampleBox = printBox . gridByRows $ spacedOutCells
− minimum-versions.txt
@@ -1,60 +0,0 @@-This package was tested to work with these dependency-versions and compiler version.-These are the minimum versions given in the .cabal file.-Tested as of: 2014-06-14 02:31:26.107979 UTC-Path to compiler: ghc-7.4.1-Compiler description: 7.4.1--/opt/ghc/7.4.1/lib/ghc-7.4.1/package.conf.d:- Cabal-1.14.0- array-0.4.0.0- base-4.5.0.0- bin-package-db-0.0.0.0- binary-0.5.1.0- bytestring-0.9.2.1- containers-0.4.2.1- deepseq-1.3.0.0- directory-1.1.0.2- extensible-exceptions-0.1.1.4- filepath-1.3.0.0- (ghc-7.4.1)- ghc-prim-0.2.0.0- (haskell2010-1.1.0.1)- (haskell98-2.0.0.1)- hoopl-3.8.7.3- hpc-0.5.1.1- integer-gmp-0.4.0.0- old-locale-1.0.0.4- old-time-1.1.0.0- pretty-1.1.1.0- process-1.1.0.1- rts-1.0- template-haskell-2.7.0.0- time-1.4- unix-2.5.1.0--/home/massysett/rainbox/sunlight-8257/db:- QuickCheck-2.7- ansi-terminal-0.6.1.1- ansi-wl-pprint-0.6.7.1- async-2.0.1.5- mtl-2.1.3.1- optparse-applicative-0.9.0- parsec-3.1.5- primitive-0.5.3.0- rainbow-0.14.0.0- rainbox-0.4.0.4- random-1.0.0.0- regex-base-0.93.2- regex-tdfa-1.2.0- stm-2.4.3- tagged-0.7.2- tasty-0.8- tasty-quickcheck-0.8.1- terminfo-0.4.0.0- text-0.11.3.1- tf-random-0.5- transformers-0.3.0.0- transformers-compat-0.3.3.3- unbounded-delays-0.1.0.7-
+ package.yaml view
@@ -0,0 +1,56 @@+# hpack package description.+# See+# https://github.com/sol/hpack+name: rainbox+synopsis: Two-dimensional box pretty printing, with colors+description: Please see README.md+version: 0.26.0.0+license: BSD3+license-file: LICENSE+copyright: Copyright 2014-2020 Omari Norman+author: Omari Norman+maintainer: omari@smileystation.com+stability: Experimental+homepage: https://www.github.com/massysett/rainbox+bug-reports: https://www.github.com/massysett/rainbox/issues+category: System+extra-source-files:+ - README.md+ - package.yaml+ - stack.yaml+ - changelog+verbatim:+ x-curation: uncurated++dependencies:+ - base >= 4.11 && < 5+ - text+ - bytestring+ - containers+ - rainbow+ - text+ - lens++ghc-options: -Wall+other-extensions: TemplateHaskell+source-dirs: lib+github: massysett/rainbox++library: {}++tests:+ rainbox-properties:+ main: rainbox-properties.hs+ source-dirs: test+ dependencies:+ - QuickCheck+ - tasty+ - tasty-quickcheck+ rainbox-visual:+ main: rainbox-visual.hs+ source-dirs: test+ dependencies:+ - QuickCheck+ - tasty+ - tasty-quickcheck+
rainbox.cabal view
@@ -1,161 +1,109 @@--- This Cabal file generated using the Cartel library.--- Cartel is available at:--- http://www.github.com/massysett/cartel+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.2. ----- Script name used to generate: genCabal.hs--- Generated on: 2014-06-13 22:45:04.610521 EDT--- Cartel library version: 0.10.0.2-name: rainbox-version: 0.4.0.4-cabal-version: >= 1.14-build-type: Simple-license: BSD3-license-file: LICENSE-copyright: Copyright 2014 Omari Norman-author: Omari Norman-maintainer: omari@smileystation.com-stability: Experimental-homepage: http://www.github.com/massysett/rainbox-bug-reports: http://www.github.com/massyett/rainbox/issues-synopsis: Two-dimensional box pretty printing, with colors-description:- Prints boxes in two dimensions, with colors. Boxes are- automatically padded with necessary whitespace.- .- For more information, please see the Haddock documentation and- .- <http://www.github.com/massysett/rainbox-category: Text-tested-with: GHC == 7.4.1, GHC == 7.6.3, GHC == 7.8.2+-- see: https://github.com/sol/hpack+--+-- hash: 193ccd0b7369b9e4c418620fcc9b26fc75f29e88edb4dad7b788f1a44827f352++name: rainbox+version: 0.26.0.0+synopsis: Two-dimensional box pretty printing, with colors+description: Please see README.md+category: System+stability: Experimental+homepage: https://www.github.com/massysett/rainbox+bug-reports: https://www.github.com/massysett/rainbox/issues+author: Omari Norman+maintainer: omari@smileystation.com+copyright: Copyright 2014-2020 Omari Norman+license: BSD3+license-file: LICENSE+build-type: Simple extra-source-files: README.md- , sunlight-test.hs- , minimum-versions.txt- , current-versions.txt- , changelog+ package.yaml+ stack.yaml+ changelog+x-curation: uncurated source-repository head type: git- location: git://github.com/massysett/rainbox.git- branch: master--Flag mosaic- Description: Build the rainbox-mosaic executable- Default: False- Manual: True+ location: https://github.com/massysett/rainbox -Library+library exposed-modules: Rainbox- , Rainbox.Array2d- , Rainbox.Box- , Rainbox.Box.Primitives- , Rainbox.Reader- , Rainbox.Tutorial- default-language: Haskell2010- ghc-options:- -Wall+ Rainbox.BicolorTable+ Rainbox.Core+ Rainbox.Tutorial+ other-modules:+ Paths_rainbox hs-source-dirs: lib+ other-extensions: TemplateHaskell+ ghc-options: -Wall build-depends:- base ((> 4.5.0.0 || == 4.5.0.0) && < 4.8.0.0)- , rainbow ((> 0.14.0.0 || == 0.14.0.0) && < 0.15)- , text ((> 0.11.3.1 || == 0.11.3.1) && < 1.2.0.0)- , transformers ((> 0.3.0.0 || == 0.3.0.0) && < 0.5.0.0)- , array ((> 0.4.0.0 || == 0.4.0.0) && < 0.6.0.0)--Executable rainbox-mosaic- main-is: rainbox-mosaic.hs- if flag(mosaic)- ghc-options:- -Wall- other-modules:- Rainbox- , Rainbox.Array2d- , Rainbox.Box- , Rainbox.Box.Primitives- , Rainbox.Reader- , Rainbox.Tutorial- , Rainbox.Array2dTests- , Rainbox.Box.PrimitivesTests- , Rainbox.BoxDir- , Rainbox.BoxTests- , Rainbox.ReaderTests- , RainboxDir- , RainboxTests- , Test.Rainbow.Generators- , Visual- hs-source-dirs:- test- , lib- default-language: Haskell2010- build-depends:- base ((> 4.5.0.0 || == 4.5.0.0) && < 4.8.0.0)- , rainbow ((> 0.14.0.0 || == 0.14.0.0) && < 0.15)- , text ((> 0.11.3.1 || == 0.11.3.1) && < 1.2.0.0)- , transformers ((> 0.3.0.0 || == 0.3.0.0) && < 0.5.0.0)- , array ((> 0.4.0.0 || == 0.4.0.0) && < 0.6.0.0)- build-depends:- tasty ((> 0.8 || == 0.8) && < 0.9)- , tasty-quickcheck ((> 0.8.1 || == 0.8.1) && < 0.9)- , QuickCheck ((> 2.7 || == 2.7) && < 2.8)- , random ((> 1.0.0.0 || == 1.0.0.0) && < 1.1)- else- buildable: False+ base >=4.11 && <5+ , bytestring+ , containers+ , lens+ , rainbow+ , text+ default-language: Haskell2010 -Test-Suite rainbox-test- ghc-options:- -Wall+test-suite rainbox-properties type: exitcode-stdio-1.0+ main-is: rainbox-properties.hs+ other-modules:+ Rainbox+ Rainbox.BicolorTable+ Rainbox.Core+ Rainbox.Tutorial+ Rainbow.Instances+ Rainbox.Instances+ Paths_rainbox hs-source-dirs:+ lib test- , lib- default-language: Haskell2010- build-depends:- base ((> 4.5.0.0 || == 4.5.0.0) && < 4.8.0.0)- , rainbow ((> 0.14.0.0 || == 0.14.0.0) && < 0.15)- , text ((> 0.11.3.1 || == 0.11.3.1) && < 1.2.0.0)- , transformers ((> 0.3.0.0 || == 0.3.0.0) && < 0.5.0.0)- , array ((> 0.4.0.0 || == 0.4.0.0) && < 0.6.0.0)- main-is: rainbox-test.hs+ other-extensions: TemplateHaskell+ ghc-options: -Wall build-depends:- tasty ((> 0.8 || == 0.8) && < 0.9)- , tasty-quickcheck ((> 0.8.1 || == 0.8.1) && < 0.9)- , QuickCheck ((> 2.7 || == 2.7) && < 2.8)+ QuickCheck+ , base >=4.11 && <5+ , bytestring+ , containers+ , lens+ , rainbow+ , tasty+ , tasty-quickcheck+ , text+ default-language: Haskell2010 -Test-Suite rainbox-visual+test-suite rainbox-visual type: exitcode-stdio-1.0- ghc-options:- -Wall+ main-is: rainbox-visual.hs other-modules: Rainbox- , Rainbox.Array2d- , Rainbox.Box- , Rainbox.Box.Primitives- , Rainbox.Reader- , Rainbox.Tutorial- , Rainbox.Array2dTests- , Rainbox.Box.PrimitivesTests- , Rainbox.BoxDir- , Rainbox.BoxTests- , Rainbox.ReaderTests- , RainboxDir- , RainboxTests- , Test.Rainbow.Generators- , Visual- main-is: rainbox-visual.hs+ Rainbox.BicolorTable+ Rainbox.Core+ Rainbox.Tutorial+ Rainbow.Instances+ Rainbox.Instances+ Paths_rainbox hs-source-dirs:+ lib test- , lib- default-language: Haskell2010- build-depends:- base ((> 4.5.0.0 || == 4.5.0.0) && < 4.8.0.0)- , rainbow ((> 0.14.0.0 || == 0.14.0.0) && < 0.15)- , text ((> 0.11.3.1 || == 0.11.3.1) && < 1.2.0.0)- , transformers ((> 0.3.0.0 || == 0.3.0.0) && < 0.5.0.0)- , array ((> 0.4.0.0 || == 0.4.0.0) && < 0.6.0.0)+ other-extensions: TemplateHaskell+ ghc-options: -Wall build-depends:- tasty ((> 0.8 || == 0.8) && < 0.9)- , tasty-quickcheck ((> 0.8.1 || == 0.8.1) && < 0.9)- , QuickCheck ((> 2.7 || == 2.7) && < 2.8)- , random ((> 1.0.0.0 || == 1.0.0.0) && < 1.1)+ QuickCheck+ , base >=4.11 && <5+ , bytestring+ , containers+ , lens+ , rainbow+ , tasty+ , tasty-quickcheck+ , text+ default-language: Haskell2010
+ stack.yaml view
@@ -0,0 +1,3 @@+resolver: lts-14.14+extra-deps:+ - rainbow-0.34.0.0
− sunlight-test.hs
@@ -1,15 +0,0 @@-module Main where--import Test.Sunlight--inputs = TestInputs- { tiDescription = Nothing- , tiCabal = "cabal"- , tiLowest = ("7.4.1", "ghc-7.4.1", "ghc-pkg-7.4.1")- , tiDefault = [ ("7.4.1", "ghc-7.4.1", "ghc-pkg-7.4.1")- , ("7.6.3", "ghc-7.6.3", "ghc-pkg-7.6.3")- , ("7.8.2", "ghc-7.8.2", "ghc-pkg-7.8.2") ]- , tiTest = []- }--main = runTests inputs
+ test/Rainbow/Instances.hs view
@@ -0,0 +1,104 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE FlexibleInstances, DeriveGeneric, StandaloneDeriving #-}++-- | QuickCheck instances for all of Rainbow. Currently Rainbow does+-- not use these instances itself; they are only here for+-- cut-and-paste for other libraries that may need them. There is an+-- executable in Rainbow that is built solely to make sure this module+-- compiles without any errors.+--+-- To use these instances, just drop them into your own project+-- somewhere. They are not packaged as a library because there are+-- orphan instances.++module Rainbow.Instances where++import Test.QuickCheck+import Rainbow.Types+import qualified Data.Text as X++instance Arbitrary a => Arbitrary (Color a) where+ arbitrary = Color <$> arbitrary+ shrink = genericShrink++instance CoArbitrary a => CoArbitrary (Color a) where+ coarbitrary (Color a) = coarbitrary a++varInt :: Int -> Gen b -> Gen b+varInt = variant++instance Arbitrary Enum8 where+ arbitrary = elements [E0, E1, E2, E3, E4, E5, E6, E7]+ shrink = genericShrink++instance CoArbitrary Enum8 where+ coarbitrary x = case x of+ E0 -> varInt 0+ E1 -> varInt 1+ E2 -> varInt 2+ E3 -> varInt 3+ E4 -> varInt 4+ E5 -> varInt 5+ E6 -> varInt 6+ E7 -> varInt 7++instance Arbitrary Format where+ arbitrary+ = Format <$> g <*> g <*> g <*> g <*> g <*> g <*> g <*> g+ where+ g = arbitrary+ shrink = genericShrink++instance CoArbitrary Format where+ coarbitrary (Format x0 x1 x2 x3 x4 x5 x6 x7)+ = coarbitrary x0+ . coarbitrary x1+ . coarbitrary x2+ . coarbitrary x3+ . coarbitrary x4+ . coarbitrary x5+ . coarbitrary x6+ . coarbitrary x7++instance Arbitrary a => Arbitrary (Style a) where+ arbitrary = Style <$> arbitrary <*> arbitrary <*> arbitrary+ shrink = genericShrink++instance CoArbitrary a => CoArbitrary (Style a) where+ coarbitrary (Style a b c)+ = coarbitrary a+ . coarbitrary b+ . coarbitrary c+++instance Arbitrary Scheme where+ arbitrary = Scheme <$> arbitrary <*> arbitrary+ shrink = genericShrink++instance CoArbitrary Scheme where+ coarbitrary (Scheme a b) = coarbitrary a . coarbitrary b++instance Arbitrary Chunk where+ arbitrary = Chunk <$> arbitrary <*> arbitrary+ shrink = genericShrink++instance CoArbitrary Chunk where+ coarbitrary (Chunk a b)+ = coarbitrary a+ . coarbitrary b++instance Arbitrary Radiant where+ arbitrary = Radiant <$> arbitrary <*> arbitrary+ shrink = genericShrink++instance CoArbitrary Radiant where+ coarbitrary (Radiant a b) = coarbitrary a . coarbitrary b++instance Arbitrary X.Text where+ arbitrary = fmap X.pack $ listOf genChar+ where+ genChar = elements ['a'..'z']+ shrink = fmap X.pack . shrink . X.unpack++instance CoArbitrary X.Text where+ coarbitrary = coarbitrary . X.unpack
− test/Rainbox/Array2dTests.hs
@@ -1,389 +0,0 @@-module Rainbox.Array2dTests where--import Test.Tasty-import Test.QuickCheck-import Test.Tasty.QuickCheck (testProperty)-import Data.Array-import Rainbox.Array2d---- | Generates a two-dimensional array of Int. The size of the--- array depends on the size parameter.-genArray :: Gen (Array (Int, Int) Int)-genArray = do- bnds <- genBounds- let nElems = rangeSize bnds- es <- vectorOf nElems arbitraryBoundedIntegral- return $ listArray bnds es---- | Generates array bounds. The size of the bounds depends on the--- size parameter.-genBounds :: Gen ((Int, Int), (Int, Int))-genBounds = do- w:x:y:z:[] <- vectorOf 4 arbitrarySizedIntegral- let (minC, maxC) | w < x = (w, x)- | otherwise = (x, w)- (minR, maxR) | y < z = (y, z)- | otherwise = (z, y)- return ((minC, minR), (maxC, maxR))--genTable :: Gen (Table (Int, [(Int, Int)]) (Int, [(Int, Int)]) Int Int Int)-genTable = do- ay <- genArray- return $ table (,) (,) ay--type LabelF- = (Int, [(Int, Int)])- -> (Int, [(Int, Int)])- -> Int -> Int -> Int -> Int--type ChangeLabelF- = (Int, [(Int, Int)])- -> Int- -> [((Int, [(Int, Int)]), Int, Int)]- -> Int--genLabelF :: Gen LabelF-genLabelF = arbitrary--genChangeLabelF :: Gen ChangeLabelF-genChangeLabelF = arbitrary---- # Properties---- | Bounds of columns in a Table matches those of the cells-propTableColsBounds- :: (Ix col, Ix row)- => Table lCol lRow col row a- -> Bool-propTableColsBounds tbl = bounds cls == tgtBounds- where- cls = lCols tbl- ((minC, _), (maxC, _)) = bounds . cells $ tbl- tgtBounds = (minC, maxC)---- | Bounds of rows in a Table matches those of the cells-propTableRowsBounds- :: (Ix col, Ix row)- => Table lCol lRow col row a- -> Bool-propTableRowsBounds tbl = bounds rws == tgtBounds- where- rws = lRows tbl- ((_, minR), (_, maxR)) = bounds . cells $ tbl- tgtBounds = (minR, maxR)---- | Generating a table using the contents of the rows as labels--- allows reconstruction of the original array--propGenRebuildByRow- :: (Ix col, Ix row, Eq a)- => Array (col, row) a- -> Bool-propGenRebuildByRow ay = ay == ay'- where- ay' = array (bounds ay) . concat . elems . lRows- . table (\_ _ -> ()) fRow $ ay- fRow rw ls = map g ls- where- g (col, a) = ((col, rw), a)---- | Generating a table using the contents of the columns as labels--- allows reconstruction of the original array--propGenRebuildByCol- :: (Ix col, Ix row, Eq a)- => Array (col, row) a- -> Bool-propGenRebuildByCol ay = ay == ay'- where- ay' = array (bounds ay) . concat . elems . lCols- . table fCol (\_ _ -> ()) $ ay- fCol cl ls = map g ls- where- g (rw, a) = ((cl, rw), a)---- | Round-tripping through rows and arrayByRows-propRoundTripRows- :: Eq a- => Array (Int, Int) a- -> Bool-propRoundTripRows ay = sameShape ay ay'- where- ay' = arrayByRows . rows $ ay---- | Round-tripping through columns and arrayByCols-propRoundTripCols- :: (Eq a, Show a)- => Array (Int, Int) a- -> Bool-propRoundTripCols ay = sameShape ay ay'- where- ay' = arrayByCols . cols $ ay---- | True if both arrays have the same shape; that is, the same--- number of rows and the same number of columns and the same--- elements.--sameShape- :: (Ix col, Ix row, Eq a)- => Array (col, row) a- -> Array (col, row) a- -> Bool-sameShape x y = rx == ry && cx == cy && ex == ey- where- ((minCx, minRx), (maxCx, maxRx)) = bounds x- ((minCy, minRy), (maxCy, maxRy)) = bounds y- rx = rangeSize (minRx, maxRx)- ry = rangeSize (minRy, maxRy)- cx = rangeSize (minCx, maxCx)- cy = rangeSize (minCy, maxCy)- ex = elems x- ey = elems y---- # mapTable properties---- | mapTable does not change lCols-mapTableNoChangeCols- :: (Ix col, Ix row, Eq lCol)- => (lCol -> lRow -> col -> row -> a -> b)- -> Table lCol lRow col row a- -> Bool-mapTableNoChangeCols f t = lCols t == lCols t'- where- t' = mapTable f t---- | mapTable does not change lRows-mapTableNoChangeRows- :: (Ix col, Ix row, Eq lRow)- => (lCol -> lRow -> col -> row -> a -> b)- -> Table lCol lRow col row a- -> Bool-mapTableNoChangeRows f t = lRows t == lRows t'- where- t' = mapTable f t---- | mapTable allows rebuild of original array-mapTableRebuildNoIndices- :: (Ix col, Ix row, Eq a)- => Table lCol lRow col row a- -> Bool-mapTableRebuildNoIndices tbl = cells tbl == ay'- where- ay' = listArray (bounds . cells $ tbl) . elems . cells- . mapTable f $ tbl- f _ _ _ _ a = a--mapTableRebuildWithIndices- :: (Ix col, Ix row, Eq a)- => Table lCol lRow col row a- -> Bool-mapTableRebuildWithIndices tbl = cells tbl == ay'- where- ay' = array (bounds . cells $ tbl) . elems . cells- . mapTable f $ tbl- f _ _ cl rw a = ((cl, rw), a)---- # labelRows and labelCols properties---- | labelCols allows rebuild of original array-propLabelColsRebuild- :: (Ix col, Ix row, Eq a)- => Array (col, row) a- -> Bool-propLabelColsRebuild ay = ay == ay'- where- ay' = array (bounds ay) . concat . elems- . labelCols f $ ay- f cl ls = map g ls- where- g (rw, a) = ((cl, rw), a)---- | labelRows allows rebuild of original array-propLabelRowsRebuild- :: (Ix col, Ix row, Eq a)- => Array (col, row) a- -> Bool-propLabelRowsRebuild ay = ay == ay'- where- ay' = array (bounds ay) . concat . elems- . labelRows f $ ay- f rw ls = map g ls- where- g (cl, a) = ((cl, rw), a)---- # mapRowLabels properties---- | mapRowLabels does not change column labels-propMapRowLabelsCols- :: (Ix col, Ix row, Eq lCol)- => (lRow -> row -> [(lCol, col, a)] -> lRow')- -> Table lCol lRow col row a- -> Bool-propMapRowLabelsCols f tb = lbls == lbls'- where- lbls = lCols tb- tb' = mapRowLabels f tb- lbls' = lCols tb'---- | mapRowLabels does not change cells-propMapRowLabelsCells- :: (Ix col, Ix row, Eq a, Eq lCol)- => (lRow -> row -> [(lCol, col, a)] -> lRow')- -> Table lCol lRow col row a- -> Bool-propMapRowLabelsCells f tb = ay == ay'- where- ay = cells tb- tb' = mapRowLabels f tb- ay' = cells tb'---- | mapRowLabels permits reconstruction of original array-propMapRowLabelsRebuild- :: (Ix col, Ix row, Eq a)- => Table lCol lRow col row a- -> Bool-propMapRowLabelsRebuild t = ay == ay'- where- ay = cells t- ay' = array (bounds ay) . concat . elems- . lRows . mapRowLabels f $ t- f _ rw ls = map g ls- where- g (_, cl, a) = ((cl, rw), a)---- | mapRowLabels gives the original row labels-propMapRowLabelsRelabel- :: (Ix col, Ix row, Eq a, Eq lRow, Eq lCol)- => Table lCol lRow col row a- -> Bool-propMapRowLabelsRelabel t = t == t'- where- t' = mapRowLabels (\r _ _ -> r) t---- # mapColLabels properties---- | mapColLabels does not change row labels-propMapColLabelsCols- :: (Ix col, Ix row, Eq lRow)- => (lCol -> col -> [(lRow, row, a)] -> lCol')- -> Table lCol lRow col row a- -> Bool-propMapColLabelsCols f tb = lbls == lbls'- where- lbls = lRows tb- tb' = mapColLabels f tb- lbls' = lRows tb'---- | mapColLabels does not change cells-propMapColLabelsCells- :: (Ix col, Ix row, Eq a, Eq lCol)- => (lCol -> col -> [(lRow, row, a)] -> lCol')- -> Table lCol lRow col row a- -> Bool-propMapColLabelsCells f tb = ay == ay'- where- ay = cells tb- tb' = mapColLabels f tb- ay' = cells tb'---- | mapColLabels permits reconstruction of original array-propMapColLabelsRebuild- :: (Ix col, Ix row, Eq a)- => Table lCol lRow col row a- -> Bool-propMapColLabelsRebuild t = ay == ay'- where- ay = cells t- ay' = array (bounds ay) . concat . elems- . lCols . mapColLabels f $ t- f _ cl ls = map g ls- where- g (_, rw, a) = ((cl, rw), a)---- | mapColLabels gives the original row labels-propMapColLabelsRelabel- :: (Ix col, Ix row, Eq a, Eq lRow, Eq lCol)- => Table lCol lRow col row a- -> Bool-propMapColLabelsRelabel t = t == t'- where- t' = mapColLabels (\r _ _ -> r) t--tests :: TestTree-tests = testGroup "Array2d"- [ testProperty "bounds of columns in Table matches those of cells" $- forAll genTable $- propTableColsBounds-- , testProperty "bounds of rows in Table matches those of cells" $- forAll genTable $- propTableRowsBounds-- , testProperty "propGenRebuildByRow" $- forAll genArray propGenRebuildByRow-- , testProperty "propGenRebuildByCol" $- forAll genArray propGenRebuildByCol-- , testProperty "propRoundTripRows" $- forAll genArray propRoundTripRows-- , testProperty "propRoundTripCols" $- forAll genArray propRoundTripCols-- , testProperty "mapTableNoChangeCols" $- forAll genLabelF $ \f ->- forAll genTable $ \t ->- mapTableNoChangeCols f t-- , testProperty "mapTableNoChangeRows" $- forAll genLabelF $ \f ->- forAll genTable $ \t ->- mapTableNoChangeRows f t-- , testProperty "mapTableRebuildNoIndices" $- forAll genTable mapTableRebuildNoIndices-- , testProperty "mapTableRebuildWithIndices" $- forAll genTable mapTableRebuildWithIndices-- , testProperty "propLabelColsRebuild" $- forAll genArray propLabelColsRebuild-- , testProperty "propLabelRowsRebuild" $- forAll genArray propLabelRowsRebuild-- , testProperty "propMapRowLabelsCols" $- forAll genChangeLabelF $ \f ->- forAll genTable $ \t ->- propMapRowLabelsCols f t-- , testProperty "propMapRowLabelsCells" $- forAll genChangeLabelF $ \f ->- forAll genTable $ \t ->- propMapRowLabelsCells f t-- , testProperty "propMapRowLabelsRebuild" $- forAll genTable propMapRowLabelsRebuild-- , testProperty "propMapRowLabelsRelabel" $- forAll genTable propMapRowLabelsRelabel-- , testProperty "propMapColLabelsCols" $- forAll genChangeLabelF $ \f ->- forAll genTable $ \t ->- propMapColLabelsCols f t-- , testProperty "propMapColLabelsCells" $- forAll genChangeLabelF $ \f ->- forAll genTable $ \t ->- propMapColLabelsCells f t-- , testProperty "propMapColLabelsRebuild" $- forAll genTable propMapColLabelsRebuild-- , testProperty "propMapColLabelsRelabel" $- forAll genTable propMapColLabelsRelabel-- ]-
− test/Rainbox/Box/PrimitivesTests.hs
@@ -1,222 +0,0 @@-module Rainbox.Box.PrimitivesTests where--import Control.Monad-import Control.Applicative-import Test.Tasty-import Test.Tasty.QuickCheck (testProperty)-import Test.QuickCheck-import System.Console.Rainbow-import qualified Data.Text as X-import qualified Test.Rainbow.Generators as G-import Rainbox.Box.Primitives-import Rainbox.Box (backgroundToTextSpec)--genText :: Gen X.Text-genText = fmap X.pack $ listOf c- where- c = elements ['0'..'Z']--genChunk :: Gen Chunk-genChunk = listOf genText >>= G.chunk--genHeight :: Gen Height-genHeight = fmap Height $ frequency [(3, nonNeg), (1, neg)]- where- nonNeg = fmap getNonNegative arbitrarySizedIntegral- neg = fmap (negate . abs) arbitrarySizedIntegral--genWidth :: Gen Width-genWidth = fmap Width $ frequency [(3, nonNeg), (1, neg)]- where- nonNeg = fmap abs arbitrarySizedIntegral- neg = fmap (negate . abs) arbitrarySizedIntegral--genBackground :: Gen Background-genBackground = liftM2 Background G.colors8 G.colors256---- | Generates blank Box.-genBlankBox :: Gen Box-genBlankBox = liftM3 blank genBackground rw cl- where- rw = fmap (Height . abs) arbitrarySizedIntegral- cl = fmap (Width . abs) arbitrarySizedIntegral---- | Generates a box using chunks.-genChunkBox :: Gen Box-genChunkBox = fmap chunks $ listOf genChunk---- | Generates a box using catH.-genCatHBox :: Gen Box-genCatHBox = sized $ \s -> do- bk <- genBackground- av <- genAlignVert- bs <- listOf (resize (s `div` 2) genBox)- return $ catH bk av bs---- | Generates a box using catV.-genCatVBox :: Gen Box-genCatVBox = sized $ \s -> do- bk <- genBackground- ah <- genAlignHoriz- bs <- listOf (resize (s `div` 2) genBox)- return $ catV bk ah bs---- | Generates a random box.-genBox :: Gen Box-genBox = oneof [ genBlankBox, genCatHBox, genCatVBox, genChunkBox ]--genChunkLen :: Background -> Int -> Gen Chunk-genChunkLen bk l = do- let ts = backgroundToTextSpec bk- txt <- fmap X.pack $ vectorOf l (elements ['0'..'Z'])- return $ Chunk ts [txt]---- | Generates a box of text; its horizontal and vertical size--- depends on the size parameter.-genTextBox :: Gen Box-genTextBox = do- w <- fmap abs arbitrarySizedIntegral- h <- fmap abs arbitrarySizedIntegral- bk <- genBackground- cks <- vectorOf h (genChunkLen bk w)- let bxs = map (chunks . (:[])) cks- bk' <- genBackground- return $ catV bk' left bxs------ # Alignment--genAlignVert :: Gen (Align Vert)-genAlignVert = elements- [ center, top, bottom ]--genAlignHoriz :: Gen (Align Horiz)-genAlignHoriz = elements [ center, left, right ]--validBox :: Box -> Bool-validBox box = case unBox box of- NoHeight i -> i > -1- WithHeight rw -> case rw of- [] -> False- x:xs -> all (== width x) . map width $ xs--biggest :: Int -> Gen a -> Gen a-biggest m g = sized $ \s -> resize (min s m) g--data Inputs = Inputs- { iChunks :: [Chunk]- , iBackground :: Background- , iHeight :: Height- , iWidth :: Width- , iVert :: Align Vert- , iHoriz :: Align Horiz- , iBoxes :: [Box]- , iBox :: Box- , iChunk :: Chunk- } deriving Show--instance Arbitrary Inputs where- arbitrary = Inputs- <$> listOf genChunk- <*> genBackground- <*> genHeight- <*> genWidth- <*> genAlignVert- <*> genAlignHoriz- <*> listOf genBlankBox- <*> genBlankBox- <*> genChunk--tests :: TestTree-tests = testGroup "BoxTests"- [ testGroup "blank"- [ testProperty "makes valid Box" $ \i ->- validBox $ blank (iBackground i) (iHeight i)- (iWidth i)-- , testProperty "has right number of rows" $ \i ->- let ht = unHeight . iHeight $ i- in (== max 0 ht) . height $ blank (iBackground i)- (iHeight i) (iWidth i)-- , testProperty "has right number of columns" $ \i ->- let wt = unWidth . iWidth $ i- in (== max 0 wt) . width $ blank (iBackground i)- (iHeight i) (iWidth i)- ]-- , testGroup "chunks"- [ testProperty "makes valid Box" $- validBox . chunks . iChunks-- , testProperty "makes Box whose height is 1" $- (== 1) . height . chunks . iChunks-- , testProperty "makes Box with cols == number of characters" $ \i ->- let cks = iChunks i- nChars = sum . map X.length . concat . map text $ cks- in (== nChars) . width $ chunks cks- ]-- , testGroup "catH"- [ testProperty "makes valid Box" $ \i ->- validBox $ catH (iBackground i) (iVert i) (iBoxes i)-- , testProperty "is as tall as tallest box" $ \i ->- let h = maximum . (0 :) . map height $ bs- bs = iBoxes i- in (== h) . height $ catH (iBackground i) (iVert i) bs-- , testProperty "is as wide as sum of all widths" $ \i ->- let s = sum . map width $ bs- bs = iBoxes i- in (== s) . width $ catH (iBackground i) (iVert i) bs- ]-- , testGroup "catV"- [ testProperty "makes a valid Box" $ \i ->- validBox $ catV (iBackground i) (iHoriz i) (iBoxes i)-- , testProperty "is as tall as the sum of all heights" $ \i ->- let h = sum . map height $ bs- bs = iBoxes i- in (== h) . height $ catV (iBackground i) (iHoriz i) bs-- , testProperty "is as wide as the widest box" $ \i ->- let w = maximum . (0:) . map width $ bs- bs = iBoxes i- in (== w) . width $ catV (iBackground i) (iHoriz i) bs- ]-- , testGroup "viewH"- [ testProperty "makes a valid Box" $ \i ->- validBox $ viewH (unWidth . iWidth $ i) (iHoriz i) (iBox i)-- , testProperty "number of rows does not change" $ \i ->- let b = iBox i- in (== height b) . height $ viewH (unWidth . iWidth $ i)- (iHoriz i) b-- , testProperty "number of columns <= number requested" $ \i ->- let c = unWidth . iWidth $ i- tgt = max 0 c- in (<= tgt) . width $ viewH c (iHoriz i) (iBox i)- ]-- , testGroup "viewV"- [ testProperty "makes a valid Box" $ \i ->- validBox $ viewV (unHeight . iHeight $ i) (iVert i) (iBox i)-- , testProperty "width does not change" $ \i ->- let b = iBox i- in (== width b) . width $ viewV (unHeight . iHeight $ i)- (iVert i) b-- , testProperty "number of rows <= number requested" $ \i ->- let r = unHeight . iHeight $ i- tgt = max 0 r- in (<= tgt) . height $ viewV r (iVert i) (iBox i)- ]- ]-
− test/Rainbox/BoxDir.hs
@@ -1,7 +0,0 @@-module Rainbox.BoxDir where--import qualified Rainbox.Box.PrimitivesTests-import Test.Tasty--tests :: TestTree-tests = testGroup "Box" [ Rainbox.Box.PrimitivesTests.tests ]
− test/Rainbox/BoxTests.hs
@@ -1,207 +0,0 @@-module Rainbox.BoxTests where--import Rainbox.Box-import Rainbox.Box.PrimitivesTests-import qualified Data.Text as X-import Test.Tasty.QuickCheck (testProperty)-import Test.Tasty-import System.Console.Rainbow-import Test.QuickCheck hiding (resize)--tests :: TestTree-tests = testGroup "RainboxTests"- [ testGroup "blankH"- [ testProperty "makes Box with no height" $ \i ->- (== 0) . height $ blankH (iBackground i) (unWidth . iWidth $ i)-- , testProperty "makes Box with correct width" $ \i ->- let w = unWidth . iWidth $ i- tgt = max 0 w- in (== tgt) . width $ blankH (iBackground i) w- ]-- , testGroup "blankV"- [ testProperty "makes Box with no width" $ \i ->- (== 0) . width $ blankV (iBackground i) (unHeight . iHeight $ i)-- , testProperty "makes Box with correct height" $ \i ->- let h = unHeight . iHeight $ i- tgt = max 0 h- in (== tgt) . height $ blankV (iBackground i) h- ]-- , testGroup "chunk"- [ testProperty "makes Box one high" $- (== 1) . height . chunk . iChunk-- , testProperty "makes Box as wide as characters in chunk" $ \i ->- let cs = sum . map X.length . text . iChunk $ i- in (== cs) . width . chunk . iChunk $ i- ]-- , testGroup "growH"- [ testProperty "does not change height" $ \i ->- let bx = iBox i- in (== height bx) . height $ growH (iBackground i)- (unWidth . iWidth $ i) (iHoriz i) bx-- , testProperty "new Box is of correct width" $ \i ->- let bx = iBox i- tgt = max wdth (width bx)- wdth = unWidth . iWidth $ i- in (== tgt) . width $- growH (iBackground i) wdth (iHoriz i) bx-- , testProperty "new Box is at least as wide as old Box" $ \i ->- let bx = iBox i- in (>= width bx) . width $ growH (iBackground i)- (unWidth . iWidth $ i) (iHoriz i) bx- ]-- , testGroup "growV"- [ testProperty "does not change width" $ \i ->- let bx = iBox i- in (== width bx) . width $ growV (iBackground i)- (unWidth . iWidth $ i) (iVert i) bx-- , testProperty "new Box is of correct height" $ \i ->- let bx = iBox i- tgt = max (height bx) hght- hght = unHeight . iHeight $ i- in (== tgt) . height $- growV (iBackground i) hght (iVert i) bx-- , testProperty "new Box is at least as tall as old Box" $ \i ->- let bx = iBox i- in (>= height bx) . height $ growV (iBackground i)- (unWidth . iWidth $ i) (iVert i) bx- ]-- , testGroup "grow"- [ testProperty "new Box is of correct width" $ \i ->- let bx = iBox i- tgt = unWidth . iWidth $ i- in (\w -> w == width bx || w == tgt) . width $- growH (iBackground i) tgt (iHoriz i) bx-- , testProperty "new Box is at least as wide as old Box" $ \i ->- let bx = iBox i- in (>= width bx) . width $ grow (iBackground i) (iHeight i)- (iWidth i) (iVert i) (iHoriz i) (iBox i)-- , testProperty "new Box is of correct height" $ \i ->- let bx = iBox i- tgt = unHeight . iHeight $ i- in (\h -> h == height bx || h == tgt) . height $- grow (iBackground i) (iHeight i) (iWidth i)- (iVert i) (iHoriz i) (iBox i)-- , testProperty "new Box is at least as tall as old Box" $ \i ->- let bx = iBox i- in (>= height bx) . height $ grow (iBackground i)- (iHeight i) (iWidth i) (iVert i) (iHoriz i) (iBox i)- ]-- , testGroup "column"- [ testProperty "number of inputs == number of outputs" $ \i ->- let bs = iBoxes i- in (== length bs) . length $ column (iBackground i) (iHoriz i) bs-- , testProperty "width of outputs is identical" $ \i ->- case column (iBackground i) (iHoriz i) (iBoxes i) of- [] -> True- x:xs -> all (== width x) . map width $ xs-- , testProperty "width of output is as wide as widest input" $ \i ->- let r = column (iBackground i) (iHoriz i) (iBoxes i)- in case iBoxes i of- [] -> null r- xs -> width (head r) == (maximum . map width $ xs)- ]-- , testGroup "resizeH"- [ testProperty "height of resulting Box unchanged" $ \i ->- let bx = iBox i- in (== height bx) . height $ resizeH (iBackground i)- (unWidth . iWidth $ i) (iHoriz i) bx-- , testProperty "result has desired width" $ \i ->- let tgt = max 0 . unWidth . iWidth $ i- in (== tgt) . width $ resizeH (iBackground i)- (unWidth . iWidth $ i) (iHoriz i) (iBox i)- ]-- , testGroup "resizeV"- [ testProperty "width of resulting Box unchanged" $ \i ->- let bx = iBox i- in (== width bx) . width $ resizeV (iBackground i)- (unHeight . iHeight $ i) (iVert i) bx-- , testProperty "result has desired height" $ \i ->- let tgt = max 0 . unHeight . iHeight $ i- in (== tgt) . height $ resizeV (iBackground i)- (unHeight . iHeight $ i) (iVert i) (iBox i)- ]-- , testGroup "resize"- [ testProperty "result has desired height" $ \i ->- let tgt = max 0 . unHeight . iHeight $ i- in (== tgt) . height $ resize (iBackground i)- (iHeight i) (iWidth i)- (iVert i) (iHoriz i) (iBox i)-- , testProperty "result has desired width" $ \i ->- let tgt = max 0 . unWidth . iWidth $ i- in (== tgt) . width $ resize (iBackground i)- (iHeight i) (iWidth i) (iVert i) (iHoriz i)- (iBox i)- ]-- , testGroup "punctuateH"- [ testProperty "result has desired width" $ \i ->- let tgt = (sum . map width $ bs)- + width bx * (max 0 $ len - 1)- len = length bs- bs = iBoxes i- bx = iBox i- in (== tgt) . width $ punctuateH (iBackground i)- (iVert i) bx bs- ]-- , testGroup "punctuateV"- [ testProperty "result has desired height" $ \i ->- let tgt = (sum . map height $ bs)- + height bx * (max 0 $ len - 1)- len = length bs- bs = iBoxes i- bx = iBox i- in (== tgt) . height $ punctuateV (iBackground i)- (iHoriz i) bx bs- ]-- -- Have to cap size on this one, which is not satisfying. There- -- are no apparent bugs. Apparently what is taking so long is the- -- Text.replicate in Box.blanks, which is applied from- -- Box.padHoriz.- , testGroup "sepH"- [ testProperty "result has correct width" $- forAll arbitrarySizedIntegral $ \len ->- forAll arbitrary $ \i ->- let tgt = (sum . map width $ bs)- + max 0 len * max 0 (length bs - 1)- bs = iBoxes i- in (== tgt) . width $ sepH (iBackground i) len- (iVert i) (iBoxes i)- ]-- , testGroup "sepV"- [ testProperty "result has correct height" $- forAll arbitrarySizedIntegral $ \len ->- forAll arbitrary $ \i ->- let tgt = (sum . map height $ bs)- + max 0 len * max 0 (length bs - 1)- bs = iBoxes i- in (== tgt) . height $ sepV (iBackground i) len- (iHoriz i) (iBoxes i)- ]- ]
+ test/Rainbox/Instances.hs view
@@ -0,0 +1,65 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Rainbox.Instances where++import Control.Monad+import Test.QuickCheck+import Rainbox.Core+import Rainbox.BicolorTable+import Rainbow.Instances ()+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq++instance Arbitrary a => Arbitrary (Alignment a) where+ arbitrary = oneof [ return Center, fmap NonCenter arbitrary ]++instance Arbitrary Horizontal where+ arbitrary = elements [ Top, Bottom ]++instance Arbitrary Vertical where+ arbitrary = elements [ Port, Starboard ]++instance Arbitrary Height where+ arbitrary = fmap Height $ frequency+ [ (3, fmap getNonNegative arbitrary)+ , (1, arbitrary)+ ]++instance Arbitrary Width where+ arbitrary = fmap Width $ frequency+ [ (3, fmap getNonNegative arbitrary)+ , (1, arbitrary)+ ]++instance Arbitrary Core where+ arbitrary = fmap Core arbitrary++instance Arbitrary Rod where+ arbitrary = fmap Rod arbitrary++newtype NonEmptySeq a = NonEmptySeq { getNonEmptySeq :: Seq a }+ deriving Show++instance Arbitrary a => Arbitrary (NonEmptySeq a) where+ arbitrary = do+ NonEmpty xs <- arbitrary+ return . NonEmptySeq . Seq.fromList $ xs++instance Arbitrary RodRows where+ arbitrary = sized $ \s -> resize (s `div` 10) $ oneof+ [ fmap (RodRowsWithHeight . getNonEmptySeq) arbitrary+ , frequency [ (1, fmap RodRowsNoHeight arbitrary)+ , (3, fmap (RodRowsNoHeight . getNonNegative) arbitrary)+ ]+ ]++instance Arbitrary a => Arbitrary (Payload a) where+ arbitrary = liftM3 Payload arbitrary arbitrary arbitrary++instance Arbitrary a => Arbitrary (Box a) where+ arbitrary = fmap Box arbitrary++instance Arbitrary Cell where+ arbitrary = liftM4 Cell arbitrary arbitrary arbitrary arbitrary++instance Arbitrary BicolorTable where+ arbitrary = BicolorTable <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
− test/Rainbox/ReaderTests.hs
@@ -1,133 +0,0 @@-module Rainbox.ReaderTests where--import qualified Rainbox.Box as R-import Rainbox.Reader-import Rainbox.Box.PrimitivesTests-import Test.QuickCheck hiding (resize)-import Test.Tasty.QuickCheck (testProperty)-import Test.Tasty-import Data.Functor.Identity--tests :: TestTree-tests = testGroup "ReaderTests"- [ testProperty "blankH" $ \(SpecPair i s) ->- let p = R.blankH (iBackground i) (unWidth . iWidth $ i)- in testEq s (blankH (unWidth . iWidth $ i)) p-- , testProperty "blankV" $ \(SpecPair i s) ->- let p = R.blankV (iBackground i) (unHeight . iHeight $ i)- in testEq s (blankV (unHeight . iHeight $ i)) p-- , testProperty "catH" $ \(SpecPair i s) ->- let p = R.catH (iBackground i) (iVert i) (iBoxes i)- in testEq s (catH (iBoxes i)) p-- , testProperty "catV" $ \(SpecPair i s) ->- let p = R.catV (iBackground i) (iHoriz i) (iBoxes i)- in testEq s (catV (iBoxes i)) p-- , testProperty "grow" $ \(SpecPair i s) ->- let p = R.grow (iBackground i) (iHeight i) (iWidth i)- (iVert i) (iHoriz i) (iBox i)- in testEq s (grow (iHeight i) (iWidth i) (iBox i)) p-- , testProperty "growH" $ \(SpecPair i s) ->- let p = R.growH (iBackground i) (unWidth . iWidth $ i)- (iHoriz i) (iBox i)- in testEq s (growH (unWidth . iWidth $ i)- (iBox i)) p-- , testProperty "growV" $ \(SpecPair i s) ->- let p = R.growV (iBackground i) (unHeight . iHeight $ i)- (iVert i) (iBox i)- in testEq s (growV (unHeight . iHeight $ i)- (iBox i)) p-- , testProperty "column" $ \(SpecPair i s) ->- let p = R.column (iBackground i) (iHoriz i) (iBoxes i)- in testEq s (column (iBoxes i)) p-- , testProperty "resize" $ \(SpecPair i s) ->- let p = R.resize (iBackground i) (iHeight i) (iWidth i)- (iVert i) (iHoriz i) (iBox i)- in testEq s (resize (iHeight i) (iWidth i) (iBox i)) p-- , testProperty "resizeH" $ \(SpecPair i s) ->- let p = R.resizeH (iBackground i) (unWidth . iWidth $ i)- (iHoriz i) (iBox i)- in testEq s (resizeH (unWidth . iWidth $ i) (iBox i)) p-- , testProperty "resizeV" $ \(SpecPair i s) ->- let p = R.resizeV (iBackground i) (unHeight . iHeight $ i)- (iVert i) (iBox i)- in testEq s (resizeV (unHeight . iHeight $ i) (iBox i)) p-- , testProperty "sepH" $ \(SpecPair i s) ->- let p = R.sepH (iBackground i) (spaceH s) (iVert i) (iBoxes i)- in testEq s (sepH (spaceH s) (iBoxes i)) p-- , testProperty "sepV" $ \(SpecPair i s) ->- let p = R.sepV (iBackground i) (spaceV s) (iHoriz i) (iBoxes i)- in testEq s (sepV (spaceV s) (iBoxes i)) p-- , testProperty "punctuateH" $ \(SpecPair i s) ->- let p = R.punctuateH (iBackground i) (iVert i) (iBox i) (iBoxes i)- in testEq s (punctuateH (iBox i) (iBoxes i)) p-- , testProperty "punctuateV" $ \(SpecPair i s) ->- let p = R.punctuateV (iBackground i) (iHoriz i) (iBox i) (iBoxes i)- in testEq s (punctuateV (iBox i) (iBoxes i)) p-- , testProperty "viewH" $ \(SpecPair i s) ->- let p = R.viewH (unWidth . iWidth $ i) (iHoriz i) (iBox i)- in testEq s (viewH (unWidth . iWidth $ i) (iBox i)) p-- , testProperty "viewV" $ \(SpecPair i s) ->- let p = R.viewV (unHeight . iHeight $ i) (iVert i) (iBox i)- in testEq s (viewV (unHeight . iHeight $ i) (iBox i)) p-- , testProperty "view" $ \(SpecPair i s) ->- let p = R.view (iHeight i) (iWidth i) (iVert i) (iHoriz i)- (iBox i)- in testEq s (view (iHeight i) (iWidth i) (iBox i)) p- ]--testEq :: Eq a => Specs -> Env Identity a -> a -> Bool-testEq s e a = r == a- where- r = runEnv s e--specs- :: Int- -- ^ Space for horizontal joins- -> Int- -- ^ Space for vertical joins- -> Inputs- -> Specs-specs h v i = Specs- { background = iBackground i- , alignH = iHoriz i- , alignV = iVert i- , spaceH = h- , spaceV = v- }--genSpecs :: Gen (Inputs, Specs)-genSpecs = do- h <- frequency [(3, fmap getPositive arbitrarySizedIntegral),- (1, arbitrarySizedIntegral)]- v <- frequency [(3, fmap getPositive arbitrarySizedIntegral),- (1, arbitrarySizedIntegral)]- i <- arbitrary- let ss = specs h v i- return (i, ss)--data SpecPair = SpecPair- { spInputs :: Inputs- , spSpecs :: Specs- } deriving Show--instance Arbitrary SpecPair where- arbitrary = do- (i, s) <- genSpecs- return $ SpecPair i s
− test/RainboxDir.hs
@@ -1,13 +0,0 @@-module RainboxDir where--import qualified Rainbox.BoxTests-import qualified Rainbox.BoxDir-import qualified Rainbox.ReaderTests-import qualified Rainbox.Array2dTests-import Test.Tasty--tests :: TestTree-tests = testGroup "RainboxDir" [ Rainbox.BoxTests.tests- , Rainbox.BoxDir.tests- , Rainbox.ReaderTests.tests- , Rainbox.Array2dTests.tests ]
− test/RainboxTests.hs
@@ -1,7 +0,0 @@-module RainboxTests where--import Test.Tasty--- import Rainbox--tests :: TestTree-tests = testGroup "Rainbox" []
− test/Test/Rainbow/Generators.hs
@@ -1,82 +0,0 @@-module Test.Rainbow.Generators where--import qualified Data.Text as X-import Prelude hiding (last)-import Control.Monad-import Data.Monoid-import Test.QuickCheck-import System.Console.Rainbow.Colors-import System.Console.Rainbow hiding- (textSpec)-import System.Console.Rainbow.Types hiding- (style8, style256, textSpec)---- | Generates one of the valid colors for an 8-color terminal,--- including the default color.-colors8 :: Gen Color8-colors8 = elements $ c8_default : map snd c8_all---- | Generates one of the valid colors for an 256-color terminal,--- including the default color.-colors256 :: Gen Color256-colors256 = elements $ c256_default : map snd c256_all---- | Generates a foreground color chunk for 8-color terminals.-fgColorChunk8 :: Gen Chunk-fgColorChunk8 = fmap fc8 colors8---- | Generates a foreground color chunk for 256-color terminals.-fgColorChunk256 :: Gen Chunk-fgColorChunk256 = fmap fc256 colors256---- | Generates a background color chunk for 8-color terminals.-bgColorChunk8 :: Gen Chunk-bgColorChunk8 = fmap bc8 colors8---- | Generates a background color chunk for 256-color terminals.-bgColorChunk256 :: Gen Chunk-bgColorChunk256 = fmap bc256 colors256---- | Generates a color chunk (half foreground, half background) for--- 8-color terminals.-colorChunk8 :: Gen Chunk-colorChunk8 = oneof [ fgColorChunk8, bgColorChunk8 ]---- | Generates a color chunk (half foreground, half background) for--- 256-color terminals.-colorChunk256 :: Gen Chunk-colorChunk256 = oneof [ fgColorChunk256, bgColorChunk256 ]---- | Generates a color chunk (half for 8-color, half for 256-color).-colorChunk :: Gen Chunk-colorChunk = oneof [ colorChunk8, colorChunk256 ]--last :: a -> Gen (Last a)-last a = frequency [ (3, return $ Last (Just a)),- (1, return $ Last Nothing)]--styleCommon :: Gen StyleCommon-styleCommon = liftM4 StyleCommon g g g g- where- g = arbitrary >>= last--style8 :: Gen Style8-style8 = liftM3 Style8- (colors8 >>= last) (colors8 >>= last) styleCommon--style256 :: Gen Style256-style256 = liftM3 Style256- (colors256 >>= last) (colors256 >>= last) styleCommon--textSpec :: Gen TextSpec-textSpec = liftM2 TextSpec style8 style256--chunk :: [X.Text] -> Gen Chunk-chunk x = liftM2 Chunk textSpec (return x)---- | Generates one Chunk for each list of Text in the list and--- combines them into one Chunk.-combinedChunks :: [[X.Text]] -> Gen Chunk-combinedChunks ls = do- cs <- mapM chunk ls- return $ foldl (<>) mempty cs
− test/Visual.hs
@@ -1,154 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}-module Visual where--import Control.Monad-import Rainbox.Box-import System.Console.Rainbow-import System.Console.Rainbow.Colors-import Data.Monoid-import Test.QuickCheck.Gen hiding (resize)-import Test.QuickCheck.Random-import Rainbox.Box.PrimitivesTests-import Data.Maybe (fromJust)-import Data.String--colors = f_yellow <> b_blue--narrow = "narrow box" <> colors--midwidth = "medium width box" <> colors--wide = "a wide box, see how wide I am?" <> colors--greenBack = same c8_green--yellowBack = same c8_yellow--all3 = [narrow, midwidth, wide]--short = chunk narrow--midheight = catV greenBack left . map chunk $ [narrow, midwidth]--tall = catV greenBack left . map chunk $ [narrow, midwidth, wide]--sizeParam = 7--putBox b = do- term <- termFromEnv- putChunks term . render $ b--describe s b = do- putStrLn (s ++ ":")- putBox b- putStrLn ""--testCompound :: String -> (Background -> [Box] -> Box) -> IO ()-testCompound d f = do- g <- newQCGen- let bxs = unGen (replicateM 5 genTextBox) g sizeParam - bk = unGen genBackground g sizeParam- describe d $ f bk bxs--testVert- :: String- -> (Background -> Align Vert -> [Box] -> Box)- -> IO ()-testVert d f = do- testCompound (d ++ ", top align") (\bk bxs -> f bk top bxs)- testCompound (d ++ ", center align") (\bk bxs -> f bk center bxs)- testCompound (d ++ ", bottom align") (\bk bxs -> f bk bottom bxs)--testHoriz- :: String- -> (Background -> Align Horiz -> [Box] -> Box)- -> IO ()-testHoriz d f = do- testCompound (d ++ ", left align") (\bk bxs -> f bk left bxs)- testCompound (d ++ ", center align") (\bk bxs -> f bk center bxs)- testCompound (d ++ ", right align") (\bk bxs -> f bk right bxs)---- | Makes a 10x10 test box.-testBox :: Box-testBox = catV defaultBackground left . map mkLine $ clrs- where- mkLine clr = chunk $ txt <> clr- txt = fromString ['0'..'9']- lkp k = bc256 . fromJust . lookup k $ c256_all- clrs = map lkp . take 10 . iterate (+6) $ 160--singleH- :: String- -> (Align Horiz -> Box)- -> IO ()-singleH desc f = do- describe (desc ++ ", left") (f left)- describe (desc ++ ", center") (f center)- describe (desc ++ ", right") (f right)--singleV- :: String- -> (Align Vert -> Box)- -> IO ()-singleV desc f = do- describe (desc ++ ", top") (f top)- describe (desc ++ ", center") (f center)- describe (desc ++ ", bottom") (f bottom)--single- :: String- -> (Align Vert -> Align Horiz -> Box)- -> IO ()-single desc f = do- singleV (desc ++ ", left") (\av -> f av left)- singleV (desc ++ ", center") (\av -> f av center)- singleV (desc ++ ", right") (\av -> f av right)-- singleH (desc ++ ", top") (f top)- singleH (desc ++ ", center") (f center)- singleH (desc ++ ", bottom") (f bottom)---tests :: IO ()-tests = do- describe "narrow box" . chunk $ narrow- describe "medium box" . chunk $ midwidth- describe "wide box" . chunk $ wide-- testHoriz "catV" catV- testVert "catH" catH-- testVert "sepH" (\bk av bxs -> sepH bk 1 av bxs)- testHoriz "sepV" (\bk ah bxs -> sepV bk 1 ah bxs)-- testVert "punctuateH" (\bk av bxs -> punctuateH bk av " " bxs)- testHoriz "punctuateV" (\bk ah bxs -> punctuateV bk ah " " bxs)-- let green = backgroundFromChunk b_green-- testHoriz "column" (\bk ah bxs -> catV defaultBackground left- (column bk ah bxs))-- describe "original box for following tests, 10x10" testBox-- single "view, 3x3"- (\av ah -> view (Height 3) (Width 3) av ah testBox)- singleH "viewH, 3" (\ah -> viewH 3 ah testBox)- singleV "viewV, 3" (\av -> viewV 3 av testBox)-- single "grow, 13x13"- (\av ah -> grow green (Height 13) (Width 13) av ah testBox)- singleH "growH, 13" (\ah -> growH green 13 ah testBox)- singleV "growV, 13" (\av -> growV green 13 av testBox)-- single "resize, 13x13"- (\av ah -> resize green (Height 13) (Width 13) av ah testBox)- singleH "resizeH, 13" (\ah -> resizeH green 13 ah testBox)- singleV "resizeV, 13" (\av -> resizeV green 13 av testBox)-- single "resize, 7x7"- (\av ah -> resize green (Height 7) (Width 7) av ah testBox)- singleH "resizeH, 7" (\ah -> resizeH green 7 ah testBox)- singleV "resizeV, 7" (\av -> resizeV green 7 av testBox)-
− test/rainbox-mosaic.hs
@@ -1,20 +0,0 @@--- | Usage:------ Input the size parameter as $1. Will generate a random box and print--- it out. Always uses colors.-module Main where--import Test.QuickCheck.Gen-import Rainbox.Box.PrimitivesTests-import System.Environment-import System.Random-import Rainbox.Box-import System.Console.Rainbow--main :: IO ()-main = do- g <- newStdGen- s:[] <- getArgs- let bx = unGen genBox g (read s)- e <- termFromEnv- putChunks e . render $ bx
+ test/rainbox-properties.hs view
@@ -0,0 +1,157 @@+module Main where++import Rainbox.Core+import Rainbox.BicolorTable+import Rainbox.Instances ()+import Rainbow.Types+import Test.Tasty+import Test.Tasty.QuickCheck+import Data.Sequence (Seq, viewl, ViewL(..))+import qualified Data.Sequence as Seq+import qualified Data.Foldable as F+import qualified Data.Text as X+import Control.Monad++main :: IO ()+main = defaultMain . testGroup "Rainbox tests" $+ [ testGroup "split" $+ [ testProperty "sum is equal to original number" $ \a ->+ let (x, y) = split a+ in x + y == a+ ]++ , testGroup "HasHeight" $+ [ testGroup "never returns less than zero" $+ let go a = let h = height a in classify (h > 2) "h > 2" (h >= 0) in+ [ testProperty "RodRows" $+ \a -> go (a `asTypeOf` (undefined :: RodRows))+ , testProperty "Core" $+ \a -> go (a `asTypeOf` (undefined :: Core))+ , testProperty "Box Vertical" $+ \a -> go (a `asTypeOf` (undefined :: Box Vertical))+ , testProperty "Box Horizontal" $+ \a -> go (a `asTypeOf` (undefined :: Box Horizontal))+ , testProperty "Payload Vertical" $+ \a -> go (a `asTypeOf` (undefined :: Payload Vertical))+ , testProperty "Payload Horizontal" $+ \a -> go (a `asTypeOf` (undefined :: Payload Horizontal))+ ]+ ]++ , testGroup "HasWidth" $+ [ testGroup "never returns less than zero" $+ let go a = let w = width a in classify (w > 2) "w > 2" (w >= 0) in+ [ testProperty "Chunk" $+ \a -> go (a `asTypeOf` (undefined :: Chunk))+ , testProperty "RodRows" $+ \a -> go (a `asTypeOf` (undefined :: RodRows))+ , testProperty "Rod" $+ \a -> go (a `asTypeOf` (undefined :: Rod))+ , testProperty "Core" $+ \a -> go (a `asTypeOf` (undefined :: Core))+ , testProperty "Box Vertical" $+ \a -> go (a `asTypeOf` (undefined :: Box Vertical))+ , testProperty "Box Horizontal" $+ \a -> go (a `asTypeOf` (undefined :: Box Horizontal))+ , testProperty "Payload Vertical" $+ \a -> go (a `asTypeOf` (undefined :: Payload Vertical))+ , testProperty "Payload Horizontal" $+ \a -> go (a `asTypeOf` (undefined :: Payload Horizontal))+ ]+ ]++ , testGroup "chunk" $+ [ testProperty "height is always 1" $ \c ->+ let _types = c :: Chunk in height c == 1+ , testProperty "width is sum of number of characters" $ \c@(Chunk _ t) ->+ let _types = c :: Chunk in width c == X.length t+ ]++ , testGroup "addVerticalPadding"+ [ testProperty "all RodRows same height" $+ allRodRowsSameHeight . addVerticalPadding+ ]++ , testGroup "UpDown"+ [ testGroup "above + below is same as height" $+ let sameAsHeight a = above a + below a == height a in+ [ testProperty "Box Horizontal"+ (\a -> sameAsHeight (a `asTypeOf` (undefined :: Box Horizontal)))+ , testProperty "Payload Horizontal"+ (\a -> sameAsHeight (a `asTypeOf` (undefined :: Payload Horizontal)))+ ]+ ]++ , testGroup "horizontalMerge"+ [ testProperty "Resulting RodRows has same height as inputs" $+ \rr i ->+ let lenR = case rr of+ RodRowsNoHeight _ -> 0+ RodRowsWithHeight sq -> Seq.length sq+ in height (horizontalMerge (Seq.replicate (getPositive i) rr)) == lenR+ ]++ , testGroup "addHorizontalPadding"+ [ testProperty "all RodRows same width" $+ allRodRowsSameWidth . addHorizontalPadding+ ]++ , testGroup "verticalMerge"+ [ testProperty "resulting RodRows same width as inputs" $ \rr i ->+ let lenR = width rr+ mrge = verticalMerge (Seq.replicate (getPositive i) rr)+ wdth = width mrge+ in counterexample (show (mrge, wdth, lenR)) $ wdth == lenR+ ]++ , testGroup "BicolorTable"+ [ testGroup "padBicolorTable"+ [ testProperty "all rows are the same length" $ \bct ->+ let padded = padBicolorTable bct+ in case Seq.viewl . _bctRows $ padded of+ EmptyL -> True+ a :< as -> all (\sq -> Seq.length sq == Seq.length a) as++ , testProperty "length of _bctAlignment is at least as long as a row" $ \bct ->+ let padded = padBicolorTable bct+ in case Seq.viewl . _bctRows $ padded of+ EmptyL -> True+ a :< _ -> Seq.length (_bctAlignments padded) >= Seq.length a+ ]+ ]+++ ]++allRodRowsSameHeight :: Seq RodRows -> Bool+allRodRowsSameHeight sqnce = case viewl sqnce of+ EmptyL -> True+ x :< xs -> F.all (== height x) . fmap height $ xs++allRodRowsSameWidth :: Seq RodRows -> Property+allRodRowsSameWidth sqnce = + case viewl sqnce of+ EmptyL -> property True+ x :< _ -> counterexample (show (sqnce, lengths, height1))+ $ F.all (== height1) lengths+ where+ lengths = join . fmap toLengths $ sqnce+ height1 = case x of+ RodRowsNoHeight w -> w+ RodRowsWithHeight sqn -> case viewl sqn of+ EmptyL -> 0+ y :< _ -> F.sum . fmap width $ y+ toLengths (RodRowsNoHeight w) = Seq.singleton w+ toLengths (RodRowsWithHeight sq) = fmap (F.sum . fmap width) sq++rodsLength :: Seq Rod -> Int+rodsLength = F.sum . fmap width++rodRowsLengths :: Seq (Seq Rod) -> Seq Int+rodRowsLengths = fmap rodsLength++seqRodsRowsLengths :: Seq RodRows -> Seq (Seq Int)+seqRodsRowsLengths sq = fmap calc sq+ where+ calc (RodRowsNoHeight w) = Seq.singleton (max 0 w)+ calc (RodRowsWithHeight sqn) = rodRowsLengths sqn
− test/rainbox-test.hs
@@ -1,11 +0,0 @@-module Main where--import qualified RainboxDir-import qualified RainboxTests-import Test.Tasty--main :: IO ()-main = defaultMain $ testGroup "rainbox"- [ RainboxDir.tests- , RainboxTests.tests- ]
test/rainbox-visual.hs view
@@ -1,10 +1,92 @@--- | Rainbox visual tests.------ Tests that are intended to be run and then examined visually.+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-}+-- | Prints all the boxes in the tutorial. The output must be+-- visually inspected. module Main where -import Visual+import Data.Function ((&))+import Rainbow+import Rainbox+import Rainbox.Tutorial +bicolorStationBox :: BicolorTable+bicolorStationBox = BicolorTable+ { _bctEvenBackground = cyan <> color256 254+ , _bctOddBackground = mempty+ , _bctSpacerWidth = 2+ , _bctAlignments = [left, center, right]+ , _bctRows =+ [ [ [ [ "Red" & fore red ]+ , [ "Orange" & fore (yellow <> color256 220) ]+ , [ "Silver" & fore (white <> grey) ]+ , [ "Blue" & fore blue ]+ ]+ , [ [ "Metro Center" ] ]+ , [ [ "607 13th St NW" ]+ , [ "Washington, ", "DC" & fore red & back white, " 20005" ]+ ]+ ]++ , [ [ [ "Orange" & fore (yellow <> color256 220) ]+ , [ "Silver" & fore (white <> grey) ]+ , [ "Blue" & fore blue ]+ , [ "Green" & fore green ]+ , [ "Yellow" & fore yellow ]+ ]+ , [ [ "L'Enfant Plaza" ] ]+ , [ [ "600 Maryland Ave SW" ]+ , [ "Washington, " , "DC" & fore red & back white, " 20024" ]+ ]+ ]++ , [ [ [ "Red" & fore red ]+ ]+ , [ [ "Silver Spring" ] ]+ , [ [ "8400 Colesville Rd" ]+ , [ "Silver Spring, ", "MD" & fore yellow & back black, " 20910" ]+ ]+ ]++ , [ [ [ "Orange" & fore (yellow <> color256 220) ]+ , [ "Silver" & fore (white <> grey) ]+ ]+ , [ [ "Court House" ] ]+ , [ [ "2100 Wilson Blvd" ]+ , [ "Arlington, " , "VA" & fore cyan & back grey, " 22201" ]+ ]+ ]++ , [ [ [ "Green" & fore green ]+ , [ "Yellow" & fore yellow ]+ ]+ , [ [ "Prince George's Plaza" ]+ ]+ , [ [ "3575 East-West Hwy" ]+ , [ "Hyattsville, ", "MD" & fore yellow & back black, " 20782" ]+ ]+ ]+ ]+ }++++printBox :: String -> IO () -> IO ()+printBox lbl act = do+ putStrLn $ replicate 50 '='+ putStrLn ""+ putStrLn $ lbl ++ ":"+ putStrLn ""+ act+ putStrLn ""+ main :: IO ()-main = Visual.tests+main = do+ printBox "box1" renderBox1+ printBox "box2" renderBox2+ printBox "box3" renderBox3+ printBox "box4" renderBox4+ printBox "box5" renderBox5+ printBox "verticalStationTable" renderVerticalStationTable+ printBox "horizontalStationTable" renderHorizontalStationTable+ printBox "bicolor station table" (putBicolorTable bicolorStationBox)