diff --git a/MANUAL.md b/MANUAL.md index f3e70af93..e6f683506 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -439,7 +439,8 @@ Included files are also affected, eg: ### Account aliases You can define account aliases to rewrite certain account names (and their subaccounts). -The format is `alias ORIGACCT = ALIAS`. Use `end aliases` to forget all previously defined aliases. +The format is `alias ORIG = ALIAS`, where ORIG and ALIAS are full account names. +To forget all aliases defined to this point, use `end aliases`. Here's an example: say a sole proprietor has a personal.journal: @@ -475,6 +476,21 @@ giving: expenses:office supplies $1 assets:business checking $-1 +You can also specify aliases on the command line. This could be useful to +rewrite account names when sharing a report with someone else, such as +your accountant: + + $ hledger --alias 'my earning=income:business' + +Command-line alias options are applied after any alias directives in the +journal. At most one alias directive and one alias option will be applied +to each account name. + +Aliases tend to be a little more reliable than post-processing with sed or +similar, as they know about account name syntax, posting type indicators +etc. Note aliases only change the displayed names, not the account +hierarchy - aliasing two accounts to the same name does not merge them +into one account. ## Core commands diff --git a/hledger-lib/Hledger/Data/Journal.hs b/hledger-lib/Hledger/Data/Journal.hs index 1067ffddf..e53b2feb0 100644 --- a/hledger-lib/Hledger/Data/Journal.hs +++ b/hledger-lib/Hledger/Data/Journal.hs @@ -242,6 +242,13 @@ journalSelectingDate ActualDate j = j journalSelectingDate EffectiveDate j = j{jtxns=map (journalTransactionWithDate EffectiveDate) $ jtxns j} +-- | Apply additional account aliases (eg from the command-line) to all postings in a journal. +journalApplyAliases :: [(AccountName,AccountName)] -> Journal -> Journal +journalApplyAliases aliases j@Journal{jtxns=ts} = j{jtxns=map fixtransaction ts} + where + fixtransaction t@Transaction{tpostings=ps} = t{tpostings=map fixposting ps} + fixposting p@Posting{paccount=a} = p{paccount=accountNameApplyAliases aliases a} + -- | Do post-parse processing on a journal, to make it ready for use. journalFinalise :: ClockTime -> LocalTime -> FilePath -> String -> JournalContext -> Journal -> Either String Journal journalFinalise tclock tlocal path txt ctx j@Journal{files=fs} = diff --git a/hledger-lib/Hledger/Data/Posting.hs b/hledger-lib/Hledger/Data/Posting.hs index aa457b5e9..2b101081a 100644 --- a/hledger-lib/Hledger/Data/Posting.hs +++ b/hledger-lib/Hledger/Data/Posting.hs @@ -99,7 +99,7 @@ postingsDateSpan [] = DateSpan Nothing Nothing postingsDateSpan ps = DateSpan (Just $ postingDate $ head ps') (Just $ addDays 1 $ postingDate $ last ps') where ps' = sortBy (comparing postingDate) ps --- balanced/non-balanced posting indicators +-- AccountName stuff that depends on PostingType accountNamePostingType :: AccountName -> PostingType accountNamePostingType a @@ -131,6 +131,15 @@ concatAccountNames :: [AccountName] -> AccountName concatAccountNames as = accountNameWithPostingType t $ intercalate ":" $ map accountNameWithoutPostingType as where t = headDef RegularPosting $ filter (/= RegularPosting) $ map accountNamePostingType as +-- | Rewrite an account name using the first applicable alias from the given list, if any. +accountNameApplyAliases :: [(AccountName,AccountName)] -> AccountName -> AccountName +accountNameApplyAliases aliases a = withorigtype + where + (a',t) = (accountNameWithoutPostingType a, accountNamePostingType a) + firstmatchingalias = headDef Nothing $ map Just $ filter (\(orig,_) -> orig == a' || orig `isAccountNamePrefixOf` a') aliases + rewritten = maybe a' (\(orig,alias) -> alias++drop (length orig) a') firstmatchingalias + withorigtype = accountNameWithPostingType t rewritten + tests_Hledger_Data_Posting = TestList [ "accountNamePostingType" ~: do diff --git a/hledger-lib/Hledger/Read/JournalReader.hs b/hledger-lib/Hledger/Read/JournalReader.hs index 4191037ac..cd3284db2 100644 --- a/hledger-lib/Hledger/Read/JournalReader.hs +++ b/hledger-lib/Hledger/Read/JournalReader.hs @@ -482,12 +482,7 @@ modifiedaccountname = do prefix <- getParentAccount let prefixed = prefix `joinAccountNames` a aliases <- getAccountAliases - let t = accountNamePostingType prefixed - a' = accountNameWithoutPostingType prefixed - match = headDef Nothing $ map Just $ filter (\(orig,_) -> orig == a' || orig `isAccountNamePrefixOf` a') aliases - rewritten = maybe a' (\(orig,alias) -> alias++drop (length orig) a') match - withtype = accountNameWithPostingType t rewritten - return withtype + return $ accountNameApplyAliases aliases prefixed -- | Parse an account name. Account names may have single spaces inside -- them, and are terminated by two or more spaces. They should have one or diff --git a/hledger/Hledger/Cli/Options.hs b/hledger/Hledger/Cli/Options.hs index 64d915f45..ba70f7039 100644 --- a/hledger/Hledger/Cli/Options.hs +++ b/hledger/Hledger/Cli/Options.hs @@ -62,6 +62,7 @@ options_cli :: [OptDescr Opt] options_cli = [ Option "f" ["file"] (ReqArg File "FILE") "use a different journal/timelog file; - means stdin" ,Option "" ["no-new-accounts"] (NoArg NoNewAccts) "don't allow to create new accounts" + ,Option "" ["alias"] (ReqArg Alias "ACCT=ALIAS") "display ACCT's name as ALIAS instead" ,Option "b" ["begin"] (ReqArg Begin "DATE") "report on transactions on or after this date" ,Option "e" ["end"] (ReqArg End "DATE") "report on transactions before this date" ,Option "p" ["period"] (ReqArg Period "EXPR") ("report on transactions during the specified period\n" ++ @@ -96,6 +97,7 @@ options_cli = [ data Opt = File {value::String} | NoNewAccts + | Alias {value::String} | Begin {value::String} | End {value::String} | Period {value::String} @@ -306,6 +308,18 @@ journalFilePathFromOpts opts = do f <- if istimequery then myTimelogPath else myJournalPath return $ last $ f : optValuesForConstructor File opts +aliasesFromOpts :: [Opt] -> [(AccountName,AccountName)] +aliasesFromOpts opts = map parseAlias $ optValuesForConstructor Alias opts + where + -- similar to ledgerAlias + parseAlias :: String -> (AccountName,AccountName) + parseAlias s = (accountNameWithoutPostingType $ strip orig + ,accountNameWithoutPostingType $ strip alias') + where + (orig, alias) = break (=='=') s + alias' = case alias of ('=':rest) -> rest + _ -> orig + -- | Gather filter pattern arguments into a list of account patterns and a -- list of description patterns. We interpret pattern arguments as -- follows: those prefixed with "desc:" are description patterns, all diff --git a/hledger/Hledger/Cli/Utils.hs b/hledger/Hledger/Cli/Utils.hs index 46186ddef..8ac8585a3 100644 --- a/hledger/Hledger/Cli/Utils.hs +++ b/hledger/Hledger/Cli/Utils.hs @@ -38,7 +38,7 @@ import System.Time (ClockTime, getClockTime, diffClockTimes, TimeDiff(TimeDiff)) import Test.HUnit import Text.Printf -import Hledger.Cli.Options (Opt(..),journalFilePathFromOpts,whichDateFromOpts) +import Hledger.Cli.Options import Hledger.Data import Hledger.Read import Hledger.Utils @@ -51,7 +51,8 @@ withJournalDo opts args _ cmd = do -- We kludgily read the file before parsing to grab the full text, unless -- it's stdin, or it doesn't exist and we are adding. We read it strictly -- to let the add command work. - journalFilePathFromOpts opts >>= readJournalFile Nothing >>= either error' (cmd opts args) + journalFilePathFromOpts opts >>= readJournalFile Nothing >>= + either error' (cmd opts args . journalApplyAliases (aliasesFromOpts opts)) -- -- | Get a journal from the given string and options, or throw an error. -- readJournalWithOpts :: [Opt] -> String -> IO Journal diff --git a/tests/aliases.test b/tests/aliases.test new file mode 100644 index 000000000..a11fe04f2 --- /dev/null +++ b/tests/aliases.test @@ -0,0 +1,49 @@ +# alias-related tests + +# 1. command-line --alias option. Note multiple applicable aliases, but +# only one is applied per account name. Spaces are allowed if quoted. +bin/hledger -f- print --alias 'a a=A' --alias b=B +<<< +2011/01/01 + a a 1 + c + +>>> +2011/01/01 + A 1 + c -1 + +>>>=0 + +# 2. alias directive, and an account with unbalanced posting indicators. +bin/hledger -f- print +<<< +alias b=B + +2011/01/01 + (b) 1 + +>>> +2011/01/01 + (B) 1 + +>>>=0 + +# 3. --alias options run after alias directives. Subaccounts are also +# matched and rewritten. Accounts with an internal part matching the alias +# are ignored. +bin/hledger -f- print --alias a=A --alias B=C +<<< +alias a=B + +2011/01/01 + [a:x] 1 + [x:a:x] + +>>> +2011/01/01 + [C:x] 1 + [x:a:x] -1 + +>>>2 +>>>=0