lib: Do not call showAmount twice for every posting.
For print -f examples/10000x10000x10.journal, this results in - A 7.7% reduction in heap allocations, from 7.6GB to 7.1GB.
This commit is contained in:
parent
7aa3d3e760
commit
7488140608
@ -75,6 +75,7 @@ import qualified Data.Text.Lazy as TL
|
|||||||
import qualified Data.Text.Lazy.Builder as TB
|
import qualified Data.Text.Lazy.Builder as TB
|
||||||
import Data.Time.Calendar (Day, fromGregorian)
|
import Data.Time.Calendar (Day, fromGregorian)
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
import Safe (maximumDef)
|
||||||
|
|
||||||
import Hledger.Utils
|
import Hledger.Utils
|
||||||
import Hledger.Data.Types
|
import Hledger.Data.Types
|
||||||
@ -218,9 +219,12 @@ renderCommentLines t =
|
|||||||
--
|
--
|
||||||
-- Posting amounts will be aligned with each other, starting about 4 columns
|
-- Posting amounts will be aligned with each other, starting about 4 columns
|
||||||
-- beyond the widest account name (see postingAsLines for details).
|
-- beyond the widest account name (see postingAsLines for details).
|
||||||
--
|
|
||||||
postingsAsLines :: Bool -> [Posting] -> [Text]
|
postingsAsLines :: Bool -> [Posting] -> [Text]
|
||||||
postingsAsLines onelineamounts ps = concatMap (postingAsLines False onelineamounts ps) ps
|
postingsAsLines onelineamounts ps = concatMap first3 linesWithWidths
|
||||||
|
where
|
||||||
|
linesWithWidths = map (postingAsLines False onelineamounts maxacctwidth maxamtwidth) ps
|
||||||
|
maxacctwidth = maximumDef 0 $ map second3 linesWithWidths
|
||||||
|
maxamtwidth = maximumDef 0 $ map third3 linesWithWidths
|
||||||
|
|
||||||
-- | Render one posting, on one or more lines, suitable for `print` output.
|
-- | Render one posting, on one or more lines, suitable for `print` output.
|
||||||
-- There will be an indented account name, plus one or more of status flag,
|
-- There will be an indented account name, plus one or more of status flag,
|
||||||
@ -241,9 +245,10 @@ postingsAsLines onelineamounts ps = concatMap (postingAsLines False onelineamoun
|
|||||||
-- increased if needed to match the posting with the longest account name.
|
-- increased if needed to match the posting with the longest account name.
|
||||||
-- This is used to align the amounts of a transaction's postings.
|
-- This is used to align the amounts of a transaction's postings.
|
||||||
--
|
--
|
||||||
postingAsLines :: Bool -> Bool -> [Posting] -> Posting -> [Text]
|
-- Also returns the account width and amount width used.
|
||||||
postingAsLines elideamount onelineamounts pstoalignwith p =
|
postingAsLines :: Bool -> Bool -> Int -> Int -> Posting -> ([Text], Int, Int)
|
||||||
concatMap (++ newlinecomments) postingblocks
|
postingAsLines elideamount onelineamounts acctwidth amtwidth p =
|
||||||
|
(concatMap (++ newlinecomments) postingblocks, thisacctwidth, thisamtwidth)
|
||||||
where
|
where
|
||||||
-- This needs to be converted to strict Text in order to strip trailing
|
-- This needs to be converted to strict Text in order to strip trailing
|
||||||
-- spaces. This adds a small amount of inefficiency, and the only difference
|
-- spaces. This adds a small amount of inefficiency, and the only difference
|
||||||
@ -253,30 +258,35 @@ postingAsLines elideamount onelineamounts pstoalignwith p =
|
|||||||
postingblocks = [map T.stripEnd . T.lines . TL.toStrict $
|
postingblocks = [map T.stripEnd . T.lines . TL.toStrict $
|
||||||
render [ textCell BottomLeft statusandaccount
|
render [ textCell BottomLeft statusandaccount
|
||||||
, textCell BottomLeft " "
|
, textCell BottomLeft " "
|
||||||
, Cell BottomLeft [amt]
|
, Cell BottomLeft [pad amt]
|
||||||
, Cell BottomLeft [assertion]
|
, Cell BottomLeft [assertion]
|
||||||
, textCell BottomLeft samelinecomment
|
, textCell BottomLeft samelinecomment
|
||||||
]
|
]
|
||||||
| amt <- shownAmounts]
|
| amt <- shownAmounts]
|
||||||
render = renderRow def{tableBorders=False, borderSpaces=False} . Group NoLine . map Header
|
render = renderRow def{tableBorders=False, borderSpaces=False} . Group NoLine . map Header
|
||||||
|
pad amt = WideBuilder (TB.fromText $ T.replicate w " ") w <> amt
|
||||||
|
where w = max 12 amtwidth - wbWidth amt -- min. 12 for backwards compatibility
|
||||||
|
|
||||||
assertion = maybe mempty ((WideBuilder (TB.singleton ' ') 1 <>).showBalanceAssertion) $ pbalanceassertion p
|
assertion = maybe mempty ((WideBuilder (TB.singleton ' ') 1 <>).showBalanceAssertion) $ pbalanceassertion p
|
||||||
statusandaccount = lineIndent . fitText (Just $ minwidth) Nothing False True $ pstatusandacct p
|
|
||||||
where
|
|
||||||
-- pad to the maximum account name width, plus 2 to leave room for status flags, to keep amounts aligned
|
-- pad to the maximum account name width, plus 2 to leave room for status flags, to keep amounts aligned
|
||||||
minwidth = maximum $ map ((2+) . textWidth . pacctstr) pstoalignwith
|
statusandaccount = lineIndent . fitText (Just $ 2 + acctwidth) Nothing False True $ pstatusandacct p
|
||||||
|
thisacctwidth = textWidth $ pacctstr p
|
||||||
|
|
||||||
|
pacctstr p' = showAccountName Nothing (ptype p') (paccount p')
|
||||||
pstatusandacct p' = pstatusprefix p' <> pacctstr p'
|
pstatusandacct p' = pstatusprefix p' <> pacctstr p'
|
||||||
pstatusprefix p' = case pstatus p' of
|
pstatusprefix p' = case pstatus p' of
|
||||||
Unmarked -> ""
|
Unmarked -> ""
|
||||||
s -> T.pack (show s) <> " "
|
s -> T.pack (show s) <> " "
|
||||||
pacctstr p' = showAccountName Nothing (ptype p') (paccount p')
|
|
||||||
|
|
||||||
-- currently prices are considered part of the amount string when right-aligning amounts
|
-- currently prices are considered part of the amount string when right-aligning amounts
|
||||||
|
-- Since we will usually be calling this function with the knot tied between
|
||||||
|
-- amtwidth and thisamtwidth, make sure thisamtwidth does not depend on
|
||||||
|
-- amtwidth at all.
|
||||||
shownAmounts
|
shownAmounts
|
||||||
| elideamount || null (amounts $ pamount p) = [mempty]
|
| elideamount || null (amounts $ pamount p) = [mempty]
|
||||||
| otherwise = showAmountsLinesB displayopts . amounts $ pamount p
|
| otherwise = showAmountsLinesB displayopts . amounts $ pamount p
|
||||||
where
|
where displayopts = noColour{displayOneLine=onelineamounts}
|
||||||
displayopts = noColour{displayOneLine=onelineamounts, displayMinWidth = Just amtwidth}
|
thisamtwidth = maximumDef 0 $ map wbWidth shownAmounts
|
||||||
amtwidth = maximum $ 12 : map (wbWidth . showAmountsB displayopts{displayMinWidth=Nothing} . amounts . pamount) pstoalignwith -- min. 12 for backwards compatibility
|
|
||||||
|
|
||||||
(samelinecomment, newlinecomments) =
|
(samelinecomment, newlinecomments) =
|
||||||
case renderCommentLines (pcomment p) of [] -> ("",[])
|
case renderCommentLines (pcomment p) of [] -> ("",[])
|
||||||
@ -306,9 +316,11 @@ showBalanceAssertion BalanceAssertion{..} =
|
|||||||
-- | Render a posting, at the appropriate width for aligning with
|
-- | Render a posting, at the appropriate width for aligning with
|
||||||
-- its siblings if any. Used by the rewrite command.
|
-- its siblings if any. Used by the rewrite command.
|
||||||
showPostingLines :: Posting -> [Text]
|
showPostingLines :: Posting -> [Text]
|
||||||
showPostingLines p = postingAsLines False False ps p where
|
showPostingLines p = first3 $ postingAsLines False False maxacctwidth maxamtwidth p
|
||||||
ps | Just t <- ptransaction p = tpostings t
|
where
|
||||||
| otherwise = [p]
|
linesWithWidths = map (postingAsLines False False maxacctwidth maxamtwidth) . maybe [p] tpostings $ ptransaction p
|
||||||
|
maxacctwidth = maximumDef 0 $ map second3 linesWithWidths
|
||||||
|
maxamtwidth = maximumDef 0 $ map third3 linesWithWidths
|
||||||
|
|
||||||
-- | Prepend a suitable indent for a posting (or transaction/posting comment) line.
|
-- | Prepend a suitable indent for a posting (or transaction/posting comment) line.
|
||||||
lineIndent :: Text -> Text
|
lineIndent :: Text -> Text
|
||||||
@ -642,8 +654,8 @@ tests_Transaction :: TestTree
|
|||||||
tests_Transaction =
|
tests_Transaction =
|
||||||
tests "Transaction" [
|
tests "Transaction" [
|
||||||
|
|
||||||
tests "postingAsLines" [
|
tests "showPostingLines" [
|
||||||
test "null posting" $ postingAsLines False False [posting] posting @?= [""]
|
test "null posting" $ showPostingLines posting @?= [""]
|
||||||
, test "non-null posting" $
|
, test "non-null posting" $
|
||||||
let p =
|
let p =
|
||||||
posting
|
posting
|
||||||
@ -654,7 +666,7 @@ tests_Transaction =
|
|||||||
, ptype = RegularPosting
|
, ptype = RegularPosting
|
||||||
, ptags = [("ptag1", "val1"), ("ptag2", "val2")]
|
, ptags = [("ptag1", "val1"), ("ptag2", "val2")]
|
||||||
}
|
}
|
||||||
in postingAsLines False False [p] p @?=
|
in showPostingLines p @?=
|
||||||
[ " * a $1.00 ; pcomment1"
|
[ " * a $1.00 ; pcomment1"
|
||||||
, " ; pcomment2"
|
, " ; pcomment2"
|
||||||
, " ; tag3: val3 "
|
, " ; tag3: val3 "
|
||||||
|
|||||||
@ -28,9 +28,10 @@ import qualified Data.Text as T
|
|||||||
import qualified Data.Text.Lazy as TL
|
import qualified Data.Text.Lazy as TL
|
||||||
import qualified Data.Text.Lazy.Builder as TB
|
import qualified Data.Text.Lazy.Builder as TB
|
||||||
import System.Console.CmdArgs.Explicit (flagNone, flagReq)
|
import System.Console.CmdArgs.Explicit (flagNone, flagReq)
|
||||||
import Hledger.Read.CsvReader (CSV, CsvRecord, printCSV)
|
import Safe (maximumDef)
|
||||||
|
|
||||||
import Hledger
|
import Hledger
|
||||||
|
import Hledger.Read.CsvReader (CSV, CsvRecord, printCSV)
|
||||||
import Hledger.Cli.CliOptions
|
import Hledger.Cli.CliOptions
|
||||||
import Hledger.Cli.Utils
|
import Hledger.Cli.Utils
|
||||||
import Text.Tabular (Header(..), Properties(..))
|
import Text.Tabular (Header(..), Properties(..))
|
||||||
@ -97,15 +98,18 @@ postingsReportItemAsCsvRecord (_, _, _, p, b) = [idx,date,code,desc,acct,amt,bal
|
|||||||
|
|
||||||
-- | Render a register report as plain text suitable for console output.
|
-- | Render a register report as plain text suitable for console output.
|
||||||
postingsReportAsText :: CliOpts -> PostingsReport -> TL.Text
|
postingsReportAsText :: CliOpts -> PostingsReport -> TL.Text
|
||||||
postingsReportAsText opts items =
|
postingsReportAsText opts items = TB.toLazyText $ foldMap first3 linesWithWidths
|
||||||
TB.toLazyText . unlinesB $
|
|
||||||
map (postingsReportItemAsText opts amtwidth balwidth) items
|
|
||||||
where
|
where
|
||||||
amtwidth = maximumStrict $ map (wbWidth . showAmt . itemamt) items
|
linesWithWidths = map (postingsReportItemAsText opts amtwidth balwidth) items
|
||||||
balwidth = maximumStrict $ map (wbWidth . showAmt . itembal) items
|
-- Tying this knot seems like it will save work, but ends up creating a big
|
||||||
itemamt (_,_,_,Posting{pamount=a},_) = a
|
-- space leak. Can we fix that leak without recalculating everything?
|
||||||
itembal (_,_,_,_,a) = a
|
-- amtwidth = maximum $ 12 : map second3 linesWithWidths
|
||||||
showAmt = showMixedAmountB noColour{displayMinWidth=Just 12}
|
-- balwidth = maximum $ 12 : map third3 linesWithWidths
|
||||||
|
amtwidth = maximumStrict $ 12 : widths (map itemamt items)
|
||||||
|
balwidth = maximumStrict $ 12 : widths (map itembal items)
|
||||||
|
widths = map wbWidth . concatMap (showAmountsLinesB noPrice)
|
||||||
|
itemamt (_,_,_,Posting{pamount=a},_) = amounts a
|
||||||
|
itembal (_,_,_,_,a) = amounts a
|
||||||
|
|
||||||
-- | Render one register report line item as plain text. Layout is like so:
|
-- | Render one register report line item as plain text. Layout is like so:
|
||||||
-- @
|
-- @
|
||||||
@ -129,23 +133,27 @@ postingsReportAsText opts items =
|
|||||||
-- has multiple commodities. Does not yet support formatting control
|
-- has multiple commodities. Does not yet support formatting control
|
||||||
-- like balance reports.
|
-- like balance reports.
|
||||||
--
|
--
|
||||||
postingsReportItemAsText :: CliOpts -> Int -> Int -> PostingsReportItem -> TB.Builder
|
-- Also returns the natural width (without padding) of the amount and balance
|
||||||
|
-- fields.
|
||||||
|
postingsReportItemAsText :: CliOpts -> Int -> Int -> PostingsReportItem -> (TB.Builder, Int, Int)
|
||||||
postingsReportItemAsText opts preferredamtwidth preferredbalwidth (mdate, menddate, mdesc, p, b) =
|
postingsReportItemAsText opts preferredamtwidth preferredbalwidth (mdate, menddate, mdesc, p, b) =
|
||||||
render
|
(table <> TB.singleton '\n', thisamtwidth, thisbalwidth)
|
||||||
|
where
|
||||||
|
table = renderRowB def{tableBorders=False, borderSpaces=False} . Group NoLine $ map Header
|
||||||
[ textCell TopLeft $ fitText (Just datewidth) (Just datewidth) True True date
|
[ textCell TopLeft $ fitText (Just datewidth) (Just datewidth) True True date
|
||||||
, spacerCell
|
, spacerCell
|
||||||
, textCell TopLeft $ fitText (Just descwidth) (Just descwidth) True True desc
|
, textCell TopLeft $ fitText (Just descwidth) (Just descwidth) True True desc
|
||||||
, spacerCell2
|
, spacerCell2
|
||||||
, textCell TopLeft $ fitText (Just acctwidth) (Just acctwidth) True True acct
|
, textCell TopLeft $ fitText (Just acctwidth) (Just acctwidth) True True acct
|
||||||
, spacerCell2
|
, spacerCell2
|
||||||
, Cell TopRight amt
|
, Cell TopRight $ map (pad amtwidth) amt
|
||||||
, spacerCell2
|
, spacerCell2
|
||||||
, Cell BottomRight bal
|
, Cell BottomRight $ map (pad balwidth) bal
|
||||||
]
|
]
|
||||||
where
|
|
||||||
render = renderRowB def{tableBorders=False, borderSpaces=False} . Group NoLine . map Header
|
|
||||||
spacerCell = Cell BottomLeft [WideBuilder (TB.singleton ' ') 1]
|
spacerCell = Cell BottomLeft [WideBuilder (TB.singleton ' ') 1]
|
||||||
spacerCell2 = Cell BottomLeft [WideBuilder (TB.fromString " ") 2]
|
spacerCell2 = Cell BottomLeft [WideBuilder (TB.fromString " ") 2]
|
||||||
|
pad fullwidth amt = WideBuilder (TB.fromText $ T.replicate w " ") w <> amt
|
||||||
|
where w = fullwidth - wbWidth amt
|
||||||
-- calculate widths
|
-- calculate widths
|
||||||
(totalwidth,mdescwidth) = registerWidthsFromOpts opts
|
(totalwidth,mdescwidth) = registerWidthsFromOpts opts
|
||||||
(datewidth, date) = case (mdate,menddate) of
|
(datewidth, date) = case (mdate,menddate) of
|
||||||
@ -176,15 +184,17 @@ postingsReportItemAsText opts preferredamtwidth preferredbalwidth (mdate, mendda
|
|||||||
desc = fromMaybe "" mdesc
|
desc = fromMaybe "" mdesc
|
||||||
acct = parenthesise . elideAccountName awidth $ paccount p
|
acct = parenthesise . elideAccountName awidth $ paccount p
|
||||||
where
|
where
|
||||||
(parenthesise, awidth) =
|
(parenthesise, awidth) = case ptype p of
|
||||||
case ptype p of
|
BalancedVirtualPosting -> (wrap "[" "]", acctwidth-2)
|
||||||
BalancedVirtualPosting -> (\s -> wrap "[" "]" s, acctwidth-2)
|
VirtualPosting -> (wrap "(" ")", acctwidth-2)
|
||||||
VirtualPosting -> (\s -> wrap "(" ")" s, acctwidth-2)
|
|
||||||
_ -> (id,acctwidth)
|
_ -> (id,acctwidth)
|
||||||
wrap a b x = a <> x <> b
|
amt = showAmountsLinesB dopts . (\x -> if null x then [nullamt] else x) . amounts $ pamount p
|
||||||
amt = showAmountsLinesB (dopts amtwidth) . (\x -> if null x then [nullamt] else x) . amounts $ pamount p
|
bal = showAmountsLinesB dopts $ amounts b
|
||||||
bal = showAmountsLinesB (dopts balwidth) $ amounts b
|
dopts = noPrice{displayColour=color_ . rsOpts $ reportspec_ opts}
|
||||||
dopts w = noPrice{displayColour=color_ . rsOpts $ reportspec_ opts, displayMinWidth=Just w, displayMaxWidth=Just w}
|
-- Since this will usually be called with the knot tied between this(amt|bal)width and
|
||||||
|
-- preferred(amt|bal)width, make sure the former do not depend on the latter to avoid loops.
|
||||||
|
thisamtwidth = maximumDef 0 $ map wbWidth amt
|
||||||
|
thisbalwidth = maximumDef 0 $ map wbWidth bal
|
||||||
|
|
||||||
-- tests
|
-- tests
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user