mirror of
https://github.com/simonmichael/hledger.git
synced 2024-12-29 05:11:33 +03:00
balance, print; more wide char support (#242)
Simple (non-multicolumn) balance reports containing wide characters should now align correctly (in apps and fonts that show wide chars as double width). Likewise, the print command.
This commit is contained in:
parent
5b5e5eeaf4
commit
42e2da4bb6
@ -118,12 +118,12 @@ elideAccountName width s
|
||||
names = splitOn ", " $ take (length s - 8) s
|
||||
widthpername = (max 0 (width - 8 - 2 * (max 1 (length names) - 1))) `div` length names
|
||||
in
|
||||
elideLeftWidth width False $
|
||||
fitString Nothing (Just width) True False $
|
||||
(++" (split)") $
|
||||
intercalate ", " $
|
||||
[accountNameFromComponents $ elideparts widthpername [] $ accountNameComponents s' | s' <- names]
|
||||
| otherwise =
|
||||
elideLeftWidth width False $ accountNameFromComponents $ elideparts width [] $ accountNameComponents s
|
||||
fitString Nothing (Just width) True False $ accountNameFromComponents $ elideparts width [] $ accountNameComponents s
|
||||
where
|
||||
elideparts :: Int -> [String] -> [String] -> [String]
|
||||
elideparts width done ss
|
||||
|
@ -29,11 +29,6 @@ module Hledger.Utils.String (
|
||||
elideLeft,
|
||||
elideRight,
|
||||
formatString,
|
||||
-- * wide-character-aware single-line layout
|
||||
strWidth,
|
||||
takeWidth,
|
||||
elideLeftWidth,
|
||||
elideRightWidth,
|
||||
-- * multi-line layout
|
||||
concatTopPadded,
|
||||
concatBottomPadded,
|
||||
@ -45,7 +40,14 @@ module Hledger.Utils.String (
|
||||
padleft,
|
||||
padright,
|
||||
cliptopleft,
|
||||
fitto
|
||||
fitto,
|
||||
-- * wide-character-aware layout
|
||||
strWidth,
|
||||
takeWidth,
|
||||
fitString,
|
||||
fitStringMulti,
|
||||
padLeftWide,
|
||||
padRightWide
|
||||
) where
|
||||
|
||||
|
||||
@ -169,26 +171,28 @@ unbracket s
|
||||
| (head s == '[' && last s == ']') || (head s == '(' && last s == ')') = init $ tail s
|
||||
| otherwise = s
|
||||
|
||||
-- | Join multi-line strings as side-by-side rectangular strings of the same height, top-padded.
|
||||
-- | Join several multi-line strings as side-by-side rectangular strings of the same height, top-padded.
|
||||
-- Treats wide characters as double width.
|
||||
concatTopPadded :: [String] -> String
|
||||
concatTopPadded strs = intercalate "\n" $ map concat $ transpose padded
|
||||
where
|
||||
lss = map lines strs
|
||||
h = maximum $ map length lss
|
||||
ypad ls = replicate (difforzero h (length ls)) "" ++ ls
|
||||
xpad ls = map (padleft w) ls where w | null ls = 0
|
||||
| otherwise = maximum $ map length ls
|
||||
xpad ls = map (padLeftWide w) ls where w | null ls = 0
|
||||
| otherwise = maximum $ map strWidth ls
|
||||
padded = map (xpad . ypad) lss
|
||||
|
||||
-- | Join multi-line strings as side-by-side rectangular strings of the same height, bottom-padded.
|
||||
-- | Join several multi-line strings as side-by-side rectangular strings of the same height, bottom-padded.
|
||||
-- Treats wide characters as double width.
|
||||
concatBottomPadded :: [String] -> String
|
||||
concatBottomPadded strs = intercalate "\n" $ map concat $ transpose padded
|
||||
where
|
||||
lss = map lines strs
|
||||
h = maximum $ map length lss
|
||||
ypad ls = ls ++ replicate (difforzero h (length ls)) ""
|
||||
xpad ls = map (padright w) ls where w | null ls = 0
|
||||
| otherwise = maximum $ map length ls
|
||||
xpad ls = map (padRightWide w) ls where w | null ls = 0
|
||||
| otherwise = maximum $ map strWidth ls
|
||||
padded = map (xpad . ypad) lss
|
||||
|
||||
|
||||
@ -237,11 +241,13 @@ difforzero :: (Num a, Ord a) => a -> a -> a
|
||||
difforzero a b = maximum [(a - b), 0]
|
||||
|
||||
-- | Convert a multi-line string to a rectangular string left-padded to the specified width.
|
||||
-- Treats wide characters as double width.
|
||||
padleft :: Int -> String -> String
|
||||
padleft w "" = concat $ replicate w " "
|
||||
padleft w s = intercalate "\n" $ map (printf (printf "%%%ds" w)) $ lines s
|
||||
|
||||
-- | Convert a multi-line string to a rectangular string right-padded to the specified width.
|
||||
-- Treats wide characters as double width.
|
||||
padright :: Int -> String -> String
|
||||
padright w "" = concat $ replicate w " "
|
||||
padright w s = intercalate "\n" $ map (printf (printf "%%-%ds" w)) $ lines s
|
||||
@ -258,27 +264,87 @@ fitto w h s = intercalate "\n" $ take h $ rows ++ repeat blankline
|
||||
fit w = take w . (++ repeat ' ')
|
||||
blankline = replicate w ' '
|
||||
|
||||
-- Functions below are aware of double-width characters eg in CJK text.
|
||||
-- Functions below treat wide (eg CJK) characters as double-width.
|
||||
|
||||
-- | Wide-character-aware string clipping to the specified width, with an ellipsis on the right.
|
||||
-- When the second argument is true, also right-pad with spaces to the specified width if needed.
|
||||
elideLeftWidth :: Int -> Bool -> String -> String
|
||||
elideLeftWidth width pad s
|
||||
| strWidth s > width = ellipsis ++ reverse (takeWidth (width - length ellipsis) $ reverse s)
|
||||
| otherwise = reverse (takeWidth width $ reverse s ++ padding)
|
||||
where
|
||||
ellipsis = ".."
|
||||
padding = if pad then repeat ' ' else ""
|
||||
-- | A version of fitString that works on multi-line strings,
|
||||
-- separate for now to avoid breakage.
|
||||
-- This will rewrite any line endings to unix newlines.
|
||||
fitStringMulti :: Maybe Int -> Maybe Int -> Bool -> Bool -> String -> String
|
||||
fitStringMulti mminwidth mmaxwidth ellipsify rightside s =
|
||||
(intercalate "\n" . map (fitString mminwidth mmaxwidth ellipsify rightside) . lines) s
|
||||
|
||||
-- | Wide-character-aware string clipping to the specified width, with an ellipsis on the left.
|
||||
-- When the second argument is true, also left-pad with spaces to the specified width if needed.
|
||||
elideRightWidth :: Int -> Bool -> String -> String
|
||||
elideRightWidth width pad s
|
||||
| strWidth s > width = takeWidth (width - length ellipsis) s ++ ellipsis
|
||||
| otherwise = takeWidth width $ s ++ padding
|
||||
-- | General-purpose single-line string layout function.
|
||||
-- It can left- or right-pad a short string to a minimum width.
|
||||
-- It can left- or right-clip a long string to a maximum width, optionally inserting an ellipsis.
|
||||
-- It clips and pads on the right if the fourth argument is true, on the left otherwise.
|
||||
-- It treats wide characters as double width.
|
||||
fitString :: Maybe Int -> Maybe Int -> Bool -> Bool -> String -> String
|
||||
fitString mminwidth mmaxwidth ellipsify rightside s = (clip . pad) s
|
||||
where
|
||||
ellipsis = ".."
|
||||
padding = if pad then repeat ' ' else ""
|
||||
clip :: String -> String
|
||||
clip s =
|
||||
case mmaxwidth of
|
||||
Just w
|
||||
| strWidth s > w ->
|
||||
case rightside of
|
||||
True -> takeWidth (w - length ellipsis) s ++ ellipsis
|
||||
False -> ellipsis ++ reverse (takeWidth (w - length ellipsis) $ reverse s)
|
||||
| otherwise -> s
|
||||
where
|
||||
ellipsis = if ellipsify then ".." else ""
|
||||
Nothing -> s
|
||||
pad :: String -> String
|
||||
pad s =
|
||||
case mminwidth of
|
||||
Just w
|
||||
| sw < w ->
|
||||
case rightside of
|
||||
True -> s ++ replicate (w - sw) ' '
|
||||
False -> replicate (w - sw) ' ' ++ s
|
||||
| otherwise -> s
|
||||
Nothing -> s
|
||||
where sw = strWidth s
|
||||
|
||||
-- | Wide-character-aware right-clip a string to the specified width.
|
||||
-- When the second argument is true, an ellipsis will be inserted if the string is clipped.
|
||||
-- When the third argument is true, a short string will be right-padded with spaces to the specified width.
|
||||
-- Works on multi-line strings too (but will rewrite non-unix line endings).
|
||||
elideLeftWidth :: Int -> Bool -> Bool -> String -> String
|
||||
elideLeftWidth width ellipsify pad s = format s --intercalate "\n" $ map format $ lines s
|
||||
where
|
||||
format s
|
||||
| strWidth s > width = ellipsis ++ reverse (takeWidth (width - length ellipsis) $ reverse s)
|
||||
| otherwise = reverse (takeWidth width $ reverse s ++ padding)
|
||||
where
|
||||
ellipsis = if ellipsify then ".." else ""
|
||||
padding = if pad then repeat ' ' else ""
|
||||
|
||||
-- | Wide-character-aware left-clip a string to the specified width.
|
||||
-- When the second argument is true, an ellipsis will be inserted if the string is clipped.
|
||||
-- When the third argument is true, a short string will be left-padded with spaces to the specified width.
|
||||
elideRightWidth :: Int -> Bool -> Bool -> String -> String
|
||||
elideRightWidth width ellipsify pad s = format s --intercalate "\n" $ map format $ lines s
|
||||
where
|
||||
format s
|
||||
| strWidth s > width = takeWidth (width - length ellipsis) s ++ ellipsis
|
||||
| otherwise = takeWidth width $ s ++ padding
|
||||
where
|
||||
ellipsis = if ellipsify then ".." else ""
|
||||
padding = if pad then repeat ' ' else ""
|
||||
|
||||
-- | Left-pad a string to the specified width. (Also clips to this width.)
|
||||
-- Treats wide characters as double width.
|
||||
-- Works on multi-line strings too (but will rewrite non-unix line endings).
|
||||
padLeftWide :: Int -> String -> String
|
||||
padLeftWide w "" = replicate w ' '
|
||||
padLeftWide w s = intercalate "\n" $ map (elideLeftWidth w False True) $ lines s
|
||||
|
||||
-- | Right-pad a string to the specified width. (Also clips to this width.)
|
||||
-- Treats wide characters as double width.
|
||||
-- Works on multi-line strings too (but will rewrite non-unix line endings).
|
||||
padRightWide :: Int -> String -> String
|
||||
padRightWide w "" = replicate w ' '
|
||||
padRightWide w s = intercalate "\n" $ map (elideRightWidth w False True) $ lines s
|
||||
|
||||
-- | Double-width-character-aware string truncation. Take as many
|
||||
-- characters as possible from a string without exceeding the
|
||||
|
@ -353,7 +353,7 @@ balanceReportAsText opts ((items, total)) = unlines $ concat lines ++ t
|
||||
-- with a custom format, extend the line to the full report width;
|
||||
-- otherwise show the usual 20-char line for compatibility
|
||||
overlinewidth | isJust (format_ opts) = maximum' $ map length $ concat lines
|
||||
| otherwise = 20
|
||||
| otherwise = defaultTotalFieldWidth
|
||||
overline = replicate overlinewidth '-'
|
||||
in overline : totallines
|
||||
Left _ -> []
|
||||
@ -407,6 +407,8 @@ renderBalanceReportItem fmt (acctname, depth, total) =
|
||||
render1 = map (renderComponent1 (acctname, depth, total))
|
||||
render = map (renderComponent (acctname, depth, total))
|
||||
|
||||
defaultTotalFieldWidth = 20
|
||||
|
||||
-- | Render one StringFormat component for a balance report item.
|
||||
renderComponent :: (AccountName, Int, MixedAmount) -> StringFormatComponent -> String
|
||||
renderComponent _ (FormatLiteral s) = s
|
||||
@ -416,7 +418,7 @@ renderComponent (acctname, depth, total) (FormatField ljust min max field) = cas
|
||||
Just m -> depth * m
|
||||
Nothing -> depth
|
||||
AccountField -> formatString ljust min max acctname
|
||||
TotalField -> formatString ljust min max $ showMixedAmountWithoutPrice total
|
||||
TotalField -> fitStringMulti min max True False $ showMixedAmountWithoutPrice total
|
||||
_ -> ""
|
||||
|
||||
-- | Render one StringFormat component for a balance report item.
|
||||
@ -431,7 +433,7 @@ renderComponent1 (acctname, depth, total) (FormatField ljust min max field) = ca
|
||||
-- better to indent the account name here rather than use a DepthField component
|
||||
-- so that it complies with width spec. Uses a fixed indent step size.
|
||||
indented = ((replicate (depth*2) ' ')++)
|
||||
TotalField -> formatString ljust min max $ ((intercalate ", " . map strip . lines) (showMixedAmountWithoutPrice total))
|
||||
TotalField -> fitStringMulti min max True False $ ((intercalate ", " . map strip . lines) (showMixedAmountWithoutPrice total))
|
||||
_ -> ""
|
||||
|
||||
-- multi-column balance reports
|
||||
|
@ -120,21 +120,21 @@ postingsReportItemAsText :: CliOpts -> PostingsReportItem -> String
|
||||
postingsReportItemAsText opts (mdate, menddate, mdesc, p, b) =
|
||||
-- use elide*Width to be wide-char-aware
|
||||
intercalate "\n" $
|
||||
[concat [elideRightWidth datewidth True date
|
||||
[concat [fitString (Just datewidth) (Just datewidth) True True date
|
||||
," "
|
||||
,elideRightWidth descwidth True desc
|
||||
,fitString (Just descwidth) (Just descwidth) True True desc
|
||||
," "
|
||||
,elideRightWidth acctwidth True acct
|
||||
,fitString (Just acctwidth) (Just acctwidth) True True acct
|
||||
," "
|
||||
,elideLeftWidth amtwidth True amtfirstline
|
||||
,fitString (Just amtwidth) (Just amtwidth) True False amtfirstline
|
||||
," "
|
||||
,elideLeftWidth balwidth True balfirstline
|
||||
,fitString (Just balwidth) (Just balwidth) True False balfirstline
|
||||
]]
|
||||
++
|
||||
[concat [spacer
|
||||
,elideLeftWidth amtwidth True a
|
||||
,fitString (Just amtwidth) (Just amtwidth) True False a
|
||||
," "
|
||||
,elideLeftWidth balwidth True b
|
||||
,fitString (Just balwidth) (Just balwidth) True False b
|
||||
]
|
||||
| (a,b) <- zip amtrest balrest
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user