Add an option to use unicode in balance tables (#528)
* Add an option to use unicode in balance tables fixes #522 * Add a test for unicode tables * Document --pretty-tables * Support --pretty-tables in BalanceView
This commit is contained in:
		
							parent
							
								
									7d0734f1ed
								
							
						
					
					
						commit
						f4b3f1c094
					
				| @ -91,6 +91,7 @@ data ReportOpts = ReportOpts { | |||||||
|     ,row_total_      :: Bool |     ,row_total_      :: Bool | ||||||
|     ,no_total_       :: Bool |     ,no_total_       :: Bool | ||||||
|     ,value_          :: Bool |     ,value_          :: Bool | ||||||
|  |     ,pretty_tables_  :: Bool | ||||||
|  } deriving (Show, Data, Typeable) |  } deriving (Show, Data, Typeable) | ||||||
| 
 | 
 | ||||||
| instance Default ReportOpts where def = defreportopts | instance Default ReportOpts where def = defreportopts | ||||||
| @ -118,6 +119,7 @@ defreportopts = ReportOpts | |||||||
|     def |     def | ||||||
|     def |     def | ||||||
|     def |     def | ||||||
|  |     def | ||||||
| 
 | 
 | ||||||
| rawOptsToReportOpts :: RawOpts -> IO ReportOpts | rawOptsToReportOpts :: RawOpts -> IO ReportOpts | ||||||
| rawOptsToReportOpts rawopts = checkReportOpts <$> do | rawOptsToReportOpts rawopts = checkReportOpts <$> do | ||||||
| @ -144,6 +146,7 @@ rawOptsToReportOpts rawopts = checkReportOpts <$> do | |||||||
|     ,row_total_   = boolopt "row-total" rawopts' |     ,row_total_   = boolopt "row-total" rawopts' | ||||||
|     ,no_total_    = boolopt "no-total" rawopts' |     ,no_total_    = boolopt "no-total" rawopts' | ||||||
|     ,value_       = boolopt "value" rawopts' |     ,value_       = boolopt "value" rawopts' | ||||||
|  |     ,pretty_tables_ = boolopt "pretty-tables" rawopts' | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| -- | Do extra validation of raw option values, raising an error if there's a problem. | -- | Do extra validation of raw option values, raising an error if there's a problem. | ||||||
|  | |||||||
| @ -280,6 +280,7 @@ balancemode = (defCommandMode $ ["balance"] ++ aliases) { -- also accept but don | |||||||
|      ,flagReq  ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "omit N leading account name parts (in flat mode)" |      ,flagReq  ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "omit N leading account name parts (in flat mode)" | ||||||
|      ,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "don't squash boring parent accounts (in tree mode)" |      ,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "don't squash boring parent accounts (in tree mode)" | ||||||
|      ,flagReq  ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format (in simple reports)" |      ,flagReq  ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format (in simple reports)" | ||||||
|  |      ,flagNone ["pretty-tables"] (\opts -> setboolopt "pretty-tables" opts) "use unicode when displaying tables" | ||||||
|      ] |      ] | ||||||
|      ++ outputflags |      ++ outputflags | ||||||
|     ,groupHidden = [] |     ,groupHidden = [] | ||||||
| @ -475,7 +476,7 @@ multiBalanceReportAsText :: ReportOpts -> MultiBalanceReport -> String | |||||||
| multiBalanceReportAsText opts r = | multiBalanceReportAsText opts r = | ||||||
|     printf "%s in %s:" typeStr (showDateSpan $ multiBalanceReportSpan r) |     printf "%s in %s:" typeStr (showDateSpan $ multiBalanceReportSpan r) | ||||||
|       ++ "\n" |       ++ "\n" | ||||||
|       ++ renderBalanceReportTable tabl |       ++ renderBalanceReportTable opts tabl | ||||||
|   where |   where | ||||||
|     tabl = balanceReportAsTable opts r |     tabl = balanceReportAsTable opts r | ||||||
|     typeStr :: String |     typeStr :: String | ||||||
| @ -487,9 +488,9 @@ multiBalanceReportAsText opts r = | |||||||
| -- | Given a table representing a multi-column balance report (for example, | -- | Given a table representing a multi-column balance report (for example, | ||||||
| -- made using 'balanceReportAsTable'), render it in a format suitable for | -- made using 'balanceReportAsTable'), render it in a format suitable for | ||||||
| -- console output. | -- console output. | ||||||
| renderBalanceReportTable :: Table String String MixedAmount -> String | renderBalanceReportTable :: ReportOpts -> Table String String MixedAmount -> String | ||||||
| renderBalanceReportTable = unlines . trimborder . lines | renderBalanceReportTable (ReportOpts { pretty_tables_ = pretty }) = unlines . trimborder . lines | ||||||
|                          . render id (" " ++) showMixedAmountOneLineWithoutPrice |                          . render pretty id (" " ++) showMixedAmountOneLineWithoutPrice | ||||||
|                          . align |                          . align | ||||||
|   where |   where | ||||||
|     trimborder = ("":) . (++[""]) . drop 1 . init . map (drop 1 . init) |     trimborder = ("":) . (++[""]) . drop 1 . init . map (drop 1 . init) | ||||||
|  | |||||||
| @ -60,6 +60,7 @@ balanceviewmode BalanceView{..} = (defCommandMode $ bvmode : bvaliases) { | |||||||
|      ,flagNone ["row-total","T"] (\opts -> setboolopt "row-total" opts) "show a row total column (in multicolumn reports)" |      ,flagNone ["row-total","T"] (\opts -> setboolopt "row-total" opts) "show a row total column (in multicolumn reports)" | ||||||
|      ,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "don't squash boring parent accounts (in tree mode)" |      ,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "don't squash boring parent accounts (in tree mode)" | ||||||
|      ,flagReq  ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format (in simple reports)" |      ,flagReq  ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format (in simple reports)" | ||||||
|  |      ,flagNone ["pretty-tables"] (\opts -> setboolopt "pretty-tables" opts) "use unicode when displaying tables" | ||||||
|      ] |      ] | ||||||
|     ,groupHidden = [] |     ,groupHidden = [] | ||||||
|     ,groupNamed = [generalflagsgroup1] |     ,groupNamed = [generalflagsgroup1] | ||||||
| @ -152,7 +153,7 @@ balanceviewReport BalanceView{..} CliOpts{reportopts_=ropts, rawopts_=raw} j = d | |||||||
|                       ) |                       ) | ||||||
|         putStrLn bvtitle |         putStrLn bvtitle | ||||||
|         mapM_ putStrLn balanceclarification |         mapM_ putStrLn balanceclarification | ||||||
|         putStrLn $ renderBalanceReportTable totTabl |         putStrLn $ renderBalanceReportTable ropts totTabl | ||||||
|   where |   where | ||||||
|     overwriteBalanceType = |     overwriteBalanceType = | ||||||
|       case reverse $ filter (`elem` ["change","cumulative","historical"]) $ map fst raw of |       case reverse $ filter (`elem` ["change","cumulative","historical"]) $ map fst raw of | ||||||
|  | |||||||
| @ -9,26 +9,27 @@ import Hledger.Utils.String | |||||||
| 
 | 
 | ||||||
| -- | for simplicity, we assume that each cell is rendered | -- | for simplicity, we assume that each cell is rendered | ||||||
| --   on a single line | --   on a single line | ||||||
| render :: (rh -> String) | render :: Bool -- ^ pretty tables | ||||||
|  |        -> (rh -> String) | ||||||
|        -> (ch -> String) |        -> (ch -> String) | ||||||
|        -> (a -> String) |        -> (a -> String) | ||||||
|        -> Table rh ch a |        -> Table rh ch a | ||||||
|        -> String |        -> String | ||||||
| render fr fc f (Table rh ch cells) = | render pretty fr fc f (Table rh ch cells) = | ||||||
|   unlines $ [ bar SingleLine   -- +--------------------------------------+ |   unlines $ [ bar SingleLine   -- +--------------------------------------+ | ||||||
|             , renderColumns sizes ch2 |             , renderColumns pretty sizes ch2 | ||||||
|             , bar DoubleLine   -- +======================================+ |             , bar DoubleLine   -- +======================================+ | ||||||
|             ] ++ |             ] ++ | ||||||
|             (renderRs $ fmap renderR $ zipHeader [] cells $ fmap fr rh) ++ |             (renderRs $ fmap renderR $ zipHeader [] cells $ fmap fr rh) ++ | ||||||
|             [ bar SingleLine ] -- +--------------------------------------+ |             [ bar SingleLine ] -- +--------------------------------------+ | ||||||
|  where |  where | ||||||
|   bar = concat . renderHLine sizes ch2 |   bar = concat . renderHLine pretty sizes ch2 | ||||||
|   -- ch2 and cell2 include the row and column labels |   -- ch2 and cell2 include the row and column labels | ||||||
|   ch2 = Group DoubleLine [Header "", fmap fc ch] |   ch2 = Group DoubleLine [Header "", fmap fc ch] | ||||||
|   cells2 = headerContents ch2 |   cells2 = headerContents ch2 | ||||||
|          : zipWith (\h cs -> h : map f cs) rhStrings cells |          : zipWith (\h cs -> h : map f cs) rhStrings cells | ||||||
|   -- |   -- | ||||||
|   renderR (cs,h) = renderColumns sizes $ Group DoubleLine |   renderR (cs,h) = renderColumns pretty sizes $ Group DoubleLine | ||||||
|                     [ Header h |                     [ Header h | ||||||
|                     , fmap fst $ zipHeader "" (map f cs) ch] |                     , fmap fst $ zipHeader "" (map f cs) ch] | ||||||
|   rhStrings = map fr $ headerContents rh |   rhStrings = map fr $ headerContents rh | ||||||
| @ -36,38 +37,73 @@ render fr fc f (Table rh ch cells) = | |||||||
|   sizes   = map (maximum . map strWidth) . transpose $ cells2 |   sizes   = map (maximum . map strWidth) . transpose $ cells2 | ||||||
|   renderRs (Header s)   = [s] |   renderRs (Header s)   = [s] | ||||||
|   renderRs (Group p hs) = concat . intersperse sep . map renderRs $ hs |   renderRs (Group p hs) = concat . intersperse sep . map renderRs $ hs | ||||||
|     where sep = renderHLine sizes ch2 p |     where sep = renderHLine pretty sizes ch2 p | ||||||
|  | 
 | ||||||
|  | verticalBar :: Bool -> Char | ||||||
|  | verticalBar pretty = if pretty then '│' else '|' | ||||||
|  | 
 | ||||||
|  | leftBar :: Bool -> String | ||||||
|  | leftBar pretty = verticalBar pretty : " " | ||||||
|  | 
 | ||||||
|  | rightBar :: Bool -> String | ||||||
|  | rightBar pretty = " " ++ [verticalBar pretty] | ||||||
|  | 
 | ||||||
|  | midBar :: Bool -> String | ||||||
|  | midBar pretty = " " ++ verticalBar pretty : " " | ||||||
|  | 
 | ||||||
|  | doubleMidBar :: Bool -> String | ||||||
|  | doubleMidBar pretty = if pretty then " ║ " else " || " | ||||||
|  | 
 | ||||||
|  | horizontalBar :: Bool -> Char | ||||||
|  | horizontalBar pretty = if pretty then '─' else '-' | ||||||
|  | 
 | ||||||
|  | doubleHorizontalBar :: Bool -> Char | ||||||
|  | doubleHorizontalBar pretty = if pretty then '═' else '=' | ||||||
| 
 | 
 | ||||||
| -- | We stop rendering on the shortest list! | -- | We stop rendering on the shortest list! | ||||||
| renderColumns :: [Int] -- ^ max width for each column | renderColumns :: Bool -- ^ pretty | ||||||
|  |               -> [Int] -- ^ max width for each column | ||||||
|               -> Header String |               -> Header String | ||||||
|               -> String |               -> String | ||||||
| renderColumns is h = "| " ++ coreLine ++ " |" | renderColumns pretty is h = leftBar pretty ++ coreLine ++ rightBar pretty | ||||||
|  where |  where | ||||||
|   coreLine = concatMap helper $ flattenHeader $ zipHeader 0 is h |   coreLine = concatMap helper $ flattenHeader $ zipHeader 0 is h | ||||||
|   helper = either hsep (uncurry padLeftWide) |   helper = either hsep (uncurry padLeftWide) | ||||||
|   hsep :: Properties -> String |   hsep :: Properties -> String | ||||||
|   hsep NoLine     = " " |   hsep NoLine     = " " | ||||||
|   hsep SingleLine = " | " |   hsep SingleLine = midBar pretty | ||||||
|   hsep DoubleLine = " || " |   hsep DoubleLine = doubleMidBar pretty | ||||||
| 
 | 
 | ||||||
| renderHLine :: [Int] -- ^ width specifications | renderHLine :: Bool -- ^ pretty | ||||||
|  |             -> [Int] -- ^ width specifications | ||||||
|             -> Header String |             -> Header String | ||||||
|             -> Properties |             -> Properties | ||||||
|             -> [String] |             -> [String] | ||||||
| renderHLine _ _ NoLine = [] | renderHLine _ _ _ NoLine = [] | ||||||
| renderHLine w h SingleLine = [renderHLine' w '-' h] | renderHLine pretty w h SingleLine = [renderHLine' pretty SingleLine w (horizontalBar pretty) h] | ||||||
| renderHLine w h DoubleLine = [renderHLine' w '=' h] | renderHLine pretty w h DoubleLine = [renderHLine' pretty DoubleLine w (doubleHorizontalBar pretty) h] | ||||||
| 
 | 
 | ||||||
| renderHLine' :: [Int] -> Char -> Header String -> String | doubleCross :: Bool -> String | ||||||
| renderHLine' is sep h = [ '+', sep ] ++ coreLine ++ [sep, '+'] | doubleCross pretty = if pretty then "╬" else "++" | ||||||
|  | 
 | ||||||
|  | doubleVerticalCross :: Bool -> String | ||||||
|  | doubleVerticalCross pretty = if pretty then "╫" else "++" | ||||||
|  | 
 | ||||||
|  | cross :: Bool -> Char | ||||||
|  | cross pretty = if pretty then '┼' else '+' | ||||||
|  | 
 | ||||||
|  | renderHLine' :: Bool -> Properties -> [Int] -> Char -> Header String -> String | ||||||
|  | renderHLine' pretty prop is sep h = [ cross pretty, sep ] ++ coreLine ++ [sep, cross pretty] | ||||||
|  where |  where | ||||||
|   coreLine        = concatMap helper $ flattenHeader $ zipHeader 0 is h |   coreLine        = concatMap helper $ flattenHeader $ zipHeader 0 is h | ||||||
|   helper          = either vsep dashes |   helper          = either vsep dashes | ||||||
|   dashes (i,_)    = replicate i sep |   dashes (i,_)    = replicate i sep | ||||||
|   vsep NoLine     = [sep] |   vsep NoLine     = [sep] | ||||||
|   vsep SingleLine = sep : "+"  ++ [sep] |   vsep SingleLine = sep : cross pretty : [sep] | ||||||
|   vsep DoubleLine = sep : "++" ++ [sep] |   vsep DoubleLine = sep : cross' ++ [sep] | ||||||
|  |   cross' = case prop of | ||||||
|  |      DoubleLine -> doubleCross pretty | ||||||
|  |      _ -> doubleVerticalCross pretty | ||||||
| 
 | 
 | ||||||
| -- padLeft :: Int -> String -> String | -- padLeft :: Int -> String -> String | ||||||
| -- padLeft l s = padding ++ s | -- padLeft l s = padding ++ s | ||||||
|  | |||||||
| @ -41,6 +41,9 @@ txt, csv. | |||||||
| `-o FILE --output-file=FILE` | `-o FILE --output-file=FILE` | ||||||
| : write output to FILE.  A file extension matching one of the above formats selects that format. | : write output to FILE.  A file extension matching one of the above formats selects that format. | ||||||
| 
 | 
 | ||||||
|  | `--pretty-tables` | ||||||
|  | : Use unicode to display prettier tables. | ||||||
|  | 
 | ||||||
| The balance command displays accounts and balances. | The balance command displays accounts and balances. | ||||||
| It is hledger's most featureful and most useful command. | It is hledger's most featureful and most useful command. | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								tests/balance/pretty.test
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								tests/balance/pretty.test
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | hledger -f balance-multicol.journal balance --pretty-tables -M | ||||||
|  | >>> | ||||||
|  | Balance changes in 2012/12/01-2013/03/31: | ||||||
|  | 
 | ||||||
|  |                  ║  2012/12  2013/01  2013/02  2013/03  | ||||||
|  | ═════════════════╬═════════════════════════════════════ | ||||||
|  |  assets          ║        0        0        1        0  | ||||||
|  |  assets:cash     ║        0        0        1        0  | ||||||
|  |  assets:checking ║       10        0        0        1  | ||||||
|  | ─────────────────╫───────────────────────────────────── | ||||||
|  |                  ║       10        0        2        1  | ||||||
|  | 
 | ||||||
|  | >>>=0 | ||||||
							
								
								
									
										27
									
								
								tests/balancesheet/pretty.test
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								tests/balancesheet/pretty.test
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | # 1. | ||||||
|  | hledger -f - balancesheet -M --pretty-tables | ||||||
|  | <<< | ||||||
|  | 2016/1/1 | ||||||
|  |   assets  1 | ||||||
|  |   b | ||||||
|  | >>> | ||||||
|  | Balance Sheet | ||||||
|  | 
 | ||||||
|  |              ║  2016/01/31  | ||||||
|  | ═════════════╬═════════════ | ||||||
|  |  Assets      ║              | ||||||
|  | ─────────────╫───────────── | ||||||
|  |  assets      ║           1  | ||||||
|  | ─────────────╫───────────── | ||||||
|  |              ║           1  | ||||||
|  | ═════════════╬═════════════ | ||||||
|  |  Liabilities ║              | ||||||
|  | ─────────────╫───────────── | ||||||
|  | ─────────────╫───────────── | ||||||
|  |              ║              | ||||||
|  | ═════════════╬═════════════ | ||||||
|  |  Total       ║              | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | >>>2 | ||||||
|  | >>>= 0 | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user