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