ascii-table 0.2.0.0 → 0.3.0.0
raw patch · 2 files changed
+160/−83 lines, 2 filesdep ~textPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: text
API changes (from Hackage documentation)
- Data.AsciiTable: makeTable :: [Text] -> [TableSlice Object] -> Table
+ Data.AsciiTable: makeTable :: [String] -> [TableSlice Object] -> Table
- Data.AsciiTable: makeTableWith :: forall h k v. (Ord k, Hashable k) => (h -> Text) -> (h -> k -> Text) -> (h -> k -> v -> Text) -> [h] -> [TableSlice (HashMap k v)] -> Table
+ Data.AsciiTable: makeTableWith :: forall header key value. (Ord key, Hashable key) => (Int -> header -> String) -> (Int -> header -> (Int, Int) -> key -> String) -> (Int -> header -> (Int, Int) -> key -> value -> String) -> [header] -> [TableSlice (HashMap key value)] -> Table
- Data.AsciiTable: pretty :: Pretty a => a -> Doc e
+ Data.AsciiTable: pretty :: a -> Doc e
- Data.AsciiTable: prettyList :: Pretty a => [a] -> Doc e
+ Data.AsciiTable: prettyList :: [a] -> Doc e
- Data.AsciiTable: prettyValue :: Value -> Text
+ Data.AsciiTable: prettyValue :: Value -> String
Files
- ascii-table.cabal +2/−2
- src/Data/AsciiTable.hs +158/−81
ascii-table.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack name: ascii-table-version: 0.2.0.0+version: 0.3.0.0 synopsis: ASCII table description: ASCII table category: Data@@ -31,7 +31,7 @@ , containers , dlist , hashable- , text >= 0.8+ , text , unordered-containers , vector , wl-pprint-extras >= 1.3 && < 3.6
src/Data/AsciiTable.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}@@ -57,6 +57,7 @@ import Control.Applicative (pure) import Data.Aeson (Object, Value(..))+import Data.Char (isPrint) import Data.Foldable (foldl', foldMap) import Data.Hashable (Hashable) import Data.HashMap.Strict (HashMap)@@ -64,13 +65,13 @@ import Data.Maybe (fromMaybe) import Data.Monoid ((<>), mempty) import Data.Set (Set)-import Data.Text (Text)-import Text.PrettyPrint.Free hiding ((<>))+import Data.Text (Text, pack, unpack)+import Text.PrettyPrint.Free hiding ((<>), text) -import qualified Data.HashMap.Strict as HashMap-import qualified Data.Set as Set-import qualified Data.Text as Text-import qualified Data.Vector as Vector+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Set as Set+import qualified Data.Vector as Vector+import qualified Text.PrettyPrint.Free.Internal as PrettyPrint {- @@ -116,9 +117,9 @@ -- Build a table with 'makeTable', and show it with the pretty-printing -- functions re-exported from this module. data Table = Table- { tableHeaders :: [Text]- , tableCellHeaders :: [[Text]]- , tableSlices :: [[[[Text]]]]+ { tableHeaders :: [String]+ , tableCellHeaders :: [[String]]+ , tableSlices :: [[[[String]]]] } deriving (Eq, Show) instance Pretty Table where@@ -135,26 +136,26 @@ , vsep (map (ppTableSlice widths) (tableSlices table)) ] where- ppTableSlice :: [[Int]] -> [[[Text]]] -> Doc e+ ppTableSlice :: [[Int]] -> [[[String]]] -> Doc e ppTableSlice ns rs = vsep (map (ppTableRow ns) rs) `above` tableSliceSep '-' ns - ppTableRow :: [[Int]] -> [[Text]] -> Doc e+ ppTableRow :: [[Int]] -> [[String]] -> Doc e ppTableRow nss rs = hsep (zipWith ppTableElem nss rs) <+> "|" where- ppTableElem :: [Int] -> [Text] -> Doc e+ ppTableElem :: [Int] -> [String] -> Doc e ppTableElem ns es = "|" <+> hsep (zipWith ppTableCell ns es) where- ppTableCell :: Int -> Text -> Doc e- ppTableCell n c = fill n (text (Text.unpack (escapeTabAndNewline c)))+ ppTableCell :: Int -> String -> Doc e+ ppTableCell n c = fill n (text (escapeTabAndNewline c)) - ppTableHeaders :: [[Int]] -> [Text] -> Doc e+ ppTableHeaders :: [[Int]] -> [String] -> Doc e ppTableHeaders nss hs = hsep (zipWith ppTableHeader nss hs) <+> "|" where- ppTableHeader :: [Int] -> Text -> Doc e- ppTableHeader ns h = "|" <+> fill (elemWidth ns) (text (Text.unpack (escapeTabAndNewline h)))+ ppTableHeader :: [Int] -> String -> Doc e+ ppTableHeader ns h = "|" <+> fill (elemWidth ns) (text (escapeTabAndNewline h)) tableSliceSep :: Char -> [[Int]] -> Doc e tableSliceSep c = (<> "+") . hcat . map elemSep@@ -182,11 +183,11 @@ then ms ++ [m + n - len] else ns in- zipWith adjust (map Text.length tableHeaders) ws0+ zipWith adjust (map printableLength tableHeaders) ws0 where- unadjustedTableWidths :: [[[Text]]] -> [[Int]]+ unadjustedTableWidths :: [[[String]]] -> [[Int]] unadjustedTableWidths =- map (map (maximum . map Text.length) . transpose)+ map (map (maximum . map printableLength) . transpose) . transpose unsnoc :: [a] -> Maybe ([a], a)@@ -196,10 +197,42 @@ (ys,y) <- unsnoc xs pure (x:ys,y) + -- Very primitive length counter that simply ignores ASNI color escape+ -- sequences, and then ignores non-printable characters.+ --+ -- For simplicity, assume that what follows "\ESC[" is an ANSI color+ -- escape sequence, and this function is probably broken if it isn't.+ printableLength :: String -> Int+ printableLength = length . filter isPrint . filterAnsiColor+ elemWidth :: [Int] -> Int elemWidth = foldr (\x y -> x+y+1) (-1) + -- Escape tabs and newlines in a 'String'.+ escapeTabAndNewline :: String -> String+ escapeTabAndNewline = replace '\n' "\\n" . replace '\t' "\\t"+ where+ replace :: Char -> String -> String -> String+ replace c s = concatMap (\c' -> if c == c' then s else [c']) + -- | Like text, but consider the length of the string after its ANSI color+ -- escape codes and unprintable characters have been filtered out.+ text :: String -> Doc e+ text s = PrettyPrint.Text (length s') s+ where+ s' = filter isPrint (filterAnsiColor s)++ filterAnsiColor :: String -> String+ filterAnsiColor "" = ""+ filterAnsiColor ('\ESC' : '[' : xs) =+ filterAnsiColor (safeTail (dropWhile (/= 'm') xs))+ filterAnsiColor (x:xs) = x : filterAnsiColor xs++ safeTail :: [a] -> [a]+ safeTail [] = []+ safeTail (_:xs) = xs++ -- | Make a 'Table' from a list of headers and a list of 'TableSlice's, each of -- which contains a list of 'TableRow's, each of which contain a list of -- 'Object's. It is assumed that all dimensions align properly (e.g. each row@@ -215,7 +248,7 @@ -- example, the table corresponding to -- -- @--- [ [{\"foo\": \"bar\"}], [{\"baz\": \"qux\"}] ] -- One 'TableSlice'+-- [ [{\"foo\": \"bar\"}], [{\"baz\": \"qux\"}] ] -- one 'TableSlice' -- @ -- -- will simply look like@@ -232,11 +265,16 @@ -- That is, each missing value is simply not displayed. -- makeTable- :: [Text] -- ^ Headers+ :: [String] -- ^ Headers -> [TableSlice Object] -- ^ Table slices -> Table makeTable headers slices =- makeTableWith id (\_ -> id) (\_ _ -> prettyValue) headers (flat slices)+ makeTableWith+ (\_ -> id)+ (\_ _ _ -> unpack)+ (\_ _ _ _ -> prettyValue)+ headers+ (flat slices) where flat :: [TableSlice Object] -> [TableSlice Object] flat = (map . map . map . fmap) flattenObject@@ -245,81 +283,121 @@ -- adding ANSI escape codes to color output, or for rendering values depending on -- what their key is. ----- For example, you may wish to render 'String's with a @\"timestamp\"@ key--- without quotation marks.+-- For example, you may wish to render 'Data.Aeson.String's with a+-- @\"timestamp\"@ key without quotation marks.+--+-- The @Int@ argument is the header's index. The @(Int, Int)@ argument is the+-- @(absolute, relative)@ index of the key and value. Visually,+--+-- @+-- +-------------+-------------++-- | 0 | 1 |+-- | | |+-- | (0,0) (1,1) | (2,0) (3,1) |+-- +=============+=============++-- | (0,0) (1,1) | (2,0) (3,1) |+-- | (0,0) (1,1) | (2,0) (3,1) |+-- +-------------+-------------++-- @+--+-- This function is (unfortunately) 'String'-based as of /0.3.0.0/, because the+-- pretty printing and ANSI escape code functions are 'String'-based, too.+-- makeTableWith- :: forall h k v.- (Ord k, Hashable k)- => (h -> Text) -- ^ Header rendering function- -> (h -> k -> Text) -- ^ Cell header rendering function- -> (h -> k -> v -> Text) -- ^ Cell rendering function- -> [h] -- ^ Headers- -> [TableSlice (HashMap k v)] -- ^ Table slices+ :: forall header key value.+ (Ord key, Hashable key)+ => (Int -> header -> String) -- ^ Header rendering function+ -> (Int -> header -> (Int, Int) -> key -> String) -- ^ Cell header rendering function+ -> (Int -> header -> (Int, Int) -> key -> value -> String) -- ^ Cell rendering function+ -> [header] -- ^ Headers+ -> [TableSlice (HashMap key value)] -- ^ Table slices -> Table makeTableWith showH showK showV headers slices = Table headers' cell_headers' slices' where- cell_headers :: [[k]]+ cell_headers :: [[key]] cell_headers = map (Set.toAscList . foldl' step mempty) . transpose . concat $ slices where- step :: Set k -> Maybe (HashMap k v) -> Set k+ step :: Set key -> Maybe (HashMap key value) -> Set key step acc Nothing = acc step acc (Just x) = acc <> Set.fromList (HashMap.keys x) - headers':: [Text]- headers' = map showH headers+ headers':: [String]+ headers' = zipWith showH [0..] headers - cell_headers' :: [[Text]]- cell_headers' = zipWith (map . showK) headers cell_headers+ cell_headers' :: [[String]]+ cell_headers' =+ zipWith3+ (\i h -> zipWith (\r (a,k) -> showK i h (a,r) k) [0..])+ [0..]+ headers+ (tag cell_headers) - slices' :: [[[[Text]]]]+ slices' :: [[[[String]]]] slices' =- (map . map) (zipWith3 go headers cell_headers) slices+ (map . map) (zipWith4 go [0..] headers (tag cell_headers)) slices where- go :: h -> [k] -> Maybe (HashMap k v) -> [Text]- go h ks (fromMaybe mempty -> m) =- map- (\k ->+ go :: Int -> header -> [(Int, key)] -> Maybe (HashMap key value) -> [String]+ go i h ks (fromMaybe mempty -> m) =+ zipWith+ (\r (a,k) -> case HashMap.lookup k m of Nothing -> ""- Just v -> showV h k v)+ Just v -> showV i h (a,r) k v)+ [0..] ks + -- Tag each element in a list of lists with its absolute index.+ --+ -- tag [[a,b,c],[d,e],[],[f]] = [[(0,a),(1,b),(2,c)],[(3,d),(4,e)],[],[(5,f)]]+ tag :: [[a]] -> [[(Int, a)]]+ tag = go 0 [] []+ where+ go _ acc0 acc1 [] = reverse (map reverse (acc1 : acc0))+ go !n acc0 acc1 (xs:xss) =+ case xs of+ [] -> go n (acc1 : acc0) [] xss+ (y:ys) -> go (n+1) acc0 ((n,y) : acc1) (ys:xss)++ -- | Pretty-print a 'Value' in one line.-prettyValue :: Value -> Text-prettyValue = \case- Object o ->- "{"- <> Vector.ifoldr'- (\i (k,v) acc ->- "\""- <> k- <> "\":"- <> prettyValue v- <> if i == HashMap.size o - 1- then acc- else ", " <> acc)- mempty- (Vector.fromList (HashMap.toList o))- <> "}"- Array a ->- "["- <> Vector.ifoldr'- (\i v acc ->- if i == Vector.length a - 1- then prettyValue v <> acc- else prettyValue v <> ", " <> acc)- mempty- a- <> "]"- String s -> "\"" <> s <> "\""- Number n -> Text.pack (show n)- Bool b -> Text.pack (show b)- Null -> "null"+prettyValue :: Value -> String+prettyValue = unpack . prettyValue'+ where+ prettyValue' :: Value -> Text+ prettyValue' = \case+ Object o ->+ "{"+ <> Vector.ifoldr'+ (\i (k,v) acc ->+ "\""+ <> k+ <> "\":"+ <> prettyValue' v+ <> if i == HashMap.size o - 1+ then acc+ else ", " <> acc)+ mempty+ (Vector.fromList (HashMap.toList o))+ <> "}"+ Array a ->+ "["+ <> Vector.ifoldr'+ (\i v acc ->+ if i == Vector.length a - 1+ then prettyValue' v <> acc+ else prettyValue' v <> ", " <> acc)+ mempty+ a+ <> "]"+ String s -> "\"" <> s <> "\""+ Number n -> pack (show n)+ Bool b -> pack (show b)+ Null -> "null" -- | Flatten an 'Object' so that it contains no top-level 'Object' values. flattenObject :: Object -> Object@@ -334,8 +412,7 @@ prependKey :: Text -> (Text, Value) -> (Text, Value) prependKey k0 (k1, v) = (k0 <> "." <> k1, v) --- | Escape tabs and newlines in a 'Text'.-escapeTabAndNewline :: Text -> Text-escapeTabAndNewline =- Text.replace (Text.singleton '\n') "\\n"- . Text.replace (Text.singleton '\t') "\\t"++zipWith4 :: (a -> b -> c -> d -> e) -> [a] -> [b] -> [c] -> [d] -> [e]+zipWith4 f (a:as) (b:bs) (c:cs) (d:ds) = f a b c d : zipWith4 f as bs cs ds+zipWith4 _ _ _ _ _ = []