-- | -- Module : Test.FitSpec.PrettyPrint -- Copyright : (c) 2015-2017 Rudy Matela -- License : 3-Clause BSD (see the file LICENSE) -- Maintainer : Rudy Matela -- -- A very simple pretty printing library used to generate 'Test.FitSpec' reports. module Test.FitSpec.PrettyPrint ( beside , above , showTuple , table , columns , showQuantity , showEach , headToUpper ) where -- TODO: Fix somewhat inefficient implementations, i.e.: heavy use of '(++)'. import Data.List (intercalate,transpose,isSuffixOf) import Data.Char (toUpper) showQuantity :: Int -> String -> String showQuantity 1 what = "1 " ++ what showQuantity n what = show n ++ " " ++ pluralize what showEach :: Show a => String -> [a] -> String showEach what [x] = what ++ " " ++ show x showEach what xs = "each of " ++ pluralize what ++ " " ++ intercalate ", " (map show $ init xs) ++ " and " ++ show (last xs) -- | Pluralizes a word. -- Is not comprehensive (and may never be), -- add missing cases as they are found. -- -- > pluralize "test case" == "test cases" -- > pluralize "property" == "properties" pluralize :: String -> String pluralize s | s `ew` "se" = s ++ "s" | s `ew` "n" = s ++ "s" | s `ew` "y" = init s ++ "ies" | otherwise = s where ew = flip isSuffixOf -- | Appends two Strings side by side, line by line -- -- > beside ["asdf\nqw\n","zxvc\nas"] == -- > "asdfzxvc\n\ -- > \qw as\n" beside :: String -> String -> String beside cs ds = unlines $ zipWith (++) (normalize ' ' css) dss where [css,dss] = normalize "" [lines cs,lines ds] -- | Append two Strings on top of each other, adding line breaks *when needed*. above :: String -> String -> String above cs ds = if last cs == '\n' || head ds == '\n' then cs ++ ds else cs ++ '\n':ds -- | Show elements of a list as a tuple. If there are multiple lines in any of -- the strings, tuple is printed multiline. -- -- > showTuple ["asdf\nqwer\n","zxvc\nasdf\n"] == -- > "( asdf\n\ -- > \ qwer\n\ -- > \, zxvc\n\ -- > \ asdf )\n" -- -- > showTuple ["asdf","qwer"] == "(asdf,qwer)" showTuple :: [String] -> String showTuple [] = "" showTuple [s] = s showTuple (s:ss) = if any ('\n' `elem`) (s:ss) then "( " `beside` s ++ init (concatMap (", " `beside`) ss) ++ " )\n" else "(" ++ intercalate "," (s:ss) ++ ")" -- | Formats a table using a given separator. -- -- > table " " [ ["asdf", "qwer", "zxvc\nzxvc"] -- > , ["0", "1", "2"] -- > , ["123", "456\n789", "3"] ] == -- > "asdf qwer zxvc\n\ -- > \ zxvc\n\ -- > \0 1 2\n\ -- > \123 456 3\n\ -- > \ 789\n\" table :: String -> [[String]] -> String table s [] = "" table s sss = unlines . map (removeTrailing ' ') . map (intercalate s) . transpose . map (normalize ' ') . foldr1 (zipWith (++)) . map (normalize "" . map lines) . normalize "" $ sss -- | Given a separator, format strings in columns -- -- > columns " | " ["asdf", "qw\nzxcv", "as\ndf"] == -- > "asdf | qw | as\n\ -- > \ | zxcv | df\n" columns :: String -> [String] -> String columns s = unlines . map (removeTrailing ' ') . map (intercalate s) . transpose . map (normalize ' ') . normalize "" . map lines -- | Fits a list to a certain width by appending a certain value -- -- > fit ' ' 6 "str" == "str " -- -- > fit 0 6 [1,2,3] == [1,2,3,0,0,0] fit :: a -> Int -> [a] -> [a] fit x n xs = xs ++ replicate (n - length xs) x -- | normalize makes all list the same length by adding a value -- -- > normalize ["asdf","qw","er"] == normalize ["asdf","qw ","er "] normalize :: a -> [[a]] -> [[a]] normalize x xs = map (x `fit` maxLength xs) xs -- | Given a list of lists returns the maximum length maxLength :: [[a]] -> Int maxLength = maximum . (0:) . map length removeTrailing :: Eq a => a -> [a] -> [a] removeTrailing x = reverse . dropWhile (==x) . reverse headToUpper :: [Char] -> [Char] headToUpper [] = [] headToUpper (c:cs) = toUpper c : cs