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