{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE Rank2Types          #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections       #-}
{-# LANGUAGE RecordWildCards #-}

{-|

A 'Journal' is a set of transactions, plus optional related data.  This is
hledger's primary data object. It is usually parsed from a journal file or
other data format (see "Hledger.Read").

-}

module Hledger.Data.Journal (
  -- * Parsing helpers
  JournalParser,
  ErroringJournalParser,
  addPriceDirective,
  addTransactionModifier,
  addPeriodicTransaction,
  addTransaction,
  journalInferMarketPricesFromTransactions,
  journalInferCommodityStyles,
  journalStyleAmounts,
  commodityStylesFromAmounts,
  journalCommodityStyles,
  journalCommodityStylesWith,
  journalToCost,
  journalInferEquityFromCosts,
  journalInferCostsFromEquity,
  journalMarkRedundantCosts,
  journalReverse,
  journalSetLastReadTime,
  journalRenumberAccountDeclarations,
  journalPivot,
  -- * Filtering
  filterJournalTransactions,
  filterJournalPostings,
  filterJournalRelatedPostings,
  filterJournalAmounts,
  filterTransactionAmounts,
  filterTransactionPostings,
  filterTransactionPostingsExtra,
  filterTransactionRelatedPostings,
  filterPostingAmount,
  -- * Mapping
  journalMapTransactions,
  journalMapPostings,
  journalMapPostingAmounts,
  -- * Querying
  journalAccountNamesUsed,
  journalAccountNamesImplied,
  journalAccountNamesDeclared,
  journalAccountNamesDeclaredOrUsed,
  journalAccountNamesDeclaredOrImplied,
  journalLeafAccountNamesDeclared,
  journalAccountNames,
  journalLeafAccountNames,
  journalAccountNameTree,
  journalAccountTags,
  journalInheritedAccountTags,
  -- journalAmountAndPriceCommodities,
  -- journalAmountStyles,
  -- overJournalAmounts,
  -- traverseJournalAmounts,
  -- journalCanonicalCommodities,
  journalPayeesDeclared,
  journalPayeesUsed,
  journalPayeesDeclaredOrUsed,
  journalTagsDeclared,
  journalTagsUsed,
  journalTagsDeclaredOrUsed,
  journalCommoditiesDeclared,
  journalCommodities,
  journalDateSpan,
  journalDateSpanBothDates,
  journalStartDate,
  journalEndDate,
  journalLastDay,
  journalDescriptions,
  journalFilePath,
  journalFilePaths,
  journalTransactionAt,
  journalNextTransaction,
  journalPrevTransaction,
  journalPostings,
  journalPostingAmounts,
  showJournalAmountsDebug,
  journalTransactionsSimilarTo,
  -- * Account types
  journalAccountType,
  journalAccountTypes,
  journalAddAccountTypes,
  journalPostingsAddAccountTags,
  -- journalPrices,
  journalConversionAccount,
  journalConversionAccounts,
  -- * Misc
  canonicalStyleFrom,
  nulljournal,
  journalConcat,
  journalNumberTransactions,
  journalNumberAndTieTransactions,
  journalUntieTransactions,
  journalModifyTransactions,
  journalApplyAliases,
  dbgJournalAcctDeclOrder,
  -- * Tests
  samplejournal,
  samplejournalMaybeExplicit,
  tests_Journal
  --
)
where

import Control.Applicative ((<|>))
import Control.Monad.Except (ExceptT(..))
import Control.Monad.State.Strict (StateT)
import Data.Char (toUpper, isDigit)
import Data.Default (Default(..))
import Data.Foldable (toList)
import Data.List ((\\), find, foldl', sortBy, union, intercalate)
import Data.List.Extra (nubSort)
import qualified Data.Map.Strict as M
import Data.Maybe (catMaybes, fromMaybe, mapMaybe, maybeToList)
import qualified Data.Set as S
import Data.Text (Text)
import qualified Data.Text as T
import Safe (headMay, headDef, maximumMay, minimumMay, lastDef)
import Data.Time.Calendar (Day, addDays, fromGregorian, diffDays)
import Data.Time.Clock.POSIX (POSIXTime)
import Data.Tree (Tree(..), flatten)
import Text.Printf (printf)
import Text.Megaparsec (ParsecT)
import Text.Megaparsec.Custom (FinalParseError)

import Hledger.Utils
import Hledger.Data.Types
import Hledger.Data.AccountName
import Hledger.Data.Amount
import Hledger.Data.Posting
import Hledger.Data.Transaction
import Hledger.Data.TransactionModifier
import Hledger.Data.Valuation
import Hledger.Query
import System.FilePath (takeFileName)
import Data.Ord (comparing)
import Hledger.Data.Dates (nulldate)
import Data.List (sort)
-- import Data.Function ((&))


-- | A parser of text that runs in some monad, keeping a Journal as state.
type JournalParser m a = StateT Journal (ParsecT HledgerParseErrorData Text m) a

-- | A parser of text that runs in some monad, keeping a Journal as
-- state, that can throw an exception to end parsing, preventing
-- further parser backtracking.
type ErroringJournalParser m a =
  StateT Journal (ParsecT HledgerParseErrorData Text (ExceptT FinalParseError m)) a

-- deriving instance Show Journal
instance Show Journal where
  show :: Journal -> RegexError
show Journal
j
    | Int
debugLevel Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
3 = RegexError -> RegexError -> Int -> Int -> RegexError
forall r. PrintfType r => RegexError -> r
printf RegexError
"Journal %s with %d transactions, %d accounts"
             (Journal -> RegexError
journalFilePath Journal
j)
             ([Transaction] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Transaction] -> Int) -> [Transaction] -> Int
forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
j)
             ([TagName] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [TagName]
accounts)
    | Int
debugLevel Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
6 = RegexError -> RegexError -> Int -> Int -> ShowS
forall r. PrintfType r => RegexError -> r
printf RegexError
"Journal %s with %d transactions, %d accounts: %s"
             (Journal -> RegexError
journalFilePath Journal
j)
             ([Transaction] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Transaction] -> Int) -> [Transaction] -> Int
forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
j)
             ([TagName] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [TagName]
accounts)
             ([TagName] -> RegexError
forall a. Show a => a -> RegexError
show [TagName]
accounts)
    | Bool
otherwise = RegexError -> RegexError -> Int -> Int -> RegexError -> ShowS
forall r. PrintfType r => RegexError -> r
printf RegexError
"Journal %s with %d transactions, %d accounts: %s, commodity styles: %s"
             (Journal -> RegexError
journalFilePath Journal
j)
             ([Transaction] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Transaction] -> Int) -> [Transaction] -> Int
forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
j)
             ([TagName] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [TagName]
accounts)
             ([TagName] -> RegexError
forall a. Show a => a -> RegexError
show [TagName]
accounts)
             (Map TagName AmountStyle -> RegexError
forall a. Show a => a -> RegexError
show (Map TagName AmountStyle -> RegexError)
-> Map TagName AmountStyle -> RegexError
forall a b. (a -> b) -> a -> b
$ Journal -> Map TagName AmountStyle
jinferredcommodities Journal
j)
             -- ++ (show $ journalTransactions l)
             where accounts :: [TagName]
accounts = (TagName -> Bool) -> [TagName] -> [TagName]
forall a. (a -> Bool) -> [a] -> [a]
filter (TagName -> TagName -> Bool
forall a. Eq a => a -> a -> Bool
/= TagName
"root") ([TagName] -> [TagName]) -> [TagName] -> [TagName]
forall a b. (a -> b) -> a -> b
$ Tree TagName -> [TagName]
forall a. Tree a -> [a]
flatten (Tree TagName -> [TagName]) -> Tree TagName -> [TagName]
forall a b. (a -> b) -> a -> b
$ Journal -> Tree TagName
journalAccountNameTree Journal
j

-- showJournalDebug j = unlines [
--                       show j
--                      ,show (jtxns j)
--                      ,show (jtxnmodifiers j)
--                      ,show (jperiodictxns j)
--                      ,show $ jparsetimeclockentries j
--                      ,show $ jpricedirectives j
--                      ,show $ jfinalcommentlines j
--                      ,show $ jparsestate j
--                      ,show $ map fst $ jfiles j
--                      ]

-- The semigroup instance for Journal is useful for two situations.
--
-- 1. concatenating finalised journals, eg with multiple -f options:
-- FIRST <> SECOND.
--
-- 2. merging a child parsed journal, eg with the include directive:
-- CHILD <> PARENT. A parsed journal's data is in reverse order, so
-- this gives what we want.
--
-- Note that (<>) is right-biased, so nulljournal is only a left identity.
-- In particular, this prevents Journal from being a monoid.
instance Semigroup Journal where Journal
j1 <> :: Journal -> Journal -> Journal
<> Journal
j2 = Journal
j1 Journal -> Journal -> Journal
`journalConcat` Journal
j2

-- | Merge two journals into one.
-- Transaction counts are summed, map fields are combined,
-- the second's list fields are appended to the first's,
-- the second's parse state is kept.
journalConcat :: Journal -> Journal -> Journal
journalConcat :: Journal -> Journal -> Journal
journalConcat Journal
j1 Journal
j2 =
  let
    f1 :: RegexError
f1 = ShowS
takeFileName ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ Journal -> RegexError
journalFilePath Journal
j1
    f2 :: RegexError
f2 = RegexError -> ShowS -> Maybe RegexError -> RegexError
forall b a. b -> (a -> b) -> Maybe a -> b
maybe RegexError
"(unknown)" ShowS
takeFileName (Maybe RegexError -> RegexError) -> Maybe RegexError -> RegexError
forall a b. (a -> b) -> a -> b
$ [RegexError] -> Maybe RegexError
forall a. [a] -> Maybe a
headMay ([RegexError] -> Maybe RegexError)
-> [RegexError] -> Maybe RegexError
forall a b. (a -> b) -> a -> b
$ Journal -> [RegexError]
jincludefilestack Journal
j2  -- XXX more accurate than journalFilePath for some reason
  in
    RegexError -> Journal -> Journal
dbgJournalAcctDeclOrder (RegexError
"journalConcat: " RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
f1 RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
" <> " RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
f2 RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
", acct decls renumbered: ") (Journal -> Journal) -> Journal -> Journal
forall a b. (a -> b) -> a -> b
$
    Journal -> Journal
journalRenumberAccountDeclarations (Journal -> Journal) -> Journal -> Journal
forall a b. (a -> b) -> a -> b
$
    RegexError -> Journal -> Journal
dbgJournalAcctDeclOrder (RegexError
"journalConcat: " RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
f1 RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
" <> " RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
f2 RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
", acct decls           : ") (Journal -> Journal) -> Journal -> Journal
forall a b. (a -> b) -> a -> b
$
    Journal {
     jparsedefaultyear :: Maybe Integer
jparsedefaultyear          = Journal -> Maybe Integer
jparsedefaultyear          Journal
j2
    ,jparsedefaultcommodity :: Maybe (TagName, AmountStyle)
jparsedefaultcommodity     = Journal -> Maybe (TagName, AmountStyle)
jparsedefaultcommodity     Journal
j2
    ,jparsedecimalmark :: Maybe Char
jparsedecimalmark          = Journal -> Maybe Char
jparsedecimalmark          Journal
j2
    ,jparseparentaccounts :: [TagName]
jparseparentaccounts       = Journal -> [TagName]
jparseparentaccounts       Journal
j2
    ,jparsealiases :: [AccountAlias]
jparsealiases              = Journal -> [AccountAlias]
jparsealiases              Journal
j2
    -- ,jparsetransactioncount     = jparsetransactioncount     j1 +  jparsetransactioncount     j2
    ,jparsetimeclockentries :: [TimeclockEntry]
jparsetimeclockentries     = Journal -> [TimeclockEntry]
jparsetimeclockentries     Journal
j1 [TimeclockEntry] -> [TimeclockEntry] -> [TimeclockEntry]
forall a. Semigroup a => a -> a -> a
<> Journal -> [TimeclockEntry]
jparsetimeclockentries     Journal
j2
    ,jincludefilestack :: [RegexError]
jincludefilestack          = Journal -> [RegexError]
jincludefilestack Journal
j2
    ,jdeclaredpayees :: [(TagName, PayeeDeclarationInfo)]
jdeclaredpayees            = Journal -> [(TagName, PayeeDeclarationInfo)]
jdeclaredpayees            Journal
j1 [(TagName, PayeeDeclarationInfo)]
-> [(TagName, PayeeDeclarationInfo)]
-> [(TagName, PayeeDeclarationInfo)]
forall a. Semigroup a => a -> a -> a
<> Journal -> [(TagName, PayeeDeclarationInfo)]
jdeclaredpayees            Journal
j2
    ,jdeclaredtags :: [(TagName, TagDeclarationInfo)]
jdeclaredtags              = Journal -> [(TagName, TagDeclarationInfo)]
jdeclaredtags              Journal
j1 [(TagName, TagDeclarationInfo)]
-> [(TagName, TagDeclarationInfo)]
-> [(TagName, TagDeclarationInfo)]
forall a. Semigroup a => a -> a -> a
<> Journal -> [(TagName, TagDeclarationInfo)]
jdeclaredtags              Journal
j2
    ,jdeclaredaccounts :: [(TagName, AccountDeclarationInfo)]
jdeclaredaccounts          = Journal -> [(TagName, AccountDeclarationInfo)]
jdeclaredaccounts          Journal
j1 [(TagName, AccountDeclarationInfo)]
-> [(TagName, AccountDeclarationInfo)]
-> [(TagName, AccountDeclarationInfo)]
forall a. Semigroup a => a -> a -> a
<> Journal -> [(TagName, AccountDeclarationInfo)]
jdeclaredaccounts          Journal
j2
    ,jdeclaredaccounttags :: Map TagName [Tag]
jdeclaredaccounttags       = Journal -> Map TagName [Tag]
jdeclaredaccounttags       Journal
j1 Map TagName [Tag] -> Map TagName [Tag] -> Map TagName [Tag]
forall a. Semigroup a => a -> a -> a
<> Journal -> Map TagName [Tag]
jdeclaredaccounttags       Journal
j2
    ,jdeclaredaccounttypes :: Map AccountType [TagName]
jdeclaredaccounttypes      = Journal -> Map AccountType [TagName]
jdeclaredaccounttypes      Journal
j1 Map AccountType [TagName]
-> Map AccountType [TagName] -> Map AccountType [TagName]
forall a. Semigroup a => a -> a -> a
<> Journal -> Map AccountType [TagName]
jdeclaredaccounttypes      Journal
j2
    ,jaccounttypes :: Map TagName AccountType
jaccounttypes              = Journal -> Map TagName AccountType
jaccounttypes              Journal
j1 Map TagName AccountType
-> Map TagName AccountType -> Map TagName AccountType
forall a. Semigroup a => a -> a -> a
<> Journal -> Map TagName AccountType
jaccounttypes              Journal
j2
    ,jglobalcommoditystyles :: Map TagName AmountStyle
jglobalcommoditystyles     = Journal -> Map TagName AmountStyle
jglobalcommoditystyles     Journal
j1 Map TagName AmountStyle
-> Map TagName AmountStyle -> Map TagName AmountStyle
forall a. Semigroup a => a -> a -> a
<> Journal -> Map TagName AmountStyle
jglobalcommoditystyles     Journal
j2
    ,jcommodities :: Map TagName Commodity
jcommodities               = Journal -> Map TagName Commodity
jcommodities               Journal
j1 Map TagName Commodity
-> Map TagName Commodity -> Map TagName Commodity
forall a. Semigroup a => a -> a -> a
<> Journal -> Map TagName Commodity
jcommodities               Journal
j2
    ,jinferredcommodities :: Map TagName AmountStyle
jinferredcommodities       = Journal -> Map TagName AmountStyle
jinferredcommodities       Journal
j1 Map TagName AmountStyle
-> Map TagName AmountStyle -> Map TagName AmountStyle
forall a. Semigroup a => a -> a -> a
<> Journal -> Map TagName AmountStyle
jinferredcommodities       Journal
j2
    ,jpricedirectives :: [PriceDirective]
jpricedirectives           = Journal -> [PriceDirective]
jpricedirectives           Journal
j1 [PriceDirective] -> [PriceDirective] -> [PriceDirective]
forall a. Semigroup a => a -> a -> a
<> Journal -> [PriceDirective]
jpricedirectives           Journal
j2
    ,jinferredmarketprices :: [MarketPrice]
jinferredmarketprices      = Journal -> [MarketPrice]
jinferredmarketprices      Journal
j1 [MarketPrice] -> [MarketPrice] -> [MarketPrice]
forall a. Semigroup a => a -> a -> a
<> Journal -> [MarketPrice]
jinferredmarketprices      Journal
j2
    ,jtxnmodifiers :: [TransactionModifier]
jtxnmodifiers              = Journal -> [TransactionModifier]
jtxnmodifiers              Journal
j1 [TransactionModifier]
-> [TransactionModifier] -> [TransactionModifier]
forall a. Semigroup a => a -> a -> a
<> Journal -> [TransactionModifier]
jtxnmodifiers              Journal
j2
    ,jperiodictxns :: [PeriodicTransaction]
jperiodictxns              = Journal -> [PeriodicTransaction]
jperiodictxns              Journal
j1 [PeriodicTransaction]
-> [PeriodicTransaction] -> [PeriodicTransaction]
forall a. Semigroup a => a -> a -> a
<> Journal -> [PeriodicTransaction]
jperiodictxns              Journal
j2
    ,jtxns :: [Transaction]
jtxns                      = Journal -> [Transaction]
jtxns                      Journal
j1 [Transaction] -> [Transaction] -> [Transaction]
forall a. Semigroup a => a -> a -> a
<> Journal -> [Transaction]
jtxns                      Journal
j2
    ,jfinalcommentlines :: TagName
jfinalcommentlines         = Journal -> TagName
jfinalcommentlines Journal
j2  -- XXX discards j1's ?
    ,jfiles :: [(RegexError, TagName)]
jfiles                     = Journal -> [(RegexError, TagName)]
jfiles                     Journal
j1 [(RegexError, TagName)]
-> [(RegexError, TagName)] -> [(RegexError, TagName)]
forall a. Semigroup a => a -> a -> a
<> Journal -> [(RegexError, TagName)]
jfiles                     Journal
j2
    ,jlastreadtime :: POSIXTime
jlastreadtime              = POSIXTime -> POSIXTime -> POSIXTime
forall a. Ord a => a -> a -> a
max (Journal -> POSIXTime
jlastreadtime Journal
j1) (Journal -> POSIXTime
jlastreadtime Journal
j2)
    }

-- | Renumber all the account declarations. This is useful to call when
-- finalising or concatenating Journals, to give account declarations
-- a total order across files.
journalRenumberAccountDeclarations :: Journal -> Journal
journalRenumberAccountDeclarations :: Journal -> Journal
journalRenumberAccountDeclarations Journal
j = Journal
j{jdeclaredaccounts=jdas'}
  where
    jdas' :: [(TagName, AccountDeclarationInfo)]
jdas' = [(TagName
a, AccountDeclarationInfo
adi{adideclarationorder=n}) | (Int
n, (TagName
a,AccountDeclarationInfo
adi)) <- [Int]
-> [(TagName, AccountDeclarationInfo)]
-> [(Int, (TagName, AccountDeclarationInfo))]
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
1..] ([(TagName, AccountDeclarationInfo)]
 -> [(Int, (TagName, AccountDeclarationInfo))])
-> [(TagName, AccountDeclarationInfo)]
-> [(Int, (TagName, AccountDeclarationInfo))]
forall a b. (a -> b) -> a -> b
$ Journal -> [(TagName, AccountDeclarationInfo)]
jdeclaredaccounts Journal
j]
    -- the per-file declaration order saved during parsing is discarded,
    -- it seems unneeded except perhaps for debugging

-- | Debug log the ordering of a journal's account declarations
-- (at debug level 5+).
dbgJournalAcctDeclOrder :: String -> Journal -> Journal
dbgJournalAcctDeclOrder :: RegexError -> Journal -> Journal
dbgJournalAcctDeclOrder RegexError
prefix =
  Int -> (Journal -> RegexError) -> Journal -> Journal
forall a. Int -> (a -> RegexError) -> a -> a
traceOrLogAtWith Int
5 ((RegexError
prefixRegexError -> ShowS
forall a. [a] -> [a] -> [a]
++) ShowS -> (Journal -> RegexError) -> Journal -> RegexError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(TagName, AccountDeclarationInfo)] -> RegexError
showAcctDeclsSummary ([(TagName, AccountDeclarationInfo)] -> RegexError)
-> (Journal -> [(TagName, AccountDeclarationInfo)])
-> Journal
-> RegexError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [(TagName, AccountDeclarationInfo)]
jdeclaredaccounts)
  where
    showAcctDeclsSummary :: [(AccountName,AccountDeclarationInfo)] -> String
    showAcctDeclsSummary :: [(TagName, AccountDeclarationInfo)] -> RegexError
showAcctDeclsSummary [(TagName, AccountDeclarationInfo)]
adis
      | [(TagName, AccountDeclarationInfo)] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [(TagName, AccountDeclarationInfo)]
adis Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< (Int
2Int -> Int -> Int
forall a. Num a => a -> a -> a
*Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
2) = RegexError
"[" RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> [(TagName, AccountDeclarationInfo)] -> RegexError
showadis [(TagName, AccountDeclarationInfo)]
adis RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
"]"
      | Bool
otherwise =
          RegexError
"[" RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> [(TagName, AccountDeclarationInfo)] -> RegexError
showadis (Int
-> [(TagName, AccountDeclarationInfo)]
-> [(TagName, AccountDeclarationInfo)]
forall a. Int -> [a] -> [a]
take Int
n [(TagName, AccountDeclarationInfo)]
adis) RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
" ... " RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> [(TagName, AccountDeclarationInfo)] -> RegexError
showadis (Int
-> [(TagName, AccountDeclarationInfo)]
-> [(TagName, AccountDeclarationInfo)]
forall a. Int -> [a] -> [a]
takelast Int
n [(TagName, AccountDeclarationInfo)]
adis) RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<> RegexError
"]"
      where
        n :: Int
n = Int
3
        showadis :: [(TagName, AccountDeclarationInfo)] -> RegexError
showadis = RegexError -> [RegexError] -> RegexError
forall a. [a] -> [[a]] -> [a]
intercalate RegexError
", " ([RegexError] -> RegexError)
-> ([(TagName, AccountDeclarationInfo)] -> [RegexError])
-> [(TagName, AccountDeclarationInfo)]
-> RegexError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((TagName, AccountDeclarationInfo) -> RegexError)
-> [(TagName, AccountDeclarationInfo)] -> [RegexError]
forall a b. (a -> b) -> [a] -> [b]
map (TagName, AccountDeclarationInfo) -> RegexError
showadi
        showadi :: (TagName, AccountDeclarationInfo) -> RegexError
showadi (TagName
a,AccountDeclarationInfo
adi) = RegexError
"("RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<>Int -> RegexError
forall a. Show a => a -> RegexError
show (AccountDeclarationInfo -> Int
adideclarationorder AccountDeclarationInfo
adi)RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<>RegexError
","RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<>TagName -> RegexError
T.unpack TagName
aRegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<>RegexError
")"
        takelast :: Int -> [a] -> [a]
takelast Int
n' = [a] -> [a]
forall a. [a] -> [a]
reverse ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> [a] -> [a]
forall a. Int -> [a] -> [a]
take Int
n' ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [a] -> [a]
forall a. [a] -> [a]
reverse

instance Default Journal where
  def :: Journal
def = Journal
nulljournal

nulljournal :: Journal
nulljournal :: Journal
nulljournal = Journal {
   jparsedefaultyear :: Maybe Integer
jparsedefaultyear          = Maybe Integer
forall a. Maybe a
Nothing
  ,jparsedefaultcommodity :: Maybe (TagName, AmountStyle)
jparsedefaultcommodity     = Maybe (TagName, AmountStyle)
forall a. Maybe a
Nothing
  ,jparsedecimalmark :: Maybe Char
jparsedecimalmark          = Maybe Char
forall a. Maybe a
Nothing
  ,jparseparentaccounts :: [TagName]
jparseparentaccounts       = []
  ,jparsealiases :: [AccountAlias]
jparsealiases              = []
  -- ,jparsetransactioncount     = 0
  ,jparsetimeclockentries :: [TimeclockEntry]
jparsetimeclockentries     = []
  ,jincludefilestack :: [RegexError]
jincludefilestack          = []
  ,jdeclaredpayees :: [(TagName, PayeeDeclarationInfo)]
jdeclaredpayees            = []
  ,jdeclaredtags :: [(TagName, TagDeclarationInfo)]
jdeclaredtags              = []
  ,jdeclaredaccounts :: [(TagName, AccountDeclarationInfo)]
jdeclaredaccounts          = []
  ,jdeclaredaccounttags :: Map TagName [Tag]
jdeclaredaccounttags       = Map TagName [Tag]
forall k a. Map k a
M.empty
  ,jdeclaredaccounttypes :: Map AccountType [TagName]
jdeclaredaccounttypes      = Map AccountType [TagName]
forall k a. Map k a
M.empty
  ,jaccounttypes :: Map TagName AccountType
jaccounttypes              = Map TagName AccountType
forall k a. Map k a
M.empty
  ,jglobalcommoditystyles :: Map TagName AmountStyle
jglobalcommoditystyles     = Map TagName AmountStyle
forall k a. Map k a
M.empty
  ,jcommodities :: Map TagName Commodity
jcommodities               = Map TagName Commodity
forall k a. Map k a
M.empty
  ,jinferredcommodities :: Map TagName AmountStyle
jinferredcommodities       = Map TagName AmountStyle
forall k a. Map k a
M.empty
  ,jpricedirectives :: [PriceDirective]
jpricedirectives           = []
  ,jinferredmarketprices :: [MarketPrice]
jinferredmarketprices      = []
  ,jtxnmodifiers :: [TransactionModifier]
jtxnmodifiers              = []
  ,jperiodictxns :: [PeriodicTransaction]
jperiodictxns              = []
  ,jtxns :: [Transaction]
jtxns                      = []
  ,jfinalcommentlines :: TagName
jfinalcommentlines         = TagName
""
  ,jfiles :: [(RegexError, TagName)]
jfiles                     = []
  ,jlastreadtime :: POSIXTime
jlastreadtime              = POSIXTime
0
  }

journalFilePath :: Journal -> FilePath
journalFilePath :: Journal -> RegexError
journalFilePath = (RegexError, TagName) -> RegexError
forall a b. (a, b) -> a
fst ((RegexError, TagName) -> RegexError)
-> (Journal -> (RegexError, TagName)) -> Journal -> RegexError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> (RegexError, TagName)
mainfile

journalFilePaths :: Journal -> [FilePath]
journalFilePaths :: Journal -> [RegexError]
journalFilePaths = ((RegexError, TagName) -> RegexError)
-> [(RegexError, TagName)] -> [RegexError]
forall a b. (a -> b) -> [a] -> [b]
map (RegexError, TagName) -> RegexError
forall a b. (a, b) -> a
fst ([(RegexError, TagName)] -> [RegexError])
-> (Journal -> [(RegexError, TagName)]) -> Journal -> [RegexError]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [(RegexError, TagName)]
jfiles

mainfile :: Journal -> (FilePath, Text)
mainfile :: Journal -> (RegexError, TagName)
mainfile = (RegexError, TagName)
-> [(RegexError, TagName)] -> (RegexError, TagName)
forall a. a -> [a] -> a
headDef (RegexError
"(unknown)", TagName
"") ([(RegexError, TagName)] -> (RegexError, TagName))
-> (Journal -> [(RegexError, TagName)])
-> Journal
-> (RegexError, TagName)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [(RegexError, TagName)]
jfiles

addTransaction :: Transaction -> Journal -> Journal
addTransaction :: Transaction -> Journal -> Journal
addTransaction Transaction
t Journal
j = Journal
j { jtxns = t : jtxns j }

addTransactionModifier :: TransactionModifier -> Journal -> Journal
addTransactionModifier :: TransactionModifier -> Journal -> Journal
addTransactionModifier TransactionModifier
mt Journal
j = Journal
j { jtxnmodifiers = mt : jtxnmodifiers j }

addPeriodicTransaction :: PeriodicTransaction -> Journal -> Journal
addPeriodicTransaction :: PeriodicTransaction -> Journal -> Journal
addPeriodicTransaction PeriodicTransaction
pt Journal
j = Journal
j { jperiodictxns = pt : jperiodictxns j }

addPriceDirective :: PriceDirective -> Journal -> Journal
addPriceDirective :: PriceDirective -> Journal -> Journal
addPriceDirective PriceDirective
h Journal
j = Journal
j { jpricedirectives = h : jpricedirectives j }  -- XXX #999 keep sorted

-- | Get the transaction with this index (its 1-based position in the input stream), if any.
journalTransactionAt :: Journal -> Integer -> Maybe Transaction
journalTransactionAt :: Journal -> Integer -> Maybe Transaction
journalTransactionAt Journal{jtxns :: Journal -> [Transaction]
jtxns=[Transaction]
ts} Integer
i =
  -- it's probably ts !! (i+1), but we won't assume
  [Transaction] -> Maybe Transaction
forall a. [a] -> Maybe a
headMay [Transaction
t | Transaction
t <- [Transaction]
ts, Transaction -> Integer
tindex Transaction
t Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
i]

-- | Get the transaction that appeared immediately after this one in the input stream, if any.
journalNextTransaction :: Journal -> Transaction -> Maybe Transaction
journalNextTransaction :: Journal -> Transaction -> Maybe Transaction
journalNextTransaction Journal
j Transaction
t = Journal -> Integer -> Maybe Transaction
journalTransactionAt Journal
j (Transaction -> Integer
tindex Transaction
t Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
1)

-- | Get the transaction that appeared immediately before this one in the input stream, if any.
journalPrevTransaction :: Journal -> Transaction -> Maybe Transaction
journalPrevTransaction :: Journal -> Transaction -> Maybe Transaction
journalPrevTransaction Journal
j Transaction
t = Journal -> Integer -> Maybe Transaction
journalTransactionAt Journal
j (Transaction -> Integer
tindex Transaction
t Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1)

-- | All postings from this journal's transactions, in order.
journalPostings :: Journal -> [Posting]
journalPostings :: Journal -> [Posting]
journalPostings = (Transaction -> [Posting]) -> [Transaction] -> [Posting]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Transaction -> [Posting]
tpostings ([Transaction] -> [Posting])
-> (Journal -> [Transaction]) -> Journal -> [Posting]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [Transaction]
jtxns

-- | All posting amounts from this journal, in order.
journalPostingAmounts :: Journal -> [MixedAmount]
journalPostingAmounts :: Journal -> [MixedAmount]
journalPostingAmounts = (Posting -> MixedAmount) -> [Posting] -> [MixedAmount]
forall a b. (a -> b) -> [a] -> [b]
map Posting -> MixedAmount
pamount ([Posting] -> [MixedAmount])
-> (Journal -> [Posting]) -> Journal -> [MixedAmount]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [Posting]
journalPostings

-- | Show the journal amounts rendered, suitable for debug logging.
showJournalAmountsDebug :: Journal -> String
showJournalAmountsDebug :: Journal -> RegexError
showJournalAmountsDebug = [RegexError] -> RegexError
forall a. Show a => a -> RegexError
show([RegexError] -> RegexError)
-> (Journal -> [RegexError]) -> Journal -> RegexError
forall b c a. (b -> c) -> (a -> b) -> a -> c
.(MixedAmount -> RegexError) -> [MixedAmount] -> [RegexError]
forall a b. (a -> b) -> [a] -> [b]
map MixedAmount -> RegexError
showMixedAmountOneLine([MixedAmount] -> [RegexError])
-> (Journal -> [MixedAmount]) -> Journal -> [RegexError]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Journal -> [MixedAmount]
journalPostingAmounts

-- | Sorted unique commodity symbols declared by commodity directives in this journal.
journalCommoditiesDeclared :: Journal -> [CommoditySymbol]
journalCommoditiesDeclared :: Journal -> [TagName]
journalCommoditiesDeclared = Map TagName Commodity -> [TagName]
forall k a. Map k a -> [k]
M.keys (Map TagName Commodity -> [TagName])
-> (Journal -> Map TagName Commodity) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> Map TagName Commodity
jcommodities

-- | Sorted unique commodity symbols declared or inferred from this journal.
journalCommodities :: Journal -> S.Set CommoditySymbol
journalCommodities :: Journal -> Set TagName
journalCommodities Journal
j = Map TagName Commodity -> Set TagName
forall k a. Map k a -> Set k
M.keysSet (Journal -> Map TagName Commodity
jcommodities Journal
j) Set TagName -> Set TagName -> Set TagName
forall a. Semigroup a => a -> a -> a
<> Map TagName AmountStyle -> Set TagName
forall k a. Map k a -> Set k
M.keysSet (Journal -> Map TagName AmountStyle
jinferredcommodities Journal
j)

-- | Unique transaction descriptions used in this journal.
journalDescriptions :: Journal -> [Text]
journalDescriptions :: Journal -> [TagName]
journalDescriptions = [TagName] -> [TagName]
forall a. Ord a => [a] -> [a]
nubSort ([TagName] -> [TagName])
-> (Journal -> [TagName]) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Transaction -> TagName) -> [Transaction] -> [TagName]
forall a b. (a -> b) -> [a] -> [b]
map Transaction -> TagName
tdescription ([Transaction] -> [TagName])
-> (Journal -> [Transaction]) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [Transaction]
jtxns

-- | Sorted unique payees declared by payee directives in this journal.
journalPayeesDeclared :: Journal -> [Payee]
journalPayeesDeclared :: Journal -> [TagName]
journalPayeesDeclared = [TagName] -> [TagName]
forall a. Ord a => [a] -> [a]
nubSort ([TagName] -> [TagName])
-> (Journal -> [TagName]) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((TagName, PayeeDeclarationInfo) -> TagName)
-> [(TagName, PayeeDeclarationInfo)] -> [TagName]
forall a b. (a -> b) -> [a] -> [b]
map (TagName, PayeeDeclarationInfo) -> TagName
forall a b. (a, b) -> a
fst ([(TagName, PayeeDeclarationInfo)] -> [TagName])
-> (Journal -> [(TagName, PayeeDeclarationInfo)])
-> Journal
-> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [(TagName, PayeeDeclarationInfo)]
jdeclaredpayees

-- | Sorted unique payees used by transactions in this journal.
journalPayeesUsed :: Journal -> [Payee]
journalPayeesUsed :: Journal -> [TagName]
journalPayeesUsed = [TagName] -> [TagName]
forall a. Ord a => [a] -> [a]
nubSort ([TagName] -> [TagName])
-> (Journal -> [TagName]) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Transaction -> TagName) -> [Transaction] -> [TagName]
forall a b. (a -> b) -> [a] -> [b]
map Transaction -> TagName
transactionPayee ([Transaction] -> [TagName])
-> (Journal -> [Transaction]) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [Transaction]
jtxns

-- | Sorted unique payees used in transactions or declared by payee directives in this journal.
journalPayeesDeclaredOrUsed :: Journal -> [Payee]
journalPayeesDeclaredOrUsed :: Journal -> [TagName]
journalPayeesDeclaredOrUsed Journal
j = Set TagName -> [TagName]
forall a. Set a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (Set TagName -> [TagName]) -> Set TagName -> [TagName]
forall a b. (a -> b) -> a -> b
$ ([TagName] -> Set TagName) -> [[TagName]] -> Set TagName
forall m a. Monoid m => (a -> m) -> [a] -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap [TagName] -> Set TagName
forall a. Ord a => [a] -> Set a
S.fromList
    [Journal -> [TagName]
journalPayeesDeclared Journal
j, Journal -> [TagName]
journalPayeesUsed Journal
j]

-- | Sorted unique tag names declared by tag directives in this journal.
journalTagsDeclared :: Journal -> [TagName]
journalTagsDeclared :: Journal -> [TagName]
journalTagsDeclared = [TagName] -> [TagName]
forall a. Ord a => [a] -> [a]
nubSort ([TagName] -> [TagName])
-> (Journal -> [TagName]) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((TagName, TagDeclarationInfo) -> TagName)
-> [(TagName, TagDeclarationInfo)] -> [TagName]
forall a b. (a -> b) -> [a] -> [b]
map (TagName, TagDeclarationInfo) -> TagName
forall a b. (a, b) -> a
fst ([(TagName, TagDeclarationInfo)] -> [TagName])
-> (Journal -> [(TagName, TagDeclarationInfo)])
-> Journal
-> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [(TagName, TagDeclarationInfo)]
jdeclaredtags

-- | Sorted unique tag names used in this journal (in account directives, transactions, postings..)
journalTagsUsed :: Journal -> [TagName]
journalTagsUsed :: Journal -> [TagName]
journalTagsUsed Journal
j = [TagName] -> [TagName]
forall a. Ord a => [a] -> [a]
nubSort ([TagName] -> [TagName]) -> [TagName] -> [TagName]
forall a b. (a -> b) -> a -> b
$ (Tag -> TagName) -> [Tag] -> [TagName]
forall a b. (a -> b) -> [a] -> [b]
map Tag -> TagName
forall a b. (a, b) -> a
fst ([Tag] -> [TagName]) -> [Tag] -> [TagName]
forall a b. (a -> b) -> a -> b
$ (Transaction -> [Tag]) -> [Transaction] -> [Tag]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Transaction -> [Tag]
transactionAllTags ([Transaction] -> [Tag]) -> [Transaction] -> [Tag]
forall a b. (a -> b) -> a -> b
$ Journal -> [Transaction]
jtxns Journal
j
  -- tags used in all transactions and postings and postings' accounts

-- | Sorted unique tag names used in transactions or declared by tag directives in this journal.
journalTagsDeclaredOrUsed :: Journal -> [TagName]
journalTagsDeclaredOrUsed :: Journal -> [TagName]
journalTagsDeclaredOrUsed Journal
j = Set TagName -> [TagName]
forall a. Set a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (Set TagName -> [TagName]) -> Set TagName -> [TagName]
forall a b. (a -> b) -> a -> b
$ ([TagName] -> Set TagName) -> [[TagName]] -> Set TagName
forall m a. Monoid m => (a -> m) -> [a] -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap [TagName] -> Set TagName
forall a. Ord a => [a] -> Set a
S.fromList
    [Journal -> [TagName]
journalTagsDeclared Journal
j, Journal -> [TagName]
journalTagsUsed Journal
j]

-- | Sorted unique account names posted to by this journal's transactions.
journalAccountNamesUsed :: Journal -> [AccountName]
journalAccountNamesUsed :: Journal -> [TagName]
journalAccountNamesUsed = [Posting] -> [TagName]
accountNamesFromPostings ([Posting] -> [TagName])
-> (Journal -> [Posting]) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [Posting]
journalPostings

-- | Sorted unique account names implied by this journal's transactions -
-- accounts posted to and all their implied parent accounts.
journalAccountNamesImplied :: Journal -> [AccountName]
journalAccountNamesImplied :: Journal -> [TagName]
journalAccountNamesImplied = [TagName] -> [TagName]
expandAccountNames ([TagName] -> [TagName])
-> (Journal -> [TagName]) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [TagName]
journalAccountNamesUsed

-- | Sorted unique account names declared by account directives in this journal.
journalAccountNamesDeclared :: Journal -> [AccountName]
journalAccountNamesDeclared :: Journal -> [TagName]
journalAccountNamesDeclared = [TagName] -> [TagName]
forall a. Ord a => [a] -> [a]
nubSort ([TagName] -> [TagName])
-> (Journal -> [TagName]) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((TagName, AccountDeclarationInfo) -> TagName)
-> [(TagName, AccountDeclarationInfo)] -> [TagName]
forall a b. (a -> b) -> [a] -> [b]
map (TagName, AccountDeclarationInfo) -> TagName
forall a b. (a, b) -> a
fst ([(TagName, AccountDeclarationInfo)] -> [TagName])
-> (Journal -> [(TagName, AccountDeclarationInfo)])
-> Journal
-> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [(TagName, AccountDeclarationInfo)]
jdeclaredaccounts

-- | Sorted unique account names declared by account directives in this journal,
-- which have no children.
journalLeafAccountNamesDeclared :: Journal -> [AccountName]
journalLeafAccountNamesDeclared :: Journal -> [TagName]
journalLeafAccountNamesDeclared = Tree TagName -> [TagName]
forall a. Tree a -> [a]
treeLeaves (Tree TagName -> [TagName])
-> (Journal -> Tree TagName) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [TagName] -> Tree TagName
accountNameTreeFrom ([TagName] -> Tree TagName)
-> (Journal -> [TagName]) -> Journal -> Tree TagName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [TagName]
journalAccountNamesDeclared

-- | Sorted unique account names declared by account directives or posted to
-- by transactions in this journal.
journalAccountNamesDeclaredOrUsed :: Journal -> [AccountName]
journalAccountNamesDeclaredOrUsed :: Journal -> [TagName]
journalAccountNamesDeclaredOrUsed Journal
j = Set TagName -> [TagName]
forall a. Set a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (Set TagName -> [TagName]) -> Set TagName -> [TagName]
forall a b. (a -> b) -> a -> b
$ ([TagName] -> Set TagName) -> [[TagName]] -> Set TagName
forall m a. Monoid m => (a -> m) -> [a] -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap [TagName] -> Set TagName
forall a. Ord a => [a] -> Set a
S.fromList
    [Journal -> [TagName]
journalAccountNamesDeclared Journal
j, Journal -> [TagName]
journalAccountNamesUsed Journal
j]

-- | Sorted unique account names declared by account directives, or posted to
-- or implied as parents by transactions in this journal.
journalAccountNamesDeclaredOrImplied :: Journal -> [AccountName]
journalAccountNamesDeclaredOrImplied :: Journal -> [TagName]
journalAccountNamesDeclaredOrImplied Journal
j = Set TagName -> [TagName]
forall a. Set a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (Set TagName -> [TagName]) -> Set TagName -> [TagName]
forall a b. (a -> b) -> a -> b
$ ([TagName] -> Set TagName) -> [[TagName]] -> Set TagName
forall m a. Monoid m => (a -> m) -> [a] -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap [TagName] -> Set TagName
forall a. Ord a => [a] -> Set a
S.fromList
    [Journal -> [TagName]
journalAccountNamesDeclared Journal
j, [TagName] -> [TagName]
expandAccountNames ([TagName] -> [TagName]) -> [TagName] -> [TagName]
forall a b. (a -> b) -> a -> b
$ Journal -> [TagName]
journalAccountNamesUsed Journal
j]

-- | Convenience/compatibility alias for journalAccountNamesDeclaredOrImplied.
journalAccountNames :: Journal -> [AccountName]
journalAccountNames :: Journal -> [TagName]
journalAccountNames = Journal -> [TagName]
journalAccountNamesDeclaredOrImplied

-- | Sorted unique account names declared or implied in this journal
-- which have no children.
journalLeafAccountNames :: Journal -> [AccountName]
journalLeafAccountNames :: Journal -> [TagName]
journalLeafAccountNames = Tree TagName -> [TagName]
forall a. Tree a -> [a]
treeLeaves (Tree TagName -> [TagName])
-> (Journal -> Tree TagName) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> Tree TagName
journalAccountNameTree

journalAccountNameTree :: Journal -> Tree AccountName
journalAccountNameTree :: Journal -> Tree TagName
journalAccountNameTree = [TagName] -> Tree TagName
accountNameTreeFrom ([TagName] -> Tree TagName)
-> (Journal -> [TagName]) -> Journal -> Tree TagName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [TagName]
journalAccountNamesDeclaredOrImplied

-- | Which tags have been declared explicitly for this account, if any ?
journalAccountTags :: Journal -> AccountName -> [Tag]
journalAccountTags :: Journal -> TagName -> [Tag]
journalAccountTags Journal{Map TagName [Tag]
jdeclaredaccounttags :: Journal -> Map TagName [Tag]
jdeclaredaccounttags :: Map TagName [Tag]
jdeclaredaccounttags} TagName
a = [Tag] -> TagName -> Map TagName [Tag] -> [Tag]
forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault [] TagName
a Map TagName [Tag]
jdeclaredaccounttags

-- | Which tags are in effect for this account, including tags inherited from parent accounts ?
journalInheritedAccountTags :: Journal -> AccountName -> [Tag]
journalInheritedAccountTags :: Journal -> TagName -> [Tag]
journalInheritedAccountTags Journal
j TagName
a =
  ([Tag] -> TagName -> [Tag]) -> [Tag] -> [TagName] -> [Tag]
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (\[Tag]
ts TagName
a' -> [Tag]
ts [Tag] -> [Tag] -> [Tag]
forall a. Eq a => [a] -> [a] -> [a]
`union` Journal -> TagName -> [Tag]
journalAccountTags Journal
j TagName
a') [] [TagName]
as
  where
    as :: [TagName]
as = TagName
a TagName -> [TagName] -> [TagName]
forall a. a -> [a] -> [a]
: TagName -> [TagName]
parentAccountNames TagName
a
-- PERF: cache in journal ?

type DateWeightedSimilarityScore = Double
type SimilarityScore = Double
type Age = Integer

-- | Find up to N most similar and most recent transactions matching
-- the given transaction description and query and exceeding the given
-- description similarity score (0 to 1, see compareDescriptions).
-- Returns transactions along with
-- their age in days compared to the latest transaction date,
-- their description similarity score,
-- and a heuristically date-weighted variant of this that favours more recent transactions.
journalTransactionsSimilarTo :: Journal -> Text -> Query -> SimilarityScore -> Int
  -> [(DateWeightedSimilarityScore, Age, SimilarityScore, Transaction)]
journalTransactionsSimilarTo :: Journal
-> TagName
-> Query
-> Double
-> Int
-> [(Double, Integer, Double, Transaction)]
journalTransactionsSimilarTo Journal{[Transaction]
jtxns :: Journal -> [Transaction]
jtxns :: [Transaction]
jtxns} TagName
desc Query
q Double
similaritythreshold Int
n =
  Int
-> [(Double, Integer, Double, Transaction)]
-> [(Double, Integer, Double, Transaction)]
forall a. Int -> [a] -> [a]
take Int
n ([(Double, Integer, Double, Transaction)]
 -> [(Double, Integer, Double, Transaction)])
-> [(Double, Integer, Double, Transaction)]
-> [(Double, Integer, Double, Transaction)]
forall a b. (a -> b) -> a -> b
$
  ([(Double, Integer, Double, Transaction)] -> RegexError)
-> [(Double, Integer, Double, Transaction)]
-> [(Double, Integer, Double, Transaction)]
forall a. Show a => (a -> RegexError) -> a -> a
dbg1With (
    [RegexError] -> RegexError
unlines ([RegexError] -> RegexError)
-> ([(Double, Integer, Double, Transaction)] -> [RegexError])
-> [(Double, Integer, Double, Transaction)]
-> RegexError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. 
    (RegexError
"up to 30 transactions above description similarity threshold "RegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<>Double -> RegexError
forall a. Show a => a -> RegexError
show Double
similaritythresholdRegexError -> ShowS
forall a. Semigroup a => a -> a -> a
<>RegexError
" ordered by recency-weighted similarity:"RegexError -> [RegexError] -> [RegexError]
forall a. a -> [a] -> [a]
:) ([RegexError] -> [RegexError])
-> ([(Double, Integer, Double, Transaction)] -> [RegexError])
-> [(Double, Integer, Double, Transaction)]
-> [RegexError]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
    Int -> [RegexError] -> [RegexError]
forall a. Int -> [a] -> [a]
take Int
30 ([RegexError] -> [RegexError])
-> ([(Double, Integer, Double, Transaction)] -> [RegexError])
-> [(Double, Integer, Double, Transaction)]
-> [RegexError]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
    ((Double, Integer, Double, Transaction) -> RegexError)
-> [(Double, Integer, Double, Transaction)] -> [RegexError]
forall a b. (a -> b) -> [a] -> [b]
map ( \(Double
w,Integer
a,Double
s,Transaction{Integer
[Tag]
[Posting]
Maybe Day
(SourcePos, SourcePos)
TagName
Day
Status
tindex :: Transaction -> Integer
tpostings :: Transaction -> [Posting]
tdescription :: Transaction -> TagName
tindex :: Integer
tprecedingcomment :: TagName
tsourcepos :: (SourcePos, SourcePos)
tdate :: Day
tdate2 :: Maybe Day
tstatus :: Status
tcode :: TagName
tdescription :: TagName
tcomment :: TagName
ttags :: [Tag]
tpostings :: [Posting]
tprecedingcomment :: Transaction -> TagName
tsourcepos :: Transaction -> (SourcePos, SourcePos)
tdate :: Transaction -> Day
tdate2 :: Transaction -> Maybe Day
tstatus :: Transaction -> Status
tcode :: Transaction -> TagName
tcomment :: Transaction -> TagName
ttags :: Transaction -> [Tag]
..}) -> RegexError
-> Double
-> Integer
-> Double
-> RegexError
-> TagName
-> RegexError
forall r. PrintfType r => RegexError -> r
printf RegexError
"weighted:%8.3f  age:%4d similarity:%5.3f  %s %s" Double
w Integer
a Double
s (Day -> RegexError
forall a. Show a => a -> RegexError
show Day
tdate) TagName
tdescription )) ([(Double, Integer, Double, Transaction)]
 -> [(Double, Integer, Double, Transaction)])
-> [(Double, Integer, Double, Transaction)]
-> [(Double, Integer, Double, Transaction)]
forall a b. (a -> b) -> a -> b
$
  ((Double, Integer, Double, Transaction)
 -> (Double, Integer, Double, Transaction) -> Ordering)
-> [(Double, Integer, Double, Transaction)]
-> [(Double, Integer, Double, Transaction)]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy (((Double, Integer, Double, Transaction) -> Double)
-> (Double, Integer, Double, Transaction)
-> (Double, Integer, Double, Transaction)
-> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing (Double -> Double
forall a. Num a => a -> a
negate(Double -> Double)
-> ((Double, Integer, Double, Transaction) -> Double)
-> (Double, Integer, Double, Transaction)
-> Double
forall b c a. (b -> c) -> (a -> b) -> a -> c
.(Double, Integer, Double, Transaction) -> Double
forall {a} {b} {c} {d}. (a, b, c, d) -> a
first4)) ([(Double, Integer, Double, Transaction)]
 -> [(Double, Integer, Double, Transaction)])
-> [(Double, Integer, Double, Transaction)]
-> [(Double, Integer, Double, Transaction)]
forall a b. (a -> b) -> a -> b
$
  ((Double, Transaction) -> (Double, Integer, Double, Transaction))
-> [(Double, Transaction)]
-> [(Double, Integer, Double, Transaction)]
forall a b. (a -> b) -> [a] -> [b]
map (\(Double
s,Transaction
t) -> ((Double, Transaction) -> Double
weightedScore (Double
s,Transaction
t), Transaction -> Integer
age Transaction
t, Double
s, Transaction
t)) ([(Double, Transaction)]
 -> [(Double, Integer, Double, Transaction)])
-> [(Double, Transaction)]
-> [(Double, Integer, Double, Transaction)]
forall a b. (a -> b) -> a -> b
$
  ((Double, Transaction) -> Bool)
-> [(Double, Transaction)] -> [(Double, Transaction)]
forall a. (a -> Bool) -> [a] -> [a]
filter ((Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
> Double
similaritythreshold)(Double -> Bool)
-> ((Double, Transaction) -> Double)
-> (Double, Transaction)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.(Double, Transaction) -> Double
forall a b. (a, b) -> a
fst)
  [(TagName -> TagName -> Double
compareDescriptions TagName
desc (TagName -> Double) -> TagName -> Double
forall a b. (a -> b) -> a -> b
$ Transaction -> TagName
tdescription Transaction
t, Transaction
t) | Transaction
t <- [Transaction]
jtxns, Query
q Query -> Transaction -> Bool
`matchesTransaction` Transaction
t]
  where
    latest :: Day
latest = Day -> [Day] -> Day
forall a. a -> [a] -> a
lastDef Day
nulldate ([Day] -> Day) -> [Day] -> Day
forall a b. (a -> b) -> a -> b
$ [Day] -> [Day]
forall a. Ord a => [a] -> [a]
sort ([Day] -> [Day]) -> [Day] -> [Day]
forall a b. (a -> b) -> a -> b
$ (Transaction -> Day) -> [Transaction] -> [Day]
forall a b. (a -> b) -> [a] -> [b]
map Transaction -> Day
tdate [Transaction]
jtxns
    age :: Transaction -> Integer
age = Day -> Day -> Integer
diffDays Day
latest (Day -> Integer) -> (Transaction -> Day) -> Transaction -> Integer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Day
tdate
    -- Combine similarity and recency heuristically. This gave decent results
    -- in my "find most recent invoice" use case in 2023-03,
    -- but will probably need more attention.
    weightedScore :: (Double, Transaction) -> Double
    weightedScore :: (Double, Transaction) -> Double
weightedScore (Double
s, Transaction
t) = Double
100 Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
s Double -> Double -> Double
forall a. Num a => a -> a -> a
- Integer -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Transaction -> Integer
age Transaction
t) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
4

-- | Return a similarity score from 0 to 1.5 for two transaction descriptions. 
-- This is based on compareStrings, with the following modifications:
--
-- - numbers are stripped out before measuring similarity
--
-- - if the (unstripped) first description appears in its entirety within the second,
--   the score is boosted by 0.5.
--
compareDescriptions :: Text -> Text -> Double
compareDescriptions :: TagName -> TagName -> Double
compareDescriptions TagName
a TagName
b =
  (if TagName
a TagName -> TagName -> Bool
`T.isInfixOf` TagName
b then (Double
0.5Double -> Double -> Double
forall a. Num a => a -> a -> a
+) else Double -> Double
forall a. a -> a
id) (Double -> Double) -> Double -> Double
forall a b. (a -> b) -> a -> b
$
  RegexError -> RegexError -> Double
compareStrings (TagName -> RegexError
simplify TagName
a) (TagName -> RegexError
simplify TagName
b)
  where
    simplify :: TagName -> RegexError
simplify = TagName -> RegexError
T.unpack (TagName -> RegexError)
-> (TagName -> TagName) -> TagName -> RegexError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> TagName -> TagName
T.filter (Bool -> Bool
not(Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
.Char -> Bool
isDigit)

-- | Return a similarity score from 0 to 1 for two strings.  This
-- was based on Simon White's string similarity algorithm
-- (http://www.catalysoft.com/articles/StrikeAMatch.html), later found
-- to be https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient,
-- and modified to handle short strings better.
-- Todo: check out http://nlp.fi.muni.cz/raslan/2008/raslan08.pdf#page=14 .
compareStrings :: String -> String -> Double
compareStrings :: RegexError -> RegexError -> Double
compareStrings RegexError
"" RegexError
"" = Double
1
compareStrings [Char
_] RegexError
"" = Double
0
compareStrings RegexError
"" [Char
_] = Double
0
compareStrings [Char
a] [Char
b] = if Char -> Char
toUpper Char
a Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char -> Char
toUpper Char
b then Double
1 else Double
0
compareStrings RegexError
s1 RegexError
s2 = Double
2 Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
commonpairs Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
totalpairs
  where
    pairs1 :: Set RegexError
pairs1      = [RegexError] -> Set RegexError
forall a. Ord a => [a] -> Set a
S.fromList ([RegexError] -> Set RegexError) -> [RegexError] -> Set RegexError
forall a b. (a -> b) -> a -> b
$ RegexError -> [RegexError]
wordLetterPairs (RegexError -> [RegexError]) -> RegexError -> [RegexError]
forall a b. (a -> b) -> a -> b
$ ShowS
uppercase RegexError
s1
    pairs2 :: Set RegexError
pairs2      = [RegexError] -> Set RegexError
forall a. Ord a => [a] -> Set a
S.fromList ([RegexError] -> Set RegexError) -> [RegexError] -> Set RegexError
forall a b. (a -> b) -> a -> b
$ RegexError -> [RegexError]
wordLetterPairs (RegexError -> [RegexError]) -> RegexError -> [RegexError]
forall a b. (a -> b) -> a -> b
$ ShowS
uppercase RegexError
s2
    commonpairs :: Double
commonpairs = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Double) -> Int -> Double
forall a b. (a -> b) -> a -> b
$ Set RegexError -> Int
forall a. Set a -> Int
S.size (Set RegexError -> Int) -> Set RegexError -> Int
forall a b. (a -> b) -> a -> b
$ Set RegexError -> Set RegexError -> Set RegexError
forall a. Ord a => Set a -> Set a -> Set a
S.intersection Set RegexError
pairs1 Set RegexError
pairs2
    totalpairs :: Double
totalpairs  = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Double) -> Int -> Double
forall a b. (a -> b) -> a -> b
$ Set RegexError -> Int
forall a. Set a -> Int
S.size Set RegexError
pairs1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Set RegexError -> Int
forall a. Set a -> Int
S.size Set RegexError
pairs2

wordLetterPairs :: String -> [String]
wordLetterPairs :: RegexError -> [RegexError]
wordLetterPairs = (RegexError -> [RegexError]) -> [RegexError] -> [RegexError]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap RegexError -> [RegexError]
letterPairs ([RegexError] -> [RegexError])
-> (RegexError -> [RegexError]) -> RegexError -> [RegexError]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RegexError -> [RegexError]
words

letterPairs :: String -> [String]
letterPairs :: RegexError -> [RegexError]
letterPairs (Char
a:Char
b:RegexError
rest) = [Char
a,Char
b] RegexError -> [RegexError] -> [RegexError]
forall a. a -> [a] -> [a]
: RegexError -> [RegexError]
letterPairs (Char
bChar -> ShowS
forall a. a -> [a] -> [a]
:RegexError
rest)
letterPairs RegexError
_ = []

-- Newer account type code.

journalAccountType :: Journal -> AccountName -> Maybe AccountType
journalAccountType :: Journal -> TagName -> Maybe AccountType
journalAccountType Journal{Map TagName AccountType
jaccounttypes :: Journal -> Map TagName AccountType
jaccounttypes :: Map TagName AccountType
jaccounttypes} = Map TagName AccountType -> TagName -> Maybe AccountType
accountNameType Map TagName AccountType
jaccounttypes

-- | Add a map of all known account types to the journal.
journalAddAccountTypes :: Journal -> Journal
journalAddAccountTypes :: Journal -> Journal
journalAddAccountTypes Journal
j = Journal
j{jaccounttypes = journalAccountTypes j}

-- | Build a map of all known account types, explicitly declared
-- or inferred from the account's parent or name.
journalAccountTypes :: Journal -> M.Map AccountName AccountType
journalAccountTypes :: Journal -> Map TagName AccountType
journalAccountTypes Journal
j = [(TagName, AccountType)] -> Map TagName AccountType
forall k a. Ord k => [(k, a)] -> Map k a
M.fromList [(TagName
a,AccountType
acctType) | (TagName
a, Just (AccountType
acctType,Bool
_)) <- Tree (TagName, Maybe (AccountType, Bool))
-> [(TagName, Maybe (AccountType, Bool))]
forall a. Tree a -> [a]
flatten Tree (TagName, Maybe (AccountType, Bool))
t']
  where
    t :: Tree TagName
t = [TagName] -> Tree TagName
accountNameTreeFrom ([TagName] -> Tree TagName) -> [TagName] -> Tree TagName
forall a b. (a -> b) -> a -> b
$ Journal -> [TagName]
journalAccountNames Journal
j :: Tree AccountName
    -- Map from the top of the account tree down to the leaves, propagating
    -- account types downward. Keep track of whether the account is declared
    -- (True), in which case the parent account should be preferred, or merely
    -- inferred (False), in which case the inferred type should be preferred.
    t' :: Tree (TagName, Maybe (AccountType, Bool))
t' = Maybe (AccountType, Bool)
-> Tree TagName -> Tree (TagName, Maybe (AccountType, Bool))
settypes Maybe (AccountType, Bool)
forall a. Maybe a
Nothing Tree TagName
t :: Tree (AccountName, Maybe (AccountType, Bool))
      where
        settypes :: Maybe (AccountType, Bool) -> Tree AccountName -> Tree (AccountName, Maybe (AccountType, Bool))
        settypes :: Maybe (AccountType, Bool)
-> Tree TagName -> Tree (TagName, Maybe (AccountType, Bool))
settypes Maybe (AccountType, Bool)
mparenttype (Node TagName
a [Tree TagName]
subs) = (TagName, Maybe (AccountType, Bool))
-> [Tree (TagName, Maybe (AccountType, Bool))]
-> Tree (TagName, Maybe (AccountType, Bool))
forall a. a -> [Tree a] -> Tree a
Node (TagName
a, Maybe (AccountType, Bool)
mtype) ((Tree TagName -> Tree (TagName, Maybe (AccountType, Bool)))
-> [Tree TagName] -> [Tree (TagName, Maybe (AccountType, Bool))]
forall a b. (a -> b) -> [a] -> [b]
map (Maybe (AccountType, Bool)
-> Tree TagName -> Tree (TagName, Maybe (AccountType, Bool))
settypes Maybe (AccountType, Bool)
mtype) [Tree TagName]
subs)
          where
            mtype :: Maybe (AccountType, Bool)
mtype = TagName
-> Map TagName (AccountType, Bool) -> Maybe (AccountType, Bool)
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup TagName
a Map TagName (AccountType, Bool)
declaredtypes Maybe (AccountType, Bool)
-> Maybe (AccountType, Bool) -> Maybe (AccountType, Bool)
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Maybe (AccountType, Bool)
minferred
              where 
                declaredtypes :: Map TagName (AccountType, Bool)
declaredtypes = (,Bool
True) (AccountType -> (AccountType, Bool))
-> Map TagName AccountType -> Map TagName (AccountType, Bool)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Journal -> Map TagName AccountType
journalDeclaredAccountTypes Journal
j
                minferred :: Maybe (AccountType, Bool)
minferred = if Bool
-> ((AccountType, Bool) -> Bool)
-> Maybe (AccountType, Bool)
-> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (AccountType, Bool) -> Bool
forall a b. (a, b) -> b
snd Maybe (AccountType, Bool)
mparenttype
                            then Maybe (AccountType, Bool)
mparenttype
                            else (,Bool
False) (AccountType -> (AccountType, Bool))
-> Maybe AccountType -> Maybe (AccountType, Bool)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TagName -> Maybe AccountType
accountNameInferType TagName
a Maybe (AccountType, Bool)
-> Maybe (AccountType, Bool) -> Maybe (AccountType, Bool)
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Maybe (AccountType, Bool)
mparenttype

-- | Build a map of the account types explicitly declared for each account.
journalDeclaredAccountTypes :: Journal -> M.Map AccountName AccountType
journalDeclaredAccountTypes :: Journal -> Map TagName AccountType
journalDeclaredAccountTypes Journal{Map AccountType [TagName]
jdeclaredaccounttypes :: Journal -> Map AccountType [TagName]
jdeclaredaccounttypes :: Map AccountType [TagName]
jdeclaredaccounttypes} =
  [(TagName, AccountType)] -> Map TagName AccountType
forall k a. Ord k => [(k, a)] -> Map k a
M.fromList ([(TagName, AccountType)] -> Map TagName AccountType)
-> [(TagName, AccountType)] -> Map TagName AccountType
forall a b. (a -> b) -> a -> b
$ [[(TagName, AccountType)]] -> [(TagName, AccountType)]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [(TagName -> (TagName, AccountType))
-> [TagName] -> [(TagName, AccountType)]
forall a b. (a -> b) -> [a] -> [b]
map (,AccountType
t) [TagName]
as | (AccountType
t,[TagName]
as) <- Map AccountType [TagName] -> [(AccountType, [TagName])]
forall k a. Map k a -> [(k, a)]
M.toList Map AccountType [TagName]
jdeclaredaccounttypes]

-- | To all postings in the journal, add any tags from their account
-- (including those inherited from parent accounts).
-- If the same tag exists on posting and account, the latter is ignored.
journalPostingsAddAccountTags :: Journal -> Journal
journalPostingsAddAccountTags :: Journal -> Journal
journalPostingsAddAccountTags Journal
j = (Posting -> Posting) -> Journal -> Journal
journalMapPostings Posting -> Posting
addtags Journal
j
  where addtags :: Posting -> Posting
addtags Posting
p = Posting
p Posting -> [Tag] -> Posting
`postingAddTags` (Journal -> TagName -> [Tag]
journalInheritedAccountTags Journal
j (TagName -> [Tag]) -> TagName -> [Tag]
forall a b. (a -> b) -> a -> b
$ Posting -> TagName
paccount Posting
p)

-- | The account to use for automatically generated conversion postings in this journal:
-- the first of the journalConversionAccounts.
journalConversionAccount :: Journal -> AccountName
journalConversionAccount :: Journal -> TagName
journalConversionAccount = TagName -> [TagName] -> TagName
forall a. a -> [a] -> a
headDef TagName
defaultConversionAccount ([TagName] -> TagName)
-> (Journal -> [TagName]) -> Journal -> TagName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> [TagName]
journalConversionAccounts

-- | All the accounts declared or inferred as Conversion type in this journal.
journalConversionAccounts :: Journal -> [AccountName]
journalConversionAccounts :: Journal -> [TagName]
journalConversionAccounts = Map TagName AccountType -> [TagName]
forall k a. Map k a -> [k]
M.keys (Map TagName AccountType -> [TagName])
-> (Journal -> Map TagName AccountType) -> Journal -> [TagName]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (AccountType -> Bool)
-> Map TagName AccountType -> Map TagName AccountType
forall a k. (a -> Bool) -> Map k a -> Map k a
M.filter (AccountType -> AccountType -> Bool
forall a. Eq a => a -> a -> Bool
==AccountType
Conversion) (Map TagName AccountType -> Map TagName AccountType)
-> (Journal -> Map TagName AccountType)
-> Journal
-> Map TagName AccountType
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Journal -> Map TagName AccountType
jaccounttypes

-- The fallback account to use for automatically generated conversion postings
-- if no account is declared with the Conversion type.
defaultConversionAccount :: TagName
defaultConversionAccount = TagName
"equity:conversion"

-- Various kinds of filtering on journals. We do it differently depending
-- on the command.

-------------------------------------------------------------------------------
-- filtering V2

-- | Keep only transactions matching the query expression.
filterJournalTransactions :: Query -> Journal -> Journal
filterJournalTransactions :: Query -> Journal -> Journal
filterJournalTransactions Query
q j :: Journal
j@Journal{[Transaction]
jtxns :: Journal -> [Transaction]
jtxns :: [Transaction]
jtxns} = Journal
j{jtxns=filter (matchesTransactionExtra (journalAccountType j) q) jtxns}

-- | Keep only postings matching the query expression.
-- This can leave unbalanced transactions.
filterJournalPostings :: Query -> Journal -> Journal
filterJournalPostings :: Query -> Journal -> Journal
filterJournalPostings Query
q j :: Journal
j@Journal{jtxns :: Journal -> [Transaction]
jtxns=[Transaction]
ts} = Journal
j{jtxns=map (filterTransactionPostingsExtra (journalAccountType j) 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 :: Query -> Journal -> Journal
filterJournalRelatedPostings Query
q j :: Journal
j@Journal{jtxns :: Journal -> [Transaction]
jtxns=[Transaction]
ts} = Journal
j{jtxns=map (filterTransactionRelatedPostings q) ts}

-- | Within each posting's amount, keep only the parts matching the query, and
-- remove any postings with all amounts removed.
-- This can leave unbalanced transactions.
filterJournalAmounts :: Query -> Journal -> Journal
filterJournalAmounts :: Query -> Journal -> Journal
filterJournalAmounts Query
q j :: Journal
j@Journal{jtxns :: Journal -> [Transaction]
jtxns=[Transaction]
ts} = Journal
j{jtxns=map (filterTransactionAmounts q) ts}

6989586621680111439">Journal
= = toList= Hledger.Read.JournalReader
  •  ybe.html#Nothing">Nothing}} (Tree (AccountName,journalAccountNamespan class="hs-special">] j =an>+q) ts} -- | Within each posting's amount, keep only the parts matching the query, and ts}m) [ts}span' SmartDate sdate where map Text -> Int 7">-- | Get the transaction that appeared immediately aftsactionAmounts q) Text -> [Text] Text -> [Text] latestSpanContainingHledger.Data.Dates, Hledger.Data, HledgerLayout) / ) /span> -> <-4.18.2 1/src/GHC.Base.html#mappecial">) /span> -> <-4.18.2 1/src/GHC.Base.html#mappecial">) /span> ) latestSpanConpan id="/td>Accountass="hs-identifierf="Hledger.Data.Types.html#Conversion">Conversion) (Map TagName AccountType -> ="hs-glyph">= j =an>+Text -> Text forall a. a -> an>Hledger.Data.Types, Hledger.Data, HledgertmprIsMultiplierHledger.Data.Types, Hledger.Data, Hedger.Utils.IO">Hledger.Utils.IO, Hledger.Utils, HledgerprDates) where Balancie lens laws for -- some fancy cases, they ma.2.1/src/GHC.Base.htm aftsactionAmounts) <- is ( < class="annot"> < class="annot"> < class="annot"> EFDaan> journalTagsDeclajtxns">jtxns=[Trser m (Int, Int) forall a. a -> Parar">ts -- reset the running baladentName">AccountName <0slclass="hs-special">[asprecision [Trser m (Int, Int) foedOrUsed">isNegativeMixedAmount-- description similarity score span class="annottext">[MixedAmount] nonzeros -- check that the sum looks like zero