diff --git a/BalanceCommand.hs b/BalanceCommand.hs index 5adda3fc4..9b8e3b67b 100644 --- a/BalanceCommand.hs +++ b/BalanceCommand.hs @@ -2,14 +2,9 @@ A ledger-compatible @balance@ command. -ledger's balance command is easy to use but hard to describe precisely. -Here are some attempts. - - -I. high level description with examples ---------------------------------------- - -We'll use sample.ledger, which has the following account tree: +ledger's balance command is easy to use but not easy to describe +precisely. In the examples below we'll use sample.ledger, which has the +following account tree: @ assets @@ -28,8 +23,9 @@ We'll use sample.ledger, which has the following account tree: @ The balance command shows accounts with their aggregate balances. -Subaccounts are displayed with more indentation. Each balance is the sum -of any transactions in that account plus any balances from subaccounts: +Subaccounts are displayed indented below their parent. Each balance is the +sum of any transactions in that account plus any balances from +subaccounts: @ $ hledger -f sample.ledger balance @@ -45,15 +41,25 @@ of any transactions in that account plus any balances from subaccounts: $1 liabilities:debts @ -Usually, the non-interesting accounts are elided or omitted. To be -precise, an interesting account is one with: a non-zero balance, or a -balance different from its single subaccount, or two or more interesting -subaccounts. (More subtleties to be filled in here.) +Usually, the non-interesting accounts are elided or omitted. Above, +@checking@ is omitted because it has no subaccounts and a zero balance. +@bank@ is elided because it has only a single displayed subaccount +(@saving@) and it would be showing the same balance as that ($1). Ditto +for @liabilities@. We will return to this in a moment. -So, above, @checking@ is omitted because it has no interesting subaccounts -and a zero balance. @bank@ is elided because it has only a single -interesting subaccount (saving) and it would be showing the same balance -($1). Ditto for @liabilities@. +The --depth argument can be used to limit the depth of the balance report. +So, to see just the top level accounts: + +@ +$ hledger -f sample.ledger balance --depth 1 + $-1 assets + $2 expenses + $-2 income + $1 liabilities +@ + +This time liabilities has no displayed subaccounts (due to --depth) and +is not elided. With one or more account pattern arguments, the balance command shows accounts whose name matches one of the patterns, plus their parents @@ -76,237 +82,15 @@ Also, the balance report shows the total of all displayed accounts, when that is non-zero. Here, it is displayed because the accounts shown add up to $-1. +Here is a more precise definition of \"interesting\" accounts in ledger's +balance report: -II. Some notes for the implementation -------------------------------------- - -- a simple balance report shows top-level accounts - -- with an account pattern, it shows accounts whose leafname matches, plus their parents - -- with the subtotal option, it also shows all subaccounts of the above - -- zero-balance leaf accounts are removed - -- the resulting account tree is displayed with each account's aggregated - balance, with boring parents prefixed to the next line - -- a boring parent has the same balance as its child and is not explicitly - matched by the display options. - -- the sum of the balances shown is displayed at the end, if it is non-zero - - -III. John's description 2009/02 -------------------------------- - -johnw: \"Since you've been re-implementing the balance report in Haskell, I thought -I'd share with you in pseudocode how I rewrote it for Ledger 3.0, since -the old method never stopped with the bugs. The new scheme uses a 5 stage -algorithm, with each stage gathering information for the next: - -STEP 1 - -Based on the user's query, walk through all the transactions in their -journal, finding which ones to include in the account subtotals. For each -transaction that matches, mark the account as VISITED. - -STEP 2 - -Recursively walk the accounts tree, depth-first, computing aggregate -totals and aggregate \"counts\" (number of transactions contributing to the -aggregate total). - -STEP 3 - -Walk the account tree again, breadth-first, and for every VISITED account, -check whether it matches the user's \"display predicate\". If so, mark the -account as MATCHING. - -STEP 4 - -Do an in-order traversal of the account tree. Except for the top-most -account (which serves strictly as a container for the other accounts): - -a. If the account was MATCHING, or two or more of its children are - MATCHING or had descendents who were MATCHING, display the account. - -b. Otherwise, if the account had *any* children or descendants who - were VISITED and *no* children or descendants who were MATCHING, - then apply the display predicate from STEP 3 to the account. If - it matches, also print this account. (This step allows -E to - report empty accounts which otherwise did match the original - query). - -STEP 5 - -When printing an account, display a \"depth spacer\" followed by the \"partial name\". -tal -The partial name is found by taking the base account's name, then -prepending to it every non-MATCHING parent until a MATCHING parent is -found. - -The depth spacer is found by outputting two spaces for every MATCHING parent. - -This way, \"Assets:Bank:Checking\" might be reported as: - - Assets - Bank - Checking - -or - - Assets - Bank:Checking - -or - - Assets:Bank:Checking - -Depending on whether the parents were or were not reported for their own reasons. -\" - -\"I just had to add one more set of tree traversals, to correctly determine -whether a final balance should be displayed - -without --flat, sort at each level in the hierarchy -with --flat, sort across all accounts\" - -IV. A functional description ------------------------------ - -1. filter the transactions, keeping only those included in the calculation. - Remember the subset of accounts involved. (VISITED) - -2. generate a full account & balance tree from all transactions - -3. Remember the subset of VISITED accounts which are matched for display. - (MATCHING) - -4. walk through the account tree: - - a. If the account is in MATCHING, or two or more of its children are or - have descendants who are, display it. - - b. Otherwise, if the account has any children or descendants in VISITED - but none in MATCHING, and it is matched for display, display it. - (allows -E to report empty accounts which otherwise did match the - original query). - -5. when printing an account, display a \"depth spacer\" followed by the - \"partial name\". The partial name is found by taking the base account's - name, then prepending to it every non-MATCHING parent until a MATCHING - parent is found. The depth spacer is two spaces per MATCHING parent. - -6. I just had to add one more set of tree traversals, to correctly - determine whether a final balance should be displayed - -7. without --flat, sort at each level in the hierarchy - with --flat, sort across all accounts - -V. Another functional description with new terminology ------------------------------------------------------- - -- included transactions are those included in the calculation, specified - by -b, -e, -p, -C, -R, account patterns and description patterns. - -- included accounts are the accounts referenced by included transactions. - -- matched transactions are the included transactions which match the - display predicate, specified by -d. - -- matched accounts are the included accounts which match the display - predicate, specified by -d, --depth, -E, -s - -- an account name tree is the full hierarchy of account names implied by a - set of transactions - -- an account tree is an account name tree augmented with the aggregate - balances and transaction counts for each named account - -- the included account tree is the account tree for the included transactions - -- a matched account tree contains one or more matched accounts - -- to generate the balance report, walk through the included account tree - and display each account if - - - it is matching - - - or it has two more more matching subtrees - - - or it has included offspring but no matching offspring - -- to display an account, display an indent then the \"partial name\". The - partial name is the account's name, prefixed by each unmatched parent - until a matched parent is found. The indent is two spaces per matched - parent. - - -VI. John's description 2009/03/11 ---------------------------------- - -johnw: \"Well, I had to rewrite the balance reporting code yet again, -because it wouldn't work with --depth correctly. Here's the new algorithm. - -STEP 1: Walk all postings, looking for those that match the user's query. - As a match is found, mark its account VISITED. - -STEP 2: Do a traversal of all accounts, sorting as need be, and collect - them all into an ordered list. - -STEP 3: Keeping that list on the side, do a *depth-first* traversal of - the account tree. - - visited = 0 - to_display = 0 - - (visited, to_display) += - - if account is VISITED or (no --flat and visited > 0): - if account matches display predicate and - (--flat or to_display != 1): - mark account as TO_DISPLAY - to_display = 1 - visited = 1 - - return (visited, to_display) - -STEP 4: top_displayed = 0 - - for every account in the ordered list: - if account has been marked TO_DISPLAY: - mark account as DISPLAYED - format the account and print - - if --flat and account is DISPLAYED: - top_displayed += 1 - - if no --flat: - for every top-most account: - if account is DISPLAYED or any children or DISPLAYED: - top_displayed += 1 - - if no --no-total and top_displayed > 1 and - top-most accounts sum to a non-zero balance: - output separator - output balance sum account as DISPLAYED - format the account and print - - if --flat and account is DISPLAYED: - top_displayed += 1 - - if no --flat: - for every top-most account: - if account is DISPLAYED or any children or DISPLAYED: - top_displayed += 1 - - if no --no-total and top_displayed > 1 and - top-most accounts sum to a non-zero balance: - output separator - output balance sum -\" +- an account which has just one interesting subaccount branch, and which + is not at the report's maximum depth, is interesting if the balance is + different from the subaccount's, and otherwise boring. +- any other account is interesting if it has a non-zero balance, or the -E + flag is used. -}