{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings #-}
module Hledger.Data.AccountName (
accountLeafName
,accountNameComponents
,accountNameDrop
,accountNameFromComponents
,accountNameLevel
,accountNameToAccountOnlyRegex
,accountNameToAccountOnlyRegexCI
,accountNameToAccountRegex
,accountNameToAccountRegexCI
,accountNameTreeFrom
,accountSummarisedName
,accountNameInferType
,accountNameInferTypeExcept
,accountNameType
,defaultBaseConversionAccount
,assetAccountRegex
,cashAccountRegex
,liabilityAccountRegex
,equityAccountRegex
,conversionAccountRegex
,revenueAccountRegex
,expenseAccountRegex
,acctsep
,acctsepchar
,clipAccountName
,clipOrEllipsifyAccountName
,getAccountNameClippedDepth
,elideAccountName
,escapeName
,expandAccountName
,expandAccountNames
,isAccountNamePrefixOf
,isSubAccountNameOf
,parentAccountName
,parentAccountNames
,subAccountNamesFrom
,topAccountNames
,topAccountName
,unbudgetedAccountName
,accountNamePostingType
,accountNameWithoutPostingType
,accountNameWithPostingType
,joinAccountNames
,concatAccountNames
,accountNameApplyAliases
,accountNameApplyAliasesMemo
,tests_AccountName
)
where
import Control.Applicative ((<|>))
import Control.Monad (foldM)
import Data.Foldable (asum, find, toList)
import qualified Data.List.NonEmpty as NE
import qualified Data.Map as M
import Data.Maybe (mapMaybe)
import Data.MemoUgly (memo)
import qualified Data.Set as S
import Data.Text (Text)
import qualified Data.Text as T
import Data.Tree (Tree(..), unfoldTree)
import Safe
import Text.DocLayout (realLength)
import Hledger.Data.Types hiding (asubs)
import Hledger.Utils
import Data.List (partition)
acctsepchar :: Char
acctsepchar :: Char
acctsepchar = Char
':'
acctsep :: Text
acctsep :: Text
acctsep = RegexError -> Text
T.pack [Char
acctsepchar]
defaultBaseConversionAccount :: a
defaultBaseConversionAccount = a
"equity:conversion"
assetAccountRegex :: Regexp
assetAccountRegex = Text -> Regexp
toRegexCI' Text
"^assets?(:|$)"
cashAccountRegex :: Regexp
cashAccountRegex = Text -> Regexp
toRegexCI' Text
"^assets?(:.+)?:(cash|bank|che(ck|que?)(ing)?|savings?|current)(:|$)"
liabilityAccountRegex :: Regexp
liabilityAccountRegex = Text -> Regexp
toRegexCI' Text
"^(debts?|liabilit(y|ies))(:|$)"
equityAccountRegex :: Regexp
equityAccountRegex = Text -> Regexp
toRegexCI' Text
"^equity(:|$)"
conversionAccountRegex :: Regexp
conversionAccountRegex = Text -> Regexp
toRegexCI' Text
"^equity:(trade|trades|trading|conversion)(:|$)"
revenueAccountRegex :: Regexp
revenueAccountRegex = Text -> Regexp
toRegexCI' Text
"^(income|revenue)s?(:|$)"
expenseAccountRegex :: Regexp
expenseAccountRegex = Text -> Regexp
toRegexCI' Text
"^expenses?(:|$)"
accountNameInferType :: AccountName -> Maybe AccountType
accountNameInferType :: Text -> Maybe AccountType
accountNameInferType Text
a
| Regexp -> Text -> Bool
regexMatchText Regexp
cashAccountRegex Text
a = AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Cash
| Regexp -> Text -> Bool
regexMatchText Regexp
assetAccountRegex Text
a = AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Asset
| Regexp -> Text -> Bool
regexMatchText Regexp
liabilityAccountRegex Text
a = AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Liability
| Regexp -> Text -> Bool
regexMatchText Regexp
conversionAccountRegex Text
a = AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Conversion
| Regexp -> Text -> Bool
regexMatchText Regexp
equityAccountRegex Text
a = AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Equity
| Regexp -> Text -> Bool
regexMatchText Regexp
revenueAccountRegex Text
a = AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Revenue
| Regexp -> Text -> Bool
regexMatchText Regexp
expenseAccountRegex Text
a = AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Expense
| Bool
otherwise = Maybe AccountType
forall a. Maybe a
Nothing
accountNameInferTypeExcept :: [AccountType] -> AccountName -> Maybe AccountType
accountNameInferTypeExcept :: [AccountType] -> Text -> Maybe AccountType
accountNameInferTypeExcept [AccountType]
excludedtypes Text
a =
case Text -> Maybe AccountType
accountNameInferType Text
a of
Just AccountType
t | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ AccountType
t AccountType -> [AccountType] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [AccountType]
excludedtypes -> AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
t
Maybe AccountType
_ -> Maybe AccountType
forall a. Maybe a
Nothing
accountNameType :: M.Map AccountName AccountType -> AccountName -> Maybe AccountType
accountNameType :: Map Text AccountType -> Text -> Maybe AccountType
accountNameType Map Text AccountType
atypes Text
a = [Maybe AccountType] -> Maybe AccountType
forall (t :: * -> *) (f :: * -> *) a.
(Foldable t, Alternative f) =>
t (f a) -> f a
asum ((Text -> Maybe AccountType) -> [Text] -> [Maybe AccountType]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> Map Text AccountType -> Maybe AccountType
forall k a. Ord k => k -> Map k a -> Maybe a
`M.lookup` Map Text AccountType
atypes) ([Text] -> [Maybe AccountType]) -> [Text] -> [Maybe AccountType]
forall a b. (a -> b) -> a -> b
$ Text
a Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: Text -> [Text]
parentAccountNames Text
a)
Maybe AccountType -> Maybe AccountType -> Maybe AccountType
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Text -> Maybe AccountType
accountNameInferType Text
a
accountNameComponents :: AccountName -> [Text]
accountNameComponents :: Text -> [Text]
accountNameComponents = HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn Text
acctsep
accountNameFromComponents :: [Text] -> AccountName
accountNameFromComponents :: [Text] -> Text
accountNameFromComponents = Text -> [Text] -> Text
T.intercalate Text
acctsep
accountLeafName :: AccountName -> Text
accountLeafName :: Text -> Text
accountLeafName = [Text] -> Text
forall a. HasCallStack => [a] -> a
last ([Text] -> Text) -> (Text -> [Text]) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [Text]
accountNameComponents
accountSummarisedName :: AccountName -> Text
accountSummarisedName :: Text -> Text
accountSummarisedName Text
a
| [Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
cs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
1 = Text -> [Text] -> Text
T.intercalate Text
":" ((Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Int -> Text -> Text
T.take Int
2) ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ [Text] -> [Text]
forall a. HasCallStack => [a] -> [a]
init [Text]
cs) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
a'
| Bool
otherwise = Text
a'
where
cs :: [Text]
cs = Text -> [Text]
accountNameComponents Text
a
a' :: Text
a' = Text -> Text
accountLeafName Text
a
accountNameLevel :: AccountName -> Int
accountNameLevel :: Text -> Int
accountNameLevel Text
"" = Int
0
accountNameLevel Text
a = Text -> Int
T.length ((Char -> Bool) -> Text -> Text
T.filter (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
==Char
acctsepchar) Text
a) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
unbudgetedAccountName :: T.Text
unbudgetedAccountName :: Text
unbudgetedAccountName = Text
"<unbudgeted>"
accountNamePostingType :: AccountName -> PostingType
accountNamePostingType :: Text -> PostingType
accountNamePostingType Text
a
| Text -> Bool
T.null Text
a = PostingType
RegularPosting
| HasCallStack => Text -> Char
Text -> Char
T.head Text
a Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'[' Bool -> Bool -> Bool
&& HasCallStack => Text -> Char
Text -> Char
T.last Text
a Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
']' = PostingType
BalancedVirtualPosting
| HasCallStack => Text -> Char
Text -> Char
T.head Text
a Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'(' Bool -> Bool -> Bool
&& HasCallStack => Text -> Char
Text -> Char
T.last Text
a Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
')' = PostingType
VirtualPosting
| Bool
otherwise = PostingType
RegularPosting
accountNameWithoutPostingType :: AccountName -> AccountName
accountNameWithoutPostingType :: Text -> Text
accountNameWithoutPostingType Text
a = case Text -> PostingType
accountNamePostingType Text
a of
PostingType
BalancedVirtualPosting -> Text -> Text
textUnbracket Text
a
PostingType
VirtualPosting -> Text -> Text
textUnbracket Text
a
PostingType
RegularPosting -> Text
a
accountNameWithPostingType :: PostingType -> AccountName -> AccountName
accountNameWithPostingType :: PostingType -> Text -> Text
accountNameWithPostingType PostingType
BalancedVirtualPosting = Text -> Text -> Text -> Text
wrap Text
"[" Text
"]" (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
accountNameWithoutPostingType
accountNameWithPostingType PostingType
VirtualPosting = Text -> Text -> Text -> Text
wrap Text
"(" Text
")" (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
accountNameWithoutPostingType
accountNameWithPostingType PostingType
RegularPosting = Text -> Text
accountNameWithoutPostingType
joinAccountNames :: AccountName -> AccountName -> AccountName
joinAccountNames :: Text -> Text -> Text
joinAccountNames Text
a Text
b = [Text] -> Text
concatAccountNames ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Text -> Bool) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Bool
T.null) [Text
a,Text
b]
concatAccountNames :: [AccountName] -> AccountName
concatAccountNames :: [Text] -> Text
concatAccountNames [Text]
as = PostingType -> Text -> Text
accountNameWithPostingType PostingType
t (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ Text -> [Text] -> Text
T.intercalate Text
":" ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
accountNameWithoutPostingType [Text]
as
where t :: PostingType
t = PostingType -> [PostingType] -> PostingType
forall a. a -> [a] -> a
headDef PostingType
RegularPosting ([PostingType] -> PostingType) -> [PostingType] -> PostingType
forall a b. (a -> b) -> a -> b
$ (PostingType -> Bool) -> [PostingType] -> [PostingType]
forall a. (a -> Bool) -> [a] -> [a]
filter (PostingType -> PostingType -> Bool
forall a. Eq a => a -> a -> Bool
/= PostingType
RegularPosting) ([PostingType] -> [PostingType]) -> [PostingType] -> [PostingType]
forall a b. (a -> b) -> a -> b
$ (Text -> PostingType) -> [Text] -> [PostingType]
forall a b. (a -> b) -> [a] -> [b]
map Text -> PostingType
accountNamePostingType [Text]
as
accountNameApplyAliases :: [AccountAlias] -> AccountName -> Either RegexError AccountName
accountNameApplyAliases :: [AccountAlias] -> Text -> Either RegexError Text
accountNameApplyAliases [AccountAlias]
aliases Text
a =
let (Text
name,PostingType
typ) = (Text -> Text
accountNameWithoutPostingType Text
a, Text -> PostingType
accountNamePostingType Text
a)
in (Text -> AccountAlias -> Either RegexError Text)
-> Text -> [AccountAlias] -> Either RegexError Text
forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM
(\Text
acct AccountAlias
alias -> RegexError -> Either RegexError Text -> Either RegexError Text
forall a. Show a => RegexError -> a -> a
dbg6 RegexError
"result" (Either RegexError Text -> Either RegexError Text)
-> Either RegexError Text -> Either RegexError Text
forall a b. (a -> b) -> a -> b
$ AccountAlias -> Text -> Either RegexError Text
aliasReplace (RegexError -> AccountAlias -> AccountAlias
forall a. Show a => RegexError -> a -> a
dbg6 RegexError
"alias" AccountAlias
alias) (RegexError -> Text -> Text
forall a. Show a => RegexError -> a -> a
dbg6 RegexError
"account" Text
acct))
Text
name
[AccountAlias]
aliases
Either RegexError Text
-> (Text -> Either RegexError Text) -> Either RegexError Text
forall a b.
Either RegexError a
-> (a -> Either RegexError b) -> Either RegexError b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Text -> Either RegexError Text
forall a b. b -> Either a b
Right (Text -> Either RegexError Text)
-> (Text -> Text) -> Text -> Either RegexError Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PostingType -> Text -> Text
accountNameWithPostingType PostingType
typ
accountNameApplyAliasesMemo :: [AccountAlias] -> AccountName -> Either RegexError AccountName
accountNameApplyAliasesMemo :: [AccountAlias] -> Text -> Either RegexError Text
accountNameApplyAliasesMemo [AccountAlias]
aliases = (Text -> Either RegexError Text) -> Text -> Either RegexError Text
forall a b. Ord a => (a -> b) -> a -> b
memo ([AccountAlias] -> Text -> Either RegexError Text
accountNameApplyAliases [AccountAlias]
aliases)
aliasReplace :: AccountAlias -> AccountName -> Either RegexError AccountName
aliasReplace :: AccountAlias -> Text -> Either RegexError Text
aliasReplace (BasicAlias Text
old Text
new) Text
a
| Text
old Text -> Text -> Bool
`isAccountNamePrefixOf` Text
a Bool -> Bool -> Bool
|| Text
old Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
a =
Text -> Either RegexError Text
forall a b. b -> Either a b
Right (Text -> Either RegexError Text) -> Text -> Either RegexError Text
forall a b. (a -> b) -> a -> b
$ Text
new Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text -> Text
T.drop (Text -> Int
T.length Text
old) Text
a
| Bool
otherwise = Text -> Either RegexError Text
forall a b. b -> Either a b
Right Text
a
aliasReplace (RegexAlias Regexp
re RegexError
repl) Text
a =
(RegexError -> Text)
-> Either RegexError RegexError -> Either RegexError Text
forall a b. (a -> b) -> Either RegexError a -> Either RegexError b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap RegexError -> Text
T.pack (Either RegexError RegexError -> Either RegexError Text)
-> (RegexError -> Either RegexError RegexError)
-> RegexError
-> Either RegexError Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Regexp -> RegexError -> RegexError -> Either RegexError RegexError
regexReplace Regexp
re RegexError
repl (RegexError -> Either RegexError Text)
-> RegexError -> Either RegexError Text
forall a b. (a -> b) -> a -> b
$ Text -> RegexError
T.unpack Text
a
accountNameDrop :: Int -> AccountName -> AccountName
accountNameDrop :: Int -> Text -> Text
accountNameDrop Int
n Text
a
| Text
a Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
unbudgetedAccountName = Text
a
| Text
unbudgetedAccountAndSep Text -> Text -> Bool
`T.isPrefixOf` Text
a =
case Int -> Text -> Text
accountNameDrop Int
n (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ Int -> Text -> Text
T.drop (Text -> Int
T.length Text
unbudgetedAccountAndSep) Text
a of
Text
"" -> Text
unbudgetedAccountName
Text
a' -> Text
unbudgetedAccountAndSep Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
a'
| Bool
otherwise = [Text] -> Text
accountNameFromComponentsOrElide ([Text] -> Text) -> ([Text] -> [Text]) -> [Text] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> [Text] -> [Text]
forall a. Int -> [a] -> [a]
drop Int
n ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ Text -> [Text]
accountNameComponents Text
a
where
unbudgetedAccountAndSep :: Text
unbudgetedAccountAndSep = Text
unbudgetedAccountName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
acctsep
accountNameFromComponentsOrElide :: [Text] -> Text
accountNameFromComponentsOrElide [] = Text
"..."
accountNameFromComponentsOrElide [Text]
xs = [Text] -> Text
accountNameFromComponents [Text]
xs
expandAccountNames :: [AccountName] -> [AccountName]
expandAccountNames :: [Text] -> [Text]
expandAccountNames = Set Text -> [Text]
forall a. Set a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (Set Text -> [Text]) -> ([Text] -> Set Text) -> [Text] -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> Set Text) -> [Text] -> Set Text
forall m a. Monoid m => (a -> m) -> [a] -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap ([Text] -> Set Text
forall a. Ord a => [a] -> Set a
S.fromList ([Text] -> Set Text) -> (Text -> [Text]) -> Text -> Set Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [Text]
expandAccountName)
expandAccountName :: AccountName -> [AccountName]
expandAccountName :: Text -> [Text]
expandAccountName = ([Text] -> Text) -> [[Text]] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map [Text] -> Text
accountNameFromComponents ([[Text]] -> [Text]) -> (Text -> [[Text]]) -> Text -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. NonEmpty [Text] -> [[Text]]
forall a. NonEmpty a -> [a]
NE.tail (NonEmpty [Text] -> [[Text]])
-> (Text -> NonEmpty [Text]) -> Text -> [[Text]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> NonEmpty [Text]
forall (f :: * -> *) a. Foldable f => f a -> NonEmpty [a]
NE.inits ([Text] -> NonEmpty [Text])
-> (Text -> [Text]) -> Text -> NonEmpty [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [Text]
accountNameComponents
topAccountNames :: [AccountName] -> [AccountName]
topAccountNames :: [Text] -> [Text]
topAccountNames = (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter ((Int
1Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
==) (Int -> Bool) -> (Text -> Int) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Int
accountNameLevel) ([Text] -> [Text]) -> ([Text] -> [Text]) -> [Text] -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> [Text]
expandAccountNames
topAccountName :: AccountName -> AccountName
topAccountName :: Text -> Text
topAccountName = (Char -> Bool) -> Text -> Text
T.takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
acctsepchar)
parentAccountName :: AccountName -> AccountName
parentAccountName :: Text -> Text
parentAccountName = [Text] -> Text
accountNameFromComponents ([Text] -> Text) -> (Text -> [Text]) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> [Text]
forall a. HasCallStack => [a] -> [a]
init ([Text] -> [Text]) -> (Text -> [Text]) -> Text -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [Text]
accountNameComponents
parentAccountNames :: AccountName -> [AccountName]
parentAccountNames :: Text -> [Text]
parentAccountNames Text
a = Text -> [Text]
parentAccountNames' (Text -> [Text]) -> Text -> [Text]
forall a b. (a -> b) -> a -> b
$ Text -> Text
parentAccountName Text
a
where
parentAccountNames' :: Text -> [Text]
parentAccountNames' Text
"" = []
parentAccountNames' Text
a2 = Text
a2 Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: Text -> [Text]
parentAccountNames' (Text -> Text
parentAccountName Text
a2)
isAccountNamePrefixOf :: AccountName -> AccountName -> Bool
isAccountNamePrefixOf :: Text -> Text -> Bool
isAccountNamePrefixOf = Text -> Text -> Bool
T.isPrefixOf (Text -> Text -> Bool) -> (Text -> Text) -> Text -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
acctsep)
isSubAccountNameOf :: AccountName -> AccountName -> Bool
Text
s isSubAccountNameOf :: Text -> Text -> Bool
`isSubAccountNameOf` Text
p =
(Text
p Text -> Text -> Bool
`isAccountNamePrefixOf` Text
s) Bool -> Bool -> Bool
&& (Text -> Int
accountNameLevel Text
s Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== (Text -> Int
accountNameLevel Text
p Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1))
subAccountNamesFrom :: [AccountName] -> AccountName -> [AccountName]
subAccountNamesFrom :: [Text] -> Text -> [Text]
subAccountNamesFrom [Text]
accts Text
a = (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Text -> Text -> Bool
`isSubAccountNameOf` Text
a) [Text]
accts
accountNameTreeFrom :: [AccountName] -> Tree AccountName
accountNameTreeFrom :: [Text] -> Tree Text
accountNameTreeFrom [Text]
accts = ((Text, [Text]) -> (Text, [(Text, [Text])]))
-> (Text, [Text]) -> Tree Text
forall b a. (b -> (a, [b])) -> b -> Tree a
unfoldTree (Text, [Text]) -> (Text, [(Text, [Text])])
grow (Text
"root", [Text] -> [Text]
expandAccountNames [Text]
accts)
where
grow :: ((AccountName, [AccountName]) -> (AccountName, [(AccountName, [AccountName])]))
grow :: (Text, [Text]) -> (Text, [(Text, [Text])])
grow (Text
a,[]) = (Text
a,[])
grow (Text
a,[Text]
rest) = (Text
a, [(Text
s, (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Text
s Text -> Text -> Bool
`isAccountNamePrefixOf`) [Text]
deepersubs) | Text
s <- [Text]
asubs])
where
([Text]
asubs, [Text]
deepersubs) = (Text -> Bool) -> [Text] -> ([Text], [Text])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition (Text -> Text -> Bool
isChildOf Text
a) [Text]
rest
isChildOf :: Text -> Text -> Bool
isChildOf Text
"root" = (Int
1Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
==) (Int -> Bool) -> (Text -> Int) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Int
accountNameLevel
isChildOf Text
acct = (Text -> Text -> Bool
`isSubAccountNameOf` Text
acct)
elideAccountName :: Int -> AccountName -> AccountName
elideAccountName :: Int -> Text -> Text
elideAccountName Int
width Text
s
| Text
" (split)" Text -> Text -> Bool
`T.isSuffixOf` Text
s =
let
names :: [Text]
names = HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn Text
", " (Text -> [Text]) -> Text -> [Text]
forall a b. (a -> b) -> a -> b
$ Int -> Text -> Text
T.take (Text -> Int
T.length Text
s Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
8) Text
s
widthpername :: Int
widthpername = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (Int
width Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
8 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
* (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 ([Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
names) Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1)) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` [Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
names
in
Maybe Int -> Maybe Int -> Bool -> Bool -> Text -> Text
fitText Maybe Int
forall a. Maybe a
Nothing (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
width) Bool
True Bool
False (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$
(Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>Text
" (split)") (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$
Text -> [Text] -> Text
T.intercalate Text
", "
[[Text] -> Text
accountNameFromComponents ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ Int -> [Text] -> [Text] -> [Text]
elideparts Int
widthpername [] ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ Text -> [Text]
accountNameComponents Text
s' | Text
s' <- [Text]
names]
| Bool
otherwise =
Maybe Int -> Maybe Int -> Bool -> Bool -> Text -> Text
fitText Maybe Int
forall a. Maybe a
Nothing (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
width) Bool
True Bool
False (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ [Text] -> Text
accountNameFromComponents ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ Int -> [Text] -> [Text] -> [Text]
elideparts Int
width [] ([Text] -> [Text]) -> [Text] -> [Text]
forall a b. (a -> b) -> a -> b
$ Text -> [Text]
accountNameComponents Text
s
where
elideparts :: Int -> [Text] -> [Text] -> [Text]
elideparts :: Int -> [Text] -> [Text] -> [Text]
elideparts Int
w [Text]
done [Text]
ss
| Text -> Int
forall a. HasChars a => a -> Int
realLength ([Text] -> Text
accountNameFromComponents ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ [Text]
done[Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++[Text]
ss) Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
w = [Text]
done[Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++[Text]
ss
| [Text] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
ss Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
1 = Int -> [Text] -> [Text] -> [Text]
elideparts Int
w ([Text]
done[Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++[Int -> Text -> Text
textTakeWidth Int
2 (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ [Text] -> Text
forall a. HasCallStack => [a] -> a
headErr [Text]
ss]) ([Text] -> [Text]
forall a. HasCallStack => [a] -> [a]
tailErr [Text]
ss)
| Bool
otherwise = [Text]
done[Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++[Text]
ss
clipAccountNameTo :: Int -> AccountName -> AccountName
clipAccountNameTo :: Int -> Text -> Text
clipAccountNameTo Int
n = [Text] -> Text
accountNameFromComponents ([Text] -> Text) -> (Text -> [Text]) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> [Text] -> [Text]
forall a. Int -> [a] -> [a]
take Int
n ([Text] -> [Text]) -> (Text -> [Text]) -> Text -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [Text]
accountNameComponents
getAccountNameClippedDepth :: DepthSpec -> AccountName -> Maybe Int
getAccountNameClippedDepth :: DepthSpec -> Text -> Maybe Int
getAccountNameClippedDepth (DepthSpec Maybe Int
flat [(Regexp, Int)]
regexps) Text
acctName =
[(Regexp, Int)] -> Maybe Int
mostSpecificRegexp [(Regexp, Int)]
regexps Maybe Int -> Maybe Int -> Maybe Int
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Maybe Int
flat
where
mostSpecificRegexp :: [(Regexp, Int)] -> Maybe Int
mostSpecificRegexp = ((Int, Int) -> Int) -> Maybe (Int, Int) -> Maybe Int
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Int, Int) -> Int
forall a b. (a, b) -> b
snd (Maybe (Int, Int) -> Maybe Int)
-> ([(Regexp, Int)] -> Maybe (Int, Int))
-> [(Regexp, Int)]
-> Maybe Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Int, Int) -> Maybe (Int, Int) -> Maybe (Int, Int))
-> Maybe (Int, Int) -> [(Int, Int)] -> Maybe (Int, Int)
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (Int, Int) -> Maybe (Int, Int) -> Maybe (Int, Int)
forall {a} {b}. Ord a => (a, b) -> Maybe (a, b) -> Maybe (a, b)
takeMax Maybe (Int, Int)
forall a. Maybe a
Nothing ([(Int, Int)] -> Maybe (Int, Int))
-> ([(Regexp, Int)] -> [(Int, Int)])
-> [(Regexp, Int)]
-> Maybe (Int, Int)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Regexp, Int) -> Maybe (Int, Int))
-> [(Regexp, Int)] -> [(Int, Int)]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (Regexp, Int) -> Maybe (Int, Int)
matchRegexp
where
takeMax :: (a, b) -> Maybe (a, b) -> Maybe (a, b)
takeMax (a
s, b
d) (Just (a
s', b
d')) = (a, b) -> Maybe (a, b)
forall a. a -> Maybe a
Just ((a, b) -> Maybe (a, b)) -> (a, b) -> Maybe (a, b)
forall a b. (a -> b) -> a -> b
$ if a
s'a -> a -> Bool
forall a. Ord a => a -> a -> Bool
>= a
s then (a
s', b
d') else (a
s, b
d)
takeMax (a
s, b
d) Maybe (a, b)
Nothing = (a, b) -> Maybe (a, b)
forall a. a -> Maybe a
Just (a
s, b
d)
matchRegexp :: (Regexp, Int) -> Maybe (Int, Int)
matchRegexp :: (Regexp, Int) -> Maybe (Int, Int)
matchRegexp (Regexp
r, Int
d) = if Regexp -> Text -> Bool
regexMatchText Regexp
r Text
acctName then (Int, Int) -> Maybe (Int, Int)
forall a. a -> Maybe a
Just (Regexp -> Int
forall {b}. (Bounded b, Num b, Enum b) => Regexp -> b
getSpecificity Regexp
r, Int
d) else Maybe (Int, Int)
forall a. Maybe a
Nothing
getSpecificity :: Regexp -> b
getSpecificity Regexp
r = b -> ((b, Text) -> b) -> Maybe (b, Text) -> b
forall b a. b -> (a -> b) -> Maybe a -> b
maybe b
forall a. Bounded a => a
maxBound (b, Text) -> b
forall a b. (a, b) -> a
fst (Maybe (b, Text) -> b) -> Maybe (b, Text) -> b
forall a b. (a -> b) -> a -> b
$ ((b, Text) -> Bool) -> [(b, Text)] -> Maybe (b, Text)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (Regexp -> Text -> Bool
regexMatchText Regexp
r (Text -> Bool) -> ((b, Text) -> Text) -> (b, Text) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (b, Text) -> Text
forall a b. (a, b) -> b
snd) [(b, Text)]
forall {a}. (Num a, Enum a) => [(a, Text)]
acctParents
acctParents :: [(a, Text)]
acctParents = [a] -> [Text] -> [(a, Text)]
forall a b. [a] -> [b] -> [(a, b)]
zip [a
1..] ([Text] -> [(a, Text)])
-> ([Text] -> [Text]) -> [Text] -> [(a, Text)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
initDef [] ([Text] -> [(a, Text)]) -> [Text] -> [(a, Text)]
forall a b. (a -> b) -> a -> b
$ Text -> [Text]
expandAccountName Text
acctName
clipAccountName :: DepthSpec -> AccountName -> AccountName
clipAccountName :: DepthSpec -> Text -> Text
clipAccountName DepthSpec
ds Text
a = (Text -> Text)
-> (Int -> Text -> Text) -> Maybe Int -> Text -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text -> Text
forall a. a -> a
id Int -> Text -> Text
clipAccountNameTo (DepthSpec -> Text -> Maybe Int
getAccountNameClippedDepth DepthSpec
ds Text
a) Text
a
clipOrEllipsifyAccountName :: DepthSpec -> AccountName -> AccountName
clipOrEllipsifyAccountName :: DepthSpec -> Text -> Text
clipOrEllipsifyAccountName DepthSpec
ds Text
a = Maybe Int -> Text
go (DepthSpec -> Text -> Maybe Int
getAccountNameClippedDepth DepthSpec
ds Text
a)
where
go :: Maybe Int -> Text
go Maybe Int
Nothing = Text
a
go (Just Int
0) = Text
"..."
go (Just Int
n) = Int -> Text -> Text
clipAccountNameTo Int
n Text
a
escapeName :: AccountName -> Text
escapeName :: Text -> Text
escapeName = (Char -> Text) -> Text -> Text
T.concatMap Char -> Text
escapeChar
where
escapeChar :: Char -> Text
escapeChar Char
c = if Char
c Char -> RegexError -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` RegexError
escapedChars then Text -> Char -> Text
T.snoc Text
"\\" Char
c else Char -> Text
T.singleton Char
c
escapedChars :: RegexError
escapedChars = [Char
'[', Char
'?', Char
'+', Char
'|', Char
'(', Char
')', Char
'*', Char
'$', Char
'^', Char
'\\']
accountNameToAccountRegex :: AccountName -> Regexp
accountNameToAccountRegex :: Text -> Regexp
accountNameToAccountRegex Text
a = Text -> Regexp
toRegex' (Text -> Regexp) -> Text -> Regexp
forall a b. (a -> b) -> a -> b
$ Text
"^" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
escapeName Text
a Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"(:|$)"
accountNameToAccountRegexCI :: AccountName -> Regexp
accountNameToAccountRegexCI :: Text -> Regexp
accountNameToAccountRegexCI Text
a = Text -> Regexp
toRegexCI' (Text -> Regexp) -> Text -> Regexp
forall a b. (a -> b) -> a -> b
$ Text
"^" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
escapeName Text
a Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"(:|$)"
accountNameToAccountOnlyRegex :: AccountName -> Regexp
accountNameToAccountOnlyRegex :: Text -> Regexp
accountNameToAccountOnlyRegex Text
a = Text -> Regexp
toRegex' (Text -> Regexp) -> Text -> Regexp
forall a b. (a -> b) -> a -> b
$ Text
"^" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
escapeName Text
a Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"$"
accountNameToAccountOnlyRegexCI :: AccountName -> Regexp
accountNameToAccountOnlyRegexCI :: Text -> Regexp
accountNameToAccountOnlyRegexCI Text
a = Text -> Regexp
toRegexCI' (Text -> Regexp) -> Text -> Regexp
forall a b. (a -> b) -> a -> b
$ Text
"^" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
escapeName Text
a Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"$"
tests_AccountName :: TestTree
tests_AccountName = RegexError -> [TestTree] -> TestTree
testGroup RegexError
"AccountName" [
RegexError -> Assertion -> TestTree
testCase RegexError
"accountNameTreeFrom" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
[Text] -> Tree Text
accountNameTreeFrom [Text
"a"] Tree Text -> Tree Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"root" [Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"a" []]
[Text] -> Tree Text
accountNameTreeFrom [Text
"a",Text
"b"] Tree Text -> Tree Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"root" [Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"a" [], Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"b" []]
[Text] -> Tree Text
accountNameTreeFrom [Text
"a",Text
"a:b"] Tree Text -> Tree Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"root" [Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"a" [Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"a:b" []]]
[Text] -> Tree Text
accountNameTreeFrom [Text
"a:b:c"] Tree Text -> Tree Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"root" [Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"a" [Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"a:b" [Text -> [Tree Text] -> Tree Text
forall a. a -> [Tree a] -> Tree a
Node Text
"a:b:c" []]]]
,RegexError -> Assertion -> TestTree
testCase RegexError
"expandAccountNames" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
[Text] -> [Text]
expandAccountNames [Text
"assets:cash",Text
"assets:checking",Text
"expenses:vacation"] [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
[Text
"assets",Text
"assets:cash",Text
"assets:checking",Text
"expenses",Text
"expenses:vacation"]
,RegexError -> Assertion -> TestTree
testCase RegexError
"isAccountNamePrefixOf" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
Text
"assets" Text -> Text -> Bool
`isAccountNamePrefixOf` Text
"assets" Bool -> Bool -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Bool
False
Text
"assets" Text -> Text -> Bool
`isAccountNamePrefixOf` Text
"assets:bank" Bool -> Bool -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Bool
True
Text
"assets" Text -> Text -> Bool
`isAccountNamePrefixOf` Text
"assets:bank:checking" Bool -> Bool -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Bool
True
Text
"my assets" Text -> Text -> Bool
`isAccountNamePrefixOf` Text
"assets:bank" Bool -> Bool -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Bool
False
,RegexError -> Assertion -> TestTree
testCase RegexError
"isSubAccountNameOf" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
Text
"assets" Text -> Text -> Bool
`isSubAccountNameOf` Text
"assets" Bool -> Bool -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Bool
False
Text
"assets:bank" Text -> Text -> Bool
`isSubAccountNameOf` Text
"assets" Bool -> Bool -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Bool
True
Text
"assets:bank:checking" Text -> Text -> Bool
`isSubAccountNameOf` Text
"assets" Bool -> Bool -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Bool
False
Text
"assets:bank" Text -> Text -> Bool
`isSubAccountNameOf` Text
"my assets" Bool -> Bool -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Bool
False
,RegexError -> Assertion -> TestTree
testCase RegexError
"accountNameInferType" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
Text -> Maybe AccountType
accountNameInferType Text
"assets" Maybe AccountType -> Maybe AccountType -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Asset
Text -> Maybe AccountType
accountNameInferType Text
"assets:cash" Maybe AccountType -> Maybe AccountType -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Cash
Text -> Maybe AccountType
accountNameInferType Text
"assets:A/R" Maybe AccountType -> Maybe AccountType -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Asset
Text -> Maybe AccountType
accountNameInferType Text
"liabilities" Maybe AccountType -> Maybe AccountType -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Liability
Text -> Maybe AccountType
accountNameInferType Text
"equity" Maybe AccountType -> Maybe AccountType -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Equity
Text -> Maybe AccountType
accountNameInferType Text
"equity:conversion" Maybe AccountType -> Maybe AccountType -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Conversion
Text -> Maybe AccountType
accountNameInferType Text
"expenses" Maybe AccountType -> Maybe AccountType -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Expense
Text -> Maybe AccountType
accountNameInferType Text
"revenues" Maybe AccountType -> Maybe AccountType -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Revenue
Text -> Maybe AccountType
accountNameInferType Text
"revenue" Maybe AccountType -> Maybe AccountType -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Revenue
Text -> Maybe AccountType
accountNameInferType Text
"income" Maybe AccountType -> Maybe AccountType -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Revenue
,RegexError -> Assertion -> TestTree
testCase RegexError
"joinAccountNames" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
Text -> Text -> Text
joinAccountNames Text
"assets" Text
"cash" Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"assets:cash"
Text -> Text -> Text
joinAccountNames Text
"assets:cash" Text
"a" Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"assets:cash:a"
Text -> Text -> Text
joinAccountNames Text
"assets" Text
"(cash)" Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"(assets:cash)"
Text -> Text -> Text
joinAccountNames Text
"assets" Text
"[cash]" Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"[assets:cash]"
Text -> Text -> Text
joinAccountNames Text
"(assets)" Text
"cash" Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"(assets:cash)"
Text -> Text -> Text
joinAccountNames Text
"" Text
"assets" Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"assets"
Text -> Text -> Text
joinAccountNames Text
"assets" Text
"" Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"assets"
,RegexError -> Assertion -> TestTree
testCase RegexError
"concatAccountNames" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ do
[Text] -> Text
concatAccountNames [Text
"assets", Text
"cash"] Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"assets:cash"
[Text] -> Text
concatAccountNames [Text
"assets:cash", Text
"a"] Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"assets:cash:a"
[Text] -> Text
concatAccountNames [Text
"assets", Text
"(cash)"] Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"(assets:cash)"
[Text] -> Text
concatAccountNames [Text
"assets", Text
"[cash]"] Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"[assets:cash]"
[Text] -> Text
concatAccountNames [Text
"(assets)", Text
"cash"] Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"(assets:cash)"
[Text] -> Text
concatAccountNames [Text
"", Text
"assets"] Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
":assets"
[Text] -> Text
concatAccountNames [Text
"assets", Text
""] Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"assets:"
]