lib!: use styleAmounts in more places; add rounding strategies

This commit is contained in:
Simon Michael 2023-09-19 07:54:25 +01:00
parent 94ab8b1ed7
commit c13c13ab1f
13 changed files with 193 additions and 173 deletions

View File

@ -48,7 +48,9 @@ module Hledger.Data.Amount (
showCommoditySymbol, showCommoditySymbol,
isNonsimpleCommodityChar, isNonsimpleCommodityChar,
quoteCommoditySymbolIfNeeded, quoteCommoditySymbolIfNeeded,
-- * Amount -- * Amount
-- ** arithmetic
nullamt, nullamt,
missingamt, missingamt,
num, num,
@ -60,27 +62,25 @@ module Hledger.Data.Amount (
at, at,
(@@), (@@),
amountWithCommodity, amountWithCommodity,
-- ** arithmetic
amountCost, amountCost,
amountIsZero, amountIsZero,
amountLooksZero, amountLooksZero,
divideAmount, divideAmount,
multiplyAmount, multiplyAmount,
-- ** styles
amountstyle,
canonicaliseAmount,
styleAmount,
amountSetStyles,
amountStyleSetRounding,
amountStylesSetRounding,
amountUnstyled,
-- ** rendering -- ** rendering
AmountDisplayOpts(..), AmountDisplayOpts(..),
noColour, noColour,
noPrice, noPrice,
oneLine, oneLine,
csvDisplay, csvDisplay,
amountstyle,
canonicaliseAmount,
styleAmount,
amountSetStyles,
amountSetStylesExceptPrecision,
amountSetMainStyle,
amountSetCostStyle,
amountStyleSetRounding,
amountUnstyled,
showAmountB, showAmountB,
showAmount, showAmount,
showAmountPrice, showAmountPrice,
@ -97,6 +97,7 @@ module Hledger.Data.Amount (
setAmountDecimalPoint, setAmountDecimalPoint,
withDecimalPoint, withDecimalPoint,
amountStripPrices, amountStripPrices,
-- * MixedAmount -- * MixedAmount
nullmixedamt, nullmixedamt,
missingmixedamt, missingmixedamt,
@ -129,12 +130,12 @@ module Hledger.Data.Amount (
maIsZero, maIsZero,
maIsNonZero, maIsNonZero,
mixedAmountLooksZero, mixedAmountLooksZero,
-- ** rendering -- ** styles
canonicaliseMixedAmount, canonicaliseMixedAmount,
styleMixedAmount, styleMixedAmount,
mixedAmountSetStyles, mixedAmountSetStyles,
mixedAmountSetStylesExceptPrecision,
mixedAmountUnstyled, mixedAmountUnstyled,
-- ** rendering
showMixedAmount, showMixedAmount,
showMixedAmountOneLine, showMixedAmountOneLine,
showMixedAmountDebug, showMixedAmountDebug,
@ -148,6 +149,7 @@ module Hledger.Data.Amount (
wbUnpack, wbUnpack,
mixedAmountSetPrecision, mixedAmountSetPrecision,
mixedAmountSetFullPrecision, mixedAmountSetFullPrecision,
-- * misc. -- * misc.
tests_Amount tests_Amount
) where ) where
@ -179,6 +181,7 @@ import Hledger.Utils (colorB, numDigitsInt)
import Hledger.Utils.Text (textQuoteIfNeeded) import Hledger.Utils.Text (textQuoteIfNeeded)
import Text.WideString (WideBuilder(..), wbFromText, wbToText, wbUnpack) import Text.WideString (WideBuilder(..), wbFromText, wbToText, wbUnpack)
import Data.Functor ((<&>)) import Data.Functor ((<&>))
-- import Hledger.Utils.Debug (dbg0)
-- A 'Commodity' is a symbol representing a currency or some other kind of -- A 'Commodity' is a symbol representing a currency or some other kind of
@ -246,15 +249,7 @@ csvDisplay :: AmountDisplayOpts
csvDisplay = oneLine{displayThousandsSep=False} csvDisplay = oneLine{displayThousandsSep=False}
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Amount styles -- Amount arithmetic
-- | Default amount style
amountstyle = AmountStyle L False Nothing (Just '.') (Precision 0) NoRounding
-------------------------------------------------------------------------------
-- Amount
instance HasAmounts Amount where styleAmounts = amountSetStyles
instance Num Amount where instance Num Amount where
abs a@Amount{aquantity=q} = a{aquantity=abs q} abs a@Amount{aquantity=q} = a{aquantity=abs q}
@ -409,6 +404,123 @@ setAmountInternalPrecision p a@Amount{ aquantity=q, astyle=s } = a{
withInternalPrecision :: Amount -> Word8 -> Amount withInternalPrecision :: Amount -> Word8 -> Amount
withInternalPrecision = flip setAmountInternalPrecision withInternalPrecision = flip setAmountInternalPrecision
-- Amount display styles
-- v1
{-# DEPRECATED canonicaliseAmount "please use styleAmounts instead" #-}
canonicaliseAmount :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
canonicaliseAmount = styleAmounts
-- v2
{-# DEPRECATED styleAmount "please use styleAmounts instead" #-}
styleAmount :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
styleAmount = styleAmounts
-- v3
{-# DEPRECATED amountSetStyles "please use styleAmounts instead" #-}
amountSetStyles :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
amountSetStyles = styleAmounts
-- v4
instance HasAmounts Amount where
-- | Given some commodity display styles, find and apply the appropriate one to this amount,
-- and its cost amount if any (and stop; we assume costs don't have costs).
-- Display precision will be applied (or not) as specified by the style's rounding strategy,
-- except that costs' precision is never changed (costs are often recorded inexactly,
-- so we don't want to imply greater precision than they were recorded with).
-- If no style is found for an amount, it is left unchanged.
styleAmounts styles a@Amount{aquantity=qty, acommodity=comm, astyle=oldstyle, aprice=mcost0} =
a{astyle=newstyle, aprice=mcost1}
where
newstyle = mknewstyle False qty oldstyle comm
mcost1 = case mcost0 of
Nothing -> Nothing
Just (UnitPrice ca@Amount{aquantity=cq, astyle=cs, acommodity=ccomm}) -> Just $ UnitPrice ca{astyle=mknewstyle True cq cs ccomm}
Just (TotalPrice ca@Amount{aquantity=cq, astyle=cs, acommodity=ccomm}) -> Just $ TotalPrice ca{astyle=mknewstyle True cq cs ccomm}
mknewstyle :: Bool -> Quantity -> AmountStyle -> CommoditySymbol -> AmountStyle
mknewstyle iscost oldq olds com =
case M.lookup com styles of
Just s ->
-- dbg0 "new style" $
amountStyleApplyWithRounding iscost oldq
(
-- dbg0 "applying style"
s)
(
-- dbg0 "old style"
olds)
Nothing -> olds
-- AmountStyle helpers
-- | Replace one AmountStyle with another, but don't just replace the display precision;
-- update that in one of several ways as selected by the new style's "rounding strategy":
--
-- NoRounding - keep the precision unchanged
--
-- SoftRounding -
--
-- if either precision is NaturalPrecision, use NaturalPrecision;
--
-- if the new precision is greater than the old, use the new (adds decimal zeros);
--
-- if the new precision is less than the old, use as close to the new as we can get
-- without dropping (more) non-zero digits (drops decimal zeros).
--
-- for a cost amount, keep the precision unchanged
--
-- HardRounding -
--
-- for a posting amount, use the new precision (may truncate significant digits);
--
-- for a cost amount, keep the precision unchanged
--
-- AllRounding -
--
-- for both posting and cost amounts, do hard rounding.
--
-- Arguments:
--
-- whether this style is for a posting amount or a cost amount,
--
-- the amount's decimal quantity (for inspecting its internal representation),
--
-- the new style,
--
-- the old style.
--
amountStyleApplyWithRounding :: Bool -> Quantity -> AmountStyle -> AmountStyle -> AmountStyle
amountStyleApplyWithRounding iscost q news@AmountStyle{asprecision=newp, asrounding=newr} AmountStyle{asprecision=oldp} =
case newr of
NoRounding -> news{asprecision=oldp}
SoftRounding -> news{asprecision=if iscost then oldp else newp'}
where
newp' = case (newp, oldp) of
(Precision new, Precision old) ->
if new >= old
then Precision new
else Precision $ max (min old internal) new
where internal = decimalPlaces $ normalizeDecimal q
_ -> NaturalPrecision
HardRounding -> news{asprecision=if iscost then oldp else newp}
AllRounding -> news
-- | Set this amount style's rounding strategy when being applied to amounts.
amountStyleSetRounding :: Rounding -> AmountStyle -> AmountStyle
amountStyleSetRounding r as = as{asrounding=r}
amountStylesSetRounding :: Rounding -> M.Map CommoditySymbol AmountStyle -> M.Map CommoditySymbol AmountStyle
amountStylesSetRounding r = M.map (amountStyleSetRounding r)
-- | Default amount style
amountstyle = AmountStyle L False Nothing (Just '.') (Precision 0) NoRounding
-- | Reset this amount's display style to the default.
amountUnstyled :: Amount -> Amount
amountUnstyled a = a{astyle=amountstyle}
-- | Set (or clear) an amount's display decimal point. -- | Set (or clear) an amount's display decimal point.
setAmountDecimalPoint :: Maybe Char -> Amount -> Amount setAmountDecimalPoint :: Maybe Char -> Amount -> Amount
setAmountDecimalPoint mc a@Amount{ astyle=s } = a{ astyle=s{asdecimalmark=mc} } setAmountDecimalPoint mc a@Amount{ astyle=s } = a{ astyle=s{asdecimalmark=mc} }
@ -417,6 +529,8 @@ setAmountDecimalPoint mc a@Amount{ astyle=s } = a{ astyle=s{asdecimalmark=mc} }
withDecimalPoint :: Amount -> Maybe Char -> Amount withDecimalPoint :: Amount -> Maybe Char -> Amount
withDecimalPoint = flip setAmountDecimalPoint withDecimalPoint = flip setAmountDecimalPoint
-- Amount rendering
-- | Strip all prices from an Amount -- | Strip all prices from an Amount
amountStripPrices :: Amount -> Amount amountStripPrices :: Amount -> Amount
amountStripPrices a = a{aprice=Nothing} amountStripPrices a = a{aprice=Nothing}
@ -433,103 +547,6 @@ showAmountPriceDebug Nothing = ""
showAmountPriceDebug (Just (UnitPrice pa)) = " @ " ++ showAmountDebug pa showAmountPriceDebug (Just (UnitPrice pa)) = " @ " ++ showAmountDebug pa
showAmountPriceDebug (Just (TotalPrice pa)) = " @@ " ++ showAmountDebug pa showAmountPriceDebug (Just (TotalPrice pa)) = " @@ " ++ showAmountDebug pa
-- Amount styling
-- v1
-- like journalCanonicaliseAmounts
-- | Canonicalise an amount's display style using the provided commodity style map.
-- Its cost amount, if any, is not affected.
canonicaliseAmount :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
canonicaliseAmount = amountSetMainStyle
{-# DEPRECATED canonicaliseAmount "please use amountSetMainStyle (or amountSetStyles) instead" #-}
-- v2
-- | Given a map of standard commodity display styles, apply the
-- appropriate one to this amount. If there's no standard style for
-- this amount's commodity, return the amount unchanged.
-- Also do the same for the cost amount if any, but leave its precision unchanged.
styleAmount :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
styleAmount = amountSetStyles
{-# DEPRECATED styleAmount "please use amountSetStyles instead" #-}
-- v3
-- | Given some commodity display styles, find and apply the appropriate
-- display style to this amount, and do the same for its cost amount if any
-- (and then stop; we assume costs don't have costs).
-- The main amount's display precision is set or not, according to its style;
-- the cost amount's display precision is left unchanged, regardless of its style.
-- If no style is found for an amount, it is left unchanged.
amountSetStyles :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
amountSetStyles styles = amountSetMainStyle styles <&> amountSetCostStyle styles
-- | Like amountSetStyles, but leave the display precision unchanged
-- in both main and cost amounts.
amountSetStylesExceptPrecision :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
amountSetStylesExceptPrecision styles a@Amount{astyle=AmountStyle{asprecision=origp}} =
case M.lookup (acommodity a) styles' of
Just s -> a{astyle=s{asprecision=origp}}
Nothing -> a
where styles' = M.map (amountStyleSetRounding NoRounding) styles
amountStyleSetRounding :: Rounding -> AmountStyle -> AmountStyle
amountStyleSetRounding r as = as{asrounding=r}
-- | Find and apply the appropriate display style, if any, to this amount.
-- The display precision is adjusted or not, as determnined by the style's rounding strategy.
amountSetMainStyle :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
amountSetMainStyle styles a@Amount{aquantity=q, acommodity=comm, astyle=s0} =
case M.lookup comm styles of
Nothing -> a
Just s -> a{astyle=amountStyleApplyPrecision q s s0}
-- | A helper for updating an Amount's display precision, more carefully than amountSetPrecision.
-- Given an Amount's decimal quantity (for inspecting its internal representation),
-- its current display style, and a new display style,
-- apply the new style's display precision to the old style,
-- using the new style's rounding strategy, as follows:
--
-- NoRounding - the precision is left unchanged
--
-- SoftRounding -
--
-- if either precision is NaturalPrecision, use NaturalPrecision;
--
-- if the new precision is greater than the old, use the new (adds decimal zeros);
--
-- if the new precision is less than the old, use as close to the new as we can get
-- without dropping (more) non-zero digits (drops decimal zeros).
--
amountStyleApplyPrecision :: Quantity -> AmountStyle -> AmountStyle -> AmountStyle
amountStyleApplyPrecision q AmountStyle{asprecision=newp, asrounding=r} s@AmountStyle{asprecision=oldp} =
case r of
NoRounding -> s
SoftRounding -> s{asprecision=p}
where
p = case (newp, oldp) of
(Precision new, Precision old) ->
if new >= old
then Precision new
else Precision $ max (min old internal) new
where internal = decimalPlaces $ normalizeDecimal q
_ -> NaturalPrecision
-- | Find and apply the appropriate display style, if any, to this amount's cost, if any.
-- The display precision is left unchanged, regardless of the style.
amountSetCostStyle :: M.Map CommoditySymbol AmountStyle -> Amount -> Amount
amountSetCostStyle styles a@Amount{aprice=mcost} =
case mcost of
Nothing -> a
Just (UnitPrice a2) -> a{aprice=Just $ UnitPrice $ amountSetStylesExceptPrecision styles a2}
Just (TotalPrice a2) -> a{aprice=Just $ TotalPrice $ amountSetStylesExceptPrecision styles a2}
-- | Reset this amount's display style to the default.
amountUnstyled :: Amount -> Amount
amountUnstyled a = a{astyle=amountstyle}
-- | Get the string representation of an amount, based on its -- | Get the string representation of an amount, based on its
-- commodity's display settings. String representations equivalent to -- commodity's display settings. String representations equivalent to
-- zero are converted to just \"0\". The special "missing" amount is -- zero are converted to just \"0\". The special "missing" amount is
@ -641,8 +658,6 @@ applyDigitGroupStyle (Just (DigitGroups c (g0:gs0))) l0 s0 = addseps (g0:|gs0) (
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- MixedAmount -- MixedAmount
instance HasAmounts MixedAmount where styleAmounts = mixedAmountSetStyles
instance Semigroup MixedAmount where instance Semigroup MixedAmount where
(<>) = maPlus (<>) = maPlus
sconcat = maSum sconcat = maSum
@ -901,35 +916,34 @@ mixedAmountCost (Mixed ma) =
-- where a' = mixedAmountStripPrices a -- where a' = mixedAmountStripPrices a
-- b' = mixedAmountStripPrices b -- b' = mixedAmountStripPrices b
-- Mixed amount styling -- Mixed amount styles
-- v1
-- | Canonicalise a mixed amount's display styles using the provided commodity style map. -- v1
-- Cost amounts, if any, are not affected. {-# DEPRECATED canonicaliseMixedAmount "please use mixedAmountSetStyle False (or styleAmounts) instead" #-}
canonicaliseMixedAmount :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount canonicaliseMixedAmount :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount
canonicaliseMixedAmount styles = mapMixedAmountUnsafe (canonicaliseAmount styles) canonicaliseMixedAmount = styleAmounts
{-# DEPRECATED canonicaliseMixedAmount "please use mixedAmountSetMainStyle (or mixedAmountSetStyles) instead" #-}
-- v2 -- v2
{-# DEPRECATED styleMixedAmount "please use styleAmounts instead" #-}
-- | Given a map of standard commodity display styles, find and apply -- | Given a map of standard commodity display styles, find and apply
-- the appropriate style to each individual amount. -- the appropriate style to each individual amount.
styleMixedAmount :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount styleMixedAmount :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount
styleMixedAmount = mixedAmountSetStyles styleMixedAmount = styleAmounts
{-# DEPRECATED styleMixedAmount "please use mixedAmountSetStyles instead" #-}
-- v3 -- v3
{-# DEPRECATED mixedAmountSetStyles "please use styleAmounts instead" #-}
mixedAmountSetStyles :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount mixedAmountSetStyles :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount
mixedAmountSetStyles styles = mapMixedAmountUnsafe (amountSetStyles styles) mixedAmountSetStyles = styleAmounts
mixedAmountSetStylesExceptPrecision :: M.Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount -- v4
mixedAmountSetStylesExceptPrecision styles = mapMixedAmountUnsafe (amountSetStylesExceptPrecision styles) instance HasAmounts MixedAmount where
styleAmounts styles = mapMixedAmountUnsafe (styleAmounts styles)
-- | Reset each individual amount's display style to the default. -- | Reset each individual amount's display style to the default.
mixedAmountUnstyled :: MixedAmount -> MixedAmount mixedAmountUnstyled :: MixedAmount -> MixedAmount
mixedAmountUnstyled = mapMixedAmountUnsafe amountUnstyled mixedAmountUnstyled = mapMixedAmountUnsafe amountUnstyled
-- Mixed amount rendering
-- | Get the string representation of a mixed amount, after -- | Get the string representation of a mixed amount, after
-- normalising it to one amount per commodity. Assumes amounts have -- normalising it to one amount per commodity. Assumes amounts have

View File

@ -101,7 +101,7 @@ transactionCheckBalanced BalancingOpts{commodity_styles_} t = errs
VirtualPosting -> (l, r) VirtualPosting -> (l, r)
-- check for mixed signs, detecting nonzeros at display precision -- check for mixed signs, detecting nonzeros at display precision
setstyles = maybe id mixedAmountSetStyles commodity_styles_ setstyles = maybe id styleAmounts commodity_styles_
postingBalancingAmount p postingBalancingAmount p
| "_price-matched" `elem` map fst (ptags p) = mixedAmountStripPrices $ pamount p | "_price-matched" `elem` map fst (ptags p) = mixedAmountStripPrices $ pamount p
| otherwise = mixedAmountCost $ pamount p | otherwise = mixedAmountCost $ pamount p
@ -250,7 +250,7 @@ transactionInferBalancingAmount styles t@Transaction{tpostings=ps}
-- Inferred amounts are converted to cost. -- Inferred amounts are converted to cost.
-- Also ensure the new amount has the standard style for its commodity -- Also ensure the new amount has the standard style for its commodity
-- (since the main amount styling pass happened before this balancing pass); -- (since the main amount styling pass happened before this balancing pass);
a' = mixedAmountSetStyles styles . mixedAmountCost $ maNegate a a' = styleAmounts styles . mixedAmountCost $ maNegate a
-- | Infer costs for this transaction's posting amounts, if needed to make -- | Infer costs for this transaction's posting amounts, if needed to make
-- the postings balance, and if permitted. This is done once for the real -- the postings balance, and if permitted. This is done once for the real
@ -453,7 +453,9 @@ journalBalanceTransactions bopts' j' =
-- ensure transactions are numbered, so we can store them by number -- ensure transactions are numbered, so we can store them by number
j@Journal{jtxns=ts} = journalNumberTransactions j' j@Journal{jtxns=ts} = journalNumberTransactions j'
-- display precisions used in balanced checking -- display precisions used in balanced checking
styles = Just $ journalCommodityStyles j styles = Just $
journalCommodityStylesWith HardRounding -- txn balancedness will be checked using commodity display precisions
j
bopts = bopts'{commodity_styles_=styles} bopts = bopts'{commodity_styles_=styles}
-- XXX ^ The commodity directive styles and default style and inferred styles -- XXX ^ The commodity directive styles and default style and inferred styles
-- are merged into the command line styles in commodity_styles_ - why ? -- are merged into the command line styles in commodity_styles_ - why ?

View File

@ -26,6 +26,7 @@ module Hledger.Data.Journal (
journalStyleAmounts, journalStyleAmounts,
commodityStylesFromAmounts, commodityStylesFromAmounts,
journalCommodityStyles, journalCommodityStyles,
journalCommodityStylesWith,
journalToCost, journalToCost,
journalInferEquityFromCosts, journalInferEquityFromCosts,
journalInferCostsFromEquity, journalInferCostsFromEquity,
@ -802,7 +803,7 @@ journalStyleAmounts :: Journal -> Either String Journal
journalStyleAmounts = fmap journalapplystyles . journalInferCommodityStyles journalStyleAmounts = fmap journalapplystyles . journalInferCommodityStyles
where where
journalapplystyles j@Journal{jpricedirectives=pds} = journalapplystyles j@Journal{jpricedirectives=pds} =
journalMapPostings (postingStyleAmounts styles) j{jpricedirectives=map fixpricedirective pds} journalMapPostings (styleAmounts styles) j{jpricedirectives=map fixpricedirective pds}
where where
styles = journalCommodityStylesWith NoRounding j -- defer rounding, in case of print --round=none styles = journalCommodityStylesWith NoRounding j -- defer rounding, in case of print --round=none
fixpricedirective pd@PriceDirective{pdamount=a} = pd{pdamount=styleAmounts styles a} fixpricedirective pd@PriceDirective{pdamount=a} = pd{pdamount=styleAmounts styles a}
@ -824,6 +825,11 @@ journalCommodityStyles j =
defaultcommoditystyle = M.fromList $ catMaybes [jparsedefaultcommodity j] defaultcommoditystyle = M.fromList $ catMaybes [jparsedefaultcommodity j]
inferredstyles = jinferredcommodities j inferredstyles = jinferredcommodities j
-- | Like journalCommodityStyles, but attach a particular rounding strategy to the styles,
-- affecting how they will affect display precisions when applied.
journalCommodityStylesWith :: Rounding -> Journal -> M.Map CommoditySymbol AmountStyle
journalCommodityStylesWith r = amountStylesSetRounding r . journalCommodityStyles
-- | Collect and save inferred amount styles for each commodity based on -- | Collect and save inferred amount styles for each commodity based on
-- the posting amounts in that commodity (excluding price amounts), ie: -- the posting amounts in that commodity (excluding price amounts), ie:
-- "the format of the first amount, adjusted to the highest precision of all amounts". -- "the format of the first amount, adjusted to the highest precision of all amounts".

View File

@ -39,7 +39,7 @@ module Hledger.Data.Posting (
postingStripPrices, postingStripPrices,
postingApplyAliases, postingApplyAliases,
postingApplyCommodityStyles, postingApplyCommodityStyles,
postingApplyCommodityStylesExceptPrecision, postingStyleAmounts,
postingAddTags, postingAddTags,
-- * date operations -- * date operations
postingDate, postingDate,
@ -107,6 +107,19 @@ instance HasAmounts Posting where
,pbalanceassertion=styleAmounts styles pbalanceassertion ,pbalanceassertion=styleAmounts styles pbalanceassertion
} }
{-# DEPRECATED postingApplyCommodityStyles "please use styleAmounts instead" #-}
-- | Find and apply the appropriate display style to the posting amounts
-- in each commodity (see journalCommodityStyles).
-- Main amount precisions may be set or not according to the styles, but cost precisions are not set.
postingApplyCommodityStyles :: M.Map CommoditySymbol AmountStyle -> Posting -> Posting
postingApplyCommodityStyles = styleAmounts
{-# DEPRECATED postingStyleAmounts "please use styleAmounts instead" #-}
-- | Like postingApplyCommodityStyles, but neither
-- main amount precisions or cost precisions are set.
postingStyleAmounts :: M.Map CommoditySymbol AmountStyle -> Posting -> Posting
postingStyleAmounts = styleAmounts
nullposting, posting :: Posting nullposting, posting :: Posting
nullposting = Posting nullposting = Posting
{pdate=Nothing {pdate=Nothing
@ -419,23 +432,6 @@ postingApplyAliases aliases p@Posting{paccount} =
err = "problem while applying account aliases:\n" ++ pshow aliases err = "problem while applying account aliases:\n" ++ pshow aliases
++ "\n to account name: "++T.unpack paccount++"\n "++e ++ "\n to account name: "++T.unpack paccount++"\n "++e
-- | Find and apply the appropriate display style to the posting amounts
-- in each commodity (see journalCommodityStyles).
-- Main amount precisions may be set or not according to the styles, but cost precisions are not set.
postingApplyCommodityStyles :: M.Map CommoditySymbol AmountStyle -> Posting -> Posting
postingApplyCommodityStyles styles p = p{pamount=mixedAmountSetStyles styles $ pamount p
,pbalanceassertion=balanceassertionsetstyles <$> pbalanceassertion p}
where
balanceassertionsetstyles ba = ba{baamount=amountSetStyles styles $ baamount ba}
-- | Like postingApplyCommodityStyles, but neither
-- main amount precisions or cost precisions are set.
postingApplyCommodityStylesExceptPrecision :: M.Map CommoditySymbol AmountStyle -> Posting -> Posting
postingApplyCommodityStylesExceptPrecision styles p = p{pamount=mixedAmountSetStylesExceptPrecision styles $ pamount p
,pbalanceassertion=balanceassertionsetstyles <$> pbalanceassertion p}
where
balanceassertionsetstyles ba = ba{baamount=amountSetStylesExceptPrecision styles $ baamount ba}
-- | Add tags to a posting, discarding any for which the posting already has a value. -- | Add tags to a posting, discarding any for which the posting already has a value.
postingAddTags :: Posting -> [Tag] -> Posting postingAddTags :: Posting -> [Tag] -> Posting
postingAddTags p@Posting{ptags} tags = p{ptags=ptags `union` tags} postingAddTags p@Posting{ptags} tags = p{ptags=ptags `union` tags}

View File

@ -25,7 +25,7 @@ import Hledger.Data.Dates
import Hledger.Data.Transaction (txnTieKnot) import Hledger.Data.Transaction (txnTieKnot)
import Hledger.Query (Query, filterQuery, matchesAmount, matchesPostingExtra, import Hledger.Query (Query, filterQuery, matchesAmount, matchesPostingExtra,
parseQuery, queryIsAmt, queryIsSym, simplifyQuery) parseQuery, queryIsAmt, queryIsSym, simplifyQuery)
import Hledger.Data.Posting (commentJoin, commentAddTag, postingAddTags, postingApplyCommodityStyles) import Hledger.Data.Posting (commentJoin, commentAddTag, postingAddTags)
import Hledger.Utils (dbg6, wrap) import Hledger.Utils (dbg6, wrap)
-- $setup -- $setup
@ -109,7 +109,7 @@ transactionModifierToFunction atypes atags styles refdate verbosetags Transactio
-- The provided TransactionModifier's query text is saved as the tags' value. -- The provided TransactionModifier's query text is saved as the tags' value.
tmPostingRuleToFunction :: Bool -> M.Map CommoditySymbol AmountStyle -> Query -> T.Text -> TMPostingRule -> (Posting -> Posting) tmPostingRuleToFunction :: Bool -> M.Map CommoditySymbol AmountStyle -> Query -> T.Text -> TMPostingRule -> (Posting -> Posting)
tmPostingRuleToFunction verbosetags styles query querytxt tmpr = tmPostingRuleToFunction verbosetags styles query querytxt tmpr =
\p -> postingApplyCommodityStyles styles . renderPostingCommentDates $ pr \p -> styleAmounts styles . renderPostingCommentDates $ pr
{ pdate = pdate pr <|> pdate p { pdate = pdate pr <|> pdate p
, pdate2 = pdate2 pr <|> pdate2 p , pdate2 = pdate2 pr <|> pdate2 p
, pamount = amount' p , pamount = amount' p

View File

@ -272,6 +272,7 @@ instance Show AmountStyle where
, show asdigitgroups , show asdigitgroups
, show asdecimalmark , show asdecimalmark
, show asprecision , show asprecision
, show asrounding
] ]
-- | The "display precision" for a hledger amount, by which we mean -- | The "display precision" for a hledger amount, by which we mean
@ -281,15 +282,15 @@ data AmountPrecision =
| NaturalPrecision -- ^ show all significant decimal digits stored internally | NaturalPrecision -- ^ show all significant decimal digits stored internally
deriving (Eq,Ord,Read,Show,Generic) deriving (Eq,Ord,Read,Show,Generic)
-- | "Rounding strategy" - when applying the display precision from AmountStyle to another -- | "Rounding strategy" - how to apply an AmountStyle's display precision
-- (as when applying commodity styles to amounts), how much padding or rounding -- to a posting amount (and its cost, if any).
-- of decimal digits should be done ? -- Mainly used to customise print's output, with --round=none|soft|hard|all.
data Rounding = data Rounding =
NoRounding -- ^ keep the amount precisions unchanged NoRounding -- ^ keep display precisions unchanged in amt and cost
| SoftRounding -- ^ add or remove trailing zeros to approach the desired precision | SoftRounding -- ^ do soft rounding of amt and cost amounts (show more or fewer decimal zeros to approximate the target precision, but don't hide significant digits)
-- | HardRounding -- ^ also remove non-zero digits, in posting amounts (lossy) | HardRounding -- ^ do hard rounding of amt (use the exact target precision, possibly hiding significant digits), and soft rounding of cost
-- | HardRoundingAndCost -- ^ also remove non-zero digits, in posting and cost amounts (lossy) | AllRounding -- ^ do hard rounding of amt and cost
deriving (Eq,Ord,Read,Generic) deriving (Eq,Ord,Read,Show,Generic)
-- | A style for displaying digit groups in the integer part of a -- | A style for displaying digit groups in the integer part of a
-- floating point number. It consists of the character used to -- floating point number. It consists of the character used to

View File

@ -129,7 +129,7 @@ mixedAmountApplyValuation priceoracle styles periodlast today postingdate v =
-- | Convert an Amount to its cost if requested, and style it appropriately. -- | Convert an Amount to its cost if requested, and style it appropriately.
amountToCost :: M.Map CommoditySymbol AmountStyle -> ConversionOp -> Amount -> Amount amountToCost :: M.Map CommoditySymbol AmountStyle -> ConversionOp -> Amount -> Amount
amountToCost styles ToCost = amountSetStyles styles . amountCost amountToCost styles ToCost = styleAmounts styles . amountCost
amountToCost _ NoConversionOp = id amountToCost _ NoConversionOp = id
-- | Apply a specified valuation to this amount, using the provided -- | Apply a specified valuation to this amount, using the provided
@ -192,7 +192,7 @@ amountValueAtDate priceoracle styles mto d a =
-- setNaturalPrecisionUpTo 8 $ -- XXX force higher precision in case amount appears to be zero ? -- setNaturalPrecisionUpTo 8 $ -- XXX force higher precision in case amount appears to be zero ?
-- Make default display style use precision 2 instead of 0 ? -- Make default display style use precision 2 instead of 0 ?
-- Leave as is for now; mentioned in manual. -- Leave as is for now; mentioned in manual.
amountSetStyles styles styleAmounts styles
nullamt{acommodity=comm, aquantity=rate * aquantity a} nullamt{acommodity=comm, aquantity=rate * aquantity a}
-- | Calculate the gain of each component amount, that is the difference -- | Calculate the gain of each component amount, that is the difference

View File

@ -374,7 +374,7 @@ journalAddForecast verbosetags (Just forecastspan) j = j{jtxns = jtxns j ++ fore
where where
{-# HLINT ignore "Move concatMap out" #-} {-# HLINT ignore "Move concatMap out" #-}
forecasttxns = forecasttxns =
map (txnTieKnot . transactionTransformPostings (postingApplyCommodityStyles $ journalCommodityStyles j)) map (txnTieKnot . transactionTransformPostings (styleAmounts $ journalCommodityStyles j))
. filter (spanContainsDate forecastspan . tdate) . filter (spanContainsDate forecastspan . tdate)
. concatMap (\pt -> runPeriodicTransaction verbosetags pt forecastspan) . concatMap (\pt -> runPeriodicTransaction verbosetags pt forecastspan)
$ jperiodictxns j $ jperiodictxns j

View File

@ -626,7 +626,7 @@ mixedAmountApplyValuationAfterSumFromOptsWith ropts j priceoracle =
gain mc spn = mixedAmountGainAtDate priceoracle styles mc (maybe err (addDays (-1)) $ spanEnd spn) gain mc spn = mixedAmountGainAtDate priceoracle styles mc (maybe err (addDays (-1)) $ spanEnd spn)
costing = case fromMaybe NoConversionOp $ conversionop_ ropts of costing = case fromMaybe NoConversionOp $ conversionop_ ropts of
NoConversionOp -> id NoConversionOp -> id
ToCost -> mixedAmountSetStyles styles . mixedAmountCost ToCost -> styleAmounts styles . mixedAmountCost
styles = journalCommodityStyles j styles = journalCommodityStyles j
err = error "mixedAmountApplyValuationAfterSumFromOptsWith: expected all spans to have an end date" err = error "mixedAmountApplyValuationAfterSumFromOptsWith: expected all spans to have an end date"

View File

@ -376,7 +376,7 @@ balance opts@CliOpts{reportspec_=rspec} j = case balancecalc_ of
_ -> error' $ unsupportedOutputFormatError fmt -- PARTIAL: _ -> error' $ unsupportedOutputFormatError fmt -- PARTIAL:
writeOutputLazyText opts $ render ropts report writeOutputLazyText opts $ render ropts report
where where
styles = journalCommodityStyles j styles = journalCommodityStylesWith HardRounding j
ropts@ReportOpts{..} = _rsReportOpts rspec ropts@ReportOpts{..} = _rsReportOpts rspec
-- Tidy csv should be consistent between single period and multiperiod reports. -- Tidy csv should be consistent between single period and multiperiod reports.
multiperiod = interval_ /= NoInterval || (layout_ == LayoutTidy && fmt == "csv") multiperiod = interval_ /= NoInterval || (layout_ == LayoutTidy && fmt == "csv")

View File

@ -78,7 +78,7 @@ invertPrice a =
-- But keep the number of decimal places unchanged. -- But keep the number of decimal places unchanged.
stylePriceDirectiveExceptPrecision :: M.Map CommoditySymbol AmountStyle -> PriceDirective -> PriceDirective stylePriceDirectiveExceptPrecision :: M.Map CommoditySymbol AmountStyle -> PriceDirective -> PriceDirective
stylePriceDirectiveExceptPrecision styles pd@PriceDirective{pdamount=a} = stylePriceDirectiveExceptPrecision styles pd@PriceDirective{pdamount=a} =
pd{pdamount = amountSetStylesExceptPrecision styles a} pd{pdamount = styleAmounts styles a}
allPostings :: Journal -> [Posting] allPostings :: Journal -> [Posting]
allPostings = concatMap tpostings . jtxns allPostings = concatMap tpostings . jtxns

View File

@ -84,7 +84,7 @@ register opts@CliOpts{rawopts_=rawopts, reportspec_=rspec} j
-- normal register report, list postings -- normal register report, list postings
| otherwise = writeOutputLazyText opts $ render $ styleAmounts styles rpt | otherwise = writeOutputLazyText opts $ render $ styleAmounts styles rpt
where where
styles = journalCommodityStyles j styles = journalCommodityStylesWith HardRounding j
rpt = postingsReport rspec j rpt = postingsReport rspec j
render | fmt=="txt" = postingsReportAsText opts render | fmt=="txt" = postingsReportAsText opts
| fmt=="csv" = printCSV . postingsReportAsCsv | fmt=="csv" = printCSV . postingsReportAsCsv

View File

@ -110,8 +110,9 @@ compoundBalanceCommandMode CompoundBalanceCommandSpec{..} =
-- | Generate a runnable command from a compound balance command specification. -- | Generate a runnable command from a compound balance command specification.
compoundBalanceCommand :: CompoundBalanceCommandSpec -> (CliOpts -> Journal -> IO ()) compoundBalanceCommand :: CompoundBalanceCommandSpec -> (CliOpts -> Journal -> IO ())
compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportspec_=rspec, rawopts_=rawopts} j = do compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportspec_=rspec, rawopts_=rawopts} j = do
writeOutputLazyText opts $ render $ styleAmounts (journalCommodityStyles j) cbr writeOutputLazyText opts $ render $ styleAmounts styles cbr
where where
styles = journalCommodityStylesWith HardRounding j
ropts@ReportOpts{..} = _rsReportOpts rspec ropts@ReportOpts{..} = _rsReportOpts rspec
-- use the default balance type for this report, unless the user overrides -- use the default balance type for this report, unless the user overrides
mbalanceAccumulationOverride = balanceAccumulationOverride rawopts mbalanceAccumulationOverride = balanceAccumulationOverride rawopts