From d985e89badf3a761a517b08022891a1f9064a6e1 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Thu, 17 Oct 2024 12:23:52 -1000 Subject: [PATCH] imp: default V accounts become just E when a new V account is declared The equity:conversion account, and its variations equity:trade(s) and equity:trading, normally detected as V/Conversion type, now become ordinary E/Equity accounts if some other account is declared as V/Conversion type. This is motivated by the next commit, in which `check accounts` will stop warning about conversion accounts and their subaccounts, which means all of the above names and their subaccounts would remain always exempt from strict account checking. Now, if the user declares their own conversion account, those default accounts will become controllable by account checking again. Which at least reduces the allowlist a bit. Hopefully this won't cause hassles. --- hledger-lib/Hledger/Data/AccountName.hs | 53 +++++++++++++++---------- hledger-lib/Hledger/Data/Journal.hs | 9 +++-- hledger/test/accounts.test | 53 +++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 25 deletions(-) diff --git a/hledger-lib/Hledger/Data/AccountName.hs b/hledger-lib/Hledger/Data/AccountName.hs index 101ed8f37..245de2f76 100644 --- a/hledger-lib/Hledger/Data/AccountName.hs +++ b/hledger-lib/Hledger/Data/AccountName.hs @@ -21,6 +21,7 @@ module Hledger.Data.AccountName ( ,accountNameTreeFrom ,accountSummarisedName ,accountNameInferType + ,accountNameInferTypeExcept ,accountNameType ,defaultBaseConversionAccount ,assetAccountRegex @@ -84,27 +85,6 @@ acctsepchar = ':' acctsep :: Text acctsep = T.pack [acctsepchar] --- accountNameComponents :: AccountName -> [String] --- accountNameComponents = splitAtElement acctsepchar - -accountNameComponents :: AccountName -> [Text] -accountNameComponents = T.splitOn acctsep - -accountNameFromComponents :: [Text] -> AccountName -accountNameFromComponents = T.intercalate acctsep - -accountLeafName :: AccountName -> Text -accountLeafName = last . accountNameComponents - --- | Truncate all account name components but the last to two characters. -accountSummarisedName :: AccountName -> Text -accountSummarisedName a - -- length cs > 1 = take 2 (head cs) ++ ":" ++ a' - | length cs > 1 = T.intercalate ":" (map (T.take 2) $ init cs) <> ":" <> a' - | otherwise = a' - where - cs = accountNameComponents a - a' = accountLeafName a -- The base conversion account name used by --infer-equity, -- when no other account of type V/Conversion has been declared. defaultBaseConversionAccount = "equity:conversion" @@ -132,6 +112,15 @@ accountNameInferType a | regexMatchText expenseAccountRegex a = Just Expense | otherwise = Nothing +-- | Like accountNameInferType, but exclude the provided types from the guesses. +-- Used eg to prevent "equity:conversion" being inferred as Conversion when a different +-- account has been declared with that type. +accountNameInferTypeExcept :: [AccountType] -> AccountName -> Maybe AccountType +accountNameInferTypeExcept excludedtypes a = + case accountNameInferType a of + Just t | not $ t `elem` excludedtypes -> Just t + _ -> Nothing + -- Extract the 'AccountType' of an 'AccountName' by looking it up in the -- provided Map, traversing the parent accounts if necessary. If none of those -- work, try 'accountNameInferType'. @@ -139,6 +128,28 @@ accountNameType :: M.Map AccountName AccountType -> AccountName -> Maybe Account accountNameType atypes a = asum (map (`M.lookup` atypes) $ a : parentAccountNames a) <|> accountNameInferType a +-- accountNameComponents :: AccountName -> [String] +-- accountNameComponents = splitAtElement acctsepchar + +accountNameComponents :: AccountName -> [Text] +accountNameComponents = T.splitOn acctsep + +accountNameFromComponents :: [Text] -> AccountName +accountNameFromComponents = T.intercalate acctsep + +accountLeafName :: AccountName -> Text +accountLeafName = last . accountNameComponents + +-- | Truncate all account name components but the last to two characters. +accountSummarisedName :: AccountName -> Text +accountSummarisedName a + -- length cs > 1 = take 2 (head cs) ++ ":" ++ a' + | length cs > 1 = T.intercalate ":" (map (T.take 2) $ init cs) <> ":" <> a' + | otherwise = a' + where + cs = accountNameComponents a + a' = accountLeafName a + -- | The level (depth) of an account name. -- -- >>> accountNameLevel "" -- special case diff --git a/hledger-lib/Hledger/Data/Journal.hs b/hledger-lib/Hledger/Data/Journal.hs index 1235384a7..efb9938d5 100644 --- a/hledger-lib/Hledger/Data/Journal.hs +++ b/hledger-lib/Hledger/Data/Journal.hs @@ -579,7 +579,7 @@ journalAddAccountTypes j = j{jaccounttypes = journalAccountTypes j} -- | An account type inherited from the parent account(s), -- and whether it was originally declared by an account directive (true) or inferred from an account name (false). -type ParentAccountType = ( AccountType, Bool ) +type ParentAccountType = (AccountType, Bool) -- | Build a map of all known account types, explicitly declared -- or inferred from the account's parent or name. @@ -587,11 +587,12 @@ journalAccountTypes :: Journal -> M.Map AccountName AccountType journalAccountTypes j = M.fromList [(a,acctType) | (a, Just (acctType,_)) <- flatten t'] where t = accountNameTreeFrom $ journalAccountNames j :: Tree AccountName - -- Map from the top of the account tree down to the leaves, applying any explicitly declared account types, + -- Traverse downward through the account tree, applying any explicitly declared account types, -- otherwise inferring account types from account names when possible, and propagating account types downward. -- Declared account types (possibly inherited from parent) are preferred, inferred types are used as a fallback. t' = setTypeHereAndBelow Nothing t :: Tree (AccountName, Maybe (AccountType, Bool)) where + declaredtypes = M.keys $ jdeclaredaccounttypes j declaredtypesbyname = journalDeclaredAccountTypes j & fmap (,True) setTypeHereAndBelow :: Maybe ParentAccountType -> Tree AccountName -> Tree (AccountName, Maybe ParentAccountType) setTypeHereAndBelow mparenttype (Node a subs) = Node (a, mnewtype) (map (setTypeHereAndBelow mnewtype) subs) @@ -601,9 +602,9 @@ journalAccountTypes j = M.fromList [(a,acctType) | (a, Just (acctType,_)) <- fla mthisacctdeclaredtype = M.lookup a declaredtypesbyname mparentacctdeclaredtype = if fromMaybe False $ snd <$> mparenttype then mparenttype else Nothing mparentacctinferredtype = if not $ fromMaybe True $ snd <$> mparenttype then mparenttype else Nothing - mthisacctinferredtype = accountNameInferType a & fmap (,False) + mthisacctinferredtype = accountNameInferTypeExcept declaredtypes a & fmap (,False) -- XXX not sure about this Except logic.. but for now, tests pass --- | Build a map of the account types explicitly declared for each account. +-- | Build a map from account names to explicitly declared account types. journalDeclaredAccountTypes :: Journal -> M.Map AccountName AccountType journalDeclaredAccountTypes Journal{jdeclaredaccounttypes} = M.fromList $ concat [map (,t) as | (t,as) <- M.toList jdeclaredaccounttypes] diff --git a/hledger/test/accounts.test b/hledger/test/accounts.test index a08a5b80a..0cc7b37cb 100644 --- a/hledger/test/accounts.test +++ b/hledger/test/accounts.test @@ -39,3 +39,56 @@ account asset $ hledger -f - accounts --types asset ; type: A unknown ; type: + +# ** 7. equity:conversion, and several other names, are detected as V/Conversion type by default. +< +account equity:conversion +account equity:trade +account equity:trades +account equity:trading +$ hledger -f- accounts --types +equity:conversion ; type: V +equity:trade ; type: V +equity:trades ; type: V +equity:trading ; type: V + +# ** 8. If any other account name is declared with V type, those default names become just ordinary equity accounts. +< +account trade ; type: V +account equity:conversion +account equity:trade +account equity:trades +account equity:trading +$ hledger -f- accounts --types +trade ; type: V +equity:conversion ; type: E +equity:trade ; type: E +equity:trades ; type: E +equity:trading ; type: E + +# ** 9. --infer-equity uses equity:conversion as its base account by default. +< +2024-01-01 + a 1A + b -1B + +$ hledger -f- accounts --infer-equity +a +b +equity:conversion:A-B:A +equity:conversion:A-B:B + +# ** 10. With a custom conversion account declared, --infer-equity uses that instead. +< +account trade ; type:V + +2024-01-01 + a 1A + b -1B + +$ hledger -f- accounts --infer-equity +trade +trade:A-B:A +trade:A-B:B +a +b