packages feed

hledger-lib 1.22 → 1.22.1

raw patch · 11 files changed

+138/−97 lines, 11 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Hledger.Data.Journal: filterJournalRelatedPostings :: Query -> Journal -> Journal
+ Hledger.Data.Journal: filterTransactionRelatedPostings :: Query -> Transaction -> Transaction
+ Hledger.Data.Journal: journalLastDay :: Bool -> Journal -> Maybe Day
+ Hledger.Data.Transaction: transactionCheckBalanced :: BalancingOpts -> Transaction -> [String]
- Hledger.Reports.AccountTransactionsReport: accountTransactionsReportItems :: Query -> Query -> MixedAmount -> (MixedAmount -> MixedAmount) -> [Transaction] -> [AccountTransactionsReportItem]
+ Hledger.Reports.AccountTransactionsReport: accountTransactionsReportItems :: Query -> Query -> MixedAmount -> (MixedAmount -> MixedAmount) -> [(Day, Transaction)] -> [AccountTransactionsReportItem]

Files

CHANGES.md view
@@ -9,6 +9,15 @@ Internal/api/developer-ish changes in the hledger-lib (and hledger) packages. For user-visible changes, see the hledger package changelog. +# 1.22.1 2021-08-02++- Allow megaparsec 9.1++- journalEndDate's behaviour has been clarified, journalLastDay has+  been added.++- transactionCheckBalanced is now exported. (#1596)+ # 1.22 2021-07-03  - GHC 9.0 is now officially supported, and GHC 8.0, 8.2, 8.4 are not;
Hledger/Data/Journal.hs view
@@ -31,9 +31,11 @@   -- * Filtering   filterJournalTransactions,   filterJournalPostings,+  filterJournalRelatedPostings,   filterJournalAmounts,   filterTransactionAmounts,   filterTransactionPostings,+  filterTransactionRelatedPostings,   filterPostingAmount,   -- * Mapping   journalMapTransactions,@@ -60,6 +62,7 @@   journalDateSpanBothDates,   journalStartDate,   journalEndDate,+  journalLastDay,   journalDescriptions,   journalFilePath,   journalFilePaths,@@ -104,7 +107,7 @@ import Data.Function ((&)) import qualified Data.HashTable.Class as H (toList) import qualified Data.HashTable.ST.Cuckoo as H-import Data.List (find, foldl', sortOn)+import Data.List ((\\), find, foldl', sortOn) import Data.List.Extra (nubSort) import qualified Data.Map.Strict as M import Data.Maybe (catMaybes, fromJust, fromMaybe, isJust, mapMaybe, maybeToList)@@ -516,6 +519,11 @@ filterJournalPostings :: Query -> Journal -> Journal filterJournalPostings q j@Journal{jtxns=ts} = j{jtxns=map (filterTransactionPostings q) ts} +-- | Keep only postings which do not match the query expression, but for which a related posting does.+-- This can leave unbalanced transactions.+filterJournalRelatedPostings :: Query -> Journal -> Journal+filterJournalRelatedPostings q j@Journal{jtxns=ts} = j{jtxns=map (filterTransactionRelatedPostings q) ts}+ -- | Within each posting's amount, keep only the parts matching the query. -- This can leave unbalanced transactions. filterJournalAmounts :: Query -> Journal -> Journal@@ -533,6 +541,11 @@ filterTransactionPostings :: Query -> Transaction -> Transaction filterTransactionPostings q t@Transaction{tpostings=ps} = t{tpostings=filter (q `matchesPosting`) ps} +filterTransactionRelatedPostings :: Query -> Transaction -> Transaction+filterTransactionRelatedPostings q t@Transaction{tpostings=ps} =+    t{tpostings=if null matches then [] else ps \\ matches}+  where matches = filter (matchesPosting q) ps+ -- | Apply a transformation to a journal's transactions. journalMapTransactions :: (Transaction -> Transaction) -> Journal -> Journal journalMapTransactions f j@Journal{jtxns=ts} = j{jtxns=map f ts}@@ -1316,10 +1329,15 @@ journalStartDate :: Bool -> Journal -> Maybe Day journalStartDate secondary j = b where DateSpan b _ = journalDateSpan secondary j --- | The latest of this journal's transaction and posting dates, or--- Nothing if there are none.+-- | The "exclusive end date" of this journal: the day following its latest transaction +-- or posting date, or Nothing if there are none. journalEndDate :: Bool -> Journal -> Maybe Day journalEndDate secondary j = e where DateSpan _ e = journalDateSpan secondary j++-- | The latest of this journal's transaction and posting dates, or+-- Nothing if there are none.+journalLastDay :: Bool -> Journal -> Maybe Day+journalLastDay secondary j = addDays (-1) <$> journalEndDate secondary j  -- | Apply the pivot transformation to all postings in a journal, -- replacing their account name by their value for the given field or tag.
Hledger/Data/Transaction.hs view
@@ -20,6 +20,7 @@   transaction,   txnTieKnot,   txnUntieKnot,+  transactionCheckBalanced,   -- * operations   showAccountName,   hasRealPostings,
Hledger/Read.hs view
@@ -191,7 +191,7 @@ requireJournalFileExists f = do   exists <- doesFileExist f   when (not exists) $ do  -- XXX might not be a journal file-    hPutStr stderr $ "The hledger journal file \"" <> show f <> "\" was not found.\n"+    hPutStr stderr $ "The hledger journal file \"" <> f <> "\" was not found.\n"     hPutStr stderr "Please create it first, eg with \"hledger add\" or a text editor.\n"     hPutStr stderr "Or, specify an existing journal file with -f or LEDGER_FILE.\n"     exitFailure
Hledger/Reports/AccountTransactionsReport.hs view
@@ -16,12 +16,12 @@ ) where -import Data.List (mapAccumL, nub, partition, sortBy)-import Data.Ord (comparing)+import Data.List (mapAccumR, nub, partition, sortBy) import Data.Maybe (catMaybes)+import Data.Ord (Down(..), comparing) import Data.Text (Text) import qualified Data.Text as T-import Data.Time.Calendar (Day)+import Data.Time.Calendar (Day, addDays)  import Hledger.Data import Hledger.Query@@ -79,92 +79,103 @@   )  accountTransactionsReport :: ReportSpec -> Journal -> Query -> Query -> AccountTransactionsReport-accountTransactionsReport rspec@ReportSpec{rsOpts=ropts} j reportq thisacctq = items+accountTransactionsReport rspec@ReportSpec{rsOpts=ropts} j reportq' thisacctq = items   where-    -- a depth limit should not affect the account transactions report-    -- seems unnecessary for some reason XXX-    reportq'   = reportq -- filterQuery (not . queryIsDepth)-    symq       = filterQuery queryIsSym reportq'-    realq      = filterQuery queryIsReal reportq'-    statusq    = filterQuery queryIsStatus reportq'+    -- A depth limit should not affect the account transactions report; it should show all transactions in/below this account.+    -- Queries on currency or amount are also ignored at this stage; they are handled earlier, before valuation.+    reportq = simplifyQuery $ And [aregisterq, periodq, excludeforecastq (forecast_ ropts)]+      where+        aregisterq = filterQuery (not . queryIsCurOrAmt) $ filterQuery (not . queryIsDepth) reportq'+        periodq = Date . periodAsDateSpan $ period_ ropts+        -- Except in forecast mode, exclude future/forecast transactions.+        excludeforecastq (Just _) = Any+        excludeforecastq Nothing  =  -- not:date:tomorrow- not:tag:generated-transaction+          And [ Not . Date $ DateSpan (Just . addDays 1 $ rsToday rspec) Nothing+              , Not generatedTransactionTag+              ]+    amtq = filterQuery queryIsCurOrAmt reportq'+    queryIsCurOrAmt q = queryIsSym q || queryIsAmt q -    -- sort by the transaction's register date, for accurate starting balance-    -- these are not yet filtered by tdate, we want to search them all for priorps-    transactions =-        ptraceAtWith 5 (("ts5:\n"++).pshowTransactions)-      . sortBy (comparing (transactionRegisterDate reportq' thisacctq))-      . jtxns-      . ptraceAtWith 5 (("ts4:\n"++).pshowTransactions.jtxns)-      -- keep just the transactions affecting this account (via possibly realness or status-filtered postings)-      . traceAt 3 ("thisacctq: "++show thisacctq)-      . ptraceAtWith 5 (("ts3:\n"++).pshowTransactions.jtxns)-      . filterJournalTransactions thisacctq-      . filterJournalPostings (And [realq, statusq])-      -- apply any cur:SYM filters in reportq'-      . ptraceAtWith 5 (("ts2:\n"++).pshowTransactions.jtxns)-      . (if queryIsNull symq then id else filterJournalAmounts symq)-      -- maybe convert these transactions to cost or value-      $ journalApplyValuationFromOpts rspec j+    -- Note that within this functions, we are only allowed limited+    -- transformation of the transaction postings: this is due to the need to+    -- pass the original transactions into accountTransactionsReportItem.+    -- Generally, we either include a transaction in full, or not at all.+    -- Do some limited filtering and valuing of the journal's transactions:+    -- - filter them by the account query if any,+    -- - discard amounts not matched by the currency and amount query if any,+    -- - then apply valuation if any.+    acctJournal =+          ptraceAtWith 5 (("ts3:\n"++).pshowTransactions.jtxns)+        -- maybe convert these transactions to cost or value+        . journalApplyValuationFromOpts rspec+        . ptraceAtWith 5 (("ts2:\n"++).pshowTransactions.jtxns)+        -- apply any cur:SYM filters in reportq+        . (if queryIsNull amtq then id else filterJournalAmounts amtq)+        -- only consider transactions which match thisacctq (possibly excluding postings+        -- which are not real or have the wrong status)+        . traceAt 3 ("thisacctq: "++show thisacctq)+        $ ptraceAtWith 5 (("ts1:\n"++).pshowTransactions.jtxns)+          j{jtxns = filter (matchesTransaction thisacctq . relevantPostings) $ jtxns j}+      where+        relevantPostings+          | queryIsNull realq && queryIsNull statusq = id+          | otherwise = filterTransactionPostings . simplifyQuery $ And [realq, statusq]+        realq   = filterQuery queryIsReal reportq+        statusq = filterQuery queryIsStatus reportq      startbal       | balancetype_ ropts == HistoricalBalance = sumPostings priorps       | otherwise                               = nullmixedamt       where-        priorps = dbg5 "priorps" $-                  filter (matchesPosting-                          (dbg5 "priorq" $-                           And [thisacctq, tostartdateq, datelessreportq]))-                         $ transactionsPostings transactions+        priorps = dbg5 "priorps" . journalPostings $ filterJournalPostings priorq acctJournal+        priorq = dbg5 "priorq" $ And [thisacctq, tostartdateq, datelessreportq]         tostartdateq =           case mstartdate of             Just _  -> Date (DateSpan Nothing mstartdate)             Nothing -> None  -- no start date specified, there are no prior postings-        mstartdate = queryStartDate (date2_ ropts) reportq'-        datelessreportq = filterQuery (not . queryIsDateOrDate2) reportq'--    -- accountTransactionsReportItem will keep transactions of any date which have any posting inside the report period.-    -- Should we also require that transaction date is inside the report period ?-    -- Should we be filtering by reportq here to apply other query terms (?)-    -- Make it an option for now.-    filtertxns = txn_dates_ ropts+        mstartdate = queryStartDate (date2_ ropts) reportq+        datelessreportq = filterQuery (not . queryIsDateOrDate2) reportq -    items = reverse $-            accountTransactionsReportItems reportq' thisacctq startbal maNegate $-            (if filtertxns then filter (reportq' `matchesTransaction`) else id) $-            transactions+    items =+        accountTransactionsReportItems reportq thisacctq startbal maNegate+      -- sort by the transaction's register date, for accurate starting balance+      . ptraceAtWith 5 (("ts4:\n"++).pshowTransactions.map snd)+      . sortBy (comparing $ Down . fst)+      . map (\t -> (transactionRegisterDate reportq thisacctq t, t))+      $ jtxns acctJournal  pshowTransactions :: [Transaction] -> String pshowTransactions = pshow . map (\t -> unwords [show $ tdate t, T.unpack $ tdescription t])  -- | Generate transactions report items from a list of transactions, -- using the provided user-specified report query, a query specifying--- which account to use as the focus, a starting balance, a sign-setting--- function and a balance-summing function. Or with a None current account--- query, this can also be used for the transactionsReport.-accountTransactionsReportItems :: Query -> Query -> MixedAmount -> (MixedAmount -> MixedAmount) -> [Transaction] -> [AccountTransactionsReportItem]+-- which account to use as the focus, a starting balance, and a sign-setting+-- function.+-- Each transaction is accompanied by the date that should be shown for it+-- in the report, which is not necessarily the transaction date; it is+-- the earliest of the posting dates which match both thisacctq and reportq,+-- otherwise the transaction's date if there are no matching postings.+accountTransactionsReportItems :: Query -> Query -> MixedAmount -> (MixedAmount -> MixedAmount)+                               -> [(Day, Transaction)] -> [AccountTransactionsReportItem] accountTransactionsReportItems reportq thisacctq bal signfn =-    catMaybes . snd .-    mapAccumL (accountTransactionsReportItem reportq thisacctq signfn) bal+    catMaybes . snd . mapAccumR (accountTransactionsReportItem reportq thisacctq signfn) bal -accountTransactionsReportItem :: Query -> Query -> (MixedAmount -> MixedAmount) -> MixedAmount -> Transaction -> (MixedAmount, Maybe AccountTransactionsReportItem)-accountTransactionsReportItem reportq thisacctq signfn bal torig = balItem-    -- 201403: This is used for both accountTransactionsReport and transactionsReport, which makes it a bit overcomplicated+accountTransactionsReportItem :: Query -> Query -> (MixedAmount -> MixedAmount) -> MixedAmount+                              -> (Day, Transaction) -> (MixedAmount, Maybe AccountTransactionsReportItem)+accountTransactionsReportItem reportq thisacctq signfn bal (d, torig)     -- 201407: I've lost my grip on this, let's just hope for the best     -- 201606: we now calculate change and balance from filtered postings, check this still works well for all callers XXX+    | null reportps = (bal, Nothing)  -- no matched postings in this transaction, skip it+    | otherwise     = (b, Just (torig, tacct{tdate=d}, numotheraccts > 1, otheracctstr, a, b))     where-      tfiltered@Transaction{tpostings=reportps} = filterTransactionPostings reportq torig-      tacct = tfiltered{tdate=transactionRegisterDate reportq thisacctq tfiltered}-      balItem = case reportps of-           [] -> (bal, Nothing)  -- no matched postings in this transaction, skip it-           _  -> (b, Just (torig, tacct, numotheraccts > 1, otheracctstr, a, b))-                 where-                  (thisacctps, otheracctps) = partition (matchesPosting thisacctq) reportps-                  numotheraccts = length $ nub $ map paccount otheracctps-                  otheracctstr | thisacctq == None  = summarisePostingAccounts reportps     -- no current account ? summarise all matched postings-                               | numotheraccts == 0 = summarisePostingAccounts thisacctps   -- only postings to current account ? summarise those-                               | otherwise          = summarisePostingAccounts otheracctps  -- summarise matched postings to other account(s)-                  a = signfn . maNegate $ sumPostings thisacctps-                  b = bal `maPlus` a+      tacct@Transaction{tpostings=reportps} = filterTransactionPostings reportq torig+      (thisacctps, otheracctps) = partition (matchesPosting thisacctq) reportps+      numotheraccts = length $ nub $ map paccount otheracctps+      otheracctstr | thisacctq == None  = summarisePostingAccounts reportps     -- no current account ? summarise all matched postings+                   | numotheraccts == 0 = summarisePostingAccounts thisacctps   -- only postings to current account ? summarise those+                   | otherwise          = summarisePostingAccounts otheracctps  -- summarise matched postings to other account(s)+      a = signfn . maNegate $ sumPostings thisacctps+      b = bal `maPlus` a  -- | What is the transaction's date in the context of a particular account -- (specified with a query) and report query, as in an account register ?
Hledger/Reports/EntriesReport.hs view
@@ -35,8 +35,9 @@ -- | Select transactions for an entries report. entriesReport :: ReportSpec -> Journal -> EntriesReport entriesReport rspec@ReportSpec{rsOpts=ropts} =-    sortBy (comparing $ transactionDateFn ropts) . jtxns . filterJournalTransactions (rsQuery rspec)+    sortBy (comparing $ transactionDateFn ropts) . jtxns     . journalApplyValuationFromOpts rspec{rsOpts=ropts{show_costs_=True}}+    . filterJournalTransactions (rsQuery rspec)  tests_EntriesReport = tests "EntriesReport" [   tests "entriesReport" [
Hledger/Reports/MultiBalanceReport.hs view
@@ -244,9 +244,9 @@ getPostings rspec@ReportSpec{rsQuery=query,rsOpts=ropts} j priceoracle =     map (\p -> (p, date p)) .     journalPostings .-    filterJournalAmounts symq .      -- remove amount parts excluded by cur:-    filterJournalPostings reportq $  -- remove postings not matched by (adjusted) query-    valuedJournal+    valueJournal .+    filterJournalAmounts symq $      -- remove amount parts excluded by cur:+    filterJournalPostings reportq j  -- remove postings not matched by (adjusted) query   where     symq = dbg3 "symq" . filterQuery queryIsSym $ dbg3 "requested q" query     -- The user's query with no depth limit, and expanded to the report span@@ -254,8 +254,8 @@     -- handles the hledger-ui+future txns case above).     reportq = dbg3 "reportq" $ depthless query     depthless = dbg3 "depthless" . filterQuery (not . queryIsDepth)-    valuedJournal | isJust (valuationAfterSum ropts) = j-                  | otherwise = journalApplyValuationFromOptsWith rspec j priceoracle+    valueJournal j' | isJust (valuationAfterSum ropts) = j'+                    | otherwise = journalApplyValuationFromOptsWith rspec j' priceoracle      date = case whichDateFromOpts ropts of         PrimaryDate   -> postingDate
Hledger/Reports/PostingsReport.hs view
@@ -119,21 +119,22 @@   where     beforestartq = dbg3 "beforestartq" $ dateqtype $ DateSpan Nothing $ spanStart reportspan     beforeandduringps =-      dbg5 "ps5" $ sortOn sortdate $                                             -- sort postings by date or date2-      dbg5 "ps4" $ (if invert_ ropts then map negatePostingAmount else id) $     -- with --invert, invert amounts-      dbg5 "ps3" $ map (filterPostingAmount symq) $                              -- remove amount parts which the query's cur: terms would exclude-      dbg5 "ps2" $ (if related_ ropts then concatMap relatedPostings else id) $  -- with -r, replace each with its sibling postings-      dbg5 "ps1" $ filter (beforeandduringq `matchesPosting`) $                  -- filter postings by the query, with no start date or depth limit-                  journalPostings $-                  journalApplyValuationFromOpts rspec j                          -- convert to cost and apply valuation+      dbg5 "ps5" $ sortOn sortdate $                                          -- sort postings by date or date2+      dbg5 "ps4" $ (if invert_ ropts then map negatePostingAmount else id) $  -- with --invert, invert amounts+                   journalPostings $+                   journalApplyValuationFromOpts rspec $                      -- convert to cost and apply valuation+      dbg5 "ps2" $ filterJournalAmounts symq $                                -- remove amount parts which the query's cur: terms would exclude+      dbg5 "ps1" $ filterJournal beforeandduringq j                           -- filter postings by the query, with no start date or depth limit++    beforeandduringq = dbg4 "beforeandduringq" $ And [depthless $ dateless q, beforeendq]       where-        beforeandduringq = dbg4 "beforeandduringq" $ And [depthless $ dateless q, beforeendq]-          where-            depthless  = filterQuery (not . queryIsDepth)-            dateless   = filterQuery (not . queryIsDateOrDate2)-            beforeendq = dateqtype $ DateSpan Nothing $ spanEnd reportspan-        sortdate = if date2_ ropts then postingDate2 else postingDate-        symq = dbg4 "symq" $ filterQuery queryIsSym q+        depthless  = filterQuery (not . queryIsDepth)+        dateless   = filterQuery (not . queryIsDateOrDate2)+        beforeendq = dateqtype $ DateSpan Nothing $ spanEnd reportspan++    sortdate = if date2_ ropts then postingDate2 else postingDate+    filterJournal = if related_ ropts then filterJournalRelatedPostings else filterJournalPostings  -- with -r, replace each posting with its sibling postings+    symq = dbg4 "symq" $ filterQuery queryIsSym q     dateqtype       | queryIsDate2 dateq || (queryIsDate dateq && date2_ ropts) = Date2       | otherwise = Date
Hledger/Reports/ReportOptions.hs view
@@ -636,8 +636,8 @@ reportPeriodOrJournalLastDay rspec j = reportPeriodLastDay rspec <|> journalOrPriceEnd   where     journalOrPriceEnd = case value_ $ rsOpts rspec of-        Just (AtEnd _) -> max (journalEndDate False j) lastPriceDirective-        _              -> journalEndDate False j+        Just (AtEnd _) -> max (journalLastDay False j) lastPriceDirective+        _              -> journalLastDay False j     lastPriceDirective = fmap (addDays 1) . maximumMay . map pddate $ jpricedirectives j  -- | Make a name for the given period in a multiperiod report, given
Hledger/Reports/TransactionsReport.hs view
@@ -66,7 +66,7 @@    where      -- XXX items' first element should be the full transaction with all postings      items = reverse $ accountTransactionsReportItems q None nullmixedamt id ts-     ts    = sortBy (comparing date) $ filter (q `matchesTransaction`) $ jtxns $ journalApplyValuationFromOpts rspec j+     ts    = sortBy (comparing fst) $ map (\t -> (date t, t)) $ filter (q `matchesTransaction`) $ jtxns $ journalApplyValuationFromOpts rspec j      date = transactionDateFn $ rsOpts rspec  -- | Split a transactions report whose items may involve several commodities,
hledger-lib.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           hledger-lib-version:        1.22+version:        1.22.1 synopsis:       A reusable library providing the core functionality of hledger description:    A reusable library containing hledger's core functionality.                 This is used by most hledger* packages so that they support the same@@ -117,7 +117,7 @@     , file-embed >=0.0.10     , filepath     , hashtables >=1.2.3.1-    , megaparsec >=7.0.0 && <9.1+    , megaparsec >=7.0.0 && <9.2     , mtl >=2.2.1     , old-time     , parser-combinators >=0.4.0@@ -167,7 +167,7 @@     , file-embed >=0.0.10     , filepath     , hashtables >=1.2.3.1-    , megaparsec >=7.0.0 && <9.1+    , megaparsec >=7.0.0 && <9.2     , mtl >=2.2.1     , old-time     , parser-combinators >=0.4.0@@ -219,7 +219,7 @@     , filepath     , hashtables >=1.2.3.1     , hledger-lib-    , megaparsec >=7.0.0 && <9.1+    , megaparsec >=7.0.0 && <9.2     , mtl >=2.2.1     , old-time     , parser-combinators >=0.4.0