lib: refactor

This commit is contained in:
Simon Michael 2018-04-19 16:49:05 -07:00
parent 0c835acd18
commit 91e3ddd4fb

View File

@ -485,21 +485,21 @@ filterJournalTransactionsByAccount apats j@Journal{jtxns=ts} = j{jtxns=filter tm
-} -}
-- | Do post-parse processing on a parsed journal to make it ready for -- | Do post-parse processing on a parsed journal to make it ready for
-- use. Reverse parsed data to normal order, canonicalise amount -- use. Reverse parsed data to normal order, standardise amount
-- formats, check/ensure that transactions are balanced, and maybe -- formats, check/ensure that transactions are balanced, and maybe
-- check balance assertions. -- check balance assertions.
journalFinalise :: ClockTime -> FilePath -> Text -> Bool -> ParsedJournal -> Either String Journal journalFinalise :: ClockTime -> FilePath -> Text -> Bool -> ParsedJournal -> Either String Journal
journalFinalise t path txt assrt j@Journal{jfiles=fs} = do journalFinalise t path txt assrt j@Journal{jfiles=fs} =
(journalTieTransactions <$> journalTieTransactions <$>
(journalBalanceTransactions assrt $ (journalBalanceTransactions assrt $
journalApplyCommodityStyles $ journalApplyCommodityStyles $
j{ jfiles = (path,txt) : reverse fs j {jfiles = (path,txt) : reverse fs
, jlastreadtime = t ,jlastreadtime = t
, jtxns = reverse $ jtxns j -- NOTE: see addTransaction ,jtxns = reverse $ jtxns j -- NOTE: see addTransaction
, jmodifiertxns = reverse $ jmodifiertxns j -- NOTE: see addModifierTransaction ,jmodifiertxns = reverse $ jmodifiertxns j -- NOTE: see addModifierTransaction
, jperiodictxns = reverse $ jperiodictxns j -- NOTE: see addPeriodicTransaction ,jperiodictxns = reverse $ jperiodictxns j -- NOTE: see addPeriodicTransaction
, jmarketprices = reverse $ jmarketprices j -- NOTE: see addMarketPrice ,jmarketprices = reverse $ jmarketprices j -- NOTE: see addMarketPrice
})) })
journalNumberAndTieTransactions = journalTieTransactions . journalNumberTransactions journalNumberAndTieTransactions = journalTieTransactions . journalNumberTransactions
@ -521,9 +521,12 @@ journalUntieTransactions t@Transaction{tpostings=ps} = t{tpostings=map (\p -> p{
-- message if any of them fail. -- message if any of them fail.
journalCheckBalanceAssertions :: Journal -> Either String Journal journalCheckBalanceAssertions :: Journal -> Either String Journal
journalCheckBalanceAssertions j = journalCheckBalanceAssertions j =
runST $ journalBalanceTransactionsST True j runST $ journalBalanceTransactionsST
(return ()) (\_ _ -> return ()) (const $ return j) -- noops True
j
(return ())
(\_ _ -> return ())
(const $ return j)
-- | Check a posting's balance assertion and return an error if it -- | Check a posting's balance assertion and return an error if it
-- fails. -- fails.
@ -561,59 +564,58 @@ checkBalanceAssertion p@Posting{ pbalanceassertion = Just (ass,_)} amt
(diffplus ++ showAmount diff) (diffplus ++ showAmount diff)
checkBalanceAssertion _ _ = Right () checkBalanceAssertion _ _ = Right ()
-- | Environment for 'CurrentBalancesModifier'
data Env s = Env { eBalances :: HT.HashTable s AccountName MixedAmount
, eStoreTx :: Transaction -> ST s ()
, eAssrt :: Bool
, eStyles :: Maybe (M.Map CommoditySymbol AmountStyle) }
-- | Monad transformer stack with a reference to a mutable hashtable
-- of current account balances and a mutable array of finished
-- transactions in original parsing order.
type CurrentBalancesModifier s = R.ReaderT (Env s) (ExceptT String (ST s))
-- | Fill in any missing amounts and check that all journal transactions -- | Fill in any missing amounts and check that all journal transactions
-- balance, or return an error message. This is done after parsing all -- balance, or return an error message. This is done after parsing all
-- amounts and applying canonical commodity styles, since balancing -- amounts and applying canonical commodity styles, since balancing
-- depends on display precision. Reports only the first error encountered. -- depends on display precision. Reports only the first error encountered.
journalBalanceTransactions :: Bool -> Journal -> Either String Journal journalBalanceTransactions :: Bool -> Journal -> Either String Journal
journalBalanceTransactions assrt j = journalBalanceTransactions assrt j =
runST $ runST $ journalBalanceTransactionsST
journalBalanceTransactionsST assrt -- check balance assertions also ?
assrt -- check balance assertions also ? (journalNumberTransactions j) -- journal to process
(journalNumberTransactions j) -- journal to process (newArray_ (1, genericLength $ jtxns j) :: forall s. ST s (STArray s Integer Transaction)) -- initialise state
(newArray_ (1, genericLength $ jtxns j) :: forall s. ST s (STArray s Integer Transaction)) -- initialise state (\arr tx -> writeArray arr (tindex tx) tx) -- update state
(\arr tx -> writeArray arr (tindex tx) tx) -- update state (fmap (\txns -> j{ jtxns = txns}) . getElems) -- summarise state
(fmap (\txns -> j{ jtxns = txns}) . getElems) -- summarise state
-- | Helper used by 'journalBalanceTransactions' and 'journalCheckBalanceAssertions'.
-- | Generalization used in the definition of
-- 'journalBalanceTransactionsST and 'journalCheckBalanceAssertions'
journalBalanceTransactionsST :: journalBalanceTransactionsST ::
Bool Bool
-> Journal -> Journal
-> ST s txns -> ST s txns -- ^ initialise state
-- ^ creates transaction store -> (txns -> Transaction -> ST s ()) -- ^ update state
-> (txns -> Transaction -> ST s ()) -> (txns -> ST s a) -- ^ summarise state
-- ^ "store" operation
-> (txns -> ST s a)
-- ^ calculate result from transactions
-> ST s (Either String a) -> ST s (Either String a)
journalBalanceTransactionsST assrt j createStore storeIn extract = journalBalanceTransactionsST assrt j createStore storeIn extract =
runExceptT $ do runExceptT $ do
bals <- lift $ HT.newSized size bals <- lift $ HT.newSized size
txStore <- lift $ createStore txStore <- lift $ createStore
flip R.runReaderT (Env bals (storeIn txStore) assrt $ let env = Env bals
Just $ jinferredcommodities j) $ do (storeIn txStore)
assrt
(Just $ jinferredcommodities j)
flip R.runReaderT env $ do
dated <- fmap snd . sortBy (comparing fst) . concat dated <- fmap snd . sortBy (comparing fst) . concat
<$> mapM' discriminateByDate (jtxns j) <$> mapM' discriminateByDate (jtxns j)
mapM' checkInferAndRegisterAmounts dated mapM' checkInferAndRegisterAmounts dated
lift $ extract txStore lift $ extract txStore
where size = genericLength $ journalPostings j where
size = genericLength $ journalPostings j
-- | This converts a transaction into a list of objects whose dates -- | Monad transformer stack with a reference to a mutable hashtable
-- have to be considered when checking balance assertions and handled -- of current account balances and a mutable array of finished
-- by 'checkInferAndRegisterAmounts'. -- transactions in original parsing order.
type CurrentBalancesModifier s = R.ReaderT (Env s) (ExceptT String (ST s))
-- | Environment for 'CurrentBalancesModifier'
data Env s = Env { eBalances :: HT.HashTable s AccountName MixedAmount
, eStoreTx :: Transaction -> ST s ()
, eAssrt :: Bool
, eStyles :: Maybe (M.Map CommoditySymbol AmountStyle)
}
-- | This converts a transaction into a list of transactions or
-- postings whose dates have to be considered when checking
-- balance assertions and handled by 'checkInferAndRegisterAmounts'.
-- --
-- Transaction without balance assignments can be balanced and stored -- Transaction without balance assignments can be balanced and stored
-- immediately and their (possibly) dated postings are returned. -- immediately and their (possibly) dated postings are returned.
@ -626,25 +628,24 @@ discriminateByDate :: Transaction
discriminateByDate tx discriminateByDate tx
| null (assignmentPostings tx) = do | null (assignmentPostings tx) = do
styles <- R.reader $ eStyles styles <- R.reader $ eStyles
balanced <- lift $ ExceptT $ return balanced <- lift $ ExceptT $ return $ balanceTransaction styles tx
$ balanceTransaction styles tx
storeTransaction balanced storeTransaction balanced
return $ fmap (postingDate &&& (Left . removePrices)) return $
$ tpostings $ balanced fmap (postingDate &&& (Left . removePrices)) $ tpostings $ balanced
| True = do | True = do
when (any (isJust . pdate) $ tpostings tx) $ when (any (isJust . pdate) $ tpostings tx) $
throwError $ unlines $ throwError $ unlines $
["Not supported: Transactions with balance assignments " ["Not supported: Transactions with balance assignments "
,"AND dated postings without amount:\n" ,"AND dated postings without amount:\n"
, showTransaction tx] , showTransaction tx]
return [(tdate tx, Right return
$ tx { tpostings = removePrices <$> tpostings tx })] [(tdate tx, Right $ tx { tpostings = removePrices <$> tpostings tx })]
-- | This function takes different objects describing changes to -- | This function takes an object describing changes to
-- account balances on a single day. It can handle either a single -- account balances on a single day - either a single posting
-- posting (from an already balanced transaction without assigments) -- (from an already balanced transaction without assignments)
-- or a whole transaction with assignments (which is required to no -- or a whole transaction with assignments (which is required to
-- posting with pdate set.). -- have no posting with pdate set).
-- --
-- For a single posting, there is not much to do. Only add its amount -- For a single posting, there is not much to do. Only add its amount
-- to its account and check the assertion, if there is one. This -- to its account and check the assertion, if there is one. This
@ -654,9 +655,9 @@ discriminateByDate tx
-- 'addAmountAndCheckBalance', if there is an amount. If there is no -- 'addAmountAndCheckBalance', if there is an amount. If there is no
-- amount, the amount is inferred by the assertion or left empty if -- amount, the amount is inferred by the assertion or left empty if
-- there is no assertion. Then, the transaction is balanced, the -- there is no assertion. Then, the transaction is balanced, the
-- inferred amount added to the balance (all in -- inferred amount added to the balance (all in 'balanceTransactionUpdate')
-- 'balanceTransactionUpdate') and the resulting transaction with no -- and the resulting transaction with no missing amounts is stored
-- missing amounts is stored in the array, for later retrieval. -- in the array, for later retrieval.
-- --
-- Again in short: -- Again in short:
-- --
@ -682,45 +683,42 @@ checkInferAndRegisterAmounts (Right oldTx) = do
(fmap (\a -> p { pamount = a, porigin = Just $ originalPosting p }) . setBalance (paccount p) . fst) (fmap (\a -> p { pamount = a, porigin = Just $ originalPosting p }) . setBalance (paccount p) . fst)
$ pbalanceassertion p $ pbalanceassertion p
-- | Adds a posting's amonut to the posting's account balance and -- | Adds a posting's amount to the posting's account balance and
-- checks a possible balance assertion. If there is no amount, it runs -- checks a possible balance assertion. Or if there is no amount,
-- the supplied fallback action. -- runs the supplied fallback action.
addAmountAndCheckBalance :: (Posting -> CurrentBalancesModifier s Posting) addAmountAndCheckBalance ::
-- ^ action to execute, if posting has no amount (Posting -> CurrentBalancesModifier s Posting) -- ^ action if posting has no amount
-> Posting -> Posting
-> CurrentBalancesModifier s Posting -> CurrentBalancesModifier s Posting
addAmountAndCheckBalance _ p | hasAmount p = do addAmountAndCheckBalance _ p | hasAmount p = do
newAmt <- addToBalance (paccount p) $ pamount p newAmt <- addToBalance (paccount p) $ pamount p
assrt <- R.reader eAssrt assrt <- R.reader eAssrt
lift $ when assrt $ ExceptT $ return lift $ when assrt $ ExceptT $ return $ checkBalanceAssertion p newAmt
$ checkBalanceAssertion p newAmt
return p return p
addAmountAndCheckBalance fallback p = fallback p addAmountAndCheckBalance fallback p = fallback p
-- | Sets an account's balance to a given amount and returns the -- | Sets an account's balance to a given amount and returns the
-- difference of new and old amount -- difference of new and old amount.
setBalance :: AccountName -> Amount -> CurrentBalancesModifier s MixedAmount setBalance :: AccountName -> Amount -> CurrentBalancesModifier s MixedAmount
setBalance acc amt = liftModifier $ \Env{ eBalances = bals } -> do setBalance acc amt = liftModifier $ \Env{ eBalances = bals } -> do
old <- HT.lookup bals acc old <- HT.lookup bals acc
let new = Mixed $ (amt :) $ maybe [] let new = Mixed $ (amt :) $ maybe []
(filter ((/= acommodity amt) . acommodity) . amounts) old (filter ((/= acommodity amt) . acommodity) . amounts) old
HT.insert bals acc new HT.insert bals acc new
return $ maybe new (new -) old return $ maybe new (new -) old
-- | Adds an amount to an account's balance and returns the resulting -- | Adds an amount to an account's balance and returns the resulting balance.
-- balance
addToBalance :: AccountName -> MixedAmount -> CurrentBalancesModifier s MixedAmount addToBalance :: AccountName -> MixedAmount -> CurrentBalancesModifier s MixedAmount
addToBalance acc amt = liftModifier $ \Env{ eBalances = bals } -> do addToBalance acc amt = liftModifier $ \Env{ eBalances = bals } -> do
new <- maybe amt (+ amt) <$> HT.lookup bals acc new <- maybe amt (+ amt) <$> HT.lookup bals acc
HT.insert bals acc new HT.insert bals acc new
return new return new
-- | Stores a transaction in the transaction array in original parsing -- | Stores a transaction in the transaction array in original parsing order.
-- order.
storeTransaction :: Transaction -> CurrentBalancesModifier s () storeTransaction :: Transaction -> CurrentBalancesModifier s ()
storeTransaction tx = liftModifier $ ($tx) . eStoreTx storeTransaction tx = liftModifier $ ($tx) . eStoreTx
-- | Helper function -- | Helper function.
liftModifier :: (Env s -> ST s a) -> CurrentBalancesModifier s a liftModifier :: (Env s -> ST s a) -> CurrentBalancesModifier s a
liftModifier f = R.ask >>= lift . lift . f liftModifier f = R.ask >>= lift . lift . f