packages feed

kvitable 1.0.3.0 → 1.1.0.0

raw patch · 16 files changed

+1193/−664 lines, 16 filesdep +named-textdep +sayabledep −pretty-showdep −prettyprinterdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: named-text, sayable

Dependencies removed: pretty-show, prettyprinter

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.KVITable.Render: sortWithNums :: [KeyVal] -> [KeyVal]
- Data.KVITable.Render.HTML: instance GHC.Show.Show Data.KVITable.Render.HTML.FmtVal
+ Data.KVITable: foldlInsertWith :: (v -> v -> v) -> KVITable v -> (KeySpec, v) -> KVITable v
+ Data.KVITable: insertWith :: (v -> v -> v) -> KeySpec -> v -> KVITable v -> KVITable v
+ Data.KVITable: lookup' :: KeySpec -> KVITable v -> Maybe v
+ Data.KVITable.Internal.Helpers: maxOf :: (Foldable t, Ord e) => e -> t e -> e
+ Data.KVITable.Internal.Helpers: single :: e -> [e]
+ Data.KVITable.Internal.Helpers: snoc :: [a] -> a -> [a]
+ Data.KVITable.Internal.Helpers: type Keys = [Name "Key"]
+ Data.KVITable.Render: [maxCells] :: RenderConfig -> Natural
+ Data.KVITable.Render: [maxCols] :: RenderConfig -> Natural
+ Data.KVITable.Render: sortNumericAlpha :: (KeyVals, KeyVals) -> (KeyVals, KeyVals)
+ Data.KVITable.Render.ASCII: [maxCells] :: RenderConfig -> Natural
+ Data.KVITable.Render.ASCII: [maxCols] :: RenderConfig -> Natural
+ Data.KVITable.Render.HTML: [maxCells] :: RenderConfig -> Natural
+ Data.KVITable.Render.HTML: [maxCols] :: RenderConfig -> Natural
+ Data.KVITable.Render.HTML: instance Lucid.Base.ToHtml (Data.Name.Internal.Named Data.Name.HTMLStyle nameOf)
+ Data.KVITable.Render.Internal: AndMore :: Natural -> TblHdr
+ Data.KVITable.Render.Internal: V :: KeyVal -> TblHdr
+ Data.KVITable.Render.Internal: data TblHdr
+ Data.KVITable.Render.Internal: instance Data.Name.ConvertName Data.Name.UTF8 "Key" "column header"
+ Data.KVITable.Render.Internal: instance Data.Name.ConvertName Data.Name.UTF8 "KeyVal" "column header"
+ Data.KVITable.Render.Internal: nLength :: Foldable t => t a -> Natural
+ Data.KVITable.Render.Internal: renderingKeyVals :: RenderConfig -> KeyVals -> (TblHdrs, TblHdrs)
+ Data.KVITable.Render.Internal: type TblHdrs = [(Key, [TblHdr])]
- Data.KVITable: KVITable :: KeyVals -> (Key -> KeyVal) -> Map KeySpec v -> Text -> KVITable v
+ Data.KVITable: KVITable :: KeyVals -> (Key -> KeyVal) -> Map KeySpec v -> Named HTMLStyle "column header" -> KVITable v
- Data.KVITable: type Key = Text
+ Data.KVITable: type Key = Name "Key"
- Data.KVITable: type KeyVal = Text
+ Data.KVITable: type KeyVal = Name "KeyVal"
- Data.KVITable: valueColName :: Lens' (KVITable v) Text
+ Data.KVITable: valueColName :: Lens' (KVITable v) (Named HTMLStyle "column header")
- Data.KVITable.Render: RenderConfig :: Bool -> Bool -> Bool -> Bool -> Maybe Key -> Bool -> [Key] -> Maybe Text -> RenderConfig
+ Data.KVITable.Render: RenderConfig :: Bool -> Bool -> Bool -> Maybe ((KeyVals, KeyVals) -> (KeyVals, KeyVals)) -> Maybe Key -> Bool -> [Key] -> Maybe (Name "caption") -> Natural -> Natural -> RenderConfig
- Data.KVITable.Render: [caption] :: RenderConfig -> Maybe Text
+ Data.KVITable.Render: [caption] :: RenderConfig -> Maybe (Name "caption")
- Data.KVITable.Render: [sortKeyVals] :: RenderConfig -> Bool
+ Data.KVITable.Render: [sortKeyVals] :: RenderConfig -> Maybe ((KeyVals, KeyVals) -> (KeyVals, KeyVals))
- Data.KVITable.Render.ASCII: RenderConfig :: Bool -> Bool -> Bool -> Bool -> Maybe Key -> Bool -> [Key] -> Maybe Text -> RenderConfig
+ Data.KVITable.Render.ASCII: RenderConfig :: Bool -> Bool -> Bool -> Maybe ((KeyVals, KeyVals) -> (KeyVals, KeyVals)) -> Maybe Key -> Bool -> [Key] -> Maybe (Name "caption") -> Natural -> Natural -> RenderConfig
- Data.KVITable.Render.ASCII: [caption] :: RenderConfig -> Maybe Text
+ Data.KVITable.Render.ASCII: [caption] :: RenderConfig -> Maybe (Name "caption")
- Data.KVITable.Render.ASCII: [sortKeyVals] :: RenderConfig -> Bool
+ Data.KVITable.Render.ASCII: [sortKeyVals] :: RenderConfig -> Maybe ((KeyVals, KeyVals) -> (KeyVals, KeyVals))
- Data.KVITable.Render.ASCII: render :: Pretty v => RenderConfig -> KVITable v -> Text
+ Data.KVITable.Render.ASCII: render :: Sayable "normal" v => RenderConfig -> KVITable v -> Text
- Data.KVITable.Render.HTML: RenderConfig :: Bool -> Bool -> Bool -> Bool -> Maybe Key -> Bool -> [Key] -> Maybe Text -> RenderConfig
+ Data.KVITable.Render.HTML: RenderConfig :: Bool -> Bool -> Bool -> Maybe ((KeyVals, KeyVals) -> (KeyVals, KeyVals)) -> Maybe Key -> Bool -> [Key] -> Maybe (Name "caption") -> Natural -> Natural -> RenderConfig
- Data.KVITable.Render.HTML: [caption] :: RenderConfig -> Maybe Text
+ Data.KVITable.Render.HTML: [caption] :: RenderConfig -> Maybe (Name "caption")
- Data.KVITable.Render.HTML: [sortKeyVals] :: RenderConfig -> Bool
+ Data.KVITable.Render.HTML: [sortKeyVals] :: RenderConfig -> Maybe ((KeyVals, KeyVals) -> (KeyVals, KeyVals))
- Data.KVITable.Render.HTML: render :: Pretty v => RenderConfig -> KVITable v -> Text
+ Data.KVITable.Render.HTML: render :: Sayable "html" v => RenderConfig -> KVITable v -> Text

Files

CHANGELOG.md view
@@ -1,6 +1,14 @@ # Revision history for KVITable -## 1.0 3.0 -- 2024-02-22+## 1.1.0.0 -- 2024-09-18++* The `RenderConfig.sortKeyVals` changed from a `Boolean` to a `Maybe fn`, where+  the `fn` is a sorting function which is provided a `(rowKeyvals, colKeyvals)`+  tuple and should return that tuple, sorted.  The `sortNumericAlpha` function+  can be used to obtain the old 'True' behavior.+* Support GHC 9.10.++## 1.0.3.0 -- 2024-02-22  * Support GHC 9.8. * Internal updates to confirm safety and avoid partial functions.
README.md view
@@ -83,87 +83,87 @@ <table class="kvitable">   <thead class="kvitable_head">     <tr class="kvitable_tr">-      <th rowspan="3" class="kvitable_th">millions</th>-      <th rowspan="3" class="kvitable_th">thousands</th>-      <th colspan="2" class="kvitable_th multicol">1</th>-      <th colspan="2" class="kvitable_th multicol">2</th>+      <th rowspan="3" class="kvitable_th"><div><span>millions</span></div></th>+      <th rowspan="3" class="kvitable_th"><div><span>thousands</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>1</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;hundreds</th>     </tr>     <tr class="kvitable_tr">-      <th colspan="2" class="kvitable_th multicol">2</th>-      <th colspan="2" class="kvitable_th multicol">2</th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;tens</th>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;ones</th>     </tr>   </thead>   <tbody class="kvitable_body">     <tr class="kvitable_tr">-      <th rowspan="3" class="kvitable_th">0</th>-      <th class="kvitable_th">0</th>+      <th rowspan="3" class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">2</th>+      <th class="kvitable_th"><div><span>2</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="3" class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>+      <th rowspan="3" class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">2</th>+      <th class="kvitable_th"><div><span>2</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="3" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="3" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">2</th>+      <th class="kvitable_th"><div><span>2</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>@@ -200,49 +200,49 @@ <table class="kvitable">   <thead class="kvitable_head">     <tr class="kvitable_tr">-      <th rowspan="4" class="kvitable_th">millions</th>-      <th colspan="4" class="kvitable_th multicol">0</th>-      <th colspan="4" class="kvitable_th multicol">1</th>-      <th colspan="4" class="kvitable_th multicol">2</th>+      <th rowspan="4" class="kvitable_th"><div><span>millions</span></div></th>+      <th colspan="4" class="kvitable_th multicol"><div><span>0</span></div></th>+      <th colspan="4" class="kvitable_th multicol"><div><span>1</span></div></th>+      <th colspan="4" class="kvitable_th multicol"><div><span>2</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;thousands</th>     </tr>     <tr class="kvitable_tr">-      <th colspan="2" class="kvitable_th multicol">1</th>-      <th colspan="2" class="kvitable_th multicol">2</th>-      <th colspan="2" class="kvitable_th multicol">1</th>-      <th colspan="2" class="kvitable_th multicol">2</th>-      <th colspan="2" class="kvitable_th multicol">1</th>-      <th colspan="2" class="kvitable_th multicol">2</th>+      <th colspan="2" class="kvitable_th multicol"><div><span>1</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>1</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>1</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;hundreds</th>     </tr>     <tr class="kvitable_tr">-      <th colspan="2" class="kvitable_th multicol">2</th>-      <th colspan="2" class="kvitable_th multicol">2</th>-      <th colspan="2" class="kvitable_th multicol">2</th>-      <th colspan="2" class="kvitable_th multicol">2</th>-      <th colspan="2" class="kvitable_th multicol">2</th>-      <th colspan="2" class="kvitable_th multicol">2</th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;tens</th>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;ones</th>     </tr>   </thead>   <tbody class="kvitable_body">     <tr class="kvitable_tr">-      <th class="kvitable_th">0</th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>@@ -257,7 +257,7 @@       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>@@ -272,7 +272,7 @@       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">2</th>+      <th class="kvitable_th"><div><span>2</span></div></th>       <td class="kvitable_td">even</td>       <td class="kvitable_td">odd</td>       <td class="kvitable_td">even</td>@@ -347,205 +347,205 @@ <table class="kvitable">   <thead class="kvitable_head">     <tr class="kvitable_tr">-      <th class="kvitable_th">millions</th>-      <th class="kvitable_th">thousands</th>-      <th class="kvitable_th">hundreds</th>-      <th class="kvitable_th">tens</th>-      <th class="kvitable_th">ones</th>-      <th class="kvitable_th">Value</th>+      <th class="kvitable_th"><div><span>millions</span></div></th>+      <th class="kvitable_th"><div><span>thousands</span></div></th>+      <th class="kvitable_th"><div><span>hundreds</span></div></th>+      <th class="kvitable_th"><div><span>tens</span></div></th>+      <th class="kvitable_th"><div><span>ones</span></div></th>+      <th class="kvitable_th"><div><span>Value</span></div></th>     </tr>   </thead>   <tbody class="kvitable_body">     <tr class="kvitable_tr">-      <th rowspan="12" class="kvitable_th">0</th>-      <th rowspan="4" class="kvitable_th">0</th>-      <th rowspan="2" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="12" class="kvitable_th"><div><span>0</span></div></th>+      <th rowspan="4" class="kvitable_th"><div><span>0</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="2" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="4" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="4" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="2" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="4" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="4" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="2" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="12" class="kvitable_th">1</th>-      <th rowspan="4" class="kvitable_th">0</th>-      <th rowspan="2" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="12" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="4" class="kvitable_th"><div><span>0</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="2" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="4" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="4" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="2" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="4" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="4" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="2" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="12" class="kvitable_th">2</th>-      <th rowspan="4" class="kvitable_th">0</th>-      <th rowspan="2" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="12" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="4" class="kvitable_th"><div><span>0</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="2" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="4" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="4" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="2" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="4" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">1</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="4" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>1</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>       <tr class="kvitable_tr">-      <th rowspan="2" class="kvitable_th">2</th>-      <th rowspan="2" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th rowspan="2" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td">even</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td">odd</td>     </tr>   </tbody>
examples/hundreds_all.md view
@@ -38,42 +38,42 @@ <table class="kvitable">   <thead class="kvitable_head">     <tr class="kvitable_tr">-      <th rowspan="3" class="kvitable_th">millions</th>-      <th rowspan="3" class="kvitable_th">thousands</th>-      <th colspan="4" class="kvitable_th multicol">0</th>-      <th colspan="4" class="kvitable_th multicol">1</th>-      <th colspan="4" class="kvitable_th multicol">2</th>+      <th rowspan="3" class="kvitable_th"><div><span>millions</span></div></th>+      <th rowspan="3" class="kvitable_th"><div><span>thousands</span></div></th>+      <th colspan="4" class="kvitable_th multicol"><div><span>0</span></div></th>+      <th colspan="4" class="kvitable_th multicol"><div><span>1</span></div></th>+      <th colspan="4" class="kvitable_th multicol"><div><span>2</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;hundreds</th>     </tr>     <tr class="kvitable_tr">-      <th colspan="2" class="kvitable_th multicol">0</th>-      <th colspan="2" class="kvitable_th multicol">2</th>-      <th colspan="2" class="kvitable_th multicol">0</th>-      <th colspan="2" class="kvitable_th multicol">2</th>-      <th colspan="2" class="kvitable_th multicol">0</th>-      <th colspan="2" class="kvitable_th multicol">2</th>+      <th colspan="2" class="kvitable_th multicol"><div><span>0</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>0</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>0</span></div></th>+      <th colspan="2" class="kvitable_th multicol"><div><span>2</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;tens</th>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;ones</th>     </tr>   </thead>   <tbody class="kvitable_body">     <tr class="kvitable_tr">-      <th rowspan="3" class="kvitable_th">0</th>-      <th class="kvitable_th">0</th>+      <th rowspan="3" class="kvitable_th"><div><span>0</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>@@ -88,7 +88,7 @@       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>@@ -103,7 +103,7 @@       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">2</th>+      <th class="kvitable_th"><div><span>2</span></div></th>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>@@ -118,8 +118,8 @@       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="3" class="kvitable_th">1</th>-      <th class="kvitable_th">0</th>+      <th rowspan="3" class="kvitable_th"><div><span>1</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>@@ -134,7 +134,7 @@       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>@@ -149,7 +149,7 @@       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">2</th>+      <th class="kvitable_th"><div><span>2</span></div></th>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>@@ -164,8 +164,8 @@       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th rowspan="3" class="kvitable_th">2</th>-      <th class="kvitable_th">0</th>+      <th rowspan="3" class="kvitable_th"><div><span>2</span></div></th>+      <th class="kvitable_th"><div><span>0</span></div></th>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>@@ -180,7 +180,7 @@       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">1</th>+      <th class="kvitable_th"><div><span>1</span></div></th>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>@@ -195,7 +195,7 @@       <td class="kvitable_td">odd</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th">2</th>+      <th class="kvitable_th"><div><span>2</span></div></th>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>       <td class="kvitable_td"></td>
examples/zoo.md view
@@ -64,122 +64,122 @@ <table class="kvitable">   <thead class="kvitable_head">     <tr class="kvitable_tr">-      <th class="kvitable_th">Location</th>-      <th class="kvitable_th">Biome</th>-      <th class="kvitable_th">Category</th>-      <th class="kvitable_th">Diet</th>-      <th class="kvitable_th">Name</th>-      <th class="kvitable_th">Subtype</th>-      <th class="kvitable_th">Count</th>+      <th class="kvitable_th"><div><span>Location</span></div></th>+      <th class="kvitable_th"><div><span>Biome</span></div></th>+      <th class="kvitable_th"><div><span>Category</span></div></th>+      <th class="kvitable_th"><div><span>Diet</span></div></th>+      <th class="kvitable_th"><div><span>Name</span></div></th>+      <th class="kvitable_th"><div><span>Subtype</span></div></th>+      <th class="kvitable_th"><div><span>Count</span></div></th>     </tr>   </thead>   <tbody class="kvitable_body">     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=4>LA</th>-      <th class="kvitable_th last_in_group">Jungle</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Herbivore</th>-      <th class="kvitable_th last_in_group">Hippo</th>-      <th class="kvitable_th last_in_group"></th>+      <th class="kvitable_th last_in_group" rowspan=4><div><span>LA</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Jungle</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Herbivore</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Hippo</span></div></th>+      <th class="kvitable_th last_in_group"><div><span></span></div></th>       <td class="kvitable_td last_in_group">1</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=3>Savannah</th>-      <th class="kvitable_th last_in_group" rowspan=3>Animal</th>-      <th class="kvitable_th">Carnivore</th>-      <th class="kvitable_th">Lion</th>-      <th class="kvitable_th"></th>+      <th class="kvitable_th last_in_group" rowspan=3><div><span>Savannah</span></div></th>+      <th class="kvitable_th last_in_group" rowspan=3><div><span>Animal</span></div></th>+      <th class="kvitable_th"><div><span>Carnivore</span></div></th>+      <th class="kvitable_th"><div><span>Lion</span></div></th>+      <th class="kvitable_th"><div><span></span></div></th>       <td class="kvitable_td">4</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th" rowspan=2>Herbivore</th>-      <th class="kvitable_th">Giraffe</th>-      <th class="kvitable_th"></th>+      <th class="kvitable_th" rowspan=2><div><span>Herbivore</span></div></th>+      <th class="kvitable_th"><div><span>Giraffe</span></div></th>+      <th class="kvitable_th"><div><span></span></div></th>       <td class="kvitable_td">2</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">Rhino</th>-      <th class="kvitable_th last_in_group"></th>+      <th class="kvitable_th last_in_group"><div><span>Rhino</span></div></th>+      <th class="kvitable_th last_in_group"><div><span></span></div></th>       <td class="kvitable_td last_in_group">3</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=3>Miami</th>-      <th class="kvitable_th last_in_group">Polar</th>-      <th class="kvitable_th last_in_group">Bird</th>-      <th class="kvitable_th last_in_group">Carnivore</th>-      <th class="kvitable_th last_in_group">Penguin</th>-      <th class="kvitable_th last_in_group">Gentoo</th>+      <th class="kvitable_th last_in_group" rowspan=3><div><span>Miami</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Polar</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Bird</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Carnivore</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Penguin</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Gentoo</span></div></th>       <td class="kvitable_td last_in_group">20</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=2>Savannah</th>-      <th class="kvitable_th last_in_group" rowspan=2>Animal</th>-      <th class="kvitable_th">Carnivore</th>-      <th class="kvitable_th">Lion</th>-      <th class="kvitable_th"></th>+      <th class="kvitable_th last_in_group" rowspan=2><div><span>Savannah</span></div></th>+      <th class="kvitable_th last_in_group" rowspan=2><div><span>Animal</span></div></th>+      <th class="kvitable_th"><div><span>Carnivore</span></div></th>+      <th class="kvitable_th"><div><span>Lion</span></div></th>+      <th class="kvitable_th"><div><span></span></div></th>       <td class="kvitable_td">2</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">Herbivore</th>-      <th class="kvitable_th last_in_group">Giraffe</th>-      <th class="kvitable_th last_in_group">Reticulated</th>+      <th class="kvitable_th last_in_group"><div><span>Herbivore</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Giraffe</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Reticulated</span></div></th>       <td class="kvitable_td last_in_group">3</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">New York</th>-      <th class="kvitable_th last_in_group">Savannah</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Carnivore</th>-      <th class="kvitable_th last_in_group">Lion</th>-      <th class="kvitable_th last_in_group"></th>+      <th class="kvitable_th last_in_group"><div><span>New York</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Savannah</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Carnivore</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Lion</span></div></th>+      <th class="kvitable_th last_in_group"><div><span></span></div></th>       <td class="kvitable_td last_in_group">3</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=7>San Diego</th>-      <th class="kvitable_th last_in_group">Jungle</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Omnivore</th>-      <th class="kvitable_th last_in_group">Bear</th>-      <th class="kvitable_th last_in_group">Sun</th>+      <th class="kvitable_th last_in_group" rowspan=7><div><span>San Diego</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Jungle</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Omnivore</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Bear</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Sun</span></div></th>       <td class="kvitable_td last_in_group">1</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=2>Plains</th>-      <th class="kvitable_th last_in_group" rowspan=2>Animal</th>-      <th class="kvitable_th" rowspan=2>Omnivore</th>-      <th class="kvitable_th" rowspan=2>Bear</th>-      <th class="kvitable_th">Black</th>+      <th class="kvitable_th last_in_group" rowspan=2><div><span>Plains</span></div></th>+      <th class="kvitable_th last_in_group" rowspan=2><div><span>Animal</span></div></th>+      <th class="kvitable_th" rowspan=2><div><span>Omnivore</span></div></th>+      <th class="kvitable_th" rowspan=2><div><span>Bear</span></div></th>+      <th class="kvitable_th"><div><span>Black</span></div></th>       <td class="kvitable_td">1</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">Brown</th>+      <th class="kvitable_th last_in_group"><div><span>Brown</span></div></th>       <td class="kvitable_td last_in_group">1</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=3>Polar</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Omnivore</th>-      <th class="kvitable_th last_in_group">Bear</th>-      <th class="kvitable_th last_in_group">Polar</th>+      <th class="kvitable_th last_in_group" rowspan=3><div><span>Polar</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Omnivore</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Bear</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Polar</span></div></th>       <td class="kvitable_td last_in_group">1</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=2>Bird</th>-      <th class="kvitable_th" rowspan=2>Carnivore</th>-      <th class="kvitable_th" rowspan=2>Penguin</th>-      <th class="kvitable_th">Emperor</th>+      <th class="kvitable_th last_in_group" rowspan=2><div><span>Bird</span></div></th>+      <th class="kvitable_th" rowspan=2><div><span>Carnivore</span></div></th>+      <th class="kvitable_th" rowspan=2><div><span>Penguin</span></div></th>+      <th class="kvitable_th"><div><span>Emperor</span></div></th>       <td class="kvitable_td">8</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">Gentoo</th>+      <th class="kvitable_th last_in_group"><div><span>Gentoo</span></div></th>       <td class="kvitable_td last_in_group">2</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">Savannah</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Carnivore</th>-      <th class="kvitable_th last_in_group">Lion</th>-      <th class="kvitable_th last_in_group"></th>+      <th class="kvitable_th last_in_group"><div><span>Savannah</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Carnivore</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Lion</span></div></th>+      <th class="kvitable_th last_in_group"><div><span></span></div></th>       <td class="kvitable_td last_in_group">9</td>     </tr>   </tbody>@@ -228,25 +228,25 @@ <table class="kvitable">   <thead class="kvitable_head">     <tr class="kvitable_tr">-      <th class="kvitable_th">Location</th>-      <th class="kvitable_th">Biome</th>-      <th class="kvitable_th">Category</th>-      <th class="kvitable_th">Diet</th>-      <th class="kvitable_th">Bear</th>-      <th class="kvitable_th">Giraffe</th>-      <th class="kvitable_th">Hippo</th>-      <th class="kvitable_th">Lion</th>-      <th class="kvitable_th">Penguin</th>-      <th class="kvitable_th">Rhino</th>+      <th class="kvitable_th"><div><span>Location</span></div></th>+      <th class="kvitable_th"><div><span>Biome</span></div></th>+      <th class="kvitable_th"><div><span>Category</span></div></th>+      <th class="kvitable_th"><div><span>Diet</span></div></th>+      <th class="kvitable_th"><div><span>Bear</span></div></th>+      <th class="kvitable_th"><div><span>Giraffe</span></div></th>+      <th class="kvitable_th"><div><span>Hippo</span></div></th>+      <th class="kvitable_th"><div><span>Lion</span></div></th>+      <th class="kvitable_th"><div><span>Penguin</span></div></th>+      <th class="kvitable_th"><div><span>Rhino</span></div></th>       <th class="rightlabel kvitable_th">&nbsp;&larr;Name</th>     </tr>   </thead>   <tbody class="kvitable_body">     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=3>LA</th>-      <th class="kvitable_th last_in_group">Jungle</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Herbivore</th>+      <th class="kvitable_th last_in_group" rowspan=3><div><span>LA</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Jungle</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Herbivore</span></div></th>       <td class="kvitable_td last_in_group">       </td>       <td class="kvitable_td last_in_group">@@ -260,9 +260,9 @@       </td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=2>Savannah</th>-      <th class="kvitable_th last_in_group" rowspan=2>Animal</th>-      <th class="kvitable_th">Carnivore</th>+      <th class="kvitable_th last_in_group" rowspan=2><div><span>Savannah</span></div></th>+      <th class="kvitable_th last_in_group" rowspan=2><div><span>Animal</span></div></th>+      <th class="kvitable_th"><div><span>Carnivore</span></div></th>       <td class="kvitable_td">       </td>       <td class="kvitable_td">@@ -276,7 +276,7 @@       </td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">Herbivore</th>+      <th class="kvitable_th last_in_group"><div><span>Herbivore</span></div></th>       <td class="kvitable_td last_in_group">       </td>       <td class="kvitable_td last_in_group">2</td>@@ -289,10 +289,10 @@       <td class="kvitable_td last_in_group">3</td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=3>Miami</th>-      <th class="kvitable_th last_in_group">Polar</th>-      <th class="kvitable_th last_in_group">Bird</th>-      <th class="kvitable_th last_in_group">Carnivore</th>+      <th class="kvitable_th last_in_group" rowspan=3><div><span>Miami</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Polar</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Bird</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Carnivore</span></div></th>       <td class="kvitable_td last_in_group">       </td>       <td class="kvitable_td last_in_group">@@ -306,9 +306,9 @@       </td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=2>Savannah</th>-      <th class="kvitable_th last_in_group" rowspan=2>Animal</th>-      <th class="kvitable_th">Carnivore</th>+      <th class="kvitable_th last_in_group" rowspan=2><div><span>Savannah</span></div></th>+      <th class="kvitable_th last_in_group" rowspan=2><div><span>Animal</span></div></th>+      <th class="kvitable_th"><div><span>Carnivore</span></div></th>       <td class="kvitable_td">       </td>       <td class="kvitable_td">@@ -322,7 +322,7 @@       </td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">Herbivore</th>+      <th class="kvitable_th last_in_group"><div><span>Herbivore</span></div></th>       <td class="kvitable_td last_in_group">       </td>       <td class="kvitable_td last_in_group">3</td>@@ -336,10 +336,10 @@       </td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">New York</th>-      <th class="kvitable_th last_in_group">Savannah</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Carnivore</th>+      <th class="kvitable_th last_in_group"><div><span>New York</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Savannah</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Carnivore</span></div></th>       <td class="kvitable_td last_in_group">       </td>       <td class="kvitable_td last_in_group">@@ -353,10 +353,10 @@       </td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=5>San Diego</th>-      <th class="kvitable_th last_in_group">Jungle</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Omnivore</th>+      <th class="kvitable_th last_in_group" rowspan=5><div><span>San Diego</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Jungle</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Omnivore</span></div></th>       <td class="kvitable_td last_in_group">1</td>       <td class="kvitable_td last_in_group">       </td>@@ -370,9 +370,9 @@       </td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">Plains</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Omnivore</th>+      <th class="kvitable_th last_in_group"><div><span>Plains</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Omnivore</span></div></th>       <td class="kvitable_td last_in_group">2</td>       <td class="kvitable_td last_in_group">       </td>@@ -386,9 +386,9 @@       </td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group" rowspan=2>Polar</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Omnivore</th>+      <th class="kvitable_th last_in_group" rowspan=2><div><span>Polar</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Omnivore</span></div></th>       <td class="kvitable_td last_in_group">1</td>       <td class="kvitable_td last_in_group">       </td>@@ -402,8 +402,8 @@       </td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">Bird</th>-      <th class="kvitable_th last_in_group">Carnivore</th>+      <th class="kvitable_th last_in_group"><div><span>Bird</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Carnivore</span></div></th>       <td class="kvitable_td last_in_group">       </td>       <td class="kvitable_td last_in_group">@@ -417,9 +417,9 @@       </td>     </tr>     <tr class="kvitable_tr">-      <th class="kvitable_th last_in_group">Savannah</th>-      <th class="kvitable_th last_in_group">Animal</th>-      <th class="kvitable_th last_in_group">Carnivore</th>+      <th class="kvitable_th last_in_group"><div><span>Savannah</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Animal</span></div></th>+      <th class="kvitable_th last_in_group"><div><span>Carnivore</span></div></th>       <td class="kvitable_td last_in_group"></td>       <td class="kvitable_td last_in_group"></td>       <td class="kvitable_td last_in_group"></td>
kvitable.cabal view
@@ -1,6 +1,6 @@ cabal-version:       >=1.10 name:                kvitable-version:             1.0.3.0+version:             1.1.0.0 synopsis:            Key/Value Indexed Table container and formatting library description:    .@@ -46,18 +46,23 @@ library   hs-source-dirs:      src   default-language:    Haskell2010-  GHC-options:         -Wall -Wcompat -Wincomplete-uni-patterns -Wpartial-fields -fhide-source-paths-  -- other-extensions:+  GHC-options:         -Wall+                       -Wcompat+                       -Wincomplete-uni-patterns+                       -Wpartial-fields+                       -fhide-source-paths   exposed-modules:     Data.KVITable+                     , Data.KVITable.Internal.Helpers                      , Data.KVITable.Render                      , Data.KVITable.Render.ASCII                      , Data.KVITable.Render.HTML-  -- other-modules:-  build-depends:       base >=4.12 && <4.20+                     , Data.KVITable.Render.Internal+  build-depends:       base >=4.12 && <4.21                      , containers                      , lucid  >= 2.9 && < 2.12                      , microlens >= 0.4 && < 0.5-                     , prettyprinter >= 1.7 && < 1.8+                     , named-text >= 1.2 && < 1.3+                     , sayable >= 1.2.4.0 && < 1.3                      , text  test-suite test-kvitable@@ -74,7 +79,6 @@                , html-parse                , kvitable                , microlens-               , pretty-show                , tasty                , tasty-hunit                , template-haskell
src/Data/KVITable.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} @@ -17,11 +18,14 @@   , fromList   , toList   , Data.KVITable.lookup+  , lookup'   , keyVals   , keyValGen   , valueColName   , insert+  , insertWith   , foldlInsert+  , foldlInsertWith   , Data.KVITable.filter   , adjust   , adjustWithKey@@ -35,11 +39,13 @@ import           Data.Function ( on ) import qualified Data.List as L import qualified Data.Map as Map-import           Data.Text ( Text )+import           Data.Name import qualified GHC.Exts import           Lens.Micro ( Lens' ) +import           Data.KVITable.Internal.Helpers + -- | The core KeyValue Indexed Table.  This table is similar to a Map, -- but the values are indexed by a list of Key+Value combinations, and -- the table contents can be sparse.@@ -62,7 +68,7 @@     -- the defaultKeyVal for any keys not explicitly provided for that     -- value. -  , valuecolName :: Text  -- ^ name of the value cells+  , valuecolName :: Named HTMLStyle "column header" -- ^ name of the value cells   }  instance Eq v => Eq (KVITable v) where@@ -78,11 +84,11 @@  -- | The 'Key' is the first half of a tuple that makes up the list of -- keys (the 'KeySpec').  The second half is the 'KeyVal'.-type Key = Text+type Key = Name "Key"  -- | The 'KeyVal' is the first half of a tuple that makes up the list of -- keys (the 'KeySpec').  The first half is the 'Key'.-type KeyVal = Text+type KeyVal = Name "KeyVal"  -- | The 'KeySpec' is the list of tuples and defines the unique key -- for a value in the 'KVITable'.@@ -150,19 +156,19 @@ toList = GHC.Exts.toList  --- | Fetch or set the keyvals list via lenses. Note that setting the--- keyval list will drop any current contents in the table that do not--- have entries in the keyvals list.+-- | Fetch or set the keyvals list via lenses. Note that setting the keyval list+-- will drop any current contents in the table that do not have entries in the+-- keyvals list; existing entries in the table under the old keys are NOT+-- migrated to the new table entries.  keyVals :: Lens' (KVITable v) KeyVals keyVals f t = (\kvs ->                  t { keyvals = kvs                    , contents =-                     let inKVS spec _ = inkv spec kvs-                         inkv [] [] = True-                         inkv ((sk,sv):srs) ((k,vs):kv)-                           | sk == k && sv `elem` vs = inkv srs kv-                         inkv _ _ = False+                     let inKVS spec _val =+                           length kvs == length spec+                           && and (keymatch <$> zip spec kvs)+                         keymatch ((sk,sv), (k,vs)) = sk == k && sv `elem` vs                      in Map.filterWithKey inKVS (contents t)                    }               ) <$> f (keyvals t)@@ -176,7 +182,7 @@ -- | Fetch or set the column name for the actual value cell in the -- 'KVITable'. -valueColName :: Lens' (KVITable v) Text+valueColName :: Lens' (KVITable v) (Named HTMLStyle "column header") valueColName f t = (\n -> t { valuecolName = n } ) <$> f (valuecolName t)  @@ -186,6 +192,13 @@ -- out of order and the lookup will still succeed (if there is a value -- for the normalized keyspec), but it will be faster to use the -- normalized key directly.+--+-- The performance of lookup degrades exponentially on a sparse KVITable: an+-- unset KVITable entry is indistinguishable from a non-normalized keyspec, so if+-- the initial query fails, the keyspec is normalized and then the lookup is+-- re-attempted.  This may be fine for an individual query, but for large numbers+-- of queries this is excessively slow.  To mitigate this, the lookup' can be+-- used, which assumes the use of a normalized key.  lookup :: KeySpec -> KVITable v -> Maybe v lookup keyspec t = case Map.lookup keyspec $ contents t of@@ -198,11 +211,20 @@ normalizeKeySpec :: KVITable v -> KeySpec -> KeySpec normalizeKeySpec t keyspec =   let keyandval s (k,vs) = case L.lookup k keyspec of-        Just v -> if v `elem` vs then s <> [(k,v)]-                  else s -- no level added, so this should never match in the Map-        Nothing -> s <> [(k, keyvalGen t k)]-  in foldl keyandval [] (keyvals t)+        Just v ->+          if v `elem` vs then snoc s (k,v)+          else s -- no level added, so this should never match in the Map+        Nothing -> snoc s (k, keyvalGen t k)+  in foldl keyandval mempty (keyvals t) ++-- | Like 'lookup', but assumes a normalized key (all key elements specified, and+-- in the proper order).  Faster than 'lookup', but will return false negatives+-- if not used with a normalized key.++lookup' :: KeySpec -> KVITable v -> Maybe v+lookup' keyspec = Map.lookup keyspec . contents+ -- | Inserts a new cell value into the table at the specified keyspec -- location.  The keyspec may be minimally specified and out-of-order. --@@ -210,10 +232,25 @@ -- for the table.  In general, insertion is expected to be less -- frequent than lookups so computation costs are biased towards the -- insertion operation.+--+-- This is equivalent to @insertWith const@.  insert :: KeySpec -> v -> KVITable v -> KVITable v-insert keyspec val t = endset t val (keyvals t) keyspec [] []+insert = insertWith const +-- | Inserts a new cell value into the table at the specified keyspec+-- location, if no value exists at that location. Otherwise, the old and+-- new value are combined via @f new old@.  The keyspec may be minimally+-- specified and out-of-order.+--+-- This may be an expensive operation if it has to extend the keyvals+-- for the table.  In general, insertion is expected to be less+-- frequent than lookups so computation costs are biased towards the+-- insertion operation.++insertWith :: (v -> v -> v) -> KeySpec -> v -> KVITable v -> KVITable v+insertWith f keyspec val t = endsetWith f t val (keyvals t) keyspec mempty mempty+ remainingKeyValDefaults :: KVITable v -> [(Key,a)] -> KeySpec remainingKeyValDefaults t = fmap (\(k,_) -> (k, keyvalGen t k)) @@ -222,18 +259,18 @@                        then e                        else (k, keyvalGen t k : vs) -endset :: KVITable v -> v -> KeyVals -> KeySpec -> KeySpec -> KeyVals -> KVITable v-endset t val rkv [] tspec kvbld =+endsetWith :: (v -> v -> v) -> KVITable v -> v -> KeyVals -> KeySpec -> KeySpec -> KeyVals -> KVITable v+endsetWith f t val rkv [] tspec kvbld =         -- Reached the end of the user's keyspec but there are more         -- known keyvals in this KVITable, so add the entry with the         -- default KeyVal for the remaining keyspec (and ensure the         -- default KeyVal is listed in the table's keyvals).         let spec = tspec <> remainingKeyValDefaults t rkv-        in t { contents = Map.insert spec val (contents t)+        in t { contents = Map.insertWith f spec val (contents t)              , keyvals = kvbld <> (addDefVal t <$> rkv)              } -endset t val [] spec tspec kvbld =+endsetWith f t val [] spec tspec kvbld =   -- Reached the end of the known keyvals for this table but the   -- user's keyspec has additional elements.  This should extend   -- the tables keyvals with the remaining keyspec; also all@@ -241,46 +278,56 @@   -- default values for the new keys in their keyspec.   let spec' = tspec <> spec       keySpecElemToKeyVals (k,v) = (k, if null curTblList-                                       then [v]+                                       then single v                                        else [v, keyvalGen t k])       keyvals' = kvbld <> (keySpecElemToKeyVals <$> spec)       curTblList = Map.toList $ contents t       defaultsExtension = remainingKeyValDefaults t spec       updTblList = fmap (\(ks,v) -> (ks <> defaultsExtension, v)) curTblList-  in t { contents = Map.insert spec' val $ Map.fromList updTblList+  in t { contents = Map.insertWith f spec' val $ Map.fromList updTblList        , keyvals = keyvals'        } -endset t val kvs@((k,vs):rkvs) ((sk,sv):srs) tspec kvbld =+endsetWith f t val kvs@((k,vs):rkvs) ((sk,sv):srs) tspec kvbld =   if k == sk   then let kv' = if sv `elem` vs-                 then kvbld <> [(k, vs)]-                 else kvbld <> [(k, sv : vs)]-       in endset t val rkvs srs (tspec <> [(k,sv)]) kv'+                 then snoc kvbld (k, vs)+                 else snoc kvbld (k, sv : vs)+       in endsetWith f t val rkvs srs (snoc tspec (k,sv)) kv'   else     -- re-arrange user spec crudely by throwing invalid     -- candidates to the end and retrying.  This isn't     -- necessarily efficient, but keyspecs aren't expected to be     -- longer than about a dozen entries.     if sk `elem` (fst <$> rkvs) && k `elem` (fst <$> srs)-    then endset t val kvs (srs <> [(sk,sv)]) tspec kvbld+    then endsetWith f t val kvs (snoc srs (sk,sv)) tspec kvbld     else       if any (`elem` (fst <$> kvs)) (fst <$> srs)-      then endset t val kvs (srs <> [(sk,sv)]) tspec kvbld+      then endsetWith f t val kvs (snoc srs (sk,sv)) tspec kvbld       else         let defVal = keyvalGen t k             vs' = if defVal `elem` vs then vs else (defVal : vs)-        in endset t val rkvs ((sk,sv):srs) (tspec <> [(k,defVal)]) (kvbld <> [(k,vs')])+        in endsetWith f t val rkvs ((sk,sv):srs) (snoc tspec (k,defVal)) (snoc kvbld (k,vs'))  --- | The foldlInsert is a convenience function that can be specified+-- | foldlInsert is a convenience function that can be specified -- as the function argument of a foldl operation over the list form of -- a KVITable to generate the associated KVITable.+--+-- This is equivalent to @foldlInsertWith const@.  foldlInsert :: KVITable v -> (KeySpec, v) -> KVITable v-foldlInsert t (k,v) = insert k v t+foldlInsert = foldlInsertWith const +-- | foldlInsertWith is a convenience function that, when curried with a+-- "combining" function, can be specified as the function argument of a foldl+-- operation over the list form of a KVITable to generate the associated+-- KVITable. +foldlInsertWith :: (v -> v -> v) -> KVITable v -> (KeySpec, v) -> KVITable v+foldlInsertWith f t (k,v) = insertWith f k v t++ -- | Filter 'KVITable' to retain only the elements that satisfy some predicate.  filter :: ((KeySpec, v) -> Bool) -> KVITable v -> KVITable v@@ -328,16 +375,16 @@ update f k t = t { contents = Map.update f (normalizeKeySpec t k) $ contents t }  -- | The 'rows' function returns a set of rows for the 'KVITable' as a--- list structure, where each list entry is a different row.  A row+-- list structure, where each returned entry is a different row.  A row -- consists of the /values/ of the keys for that row followed by the -- value of the entry (to get the names of the keys, use 'keyVals').  rows :: KVITable v -> [ ([KeyVal], v) ]-rows t = go (keyvals t) []+rows t = go (keyvals t) mempty   where     go [] spec = let spec' = reverse spec                  in case Map.lookup spec' (contents t) of-                      Nothing -> []-                      Just v -> [ (snd <$> spec', v) ]+                      Nothing -> mempty+                      Just v -> single (snd <$> spec', v)     go ((key, vals):kvs) spec =       concatMap (\v -> let spec' = (key,v):spec in go kvs spec') vals
+ src/Data/KVITable/Internal/Helpers.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE DataKinds #-}++module Data.KVITable.Internal.Helpers where++import Data.Name ( Name )+++type Keys = [Name "Key"]+++single :: e -> [e]+single = (:[])++snoc :: [a] -> a -> [a]+snoc l e = l <> [e]++maxOf :: (Foldable t, Ord e) => e -> t e -> e+maxOf v l = if null l then v else max v $ maximum l
src/Data/KVITable/Render.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DataKinds #-}+ -- | Common definitions (and support functions) for rendering a -- 'KVITable'. @@ -5,15 +7,18 @@   (     RenderConfig(..)   , defaultRenderConfig-  , sortWithNums+  , sortNumericAlpha   ) where -import           Data.KVITable import qualified Data.List as L-import           Data.Text ( Text )+import           Data.Name ( Name, nameText, nameLength, nullName ) import qualified Data.Text as T+import           Numeric.Natural +import           Data.KVITable++ -- | Returns the default rendering configuration, to be used with a -- format-specific @render@ call. @@ -22,16 +27,18 @@   { hideBlankRows = True   , hideBlankCols = True   , equisizedCols = True-  , sortKeyVals   = False+  , sortKeyVals   = Nothing   , colStackAt    = Nothing   , rowRepeat     = True   , rowGroup      = []   , caption       = Nothing+  , maxCells      = 40000 -- even this is probably too big+  , maxCols       = 40000 -- by default, this should be >= maxCells   }  -- | The 'RenderConfig' specifies the various controls and -- configurations used when rendering a 'KVITable' in various formats.--- The 'RenderConfig' is global t oall formats, although some of the+-- The 'RenderConfig' is global to all formats, although some of the -- fields in the 'RenderConfig' will be ignored as not-applicable by -- some formats. @@ -50,13 +57,31 @@     -- (e.g. HTML) where the backend provides table rendering     -- functionality. -  , sortKeyVals :: Bool-    -- ^ 'True' (default is False) to sort the KeyVal entries when-    -- rendering a table.+  , sortKeyVals :: Maybe ((KeyVals, KeyVals) -> (KeyVals, KeyVals))+    -- ^ Specifies a function to sort the KeyVals (rows and columns,+    -- respectively) for the output.  If no function provided, no sorting is+    -- performed.  The provided KeyVals are in the order in which the Keys are+    -- declared to the KVITable; the values for each key may be in an arbitrary+    -- order. The usual expectation is that this will only sort the values for+    -- each Key, but it is allowed to re-order the Keys as well.+    --+    -- Sorting is done *prior* to applying maxCells and maxCols limitations; this+    -- provides the unsurprising results for the user but means that this+    -- function may perform extra work to sort rows and columns that will not be+    -- shown.+    --+    -- It is permissible to move entries from the rows to the columns and+    -- vice-versa; this function has control over the shape of the resulting+    -- table, which includes redistributing the rows and columns such that the+    -- result will violate maxCols (but not maxCells).+    --+    -- The columns keyvals will be empty if colStackAt is Nothing.    , colStackAt :: Maybe Key-    -- ^ Column key to begin stacking keys in columns and sub-columns-    -- rather than creating additional sub-rows.+    -- ^ Column key to begin stacking keys in columns and sub-columns rather than+    -- creating additional sub-rows.  This is performed on the+    -- originally-specified key order, prior to calling any provided+    -- 'sortKeyVals' function.    , rowRepeat :: Bool     -- ^ 'True' (default) if an identical 'KeyVal' is to be repeated@@ -66,13 +91,56 @@     -- ^ List of Key names that should by grouped by inserting     -- horizontal row lines between KeyVals -  , caption :: Maybe Text+  , caption :: Maybe (Name "caption")     -- ^ Caption to render for table for backends which support     -- captions; otherwise ignored.++  , maxCells :: Natural+    -- ^ The maximum number of cells that will be rendered.  The size of the+    -- table is #rows times #cols, so a 100x100 table is 10000 entries. This+    -- value is used to limit the size of the rendered table to provide a+    -- reasonable output in a reasonable amount of time and memory; tables larger+    -- than this limit will return a "table too big" message when rendered.+    --+    -- ASCII: At least 1 character for the cell, plus 3.5 characters for+    --  boundaries means that even on an ultra-wide monitor with a very small+    --  font, the table isn't very readable beyond 30-40 columns.  In addition,+    --  the algorithm needs to continually adjust table column widths to+    --  accomodate new values, so there is a great deal of backtracking involved+    --  and the time taken to render grows quite quickly.+    --+    -- HTML: A browser has reasonable constraints on displaying a table: 10000+    --  entries is difficult for the user to comprehend, but the browser is+    --  probably reasonably performant.  At 500x500, the browser is likely to be+    --  very sluggish, with visible delays in rendering visible regions during+    --  scrolling.++  , maxCols :: Natural+    -- ^ The maximum number of columns to render.  This limit is only useful if+    -- it is set to less than the 'maxCells' value, and it is useful in that case+    -- to ensure that more than one (partial) row is displayed.  The 'maxCells'+    -- value takes priority over this value.  See the 'maxCells' for more+    -- information.   }  +-- | Table KeyVals sorting function; alphanumeric with numeric preference.  This+-- can be used as the sortKeyVals function for the table.+--+-- If the value starts or ends with a digit, then this should do a rough numeric+-- sort on the expectation that the digits represent a version or some other+-- numeric value.  As an approximation of a numeric sort, sort by word size and+-- then string value.  This will result in [ "1", "2", "10", "50", "400" ], but+-- would fail with [ "v1.0", "v2.0", "v3.0", "v2.0.5", "v1.0.0.3" ], but it's a+-- reasonably fast heuristic and probably better than a straight ascii sort. ++sortNumericAlpha :: (KeyVals, KeyVals) -> (KeyVals, KeyVals)+sortNumericAlpha (rowkeys, colkeys) = ( fmap sortWithNums <$> rowkeys+                                      , fmap sortWithNums <$> colkeys+                                      )++ -- | Sorting for KeyVals.  If the value starts or ends with a digit, -- then this should do a rough numeric sort on the expectation that -- the digits represent a version or some other numeric value.  As an@@ -87,10 +155,10 @@ sortWithNums :: [KeyVal] -> [KeyVal] sortWithNums kvs =   let skvs = zip (rank <$> kvs) kvs-      rank e = if (not $ T.null e) &&-                  or [ T.head e `elem` ['0'..'9']-                     , T.last e `elem` ['0'..'9']+      rank e = if (not $ nullName e) &&+                  or [ T.head (nameText e) `elem` ['0'..'9']+                     , T.last (nameText e) `elem` ['0'..'9']                      ]-               then T.length e+               then nameLength e                else 0   in snd <$> L.sort skvs
src/Data/KVITable/Render/ASCII.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -17,14 +19,20 @@ where  import qualified Data.List as L-import           Data.Maybe ( fromMaybe, isNothing )+import           Data.Maybe ( isNothing )+import           Data.Name+import           Data.String ( fromString ) import           Data.Text ( Text ) import qualified Data.Text as T import           Lens.Micro ( (^.) )-import qualified Prettyprinter as PP+import           Numeric.Natural+import           Text.Sayable -import           Data.KVITable+import           Data.KVITable ( KVITable, KeySpec, keyVals )+import qualified Data.KVITable as KVIT+import           Data.KVITable.Internal.Helpers import           Data.KVITable.Render+import           Data.KVITable.Render.Internal  import           Prelude hiding ( lookup ) @@ -32,27 +40,27 @@ -- | Renders the specified table in ASCII format, using the specified -- 'RenderConfig' controls. -render :: PP.Pretty v => RenderConfig -> KVITable v -> Text+render :: Sayable "normal" v => RenderConfig -> KVITable v -> Text render cfg t =-  let kseq = fst <$> t ^. keyVals-      (fmt, hdr) = renderHdrs cfg t kseq-      bdy = renderSeq cfg fmt kseq t+  let kmap = renderingKeyVals cfg $ t ^. keyVals+      (fmt, hdr) = renderHdrs cfg t kmap+      bdy = renderSeq cfg fmt kmap t   in T.unlines $ hdr <> bdy  ---------------------------------------------------------------------- -data FmtLine = FmtLine [Int] Sigils Sigils  -- last is for sepline+data FmtLine = FmtLine [Natural] Sigils Sigils  -- last is for sepline data Sigils = Sigils { sep :: Text, pad :: Text, cap :: Text } -fmtLine :: [Int] -> FmtLine+fmtLine :: [Natural] -> FmtLine fmtLine cols = FmtLine cols                Sigils { sep = "|", pad = " ", cap = "_" }                Sigils { sep = "+", pad = "-", cap = "_" } -fmtColCnt :: FmtLine -> Int-fmtColCnt (FmtLine cols _ _) = length cols+fmtColCnt :: FmtLine -> Natural+fmtColCnt (FmtLine cols _ _) = nLength cols -perColOvhd :: Int+perColOvhd :: Natural perColOvhd = 2 -- pad chars on either side of each column's entry  -- | Formatted width of output, including pad on either side of each@@ -61,18 +69,19 @@ -- Note that a column size of 0 indicates that hideBlankCols is active -- and the column was found to be empty of values, so it should not be -- counted.-fmtWidth :: FmtLine -> Int+fmtWidth :: FmtLine -> Natural fmtWidth (FmtLine cols _ _) =   let cols' = L.filter (/= 0) cols-  in sum cols' + ((perColOvhd + 1) * (length cols' - 1))+  in sum cols' + ((perColOvhd + 1) * (nLength cols' - 1))  fmtEmptyCols :: FmtLine -> Bool fmtEmptyCols (FmtLine cols _ _) = sum cols == 0 -fmtAddColLeft :: Int -> FmtLine -> FmtLine+fmtAddColLeft :: Natural -> FmtLine -> FmtLine fmtAddColLeft leftCol (FmtLine cols s s') = FmtLine (leftCol : cols) s s'  data FmtVal = Separator | TxtVal Text | CenterVal Text+            | Overage  -- cells for the overflow row/column  fmtRender :: FmtLine -> [FmtVal] -> Text fmtRender (FmtLine _cols _sigils _sepsigils) [] = ""@@ -82,20 +91,28 @@                        Separator   -> f sepsigils                        TxtVal _    -> f sigils                        CenterVal _ -> f sigils+                       Overage     -> f sigils            l = sig sep val+           charRepeat n c = T.pack (replicate (fromEnum n) c)+           rightAlign n t = let tl = toEnum $ T.length t+                                rt = charRepeat (n - tl) ' ' <> t+                            in if tl >= n then t else rt+           centerIn n t = let tl = toEnum $ T.length t+                              (w,e) = (n - tl - 2) `divMod` 2+                              m = cap sigils+                              ls = T.replicate (fromEnum $ w + 0) m+                              rs = T.replicate (fromEnum $ w + e) m+                          in if tl + 2 >= n+                             then rightAlign n t+                             else ls <> " " <> t <> " " <> rs        in l <>           T.concat           [ sig pad fld <>             (case fld of-                Separator   -> T.pack (replicate sz '-')-                TxtVal v    -> T.pack (replicate (sz - T.length v) ' ') <> v-                CenterVal t -> let (w,e) = (sz - T.length t - 2) `divMod` 2-                                   m = cap sigils-                                   ls = T.replicate (w + 0) m-                                   rs = T.replicate (w + e) m-                               in if T.length t + 2 >= sz-                                  then (T.replicate (sz - T.length t) " ") <> t-                                  else ls <> " " <> t <> " " <> rs+                Separator   -> charRepeat sz '-'+                TxtVal v    -> rightAlign sz v+                CenterVal t -> centerIn sz t+                Overage     -> centerIn sz "+"             ) <>             sig pad fld <>             sig sep fld  -- KWQ or if next fld is Nothing@@ -109,75 +126,83 @@  data HeaderLine = HdrLine FmtLine HdrVals Trailer type HdrVals = [FmtVal]-type Trailer = Text+type Trailer = Name "column header"  hdrFmt :: HeaderLine -> FmtLine hdrFmt (HdrLine fmt _ _) = fmt -renderHdrs :: PP.Pretty v => RenderConfig -> KVITable v -> [Key] -> (FmtLine, [Text])-renderHdrs cfg t keys =+renderHdrs :: Sayable "normal" v+           => RenderConfig -> KVITable v -> (TblHdrs, TblHdrs)+           -> (FmtLine, [Text])+renderHdrs cfg t kmap =   ( lastFmt   , [ fmtRender fmt hdrvals-      <> (if T.null trailer then "" else (" <- " <> trailer))+      <> (if nullName trailer then "" else (" <- " <> nameText trailer))     | (HdrLine fmt hdrvals trailer) <- hrows     ] <>-    [ fmtRender lastFmt (replicate (fmtColCnt lastFmt) Separator) ])+    (single $ let sz = fromEnum $ fmtColCnt lastFmt+              in fmtRender lastFmt (replicate sz Separator)) )   where-    hrows = hdrstep cfg t keys+    hrows = hdrstep cfg t kmap     lastFmt = case reverse hrows of-                [] -> fmtLine []+                [] -> fmtLine mempty                 (hrow:_) -> hdrFmt hrow -hdrstep :: PP.Pretty v => RenderConfig -> KVITable v -> [Key] -> [HeaderLine]-hdrstep _cfg t [] =+hdrstep :: Sayable "normal" v+        => RenderConfig+        -> KVITable v+        -> (TblHdrs, TblHdrs)+        -> [HeaderLine]+hdrstep _cfg t ([], []) =   -- colStackAt wasn't recognized, so devolve into a non-colstack table-  let valcoltxt = t ^. valueColName-      valcoltsz = T.length valcoltxt-      valsizes  = length . show . PP.pretty . snd <$> toList t-      valwidth  = maximum $ valcoltsz : valsizes-  in [ HdrLine (fmtLine [valwidth]) [TxtVal valcoltxt] "" ]-hdrstep cfg t (key:keys) =-  if colStackAt cfg == Just key-  then hdrvalstep cfg t [] (key:keys)  -- switch to column-stacking mode-  else-    let keyw = maximum ( T.length key :-                         fmap T.length (fromMaybe [] $ L.lookup key $ t ^. keyVals) )-        mkhdr (hs, v) (HdrLine fmt hdrvals trailer) =-          ( HdrLine (fmtAddColLeft keyw fmt) (TxtVal v : hdrvals) trailer : hs , "")-    in reverse $ fst $ foldl mkhdr ([], key) $ hdrstep cfg t keys-         -- first line shows hdrval for non-colstack'd columns, others are blank+  let valcoltxt = t ^. KVIT.valueColName+      valcoltsz = nameLength valcoltxt+      valsizes  = nLength . sez @"normal" . snd <$> KVIT.toList t+      valwidth  = maxOf 0 $ valcoltsz : valsizes+      hdrVal = TxtVal $ nameText valcoltxt+  in single $ HdrLine (fmtLine $ single valwidth) (single hdrVal) ""+hdrstep cfg t ([], colKeyMap) =+  hdrvalstep cfg t colKeyMap mempty  -- switch to column-stacking mode+hdrstep cfg t ((key,keyvals) : keys, colKeyMap) =+  let keyw = max (nameLength key)+             $ (maxOf 0 . fmap (nameLength . toHdrText)) keyvals+      mkhdr (hs, v) (HdrLine fmt hdrvals trailer) =+        ( HdrLine (fmtAddColLeft keyw fmt) (TxtVal (nameText v) : hdrvals) trailer : hs , "")+  in reverse $ fst $ foldl mkhdr (mempty, key) $ hdrstep cfg t (keys, colKeyMap)+  -- first line shows hdrval for non-colstack'd columns, others are blank -hdrvalstep :: PP.Pretty v => RenderConfig -> KVITable v -> KeySpec -> [Key] -> [HeaderLine]-hdrvalstep _ _ _ [] = error "ASCII hdrvalstep with empty keys after matching colStackAt -- impossible"-hdrvalstep cfg t steppath (key:[]) =-  let titles = ordering $ fromMaybe [] $ L.lookup key $ t ^. keyVals-      ordering = if sortKeyVals cfg then sortWithNums else id-      cvalWidths kv = fmap (length . show . PP.pretty . snd) $-                      L.filter ((L.isSuffixOf (steppath <> [(key, kv)])) . fst) $-                      toList t+hdrvalstep :: Sayable "normal" v+           => RenderConfig -> KVITable v -> TblHdrs -> KeySpec+           -> [HeaderLine]+hdrvalstep cfg t ((key,titles) : []) steppath =+  let cvalWidths = \case+        V kv -> fmap (nLength . sez @"normal" . snd)+                $ filter ((L.isSuffixOf (snoc steppath (key, kv))) . fst)+                $ KVIT.toList t+        AndMore _ -> [1] -- always show this column, although it has no contents       colWidth kv = let cvw = cvalWidths kv-                    in if and [ hideBlankCols cfg, sum cvw == 0 ]-                    then 0-                    else maximum $ T.length kv : cvw+                    in if hideBlankCols cfg && sum cvw == 0+                       then 0+                       else maxOf (nameLength $ toHdrText kv) cvw       cwidths = fmap colWidth titles       fmtcols = if equisizedCols cfg-                then (replicate (length cwidths) (maximum cwidths))+                then (replicate (length cwidths) (maxOf 0 cwidths))                 else cwidths-  in [ HdrLine (fmtLine $ fmtcols) (TxtVal <$> titles) key ]-hdrvalstep cfg t steppath (key:keys) =-  let vals = ordering $ fromMaybe [] $ L.lookup key $ t ^. keyVals-      ordering = if sortKeyVals cfg then sortWithNums else id-      subhdrsV v = hdrvalstep cfg t (steppath <> [(key,v)]) keys-      subTtlHdrs = let subAtVal v = (T.length v, subhdrsV v)+      tr = convertName key+      hdrTxt = nameText . toHdrText+  in single $ HdrLine (fmtLine $ fmtcols) (TxtVal . hdrTxt <$> titles) tr+hdrvalstep cfg t ((key,vals) : keys) steppath =+  let subhdrsV = \case+        V v -> hdrvalstep cfg t keys (snoc steppath (key,v))+        _ -> mempty+      subTtlHdrs = let subAtVal v = (nameLength $ toHdrText v, subhdrsV v)                    in fmap subAtVal vals       szexts = let subW (hl,sh) =                      case sh of                        [] -> (0, 0)  -- should never be the case                        (sh0:_) ->                          let sv = fmtWidth $ hdrFmt sh0-                         in if and [ hideBlankCols cfg,-                                     fmtEmptyCols $ hdrFmt sh0-                                   ]+                         in if hideBlankCols cfg && (fmtEmptyCols $ hdrFmt sh0)                             then (0, 0)                             else (hl, sv)                in fmap (uncurry max . subW) subTtlHdrs@@ -185,68 +210,88 @@                        L.transpose $                        fmap (uncurry rsz_hdrstack) $                        zip szhdrs $ fmap snd subTtlHdrs-      largest = maximum szexts+      largest = maxOf 0 szexts       szhdrs = if equisizedCols cfg && not (hideBlankCols cfg)                then replicate (length vals) largest                else szexts       rsz_hdrstack s vhs = fmap (rsz_hdrs s) vhs       rsz_hdrs hw (HdrLine (FmtLine c s j) v r) =         let nzCols = L.filter (/= 0) c-            pcw = sum nzCols + ((perColOvhd + 1) * (length nzCols - 1))+            numNZCols = nLength nzCols+            pcw = sum nzCols + ((perColOvhd + 1) * (numNZCols - 1))             (ew,w0) = let l = length nzCols                       in if l == 0 then (0,0)-                         else max 0 (hw - pcw) `divMod` length nzCols-            c' = fst $ foldl (\(c'',n) w -> (c''<>[n+w],ew)) ([],ew+w0) c+                         else max 0 (hw - pcw) `divMod` numNZCols+            c' = fst $ foldl (\(c'',n) w -> (snoc c'' $ n+w, ew)) (mempty,ew+w0) c         in HdrLine (FmtLine c' s j) v r-      hdrJoin hl = foldl hlJoin (HdrLine (fmtLine []) [] "") hl+      hdrJoin hl = foldl hlJoin (HdrLine (fmtLine mempty) mempty "") hl       hlJoin (HdrLine (FmtLine c s j) v _) (HdrLine (FmtLine c' _ _) v' r) =         HdrLine (FmtLine (c<>c') s j) (v<>v') r-      tvals = fmap CenterVal vals-  in HdrLine (fmtLine szhdrs) tvals key : rsz_extsubhdrs+      tvals = CenterVal . nameText . toHdrText <$> vals+  in HdrLine (fmtLine szhdrs) tvals (convertName key) : rsz_extsubhdrs+hdrvalstep _ _ [] _ = error "ASCII hdrvalstep with empty keys after matching colStackAt -- impossible" -renderSeq :: PP.Pretty v => RenderConfig -> FmtLine -> [Key] -> KVITable v -> [Text]-renderSeq cfg fmt keys kvitbl = fmtRender fmt . snd <$> asciiRows keys []+toHdrText :: TblHdr -> Name "column header"+toHdrText = \case+  V kv -> convertName kv+  AndMore n -> fromString $ sez @"normal" $ t'"{+" &+ n &+ '}'++----------------------------------------------------------------------++renderSeq :: Sayable "normal" v+          => RenderConfig+          -> FmtLine+          -> (TblHdrs, TblHdrs)+          -> KVITable v+          -> [Text]+renderSeq cfg fmt kmap kvitbl = fmtRender fmt . snd <$> asciiRows kmap mempty   where-    asciiRows :: [Key] -> KeySpec -> [ (Bool, [FmtVal]) ]-    asciiRows [] path =-      let v = lookup path kvitbl+    filterBlank = if hideBlankRows cfg+                  then L.filter (not . all isNothing . snd)+                  else id+    asciiRows :: (TblHdrs, TblHdrs)+              -> KeySpec+              -> [ (Bool, [FmtVal]) ]+    asciiRows ([], []) path =+      let v = KVIT.lookup' path kvitbl           skip = case v of                    Nothing -> hideBlankRows cfg                    Just _  -> False-      in if skip then []-         else [ (False, [ maybe (TxtVal "") TxtVal (T.pack . show . PP.pretty <$> v) ]) ]-    asciiRows (key:kseq) path-      | colStackAt cfg == Just key =-        let filterOrDefaultBlankRows =-              fmap (fmap defaultBlanks) .-              if hideBlankRows cfg-              then L.filter (not . all isNothing . snd)-              else id-            defaultBlanks = fmap (\v -> maybe (TxtVal "") TxtVal v)-        in filterOrDefaultBlankRows $ [ (False, multivalRows (key:kseq) path) ]-      | otherwise =-        let subrows keyval = asciiRows kseq $ path <> [ (key, keyval) ]-            grprow = \case-              subs@(sub0:_) | key `elem` rowGroup cfg ->-                  let subl = [ (True, replicate (length $ snd sub0) Separator) ]-                  in if fst (last subs)-                     then init subs <> subl-                     else subs <> subl-              subs -> subs-            addSubrows ret keyval = ret <> (grprow $ fst $-                                            foldl leftAdd ([],keyval) $ subrows keyval)-            leftAdd (acc,kv) (b,subrow) = (acc <> [ (b, TxtVal kv : subrow) ],-                                           if rowRepeat cfg then kv else "")-            ordering = if sortKeyVals cfg then sortWithNums else id-        in foldl addSubrows [] $ ordering $ fromMaybe [] $ L.lookup key $ kvitbl ^. keyVals-    multivalRows :: [Key] -> KeySpec -> [ Maybe Text ]-    multivalRows (key:[]) path =-      let ordering = if sortKeyVals cfg then sortWithNums else id-          keyvals = ordering $ fromMaybe [] $ L.lookup key $ kvitbl ^. keyVals-          showEnt = T.pack . show . PP.pretty-      in (\v -> (showEnt <$> (lookup (path <> [(key,v)]) kvitbl))) <$> keyvals-    multivalRows (key:kseq) path =-      let ordering = if sortKeyVals cfg then sortWithNums else id-          keyvals = ordering $ fromMaybe [] $ L.lookup key  $ kvitbl ^. keyVals-      in concatMap (\v -> multivalRows kseq (path <> [(key,v)])) keyvals+      in if skip then mempty+         else single $ (False, single $ maybe (TxtVal "") TxtVal (T.pack . sez @"normal" <$> v) )+    asciiRows ([], colKeyMap) path =+      let filterOrDefaultBlankRows = fmap (fmap defaultBlanks) . filterBlank+          defaultBlanks = fmap (\v -> maybe (TxtVal "") TxtVal v)+      in filterOrDefaultBlankRows $ single $ (False, multivalRows colKeyMap path)+    asciiRows ((key, keyvals) : kseq, colKeyMap) path =+      let subrows = \case+            V keyval -> asciiRows (kseq, colKeyMap) $ snoc path (key, keyval)+            _ ->+              let ttlcols = product ( length . snd <$> colKeyMap)+              in [ (False , replicate (ttlcols + length kseq) $ Overage) ]+          grprow = \case+            subs@(sub0:_) | key `elem` rowGroup cfg ->+              let subl = single (True, replicate (length $ snd sub0) Separator)+              in if fst (last subs) then init subs <> subl else subs <> subl+            subs -> subs+          genSubRow keyval = grprow $ fst+                             $ foldl leftAdd (mempty, toHdrText keyval) $ subrows keyval+          leftAdd (acc,kv) (b,subrow) =+            (snoc acc (b, TxtVal (nameText kv) : subrow)+            , if rowRepeat cfg then kv else ""+            )+      in concat (genSubRow <$> keyvals)++    multivalRows :: TblHdrs -> KeySpec -> [ Maybe Text ]+    multivalRows ((key, keyvals) : []) path =+      let showEnt = T.pack . sez @"normal"+      in (\case+             V v -> (showEnt <$> (KVIT.lookup' (snoc path (key,v)) kvitbl))+             _ -> Nothing+         ) <$> keyvals+    multivalRows ((key, keyvals) : kseq) path =+      concatMap (\case+                    V v -> multivalRows kseq (snoc path (key,v))+                    _ -> mempty+                ) keyvals     multivalRows [] _ = error "multivalRows cannot be called with no keys!"
src/Data/KVITable/Render/HTML.hs view
@@ -1,6 +1,11 @@ {-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}  -- | This module provides the 'KVITable' 'render' function for -- rendering the table in a HTML table format.  The various HTML table@@ -20,16 +25,22 @@ import qualified Data.List as L import           Data.List.NonEmpty ( NonEmpty( (:|) ) ) import qualified Data.List.NonEmpty as NEL-import           Data.Maybe ( fromMaybe, isNothing )+import           Data.Maybe ( isNothing )+import           Data.Name ( Named, HTMLStyle, UTF8, convertName+                           , convertStyle, nameText )+import           Data.String ( fromString ) import           Data.Text ( Text ) import qualified Data.Text as T import qualified Data.Text.Lazy as TL import           Lens.Micro ( (^.) ) import           Lucid-import qualified Prettyprinter as PP+import           Numeric.Natural+import           Text.Sayable -import           Data.KVITable+import           Data.KVITable as KVIT+import           Data.KVITable.Internal.Helpers import           Data.KVITable.Render+import           Data.KVITable.Render.Internal  import           Prelude hiding ( lookup ) @@ -39,20 +50,27 @@ -- definition; it is intended to be embedded in a larger HTML -- document. -render :: PP.Pretty v => RenderConfig -> KVITable v -> Text+render :: Sayable "html" v => RenderConfig -> KVITable v -> Text render cfg t =-  let kseq = fst <$> t ^. keyVals-      (fmt, hdr) = renderHdrs cfg t kseq-      bdy = renderSeq cfg fmt kseq t+  let kmap = renderingKeyVals cfg $ t ^. keyVals+      (fmt, hdr) = renderHdrs cfg kmap t+      bdy = renderSeq cfg fmt kmap t   in TL.toStrict $ renderText $      table_ [ class_ "kvitable" ] $-     do maybe mempty (caption_ . toHtml) $ Data.KVITable.Render.caption cfg+     do maybe mempty (caption_ . toHtml . convertStyle @UTF8 @HTMLStyle)+          $ Data.KVITable.Render.caption cfg         thead_ [ class_ "kvitable_head" ] hdr         tbody_ [ class_ "kvitable_body" ] bdy ++instance ToHtml (Named HTMLStyle nameOf) where+  toHtml = toHtmlRaw . nameText+  -- Note: toHtml uses toHtmlRaw because Named HTMLStyle is already escaped+  toHtmlRaw = toHtmlRaw . nameText+ ---------------------------------------------------------------------- -data FmtLine = FmtLine [Int]  -- colspans, length is # columns+data FmtLine = FmtLine [Natural]  -- colspans, length is # columns  instance Semigroup FmtLine where   (FmtLine c1) <> (FmtLine c2) = FmtLine $ c1 <> c2@@ -60,40 +78,56 @@ instance Monoid FmtLine where   mempty = FmtLine mempty -fmtAddColLeft :: Int -> FmtLine -> FmtLine+fmtAddColLeft :: Natural -> FmtLine -> FmtLine fmtAddColLeft lspan (FmtLine col) = FmtLine $ lspan : col -data FmtVal = Val Height LastInGroup Text-            | Hdr Height LastInGroup Text-            deriving Show-type Height = Int+data FmtVal = Val Span LastInGroup Bool Text+            | Hdr Span LastInGroup (Named HTMLStyle "column header")+data Span = Singular | Cols Width | Rows Height+type Height = Natural+type Width = Natural type LastInGroup = Bool-type RightLabel = Text+type RightLabel = Named HTMLStyle "column header" -fmtRender :: FmtLine -> [FmtVal] -> Maybe RightLabel -> Html ()-fmtRender (FmtLine cols) vals mbRLabel = do+fmtRender :: FmtLine -> Maybe RightLabel -> [FmtVal] -> Html ()+fmtRender (FmtLine cols) mbRLabel vals = do   tr_ [ class_ "kvitable_tr" ] $     let excessColCnt = length cols - length vals         cell (w,Hdr h l v) =           let a = [ [ class_ "kvitable_th" ]-                  , if h == 1 then []-                    else [ rowspan_ $ T.pack $ show h ]-                  , if w == 1 then []+                  , case h of+                      Singular -> mempty+                      Cols n -> if w == 1 && n == 1+                                then mempty+                                else [ colspan_ $ T.pack $ show (n*w)+                                     , class_ " multicol"+                                     ]+                      Rows 1 -> mempty+                      Rows n -> [ rowspan_ $ T.pack $ show n ]+                  , if w == 1+                       || case h of+                            Cols n | n > 1 -> True+                            _ -> False+                    then mempty                     else [ colspan_ $ T.pack $ show w                          , class_ " multicol" ]-                  , if l then [ class_ " last_in_group" ] else []+                  , if l then [ class_ " last_in_group" ] else mempty                   ]-          in th_ (concat $ reverse a) (toHtml v)-        cell (w,Val h l v) =+          in th_ (concat $ reverse a) (div_ $ span_ $ toHtml v)+        cell (w,Val h l i v) =           let a = [ [ class_ "kvitable_td" ]-                  , if h == 1 then []-                    else [ rowspan_ $ T.pack $ show h ]-                  , if w == 1 then []+                  , case h of+                      Singular -> mempty+                      Cols 1 -> mempty+                      Cols n -> [ colspan_ $ T.pack $ show n ]+                      Rows 1 -> mempty+                      Rows n -> [ rowspan_ $ T.pack $ show n ]+                  , if w == 1 then mempty                     else [ colspan_ $ T.pack $ show w                          , class_ " multicol" ]-                  , if l then [ class_ " last_in_group" ] else []+                  , if l then [ class_ " last_in_group" ] else mempty                   ]-          in td_ (concat $ reverse a) (toHtml v)+          in td_ (concat $ reverse a) $ if i then i_ (toHtml v) else toHtml v         labelMark = toHtmlRaw ("&nbsp;&larr;" :: Text)         labelHtml = th_ [ class_ "rightlabel kvitable_th" ] .                     (labelMark <>) .@@ -107,7 +141,7 @@  data HeaderLine = HdrLine FmtLine HdrVals Trailer type HdrVals = [FmtVal]-type Trailer = Maybe Text+type Trailer = Maybe RightLabel  instance Semigroup HeaderLine where   (HdrLine fmt1 hv1 t1) <> (HdrLine fmt2 hv2 _) =@@ -116,140 +150,177 @@ hdrFmt :: HeaderLine -> FmtLine hdrFmt (HdrLine fmt _ _) = fmt -renderHdrs :: PP.Pretty v-           => RenderConfig -> KVITable v -> [Key]+renderHdrs :: Sayable "html" v+           => RenderConfig+           -> (TblHdrs, TblHdrs)+           -> KVITable v            -> ( FmtLine, Html () )-renderHdrs cfg t keys = ( rowfmt, sequence_ hdrs )+renderHdrs cfg kmap t = ( rowfmt, sequence_ hdrs )   where     hdrs = fmap renderHdr hrows-    (hrows, rowfmt) = hdrstep cfg t keys-    renderHdr (HdrLine fmt hdrvals trailer) = fmtRender fmt hdrvals trailer+    (hrows, rowfmt) = hdrstep cfg t kmap+    renderHdr (HdrLine fmt hdrvals trailer) = fmtRender fmt trailer hdrvals -hdrstep :: PP.Pretty v-        => RenderConfig -> KVITable v -> [Key]+hdrstep :: Sayable "html" v+        => RenderConfig+        -> KVITable v+        -> (TblHdrs, TblHdrs)         -> (NEL.NonEmpty HeaderLine, FmtLine)-hdrstep _cfg t [] =-  ( HdrLine (FmtLine [1]) [Hdr 1 False $ t ^. valueColName] Nothing :| []-  , FmtLine [1]-  )-hdrstep cfg t (key:keys) =-  if colStackAt cfg == Just key-  then hdrvalstep cfg t [] (key:keys) -- switch to column stacking mode-  else-    let (nexthdr0 :| nexthdrs, lowestfmt) = hdrstep cfg t keys-        (HdrLine fmt vals tr) = nexthdr0-        fmt' = fmtAddColLeft 1 fmt-        val = Hdr (length nexthdrs + 1) False key-    in ( (HdrLine fmt' (val : vals) tr) :| nexthdrs-       , fmtAddColLeft 1 lowestfmt-       )+hdrstep _cfg t ([], []) =+  let hdr = Hdr Singular False $ t ^. valueColName+      one = single 1+  in ( HdrLine (FmtLine one) (single hdr) Nothing :| mempty+     , FmtLine one+     )+hdrstep cfg t ([], colKeys) =+  hdrvalstep cfg t colKeys mempty -- switch to column stacking mode+hdrstep cfg t ((key,_) : keys, colKeys) =+  let (nexthdr0 :| nexthdrs, lowestfmt) = hdrstep cfg t (keys, colKeys)+      (HdrLine fmt vals tr) = nexthdr0+      fmt' = fmtAddColLeft 1 fmt+      val = Hdr (Rows $ nLength nexthdrs + 1) False+            $ convertStyle $ convertName key+  in ( (HdrLine fmt' (val : vals) tr) :| nexthdrs+     , fmtAddColLeft 1 lowestfmt+     ) -hdrvalstep :: PP.Pretty v-           => RenderConfig -> KVITable v -> KeySpec -> [Key]+hdrvalstep :: Sayable "html" v+           => RenderConfig+           -> KVITable v+           -> TblHdrs+           -> KeySpec            -> (NEL.NonEmpty HeaderLine, FmtLine)-hdrvalstep _ _ _ [] = error "HTML hdrvalstep with empty keys after matching colStackAt -- impossible"-hdrvalstep cfg t steppath (key:[]) =-  let titles = ordering $ fromMaybe [] $ L.lookup key $ t ^. keyVals-      ordering = if sortKeyVals cfg then sortWithNums else id-      cvalWidths kv = fmap (length . show . PP.pretty . snd) $-                      L.filter ((L.isSuffixOf (steppath <> [(key, kv)])) . fst) $-                      toList t-      cwidth c = if and [ hideBlankCols cfg-                        , 0 == (sum $ cvalWidths c) ]-                 then 0-                 else 1-      fmt = FmtLine $ fmap cwidth titles-  in ( HdrLine fmt (Hdr 1 False <$> titles) (Just key) :| [], fmt)-hdrvalstep cfg t steppath (key:keys) =-  let ordering = if sortKeyVals cfg then sortWithNums else id-  in case ordering $ fromMaybe [] $ L.lookup key $ t ^. keyVals of-       [] -> error "cannot happen"-       (ttl:ttls) ->-         let-           titles = ttl :| ttls-           subhdrsV v = hdrvalstep cfg t (steppath <> [(key,v)]) keys-           subTtlHdrs :: NEL.NonEmpty (NEL.NonEmpty HeaderLine, FmtLine)-           subTtlHdrs = subhdrsV <$> titles-           subhdrs :: NEL.NonEmpty (NEL.NonEmpty HeaderLine, FmtLine)-           subhdrs = if hideBlankCols cfg-                     then subTtlHdrs-                     else-                       -- Want to repeat the first element of subTtlHdrs to get a-                       -- NonEmpty the same length as titles.  Both titles and-                       -- subTtlHdrs are NonEmpty, but NonEmpty has no replicate-                       -- function.-                       let n = length titles -- >= 1 because titles is NonEmpty-                           e = NEL.head subTtlHdrs-                           tail' = NEL.take (n-1) $ NEL.repeat e-                       in e :| tail'-           subhdr_rollup = joinHdrs <$> NEL.transpose (fst <$> subhdrs)-           joinHdrs :: NEL.NonEmpty HeaderLine -> HeaderLine-           joinHdrs (hl0 :| hls) = foldl (<>) hl0 hls-           superFmt :: (NEL.NonEmpty HeaderLine, FmtLine) -> Int-           superFmt sub = let FmtLine subcols = hdrFmt $ NEL.last $ fst sub-                          in if sum subcols == 0-                             then 0-                             else length $ L.filter (/= 0) subcols-           topfmt = FmtLine $ NEL.toList (superFmt <$> subhdrs)-           tophdr = HdrLine topfmt (NEL.toList (Hdr 1 False <$> titles)) $ Just key-         in ( NEL.cons tophdr subhdr_rollup, F.fold (snd <$> subTtlHdrs))+hdrvalstep _ _ [] _ = error "HTML hdrvalstep with empty keys after matching colStackAt -- impossible"+hdrvalstep cfg t ((key, titles) : []) steppath =+  let cvalWidths kv = fmap (length . sez @"html" . snd) $+                      L.filter ((L.isSuffixOf (snoc steppath (key, kv))) . fst)+                      $ KVIT.toList t+      cwidth = \case+        V c -> if hideBlankCols cfg && 0 == (sum $ cvalWidths c) then 0 else 1+        AndMore _ -> 1+      fmt = FmtLine (cwidth <$> titles)+      hdr = Hdr Singular False . toHdrText <$> titles+      k = convertStyle @UTF8 @HTMLStyle $ convertName key+  in ( HdrLine fmt hdr (Just k) :| mempty, fmt)+hdrvalstep _cfg _t ((_key, []) : _keys) _steppath = error "cannot happen"+hdrvalstep cfg t ((key, ttl:ttls) : keys) steppath =+  let+    titles = ttl :| ttls+    subhdrsV v = hdrvalstep cfg t keys (case v of+                                          V kv -> snoc steppath (key,kv)+                                          _ -> steppath+                                       )+    subTtlHdrs :: NEL.NonEmpty (NEL.NonEmpty HeaderLine, FmtLine)+    subTtlHdrs = subhdrsV <$> titles+    subhdrs :: NEL.NonEmpty (NEL.NonEmpty HeaderLine, FmtLine)+    subhdrs = if hideBlankCols cfg+              then subTtlHdrs+              else+                -- Want to repeat the first element of subTtlHdrs to get a+                -- NonEmpty the same length as titles.  Both titles and+                -- subTtlHdrs are NonEmpty, but NonEmpty has no replicate+                -- function.+                let n = length titles -- >= 1 because titles is NonEmpty+                    e = NEL.head subTtlHdrs+                    tail' = NEL.take (n-1) $ NEL.repeat e+                in e :| tail'+    subhdr_rollup = joinHdrs <$> NEL.transpose (fst <$> subhdrs)+    joinHdrs :: NEL.NonEmpty HeaderLine -> HeaderLine+    joinHdrs (hl0 :| hls) = foldl (<>) hl0 hls+    superFmt :: (NEL.NonEmpty HeaderLine, FmtLine) -> Natural+    superFmt sub = let FmtLine subcols = hdrFmt $ NEL.last $ fst sub+                   in if sum subcols == 0+                      then 0+                      else nLength $ L.filter (/= 0) subcols+    topfmt = FmtLine $ NEL.toList (superFmt <$> subhdrs)+    tophdr = let h = Hdr Singular False . toHdrText <$> titles+                 tr = convertStyle @UTF8 @HTMLStyle $ convertName key+             in HdrLine topfmt (NEL.toList h) $ Just tr+  in ( NEL.cons tophdr subhdr_rollup, F.fold (snd <$> subTtlHdrs)) ++toHdrText :: TblHdr -> Named HTMLStyle "column header"+toHdrText th = case toHdrText' th of+                 Right t -> t+                 Left n -> fromString $ sez @"html" $ t'"{+" &+ n &+ '}'++toHdrText' :: TblHdr -> Either Natural (Named HTMLStyle "column header")+toHdrText' = \case+  V kv -> Right $ convertStyle @UTF8 @HTMLStyle $ convertName kv+  AndMore n -> Left n+ ---------------------------------------------------------------------- -renderSeq :: PP.Pretty v-          => RenderConfig -> FmtLine -> [Key] -> KVITable v -> Html ()-renderSeq cfg fmt keys t =-  mapM_ (flip (fmtRender fmt) Nothing) $ htmlRows keys []+renderSeq :: Sayable "html" v+          => RenderConfig -> FmtLine+          -> (TblHdrs, TblHdrs)+          -> KVITable v+          -> Html ()+renderSeq cfg fmt kmap t =+  let lst = htmlRows kmap mempty+      rndr = fmtRender fmt Nothing+  in sequence_ (each rndr lst)   where-    mkVal = Val 1 False . T.pack . show . PP.pretty-    htmlRows :: [Key] -> KeySpec -> [ [FmtVal] ]-    htmlRows [] path =-      let v = lookup path t+    each = map++    filterBlank = if hideBlankRows cfg+                  then L.filter (not . all isNothing)+                  else id++    mkVal = Val Singular False False . T.pack . sez @"html"++    htmlRows :: (TblHdrs, TblHdrs) -> KeySpec -> [ [FmtVal] ]+    htmlRows ([], []) path =+      let v = lookup' path t           skip = case v of             Nothing -> hideBlankRows cfg             Just _ -> False-          row = maybe (Val 1 False "") mkVal v-      in if skip then [] else [ [row] ]-    htmlRows (key:kseq) path-      | colStackAt cfg == Just key =+          row = maybe (Val Singular False False "") mkVal v+      in if skip then mempty else single $ single row+    htmlRows ([], colKeyMap) path =           let filterOrDefaultBlankRows =-                fmap (fmap (maybe (Val 1 False "") id)) .-                if hideBlankRows cfg-                then L.filter (not . all isNothing)-                else id-          in filterOrDefaultBlankRows $-             [ multivalRows (key:kseq) path ]-      | otherwise =-          let keyvals = ordering $ fromMaybe [] $ L.lookup key $ t ^. keyVals-              ordering = if sortKeyVals cfg then sortWithNums else id-              subrows keyval = htmlRows kseq $ path <> [(key,keyval)]-              endOfGroup = key `elem` rowGroup cfg-              addSubrows ret keyval =-                let sr = subrows keyval-                in ret <> (fst $-                           foldl (leftAdd (length sr)) ([],Just keyval) $-                           reverse $ zip (endOfGroup: L.repeat False) $ reverse sr)-              leftAdd nrows (acc,mb'kv) (endGrp, subrow) =-                let sr = setValGrouping endGrp <$> subrow-                    setValGrouping g (Val h g' v) = Val h (g || g') v-                    setValGrouping g (Hdr h g' v) = Hdr h (g || g') v-                in ( acc <> [ (case mb'kv of-                                    Nothing -> sr-                                    Just kv -> let w = if rowRepeat cfg-                                                       then 1-                                                       else nrows-                                               in Hdr w endOfGroup kv : sr-                              ) ]-                   , if rowRepeat cfg then mb'kv else Nothing)-          in foldl addSubrows [] keyvals-+                fmap (fmap (maybe (Val Singular False False "") id)) . filterBlank+          in filterOrDefaultBlankRows $ single $ multivalRows colKeyMap path+    htmlRows ((key,keyvals) : kseq, colKeyMap) path =+      let subrows = \case+            V keyval -> htmlRows (kseq, colKeyMap) $ snoc path (key,keyval)+            _ -> [ [Val (Cols nCols) True True "more"] ]+          nCols = product ( nLength . snd <$> colKeyMap )+          nHdrs = nLength kseq + 1+          endOfGroup = key `elem` rowGroup cfg+          genSubrows keyval =+            let sr = subrows keyval+                kv = toHdrText' keyval+            in fst+               $ foldl (leftAdd (nLength sr)) (mempty, Just kv)+               $ reverse+               $ zip (endOfGroup : L.repeat False)+               $ reverse sr+          leftAdd nrows (acc,mb'kv) (endGrp, subrow) =+            let sr = setValGrouping endGrp <$> subrow+                setValGrouping g (Val h g' i v) = Val h (g || g') i v+                setValGrouping g (Hdr h g' v) = Hdr h (g || g') v+            in ( snoc acc+                 (case mb'kv of+                     Nothing -> sr+                     Just (Right kv) ->+                       let w = if rowRepeat cfg then 1 else nrows+                       in Hdr (Rows w) endOfGroup kv : sr+                     Just (Left n) ->+                       let m = fromString $ sez @"html" $ t'"{+" &+ n &+ '}'+                       in Hdr (Cols nHdrs) True m : sr+                 )+               , if rowRepeat cfg then mb'kv else Nothing)+      in concat $ each genSubrows keyvals      multivalRows [] _ = error "HTML multivalRows cannot be called with no keys!"-    multivalRows (key:[]) path =-      let keyvals = ordering $ fromMaybe [] $ L.lookup key $ t ^. keyVals-          ordering = if sortKeyVals cfg then sortWithNums else id-      in (\v -> mkVal <$> lookup (path <> [(key,v)]) t) <$> keyvals-    multivalRows (key:kseq) path =-      let keyvals = ordering $ fromMaybe [] $ L.lookup key $ t ^. keyVals-          ordering = if sortKeyVals cfg then sortWithNums else id-      in concatMap (\v -> multivalRows kseq (path <> [(key,v)])) keyvals+    multivalRows ((key, keyvals) : []) path =+      (\case+          V v -> mkVal <$> lookup' (snoc path (key,v)) t+          _ -> Just $ Val Singular False True "... -->"+      ) <$> keyvals+    multivalRows ((key, keyvals) : kseq) path =+      concatMap (\case+                   V v -> multivalRows kseq (snoc path (key,v))+                   _ -> mempty+                ) keyvals
+ src/Data/KVITable/Render/Internal.hs view
@@ -0,0 +1,125 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Data.KVITable.Render.Internal where++import qualified Data.List as L+import           Data.Name ( ConvertName, UTF8 )+import           Data.String ( fromString )+import           Numeric.Natural++import           Data.KVITable+import           Data.KVITable.Internal.Helpers+import           Data.KVITable.Render+++-- TODO: to allow for hideBlankCols, the KVITable should keep track of what the+-- pre-declared vals for each key are v.s. what additional vals may be set by+-- setting a value in that region.  Then there is enough information here+-- (without re-scanning the table) to properly compute the keyvals that should be+-- displayed (which would also mean that the hideCols/hideRows determinations in+-- the rendering functions below are no longer needed.++data TblHdr = V (KeyVal) | AndMore Natural++type TblHdrs = [ (Key, [TblHdr]) ]++-- | Returns the rows and columns KeyVals, with appropriate application of+-- RenderConfig specifications: colStackAt, maxCells, maxCols.  Does not collapse+-- empty rows or columns.++renderingKeyVals :: RenderConfig+                 -> KeyVals+                 -> (TblHdrs, TblHdrs)+renderingKeyVals cfg inpKvs =+  let maxNumKeys = maxCells cfg+      origNumKeys = toEnum $ length inpKvs+      maxNumCols = min (maxCells cfg) (maxCols cfg)++  in case colStackAt cfg of+       Nothing ->+         -- width is just keys, height is combination of keys and values+         let okKvs = if origNumKeys > maxNumKeys+                     then snoc (take (fromEnum maxNumKeys) (fst kvs))+                          (fromString+                           $ "{+ " <> show (origNumKeys - maxNumKeys) <> " MORE}"+                          , mempty+                          )+                     else (fst kvs)+             -- n.b. maxCols is not really useful here, since all but the last+             -- column are headers and values are only shown in that last column.+         in (snd $ trimStacked True 1 maxNumKeys okKvs, [])+       Just _c ->+         let (kvsRows, kvsCols) = kvs+             numRegularColKvs = let v = length inpKvs - length kvsCols+                                in if v < 0 then error "BAD1" else toEnum v+             numStackedCols = countStacked kvsCols+             origNumCols = numRegularColKvs + numStackedCols+             allowedNumCols = subOrDef 1 maxNumCols numRegularColKvs+             okKvsCols = if origNumCols > maxNumCols+                      then if numStackedCols <= maxNumCols+                           then kvsCols+                           else snd $ trimStacked False 1 allowedNumCols kvsCols+                      else kvsCols+             allowedNumRows = subOrDef 1 (maxCells cfg)+                           (if origNumCols > maxNumCols+                             then if numStackedCols <= maxNumCols+                                  then numStackedCols+                                  else allowedNumCols+                             else numStackedCols+                           )+             eachRowCols = min maxNumCols numStackedCols+             okKvsRows = snd $ trimStacked False eachRowCols allowedNumRows kvsRows+         in (okKvsRows, okKvsCols)++  where++    -- subtracting Naturals must be done carefully to not allow the result to be+    -- < 0; a post-subtraction max is not enough to protect against the initial+    -- value.+    subOrDef d a b = if a < b then d else a - b++    kvs = let kvs' = case colStackAt cfg of+                       Nothing -> (inpKvs, mempty)+                       Just c -> span ((/= c) . fst) inpKvs+              ksrt = case sortKeyVals cfg of+                       Nothing -> kvs'+                       Just fn -> fn kvs'+          in ( fmap (fmap V) <$> fst ksrt+             , fmap (fmap V) <$> snd ksrt+             )++    countStacked = \case -- does not allow for hiddenCols+      [] -> 1+      ((_,vs):r) -> toEnum (L.length vs) * countStacked r+    trimStacked _ each n [] = ((n,each), [])+    trimStacked _mulSubs each n ((k,vs):[]) =+      let lvs = toEnum $ length vs+          mvs = foldl (\a b -> if b * each < n then b else a) 1 $ [0..lvs]+          tvs = snoc (take (fromEnum mvs) vs) $ AndMore $ lvs - mvs+          rvs = if mvs < lvs then tvs else vs+      in ((subOrDef 0 n mvs, mvs * each), [(k,rvs)])+    trimStacked mulSubs each n ((k,vs):rkvs) =+      let lvs = toEnum $ length vs+          ((n',w), kvs') = trimStacked mulSubs each n rkvs+          mvs = foldl (\a b -> if b * w < n then b else a) 1 $ [0..lvs]+          tvs = snoc (take (fromEnum mvs) vs) $ AndMore $ remcnt (lvs - mvs) rkvs+          rvs = if mvs < lvs then tvs else vs+      in ((subOrDef 0 n' mvs, mvs * w), (k,rvs):kvs')++    remcnt n [] = n+    remcnt n (rkv:rkvs) = n * remcnt (toEnum $ length (snd rkv)) rkvs+++instance ConvertName UTF8 "Key" "column header"+instance ConvertName UTF8 "KeyVal" "column header"+++nLength :: Foldable t => t a -> Natural+nLength = toEnum . length
test/AsciiRenderTests.hs view
@@ -5,6 +5,7 @@ module AsciiRenderTests where  import           Control.Monad ( unless )+import           Data.String ( fromString ) import           Data.Text ( Text ) import qualified Data.Text as T import           Lens.Micro ( (^.), (.~), (%~), (&) )@@ -44,6 +45,7 @@                    , fmap (de "∌ ") $ drop (length al) el                    , fmap (da "∹ ") $ drop (length el) al                    ])+    -- writeFile "test.txt" $ T.unpack $ T.unlines al     assertFailure $ T.unpack $ T.unlines details  @@ -120,7 +122,7 @@      , testCase "medium sized table render, sorted" $       cmpTables "medium table"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True }) mediumKVI) [sq|+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha }) mediumKVI) [sq| **** | compiler | debug | optimization | Value | +----------+-------+--------------+-------+@@ -142,7 +144,7 @@      , testCase "medium sized table render, unsorted" $       cmpTables "medium table"-      (KTRA.render (cfg0 { KTR.sortKeyVals = False }) mediumKVI) [sq|+      (KTRA.render (cfg0 { KTR.sortKeyVals = Nothing }) mediumKVI) [sq| **** | compiler | debug | optimization | Value | +----------+-------+--------------+-------+@@ -164,7 +166,7 @@      , testCase "medium table, sorted, blank, row skip, no repeats" $       cmpTables "medium table"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.rowRepeat = False }) mediumKVI) [sq| **** | compiler | debug | optimization | Value |@@ -187,7 +189,7 @@      , testCase "medium table, sorted, blank row skip, no repeats, group unknown" $       cmpTables "medium table s 0brow 0rep [unknown]"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.rowRepeat = False                          , KTR.rowGroup = ["unknown"]                          }) mediumKVI) [sq|@@ -212,7 +214,7 @@      , testCase "medium table, sorted, blank row skip, no repeats, group compiler" $       cmpTables "medium table s 0brow 0rep [unknown]"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.rowRepeat = False                          , KTR.rowGroup = ["compiler"]                          }) mediumKVI) [sq|@@ -242,7 +244,7 @@      , testCase "medium table, sorted, blank row skip, no repeats, multi-group" $       cmpTables "medium table s 0brow 0rep [compiler,debug]"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.rowRepeat = False                          , KTR.rowGroup = ["unknown", "compiler", "unk", "debug", "huh"]                          }) mediumKVI) [sq|@@ -275,7 +277,7 @@      , testCase "medium table, sorted, blank" $       cmpTables "medium table"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.hideBlankRows = False }) mediumKVI) [sq| **** | compiler | debug | optimization | Value |@@ -314,7 +316,7 @@      , testCase "medium sized table render, sorted, no blank, colstack unknown" $       cmpTables "medium table s 0blnk colstk=unknown"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.colStackAt  = Just "unknown"                          }) mediumKVI) [sq| ****@@ -338,7 +340,7 @@      , testCase "medium sized table render, sorted, !blank, colstk optimization" $       cmpTables "medium table s 0blnk colstk=optimization"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.hideBlankRows = True                          , KTR.colStackAt  = Just "optimization"                          }) mediumKVI) [sq|@@ -357,7 +359,7 @@      , testCase "medium, sorted, !blank, !row rpt, rgrp compiler colstk optimization" $       cmpTables "medium table s 0blnk colstk=optimization rowgrp=compiler"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.hideBlankRows = True                          , KTR.colStackAt  = Just "optimization"                          , KTR.rowRepeat   = False@@ -383,7 +385,7 @@      , testCase "medium sized table render, sorted, !blank, equisize, colstack debug" $       cmpTables "medium table s 0blnk equisize colstk=debug"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.hideBlankRows = True                            , KTR.equisizedCols = True                            , KTR.colStackAt  = Just "debug"@@ -401,7 +403,7 @@      , testCase "medium sized table render, sorted, !blank, fitsize, colstack debug" $       cmpTables "medium table s 0blnk fitsize colstk=debug"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.hideBlankRows = True                            , KTR.equisizedCols = False                            , KTR.colStackAt  = Just "debug"@@ -431,7 +433,7 @@                              ]             step "ASCII rendering unsorted"             cmpTables "small table right aligned unsorted"-              (KTRA.render (cfg0 { KTR.sortKeyVals = False }) tbl) [sq|+              (KTRA.render (cfg0 { KTR.sortKeyVals = Nothing }) tbl) [sq| **** | id |         name | +----+--------------+@@ -441,7 +443,7 @@ ****|]             step "ASCII rendering sorted"             cmpTables "small table right aligned sorted"-              (KTRA.render (cfg0 { KTR.sortKeyVals = True }) tbl) [sq|+              (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha }) tbl) [sq| **** | id |         name | +----+--------------+@@ -462,7 +464,7 @@                 , ([("City name", "Sydney"),   ("Area", "2058"), ("Population", "4336374")], 1214.8)                 ]       in cmpTables "small table float value table"-         (KTRA.render (cfg0 { KTR.sortKeyVals = True }) tbl) [sq|+         (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha }) tbl) [sq| **** | City name | Area | Population | Annual Rainfall | +-----------+------+------------+-----------------+@@ -477,7 +479,7 @@      , testCase "big table grouped sorted" $       cmpTables "big table grouped sorted"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.rowRepeat = False                          , KTR.rowGroup = [ "Location", "Biome", "Category" ]                          }) zooTable2)@@ -493,7 +495,7 @@                  Nothing -> (ks,v) : newl                  Just v' -> (ks, v' + v) : filter ((ks /=) . fst) newl       in cmpTables "big table grouped sorted no-subtype colstack"-         (KTRA.render (cfg0 { KTR.sortKeyVals = True+         (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                             , KTR.rowRepeat = False                             , KTR.rowGroup = [ "Location", "Biome", "Category" ]                             , KTR.colStackAt = Just "Name"@@ -503,7 +505,7 @@      , testCase "big table grouped sorted equisized" $       cmpTables "big table grouped sorted equisized"-      (KTRA.render (cfg0 { KTR.sortKeyVals = True+      (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.rowRepeat = False                          , KTR.hideBlankCols = False                          , KTR.equisizedCols = True@@ -531,7 +533,7 @@      , testCase "big table grouped sorted fitsize colstack=ghcver" $       cmpTables "big table grouped sorted fitsize colstack=ghcver"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.rowRepeat = False                          , KTR.hideBlankCols = False                          , KTR.equisizedCols = False@@ -559,7 +561,7 @@      , testCase "big table grouped sorted fitsize colstack=Strategy" $       cmpTables "big table grouped sorted fitsize colstack=Strategy"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.rowRepeat = False                          , KTR.hideBlankCols = False                          , KTR.equisizedCols = False@@ -585,7 +587,7 @@      , testCase "nested table hide=blankRows,blankCols colstack=ones" $       cmpTables "nested table hide=blankRows,blankCols colstack=ones"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.rowRepeat = False                            , KTR.hideBlankCols = True                            , KTR.hideBlankRows = True@@ -618,7 +620,7 @@      , testCase "nested table colstack=tens" $       cmpTables "nested table colstack=tens"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.rowRepeat = False                            , KTR.hideBlankCols = True                            , KTR.hideBlankRows = True@@ -655,7 +657,7 @@      , testCase "nested table hide=blankCols,blankRows colstack=hundreds" $       cmpTables "nested table hide-blankCols,blankRows colstack=hundreds"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.rowRepeat = False                            , KTR.hideBlankCols = True                            , KTR.hideBlankRows = True@@ -666,7 +668,7 @@      , testCase "nested table hide=none colstack=hundreds" $       cmpTables "nested table hide=none colstack=hundreds"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.rowRepeat = False                            , KTR.hideBlankCols = False                            , KTR.hideBlankRows = False@@ -675,9 +677,32 @@                            }) nestedTable)         [uq_f|examples/hundreds_all.md|] +    , testCase "nested table hide=none colstack=hundreds testfile" $+      cmpTables "nested table hide=none colstack=hundreds testfile"+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha+                           , KTR.rowRepeat = False+                           , KTR.hideBlankCols = False+                           , KTR.hideBlankRows = False+                           , KTR.equisizedCols = False+                           , KTR.colStackAt = Just "hundreds"+                           }) nestedTable)+        [sq_f|test/evenodd.md|]++    , testCase "nested table hide=none colstack=hundreds, maxCells=60" $+      cmpTables "nested table hide=none colstack=hundreds, maxCells=60"+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha+                           , KTR.rowRepeat = False+                           , KTR.hideBlankCols = False+                           , KTR.hideBlankRows = False+                           , KTR.equisizedCols = False+                           , KTR.colStackAt = Just "hundreds"+                           , KTR.maxCells = 60+                           }) nestedTable)+        [sq3_f|test/evenodd.md|]+     , testCase "nested table hide=none colstack=hundreds equisized" $       cmpTables "nested table hide=none colstack=hundreds equisized"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.rowRepeat = False                            , KTR.hideBlankCols = False                            , KTR.hideBlankRows = False@@ -688,7 +713,7 @@      , testCase "nested table hideBlank=rol,col colstack=thousands" $       cmpTables "nested table hideBlank=row,col colstack=thousands"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.rowRepeat = False                            , KTR.hideBlankCols = True                            , KTR.hideBlankRows = True@@ -699,7 +724,7 @@      , testCase "nested table hideBlank=rol,col" $       cmpTables "nested table hideBlank=row,col"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.rowRepeat = False                            , KTR.hideBlankCols = True                            , KTR.hideBlankRows = True@@ -707,9 +732,20 @@                            }) nestedTable)         [uq3_f|README.md|] +    , testCase "nested table hideBlank=rol,col, maxCells=60" $+      cmpTables "nested table hideBlank=row,col, maxCells=60"+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha+                           , KTR.rowRepeat = False+                           , KTR.hideBlankCols = True+                           , KTR.hideBlankRows = True+                           , KTR.equisizedCols = False+                           , KTR.maxCells = 60+                           }) nestedTable)+        [sq5_f|test/evenodd.md|]+     , testCase "nested table hideBlank=none" $       cmpTables "nested table hideBlank=none"-        (KTRA.render (cfg0 { KTR.sortKeyVals = True+        (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.rowRepeat = False                            , KTR.hideBlankCols = False                            , KTR.hideBlankRows = False@@ -829,4 +865,30 @@ |          |           |          |      |    1 |   odd | ****|] ++    , testCase "big square table, numCols and numCells limited" $+      let tbl = foldl KVI.foldlInsert+                (mempty & KVI.valueColName .~ "Hits"+                 & KVI.keyVals+                  .~ [ ("Pitcher", [ fromString ("P" <> show n)+                                   | n <- [0..100::Int] ])+                     , ("Batter", [ fromString ("B" <> show n)+                                  | n <- [0..100::Int] ])+                     ])+                [ ([ ("Pitcher", fromString ("P" <> show p))+                   , ("Batter", fromString ("B" <> show b))+                   ], p * b)+                | p <- [0..100::Int]+                , b <- [0..100::Int]+                ]+      in cmpTables "big table grouped sorted no-subtype colstack"+         (KTRA.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha+                            , KTR.rowRepeat   = False+                            , KTR.rowGroup    = [ "Location", "Biome", "Category" ]+                            , KTR.colStackAt  = Just "Batter"+                            , KTR.equisizedCols = False+                            , KTR.maxCols = 20+                            , KTR.maxCells = 600+                            }) tbl)+         [sq_f|test/bigsquare.md|]       ]
test/HTMLRenderTests.hs view
@@ -6,6 +6,7 @@  import           Control.Monad ( unless ) import qualified Data.List as L+import           Data.String ( fromString ) import           Data.Text ( Text ) import qualified Data.Text as T import qualified Data.Text.Lazy as TL@@ -55,6 +56,7 @@                    , fmap (de "∌ ") $ drop (length al) el                    , fmap (da "∹ ") $ drop (length el) al                    ])+    -- writeFile "test.html" $ T.unpack $ T.unlines al     assertFailure $ T.unpack $ T.unlines details  @@ -71,7 +73,7 @@ **** <table class="kvitable">   <thead class="kvitable_head">-    <tr class="kvitable_tr"><th class="kvitable_th">Value</th></tr>+    <tr class="kvitable_tr"><th class="kvitable_th"><div><span>Value</span></div></th></tr>   </thead>   <tbody class="kvitable_body">   </tbody>@@ -85,7 +87,7 @@ **** <table class="kvitable">   <thead class="kvitable_head">-    <tr class="kvitable_tr"><th class="kvitable_th">Value</th></tr>+    <tr class="kvitable_tr"><th class="kvitable_th"><div><span>Value</span></div></th></tr>   </thead>   <tbody class="kvitable_body">     <tr class="kvitable_tr">@@ -103,9 +105,9 @@ <table class="kvitable">   <thead class="kvitable_head">     <tr class="kvitable_tr">-      <th class="kvitable_th">foo</th>-      <th class="kvitable_th">dog</th>-      <th class="kvitable_th">Value</th>+      <th class="kvitable_th"><div><span>foo</span></div></th>+      <th class="kvitable_th"><div><span>dog</span></div></th>+      <th class="kvitable_th"><div><span>Value</span></div></th>     </tr>   </thead>   <tbody class="kvitable_body">@@ -116,7 +118,7 @@      , testCase "nested table hideBlank=rows,cols, fitted, colstack=hundreds" $       cmpTables "nested table hideBlank=rows,cols, fitted, colstack=hundreds"-      (KTRH.render (cfg0 { KTR.sortKeyVals     = True+      (KTRH.render (cfg0 { KTR.sortKeyVals   = Just KTR.sortNumericAlpha                          , KTR.rowRepeat     = False                          , KTR.hideBlankCols = True                          , KTR.hideBlankRows = True@@ -127,7 +129,7 @@      , testCase "nested table hide=none, fitted, colstack=hundreds" $       cmpTables "nested table hide=none, fitted, colstack=hundreds"-      (KTRH.render (cfg0 { KTR.sortKeyVals   = True+      (KTRH.render (cfg0 { KTR.sortKeyVals   = Just KTR.sortNumericAlpha                          , KTR.rowRepeat     = False                          , KTR.hideBlankCols = False                          , KTR.hideBlankRows = False@@ -136,9 +138,48 @@                          }) nestedTable)       [sq_f|examples/hundreds_all.md|] +        -- duplication of the above in test/evenodd.md+    , testCase "nested table hide=none, fitted, colstack=hundreds" $+      do+       cmpTables "nested table hide=none, fitted, colstack=hundreds"+        (KTRH.render (cfg0 { KTR.sortKeyVals   = Just KTR.sortNumericAlpha+                           , KTR.rowRepeat     = False+                           , KTR.hideBlankCols = False+                           , KTR.hideBlankRows = False+                           , KTR.equisizedCols = False+                           , KTR.colStackAt    = Just "hundreds"+                           }) nestedTable)+        [sq2_f|test/evenodd.md|]++    , testCase "nested table hide=none, fitted, colstack=hundreds, maxCells=60" $+      do+       cmpTables "nested table hide=none, fitted, colstack=hundreds, maxCells=60"+        (KTRH.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha+                         , KTR.rowRepeat     = False+                         , KTR.hideBlankCols = False+                         , KTR.hideBlankRows = False+                         , KTR.equisizedCols = False+                         , KTR.colStackAt    = Just "hundreds"+                         , KTR.maxCells      = 60+                         }) nestedTable)+        [sq4_f|test/evenodd.md|]++    , testCase "nested table hide=none, fitted, no colstack, maxCells=60" $+      do+       cmpTables "nested table hide=none, fitted, no colstack, maxCells=60"+        (KTRH.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha+                         , KTR.rowRepeat     = False+                         , KTR.hideBlankCols = False+                         , KTR.hideBlankRows = False+                         , KTR.equisizedCols = False+                         , KTR.colStackAt    = Nothing+                         , KTR.maxCells      = 60+                         }) nestedTable)+        [sq6_f|test/evenodd.md|]+     , testCase "nested table hideBlank=rol,col colstack=thousands" $       cmpTables "nested table hideBlank=row,col colstack=thousands"-        (KTRH.render (cfg0 { KTR.sortKeyVals = True+        (KTRH.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.rowRepeat = False                            , KTR.hideBlankCols = True                            , KTR.hideBlankRows = True@@ -149,7 +190,7 @@      , testCase "nested table hideBlank=rol,col" $       cmpTables "nested table hideBlank=row,col"-        (KTRH.render (cfg0 { KTR.sortKeyVals = True+        (KTRH.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                            , KTR.rowRepeat = False                            , KTR.hideBlankCols = True                            , KTR.hideBlankRows = True@@ -160,7 +201,7 @@      , testCase "big table grouped sorted" $       cmpTables "big table grouped sorted"-      (KTRH.render (cfg0 { KTR.sortKeyVals = True+      (KTRH.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                          , KTR.rowRepeat = False                          , KTR.rowGroup = [ "Location", "Biome", "Category" ]                          }) zooTable2)@@ -176,7 +217,7 @@                  Nothing -> (ks,v) : newl                  Just v' -> (ks, v' + v) : filter ((ks /=) . fst) newl       in cmpTables "big table grouped sorted no-subtype colstack"-         (KTRH.render (cfg0 { KTR.sortKeyVals = True+         (KTRH.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha                             , KTR.rowRepeat   = False                             , KTR.rowGroup    = [ "Location", "Biome", "Category" ]                             , KTR.colStackAt  = Just "Name"@@ -184,5 +225,29 @@                             }) zt)          [sq2_f|examples/zoo.md|] -+    , testCase "big square table, numCols and numCells limited" $+      let tbl = foldl KVI.foldlInsert+                (mempty & KVI.valueColName .~ "Hits"+                 & KVI.keyVals+                  .~ [ ("Pitcher", [ fromString ("P" <> show n)+                                   | n <- [0..100::Int] ])+                     , ("Batter", [ fromString ("B" <> show n)+                                  | n <- [0..100::Int] ])+                     ])+                [ ([ ("Pitcher", fromString ("P" <> show p))+                   , ("Batter", fromString ("B" <> show b))+                   ], p * b)+                | p <- [0..100::Int]+                , b <- [0..100::Int]+                ]+      in cmpTables "big table grouped sorted no-subtype colstack"+         (KTRH.render (cfg0 { KTR.sortKeyVals = Just KTR.sortNumericAlpha+                            , KTR.rowRepeat   = False+                            , KTR.rowGroup    = [ "Location", "Biome", "Category" ]+                            , KTR.colStackAt  = Just "Batter"+                            , KTR.equisizedCols = False+                            , KTR.maxCols = 20+                            , KTR.maxCells = 600+                            }) tbl)+         [sq2_f|test/bigsquare.md|]     ]
test/SampleTables.hs view
@@ -3,6 +3,7 @@ module SampleTables where  import           Data.KVITable+import           Data.String ( fromString ) import           Data.Text ( Text ) import qualified Data.Text as T import           Lens.Micro ( (^.), (.~), (%~), (&) )@@ -195,11 +196,11 @@                              , ("ones",      ["0"])                              ]               )-              [ let keyvals = [("millions", T.pack $ show m)-                              ,("thousands", T.pack $ show t)-                              ,("hundreds", T.pack $ show h)-                              ,("tens", T.pack $ show d)-                              ,("ones", T.pack $ show o)]+              [ let keyvals = [("millions", fromString $ show m)+                              ,("thousands", fromString $ show t)+                              ,("hundreds", fromString $ show h)+                              ,("tens", fromString $ show d)+                              ,("ones", fromString $ show o)]                     value = if (o `rem` 2) == 1 then "odd" else "even"                 in (keyvals, value)               | m <- [0..2 :: Int]
test/TestMain.hs view
@@ -165,6 +165,18 @@                           , ([ "baz", "beam", "woof"], "yo")                           ] +         , testCaseSteps "insertWith" $ \step ->+             do+               let keyvals = [ ("foo", "bar"), ("moo", "cow") ]++               step "inserting a new value"+               let t1 = insertWith (<>) keyvals "old" kvi0+               Just "old" @=? lookup keyvals t1++               step "combining with an existing value"+               let t2 = insertWith (<>) keyvals "new" t1+               Just "newold" @=? lookup keyvals t2+          , testCase "medium sized table rows" $            rows mediumKVI @?=            [ ([ "gcc7", "yes", "0"], "good" )
test/TestQQDefs.hs view
@@ -13,6 +13,9 @@ sq_f = quoteFile sq sq2_f = quoteFile $ qqExtractor 2 "\n****" sq3_f = quoteFile $ qqExtractor 3 "\n****"+sq4_f = quoteFile $ qqExtractor 4 "\n****"+sq5_f = quoteFile $ qqExtractor 5 "\n****"+sq6_f = quoteFile $ qqExtractor 6 "\n****"  uq :: QuasiQuoter uq = qqExtractor 1 "\n____"