diff options
-rw-r--r-- | amount.cc | 37 | ||||
-rw-r--r-- | balance.h | 40 | ||||
-rw-r--r-- | binary.cc | 2 | ||||
-rw-r--r-- | expr.cc | 8 | ||||
-rw-r--r-- | expr.h | 6 | ||||
-rw-r--r-- | format.cc | 84 | ||||
-rw-r--r-- | format.h | 11 | ||||
-rw-r--r-- | item.cc | 169 | ||||
-rw-r--r-- | item.h | 64 | ||||
-rw-r--r-- | ledger.cc | 128 | ||||
-rw-r--r-- | ledger.h | 18 | ||||
-rw-r--r-- | main.cc | 500 | ||||
-rw-r--r-- | textual.cc | 127 | ||||
-rw-r--r-- | textual.h | 6 |
14 files changed, 633 insertions, 567 deletions
@@ -10,7 +10,7 @@ namespace ledger { -static void mpz_round(mpz_t value, int precision) +static void mpz_round(mpz_t out, mpz_t value, int precision) { mpz_t divisor; @@ -33,17 +33,17 @@ static void mpz_round(mpz_t value, int precision) mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - precision); mpz_add(remainder, divisor, remainder); mpz_ui_sub(remainder, 0, remainder); - mpz_add(value, value, remainder); + mpz_add(out, value, remainder); } else { - mpz_sub(value, value, remainder); + mpz_sub(out, value, remainder); } } else { if (mpz_cmp(remainder, divisor) >= 0) { mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - precision); mpz_sub(remainder, divisor, remainder); - mpz_add(value, value, remainder); + mpz_add(out, value, remainder); } else { - mpz_sub(value, value, remainder); + mpz_sub(out, value, remainder); } } @@ -357,7 +357,7 @@ amount_t amount_t::round(int precision) const return *this; } else { amount_t temp = *this; - mpz_round(MPZ(temp.quantity), + mpz_round(MPZ(temp.quantity), MPZ(temp.quantity), precision == -1 ? commodity->precision : precision); return temp; } @@ -380,8 +380,14 @@ std::ostream& operator<<(std::ostream& out, const amount_t& amt) bool negative = false; + // Ensure the value is rounded to the commodity's precision before + // outputting it. NOTE: `rquotient' is used here as a temp variable! + + if (amt.commodity->precision != MAX_PRECISION) + mpz_round(rquotient, MPZ(amt.quantity), amt.commodity->precision); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); - mpz_tdiv_qr(quotient, remainder, MPZ(amt.quantity), divisor); + mpz_tdiv_qr(quotient, remainder, rquotient, divisor); if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) negative = true; @@ -392,10 +398,6 @@ std::ostream& operator<<(std::ostream& out, const amount_t& amt) if (amt.commodity->precision == MAX_PRECISION) { mpz_set(rquotient, remainder); } else { - // Ensure the value is rounded to the commodity's precision before - // outputting it - mpz_round(MPZ(amt.quantity), amt.commodity->precision); - assert(MAX_PRECISION - amt.commodity->precision > 0); mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - amt.commodity->precision); mpz_tdiv_qr(rquotient, remainder, remainder, divisor); @@ -681,6 +683,19 @@ void (*commodity_t::updater)(commodity_t * commodity, commodities_map commodity_t::commodities; +struct cleanup_commodities +{ + ~cleanup_commodities() { + for (commodities_map::iterator i + = commodity_t::commodities.begin(); + i != commodity_t::commodities.end(); + i++) + delete (*i).second; + } +}; + +static cleanup_commodities cleanup; + commodity_t * commodity_t::find_commodity(const std::string& symbol, bool auto_create) { @@ -274,6 +274,27 @@ class balance_t return amount(amt.commodity) >= amt; } + bool operator==(const balance_t& bal) const { + amounts_map::const_iterator i, j; + for (i = amounts.begin(), j = bal.amounts.begin(); + i != amounts.end() && j != bal.amounts.end(); + i++, j++) { + if (! ((*i).first == (*j).first && + (*i).second == (*j).second)) + return false; + } + return i == amounts.end() && j == bal.amounts.end(); + } + bool operator==(const amount_t& amt) const { + return amounts.size() == 1 && (*amounts.begin()).second == amt; + } + bool operator!=(const balance_t& bal) const { + return ! (*this == bal); + } + bool operator!=(const amount_t& amt) const { + return ! (*this == amt); + } + // unary negation balance_t& negate() { for (amounts_map::iterator i = amounts.begin(); @@ -561,6 +582,25 @@ class balance_pair_t return quantity >= amt; } + bool operator==(const balance_pair_t& bal_pair) const { + return quantity == bal_pair.quantity; + } + bool operator==(const balance_t& bal) const { + return quantity == bal; + } + bool operator==(const amount_t& amt) const { + return quantity == amt; + } + bool operator!=(const balance_pair_t& bal_pair) const { + return ! (*this == bal_pair); + } + bool operator!=(const balance_t& bal) const { + return ! (*this == bal); + } + bool operator!=(const amount_t& amt) const { + return ! (*this == amt); + } + // unary negation balance_pair_t& negate() { quantity.negate(); @@ -15,7 +15,7 @@ namespace ledger { unsigned long magic_number = 0xFFEED765; -static unsigned long format_version = 0x00020008; +static unsigned long format_version = 0x00020009; static char buf[4096]; @@ -129,15 +129,11 @@ balance_t node_t::compute(const item_t * item) const break; case CLEARED: -#if 0 - temp = amount_t(item->state == CLEARED ? 1 : 0); -#endif + temp = amount_t(item->state == entry_t::CLEARED ? 1 : 0); break; case REAL: -#if 0 temp = amount_t(item->flags & TRANSACTION_VIRTUAL ? 0 : 1); -#endif break; case INDEX: @@ -162,7 +158,7 @@ balance_t node_t::compute(const item_t * item) const case F_PAYEE_MASK: assert(mask); - temp = mask->match(item->payee); + temp = (mask->match(item->payee) || mask->match(item->note)) ? 1 : 0; break; case F_ACCOUNT_MASK: @@ -136,7 +136,10 @@ class value_predicate } else { item_t temp; temp.date = xact->entry->date; + temp.state = xact->entry->state; + temp.code = xact->entry->code; temp.payee = xact->entry->payee; + temp.flags = xact->flags; temp.account = xact->account; return predicate->compute(&temp); } @@ -149,6 +152,8 @@ class value_predicate item_t temp; temp.date = entry->date; temp.payee = entry->payee; + temp.state = entry->state; + temp.code = entry->code; // Although there may be conflicting account masks for the whole // set of transactions -- for example, /rent/&!/expenses/, which @@ -159,6 +164,7 @@ class value_predicate for (transactions_list::const_iterator i = entry->transactions.begin(); i != entry->transactions.end(); i++) { + temp.flags = (*i)->flags; temp.account = (*i)->account; if (predicate->compute(&temp)) return true; @@ -24,8 +24,8 @@ std::string maximal_account_name(const item_t * item, const item_t * parent) return name; } -node_t * format_t::value_expr = NULL; -node_t * format_t::total_expr = NULL; +std::auto_ptr<node_t> format_t::value_expr; +std::auto_ptr<node_t> format_t::total_expr; element_t * format_t::parse_elements(const std::string& fmt) { @@ -110,9 +110,12 @@ element_t * format_t::parse_elements(const std::string& fmt) current->chars = "%Y/%m/%d"; break; + case 'X': current->type = element_t::CLEARED; break; + case 'C': current->type = element_t::CODE; break; case 'p': current->type = element_t::PAYEE; break; case 'n': current->type = element_t::ACCOUNT_NAME; break; case 'N': current->type = element_t::ACCOUNT_FULLNAME; break; + case 'o': current->type = element_t::OPT_AMOUNT; break; case 't': current->type = element_t::VALUE; break; case 'T': current->type = element_t::TOTAL; break; case '_': current->type = element_t::SPACER; break; @@ -174,28 +177,89 @@ void format_t::format_elements(std::ostream& out, const item_t * item, } break; + case element_t::CLEARED: + if (item->state == entry_t::CLEARED) + out << "* "; + else + out << ""; + break; + + case element_t::CODE: + if (! item->code.empty()) + out << "(" << item->code << ") "; + else + out << ""; + break; + case element_t::PAYEE: out << (elem->max_width == 0 ? item->payee : truncated(item->payee, elem->max_width)); break; case element_t::ACCOUNT_NAME: + case element_t::ACCOUNT_FULLNAME: if (item->account) { - std::string name = maximal_account_name(item, displayed_parent); - out << (elem->max_width == 0 ? name : truncated(name, elem->max_width)); + std::string name = (elem->type == element_t::ACCOUNT_FULLNAME ? + item->account->fullname() : + maximal_account_name(item, displayed_parent)); + if (elem->max_width > 0) + name = truncated(name, elem->max_width); + + if (item->flags & TRANSACTION_VIRTUAL) { + if (item->flags & TRANSACTION_BALANCE) + name = "[" + name + "]"; + else + name = "(" + name + ")"; + } + out << name; } else { out << " "; } break; - case element_t::ACCOUNT_FULLNAME: - if (item->account) - out << (elem->max_width == 0 ? - item->account->fullname() : - truncated(item->account->fullname(), elem->max_width)); + case element_t::OPT_AMOUNT: { + std::string disp; + bool use_disp = false; + + if (std::find(displayed_parent->subitems.begin(), + displayed_parent->subitems.end(), item) != + displayed_parent->subitems.end()) { + if (displayed_parent->subitems.size() == 2 && + item == displayed_parent->subitems.back() && + (displayed_parent->subitems.front()->value.quantity == + displayed_parent->subitems.front()->value.cost) && + (displayed_parent->subitems.front()->value == + - displayed_parent->subitems.back()->value)) { + use_disp = true; + } + else if (displayed_parent->subitems.size() != 2 && + item->value.quantity != item->value.cost && + item->value.quantity.amounts.size() == 1 && + item->value.cost.amounts.size() == 1 && + ((*item->value.quantity.amounts.begin()).first != + (*item->value.cost.amounts.begin()).first)) { + amount_t unit_cost + = ((*item->value.cost.amounts.begin()).second / + (*item->value.quantity.amounts.begin()).second); + std::ostringstream stream; + stream << item->value.quantity << " @ " << unit_cost; + disp = stream.str(); + use_disp = true; + } + } + + if (use_disp) + out << disp; else - out << " "; + item->value.quantity.write(out, elem->min_width, + elem->max_width > 0 ? + elem->max_width : elem->min_width); + + // jww (2004-07-31): this should be handled differently + if (! item->note.empty()) + out << " ; " << item->note; break; + } case element_t::VALUE: { balance_t value = compute_value(item); @@ -16,9 +16,12 @@ struct element_t STRING, VALUE_EXPR, DATE_STRING, + CLEARED, + CODE, PAYEE, ACCOUNT_NAME, ACCOUNT_FULLNAME, + OPT_AMOUNT, VALUE, TOTAL, SPACER @@ -47,8 +50,8 @@ struct format_t { element_t * elements; - static node_t * value_expr; - static node_t * total_expr; + static std::auto_ptr<node_t> value_expr; + static std::auto_ptr<node_t> total_expr; format_t(const std::string& _format) { elements = parse_elements(_format); @@ -63,10 +66,10 @@ struct format_t const item_t * displayed_parent = NULL) const; static balance_t compute_value(const item_t * item) { - return value_expr ? value_expr->compute(item) : balance_t(); + return value_expr.get() ? value_expr->compute(item) : balance_t(); } static balance_t compute_total(const item_t * item) { - return total_expr ? total_expr->compute(item) : balance_t(); + return total_expr.get() ? total_expr->compute(item) : balance_t(); } }; @@ -3,51 +3,6 @@ namespace ledger { -// jww (2004-07-21): If format.show_empty is set, then include all -// subaccounts, empty, balanced or no - -item_t * walk_accounts(const account_t * account, - const node_t * predicate, - const bool show_subtotals) -{ - item_t * item = new item_t; - item->account = account; - - std::time_t latest = 0; - for (transactions_list::const_iterator i - = std::find_if(account->transactions.begin(), - account->transactions.end(), - value_predicate(predicate)); - i != account->transactions.end(); - i = std::find_if(++i, account->transactions.end(), - value_predicate(predicate))) { - if (std::difftime(latest, (*i)->entry->date) < 0) - latest = (*i)->entry->date; - - item->value += *(*i); - if (show_subtotals) - item->total += *(*i); - } - item->date = latest; - - for (accounts_map::const_iterator i = account->accounts.begin(); - i != account->accounts.end(); - i++) { - item_t * subitem = walk_accounts((*i).second, predicate, show_subtotals); - subitem->parent = item; - - if (std::difftime(item->date, subitem->date) < 0) - item->date = subitem->date; - - if (show_subtotals) - item->total += subitem->total; - if (show_subtotals ? subitem->total : subitem->value) - item->subitems.push_back(subitem); - } - - return item; -} - static inline void sum_items(const item_t * top, const bool show_subtotals, item_t * item) @@ -64,27 +19,88 @@ static inline void sum_items(const item_t * top, sum_items(*i, show_subtotals, item); } -item_t * walk_items(const item_t * top, - const account_t * account, - const node_t * predicate, - const bool show_subtotals) +item_t * walk_accounts(const item_t * top, + account_t * account, + const node_t * predicate, + const bool show_subtotals, + const bool show_flattened) { item_t * item = new item_t; item->account = account; - sum_items(top, show_subtotals, item); + if (top) { + sum_items(top, show_subtotals, item); + } else { + std::time_t latest = 0; + for (transactions_list::iterator i + = std::find_if(account->transactions.begin(), + account->transactions.end(), + value_predicate(predicate)); + i != account->transactions.end(); + i = std::find_if(++i, account->transactions.end(), + value_predicate(predicate))) { + if (std::difftime(latest, (*i)->entry->date) < 0) + latest = (*i)->entry->date; + + item->value += *(*i); + if (show_subtotals) + item->total += *(*i); + } + item->date = latest; + } - for (accounts_map::const_iterator i = account->accounts.begin(); + for (accounts_map::iterator i = account->accounts.begin(); i != account->accounts.end(); i++) { - item_t * subitem = walk_items(top, (*i).second, predicate, show_subtotals); + std::auto_ptr<item_t> + subitem(walk_accounts(top, (*i).second, predicate, show_subtotals, + show_flattened)); subitem->parent = item; + if (std::difftime(item->date, subitem->date) < 0) + item->date = subitem->date; + + if (show_flattened) { + item_t * ptr = item; + balance_pair_t total; + + for (items_deque::const_iterator i = subitem->subitems.begin(); + i != subitem->subitems.end(); + i++) + if (show_subtotals ? (*i)->total : (*i)->value) { + if (! account->parent) { + if (! total) { + item_t * temp = new item_t; + temp->date = top ? top->date : item->date; + temp->payee = "Opening balance"; + item->subitems.push_back(temp); + ptr = temp; + } + total += show_subtotals ? (*i)->total : (*i)->value; + } + + ptr->subitems.push_back(new item_t(*i)); + ptr->subitems.back()->date = ptr->date; + ptr->subitems.back()->payee = ptr->payee; + } + + if (total) { + item_t * temp = new item_t; + temp->date = ptr->date; + temp->payee = ptr->payee; + temp->account = account->find_account("Equity:Opening Balances"); + temp->value = total; + temp->value.negate(); + ptr->subitems.push_back(temp); + } + } + if (show_subtotals) item->total += subitem->total; - if (show_subtotals ? subitem->total : subitem->value) - item->subitems.push_back(subitem); + if ((! show_flattened || account->parent) && + show_subtotals ? subitem->total : subitem->value) + item->subitems.push_back(subitem.release()); } return item; @@ -103,6 +119,7 @@ item_t * walk_entries(entries_list::const_iterator begin, for (entries_list::const_iterator i = std::find_if(begin, end, pred_obj); i != end; i = std::find_if(++i, end, pred_obj)) { + transactions_list reckoned; item_t * item = NULL; for (transactions_list::const_iterator j @@ -115,39 +132,32 @@ item_t * walk_entries(entries_list::const_iterator begin, assert(*i == (*j)->entry); if (! item) { - item = new item_t; + item = new item_t(*i); item->index = count++; - item->date = (*i)->date; - item->payee = (*i)->payee; } // If show_inverted is true, it implies show_related. - if (! show_inverted) { - item_t * subitem = new item_t; - subitem->parent = item; - subitem->date = item->date; - subitem->payee = item->payee; - subitem->account = (*j)->account; - subitem->value = *(*j); - item->value += subitem->value; - item->subitems.push_back(subitem); + if (! show_inverted && + std::find(reckoned.begin(), + reckoned.end(), *j) == reckoned.end()) { + item->add_item(new item_t(*j)); + reckoned.push_back(*j); } if (show_related) for (transactions_list::iterator k = (*i)->transactions.begin(); k != (*i)->transactions.end(); - k++) - if (*k != *j && ! ((*k)->flags & TRANSACTION_VIRTUAL)) { - item_t * subitem = new item_t; - subitem->parent = item; - subitem->date = item->date; - subitem->payee = item->payee; - subitem->account = (*k)->account; - subitem->value = *(*k); - if (show_inverted) - subitem->value.negate(); - item->subitems.push_back(subitem); - } + k++) { + if (*k == *j || ((*k)->flags & TRANSACTION_AUTO) || + std::find(reckoned.begin(), + reckoned.end(), *k) != reckoned.end()) + continue; + + item->add_item(new item_t(*k)); + if (show_inverted) + item->subitems.back()->value.negate(); + reckoned.push_back(*k); + } } if (item) { @@ -155,6 +165,9 @@ item_t * walk_entries(entries_list::const_iterator begin, result = new item_t; item->parent = result; result->subitems.push_back(item); + + if (std::difftime(result->date, item->date) < 0) + result->date = item->date; } } @@ -14,17 +14,38 @@ typedef std::deque<item_t *> items_deque; struct item_t { - struct item_t * parent; - items_deque subitems; + struct item_t * parent; + items_deque subitems; - unsigned int index; - std::time_t date; - std::string payee; - const account_t * account; - balance_pair_t value; - balance_pair_t total; + unsigned int index; + std::time_t date; + entry_t::entry_state_t state; + std::string code; + std::string payee; + unsigned int flags; + const account_t * account; + balance_pair_t value; + balance_pair_t total; + std::string note; - item_t() : parent(NULL), index(0), date(-1), account(NULL) {} + item_t() : parent(NULL), index(0), date(-1), + state(entry_t::UNCLEARED), flags(0), account(NULL) {} + + item_t(const item_t * item) + : parent(NULL), index(0), date(item->date), state(item->state), + code(item->code), payee(item->payee), flags(item->flags), + account(item->account), value(item->value), total(item->total), + note(item->note) {} + + item_t(const entry_t * entry) + : parent(NULL), index(0), date(entry->date), state(entry->state), + code(entry->code), payee(entry->payee) {} + + item_t(const transaction_t * xact) + : parent(NULL), index(0), date(xact->entry->date), + state(xact->entry->state), code(xact->entry->code), + payee(xact->entry->payee), flags(xact->flags), + account(xact->account), value(*xact), note(xact->note) {} ~item_t() { for (items_deque::iterator i = subitems.begin(); @@ -33,25 +54,28 @@ struct item_t delete *i; } + void add_item(item_t * item) { + item->parent = this; + value += item->value; + subitems.push_back(item); + } + void sort(const node_t * sort_order); }; struct node_t; -item_t * walk_accounts(const account_t * account, - const node_t * predicate, - const bool show_subtotals); - -item_t * walk_items(const item_t * top, - const account_t * account, - const node_t * predicate, - const bool show_subtotals); +item_t * walk_accounts(const item_t * top, + account_t * account, + const node_t * predicate = NULL, + const bool show_subtotals = true, + const bool show_flattened = false); item_t * walk_entries(entries_list::const_iterator begin, entries_list::const_iterator end, - const node_t * predicate, - const bool show_related, - const bool show_inverted); + const node_t * predicate = NULL, + const bool show_related = false, + const bool show_inverted = false); } // namespace report @@ -1,4 +1,5 @@ #include "ledger.h" +#include "expr.h" #include "textual.h" #include "binary.h" @@ -52,6 +53,133 @@ bool ledger_t::remove_entry(entry_t * entry) return true; } +entry_t * ledger_t::derive_entry(int argc, char **argv) const +{ + entry_t * added = new entry_t; + entry_t * matching = NULL; + int index = 0; + + assert(index < argc); + + if (! parse_date(argv[index++], &added->date)) { + std::cerr << "Error: Bad entry date: " << argv[index - 1] + << std::endl; + return false; + } + + if (index == argc) { + std::cerr << "Error: Too few arguments to 'entry'." << std::endl; + return false; + } + + mask_t regexp(argv[index++]); + + for (entries_list::const_reverse_iterator i = entries.rbegin(); + i != entries.rend(); + i++) + if (regexp.match((*i)->payee)) { + matching = *i; + break; + } + + added->payee = matching ? matching->payee : regexp.pattern; + + if (index == argc) { + std::cerr << "Error: Too few arguments to 'entry'." << std::endl; + return false; + } + + if (argv[index][0] == '-' || std::isdigit(argv[index][0])) { + if (! matching) { + std::cerr << "Error: Missing account name for non-matching entry." + << std::endl; + return false; + } + + transaction_t * m_xact, * xact, * first; + m_xact = matching->transactions.front(); + + amount_t amt(argv[index++]); + first = xact = new transaction_t(added, m_xact->account, amt, amt); + + if (xact->amount.commodity->symbol.empty()) { + xact->amount.commodity = m_xact->amount.commodity; + xact->cost.commodity = m_xact->amount.commodity; + } + added->add_transaction(xact); + + m_xact = matching->transactions.back(); + + xact = new transaction_t(added, m_xact->account, + - first->amount, - first->amount); + added->add_transaction(xact); + + if ((index + 1) < argc && std::string(argv[index]) == "-from") + if (account_t * acct = find_account(argv[++index])) + added->transactions.back()->account = acct; + } else { + while (index < argc && std::string(argv[index]) != "-from") { + mask_t acct_regex(argv[index++]); + + account_t * acct = NULL; + commodity_t * cmdty = NULL; + + if (matching) { + for (transactions_list::iterator x + = matching->transactions.begin(); + x != matching->transactions.end(); + x++) { + if (acct_regex.match((*x)->account->fullname())) { + acct = (*x)->account; + cmdty = (*x)->amount.commodity; + break; + } + } + } + + if (! acct) + acct = find_account(acct_regex.pattern); + + if (! acct) { + std::cerr << "Error: Could not find account name '" + << acct_regex.pattern << "'." << std::endl; + return false; + } + + if (index == argc) { + std::cerr << "Error: Too few arguments to 'entry'." << std::endl; + return false; + } + + amount_t amt(argv[index]++); + transaction_t * xact = new transaction_t(added, acct, amt, amt); + + if (! xact->amount.commodity) + xact->amount.commodity = cmdty; + + added->add_transaction(xact); + } + + if ((index + 1) < argc && std::string(argv[index]) == "-from") { + if (account_t * acct = find_account(argv[++index])) { + transaction_t * xact = new transaction_t(NULL, acct); + added->add_transaction(xact); + } + } else { + if (! matching) { + std::cerr << "Error: Could not figure out the account to draw from." + << std::endl; + std::exit(1); + } + transaction_t * xact + = new transaction_t(added, matching->transactions.back()->account); + added->add_transaction(xact); + } + } + + return added; +} + int parse_ledger_file(char * p, ledger_t * journal) { char * sep = std::strrchr(p, '='); @@ -288,6 +288,7 @@ class commodity_t #define TRANSACTION_NORMAL 0x0 #define TRANSACTION_VIRTUAL 0x1 #define TRANSACTION_BALANCE 0x2 +#define TRANSACTION_AUTO 0x4 class transaction_t { @@ -322,11 +323,13 @@ class entry_t UNCLEARED, CLEARED, PENDING }; - std::time_t date; - enum entry_state_t state; - std::string code; - std::string payee; - transactions_list transactions; + std::time_t date; + entry_state_t state; + std::string code; + std::string payee; + transactions_list transactions; + + entry_t() : date(-1), state(UNCLEARED) {} ~entry_t() { for (transactions_list::iterator i = transactions.begin(); @@ -429,9 +432,14 @@ class ledger_t account_t * find_account(const std::string& name, bool auto_create = true) { return master->find_account(name, auto_create); } + account_t * find_account(const std::string& name) const { + return master->find_account(name, false); + } bool add_entry(entry_t * entry); bool remove_entry(entry_t * entry); + + entry_t * derive_entry(int argc, char **argv) const; }; int parse_ledger_file(char * p, ledger_t * journal); @@ -83,11 +83,15 @@ void balance_report(std::ostream& out, ////////////////////////////////////////////////////////////////////// // -// The command-line register report +// The command-line register and print report // static const std::string reg_fmt - = "%10d %-.20p %-.22N %12.66t %12.80T\n%/%22_ %-.22N %12.66t %12.80T\n"; + = "%10d %-.20p %-.22N %12.66t %12.80T\n\ +%/ %-.22N %12.66t %12.80T\n"; + +static const std::string print_fmt + = "\n%10d %X%C%p\n %-34N %12o\n%/ %-34N %12o\n"; static bool show_commodities_revalued = false; static bool show_commodities_revalued_only = false; @@ -151,11 +155,11 @@ void register_report(std::ostream& out, bool first = true; if ((*i)->subitems.size() > 1 && ! show_expanded) { - item_t summary; - summary.date = (*i)->date; + item_t summary(*i); summary.parent = *i; summary.account = &splits; + summary.value = 0; for (items_deque::const_iterator j = (*i)->subitems.begin(); j != (*i)->subitems.end(); j++) @@ -171,7 +175,7 @@ void register_report(std::ostream& out, if (show) { if (! show_commodities_revalued_only) - first_line_format.format_elements(out, *i, top); + first_line_format.format_elements(out, &summary, top); if (show_commodities_revalued) last_reported = balance; @@ -215,139 +219,6 @@ void register_report(std::ostream& out, } -bool add_new_entry(int index, int argc, char **argv, ledger_t * ledger) -{ - masks_list regexps; - entry_t added; - entry_t * matching = NULL; - - added.state = entry_t::UNCLEARED; - - assert(index < argc); - - if (! parse_date(argv[index++], &added.date)) { - std::cerr << "Error: Bad entry date: " << argv[index - 1] - << std::endl; - return false; - } - - if (index == argc) { - std::cerr << "Error: Too few arguments to 'entry'." << std::endl; - return false; - } - - regexps.push_back(mask_t(argv[index++])); - - for (entries_list::reverse_iterator i = ledger->entries.rbegin(); - i != ledger->entries.rend(); - i++) - if (matches(regexps, (*i)->payee)) { - matching = *i; - break; - } - - added.payee = matching ? matching->payee : regexps.front().pattern; - - if (index == argc) { - std::cerr << "Error: Too few arguments to 'entry'." << std::endl; - return false; - } - - if (argv[index][0] == '-' || std::isdigit(argv[index][0])) { - if (! matching) { - std::cerr << "Error: Missing account name for non-matching entry." - << std::endl; - return false; - } - - transaction_t * m_xact, * xact, * first; - m_xact = matching->transactions.front(); - - amount_t amt(argv[index++]); - first = xact = new transaction_t(&added, m_xact->account, amt, amt); - - if (xact->amount.commodity->symbol.empty()) { - xact->amount.commodity = m_xact->amount.commodity; - xact->cost.commodity = m_xact->amount.commodity; - } - added.add_transaction(xact); - - m_xact = matching->transactions.back(); - - xact = new transaction_t(&added, m_xact->account, - - first->amount, - first->amount); - added.add_transaction(xact); - - if ((index + 1) < argc && std::string(argv[index]) == "-from") - if (account_t * acct = ledger->find_account(argv[++index])) - added.transactions.back()->account = acct; - } else { - while (index < argc && std::string(argv[index]) != "-from") { - mask_t acct_regex(argv[index++]); - - account_t * acct = NULL; - commodity_t * cmdty = NULL; - - if (matching) { - for (transactions_list::iterator x - = matching->transactions.begin(); - x != matching->transactions.end(); - x++) { - if (acct_regex.match((*x)->account->fullname())) { - acct = (*x)->account; - cmdty = (*x)->amount.commodity; - break; - } - } - } - - if (! acct) - acct = ledger->find_account(acct_regex.pattern); - - if (! acct) { - std::cerr << "Error: Could not find account name '" - << acct_regex.pattern << "'." << std::endl; - return false; - } - - if (index == argc) { - std::cerr << "Error: Too few arguments to 'entry'." << std::endl; - return false; - } - - amount_t amt(argv[index]++); - transaction_t * xact = new transaction_t(&added, acct, amt, amt); - - if (! xact->amount.commodity) - xact->amount.commodity = cmdty; - - added.add_transaction(xact); - } - - if ((index + 1) < argc && std::string(argv[index]) == "-from") { - if (account_t * acct = ledger->find_account(argv[++index])) { - transaction_t * xact = new transaction_t(NULL, acct); - added.add_transaction(xact); - } - } else { - if (! matching) { - std::cerr << "Error: Could not figure out the account to draw from." - << std::endl; - std::exit(1); - } - transaction_t * xact - = new transaction_t(&added, matching->transactions.back()->account); - added.add_transaction(xact); - } - } - - // if (added.finalize()) - print_textual_entry(std::cout, &added); - - return true; -} - - void set_price_conversion(const std::string& setting) { char buf[128]; @@ -468,23 +339,24 @@ static void show_help(std::ostream& out) << " balance show balance totals" << std::endl << " register display a register for ACCOUNT" << std::endl << " print print all ledger entries" << std::endl - << " equity generate equity ledger for all entries" << std::endl << " entry output a newly formed entry, based on arguments" << std::endl - << " price show the last known price for matching commodities" << std::endl; + << " equity output equity entries for specified accounts" << std::endl; } int main(int argc, char * argv[]) { - std::list<std::string> files; - - std::string predicate_string; - ledger::node_t * predicate = NULL; - std::string format_string; - std::string sort_string; - ledger::node_t * sort_order = NULL; - std::string value_expr = "a"; - std::string total_expr = "T"; - ledger::ledger_t * journal = new ledger::ledger_t; + std::auto_ptr<ledger::ledger_t> journal(new ledger::ledger_t); + std::list<std::string> files; + std::auto_ptr<ledger::node_t> predicate; + std::auto_ptr<ledger::node_t> display_predicate; + std::auto_ptr<ledger::node_t> sort_order; + + std::string predicate_string; + std::string display_predicate_string; + std::string format_string; + std::string sort_string; + std::string value_expr = "a"; + std::string total_expr = "T"; bool show_subtotals = true; bool show_expanded = false; @@ -521,11 +393,9 @@ int main(int argc, char * argv[]) if (access(p, R_OK) != -1) { std::ifstream instr(p); if (! ledger::read_binary_ledger(instr, std::getenv("LEDGER"), - journal)) { - // We need to throw away what we've read, and create a new - // ledger - delete journal; - journal = new ledger::ledger_t; + journal.get())) { + // Throw away what's been read, and create a new journal + journal.reset(new ledger::ledger_t); } else { ledger::cache_dirty = false; } @@ -652,6 +522,14 @@ int main(int argc, char * argv[]) predicate_string += ")"; break; + case 'd': + if (! display_predicate_string.empty()) + display_predicate_string += "&"; + display_predicate_string += "("; + display_predicate_string += optarg; + display_predicate_string += ")"; + break; + // Commodity reporting case 'P': ledger::price_db = optarg; @@ -747,14 +625,14 @@ int main(int argc, char * argv[]) if (files.empty()) { if (char * p = std::getenv("LEDGER")) for (p = std::strtok(p, ":"); p; p = std::strtok(NULL, ":")) - entry_count += parse_ledger_file(p, journal); + entry_count += parse_ledger_file(p, journal.get()); } else { for (std::list<std::string>::iterator i = files.begin(); i != files.end(); i++) { char buf[4096]; char * p = buf; std::strcpy(p, (*i).c_str()); - entry_count += parse_ledger_file(p, journal); + entry_count += parse_ledger_file(p, journal.get()); } } @@ -765,7 +643,8 @@ int main(int argc, char * argv[]) const char * path = ledger::price_db.c_str(); std::ifstream db(path); journal->sources.push_back(path); - entry_count += ledger::parse_textual_ledger(db, journal, journal->master); + entry_count += ledger::parse_textual_ledger(db, journal.get(), + journal->master); } } catch (ledger::error& err) { @@ -780,52 +659,98 @@ int main(int argc, char * argv[]) } } - // Read the command word, and handle the "entry" command specially, - // without any other processing. + // Read the command word, and then check and simplify it - const std::string command = argv[index++]; + std::string command = argv[index++]; - if (command == "entry") - return add_new_entry(index, argc, argv, journal) ? 0 : 1; + if (command == "balance" || command == "bal" || command == "b") + command = "b"; + else if (command == "register" || command == "reg" || command == "r") + command = "r"; + else if (command == "print" || command == "p") + command = "p"; + else if (command == "entry") + command = "e"; + else if (command == "equity") + command = "E"; + else { + std::cerr << "Error: Unrecognized command '" << command << "'." + << std::endl; + return 1; + } - // Interpret the remaining arguments as regular expressions, used - // for refining report results. + // Process the remaining command-line arguments - for (; index < argc; index++) { - if (std::strcmp(argv[index], "--") == 0) { - index++; - break; - } + std::auto_ptr<ledger::entry_t> new_entry; + if (command == "entry") { + new_entry.reset(journal->derive_entry(argc - index, &argv[index])); + } else { + // Treat the remaining command-line arguments as regular + // expressions, used for refining report results. - show_expanded = true; + bool have_regexps = index < argc; + bool first = true; - if (! predicate_string.empty()) - predicate_string += "&"; + for (; index < argc; index++) { + if (std::strcmp(argv[index], "--") == 0) { + index++; + if (! first && index < argc) + predicate_string += ")"; + break; + } - if (argv[index][0] == '-') { - predicate_string += "(!/"; - predicate_string += argv[index] + 1; - } else { - predicate_string += "(/"; - predicate_string += argv[index]; + if (! show_expanded && command == "b") + show_expanded = true; + + if (first) { + if (! predicate_string.empty()) + predicate_string += "&("; + else + predicate_string += "("; + first = false; + } else { + predicate_string += "|"; + } + + if (argv[index][0] == '-') { + predicate_string += "!/"; + predicate_string += argv[index] + 1; + } else { + predicate_string += "/"; + predicate_string += argv[index]; + } + predicate_string += "/"; } - predicate_string += "/)"; - } - for (; index < argc; index++) { - show_expanded = true; + if (index < argc) { + if (! predicate_string.empty()) + predicate_string += "&("; + else + predicate_string += "("; + } - if (! predicate_string.empty()) - predicate_string += "&"; + first = true; + for (; index < argc; index++) { + if (! show_expanded && command == "b") + show_expanded = true; - if (argv[index][0] == '-') { - predicate_string += "(!//"; - predicate_string += argv[index] + 1; - } else { - predicate_string += "(//"; - predicate_string += argv[index]; + if (first) + first = false; + else + predicate_string += "|"; + + if (argv[index][0] == '-') { + predicate_string += "!//"; + predicate_string += argv[index] + 1; + } else { + predicate_string += "//"; + predicate_string += argv[index]; + } + predicate_string += "/"; } - predicate_string += "/)"; + + if (have_regexps) + predicate_string += ")"; } // Compile the predicate @@ -835,122 +760,110 @@ int main(int argc, char * argv[]) if (debug) std::cerr << "predicate = " << predicate_string << std::endl; #endif - predicate = ledger::parse_expr(predicate_string); + predicate.reset(ledger::parse_expr(predicate_string)); + } + + if (! display_predicate_string.empty()) { +#ifdef DEBUG + if (debug) + std::cerr << "display predicate = " << display_predicate_string + << std::endl; +#endif + display_predicate.reset(ledger::parse_expr(display_predicate_string)); } // Compile the sorting string if (! sort_string.empty()) - sort_order = ledger::parse_expr(sort_string); + sort_order.reset(ledger::parse_expr(sort_string)); // Setup the meaning of %t and %T encountered in format strings - ledger::format_t::value_expr = ledger::parse_expr(value_expr); - ledger::format_t::total_expr = ledger::parse_expr(total_expr); + ledger::format_t::value_expr.reset(ledger::parse_expr(value_expr)); + ledger::format_t::total_expr.reset(ledger::parse_expr(total_expr)); // Now handle the command that was identified above. - if (command == "print") { -#if 0 - if (ledger::item_t * top - = ledger::walk_entries(journal->entries.begin(), - journal->entries.end(), predicate, - show_related, show_inverted)) { - ledger::format_t * format = new ledger::format_t(format_string); - ledger::entry_report(std::cout, top, *format); -#ifdef DEBUG - delete top; - delete format; -#endif - } -#endif + if (command == "p" || command == "e") { + show_related = true; + show_expanded = true; } - else if (command == "equity") { -#if 0 - if (ledger::item_t * top - = ledger::walk_accounts(journal->master, predicate, show_subtotals)) { - ledger::format_t * format = new ledger::format_t(format_string); - ledger::entry_report(std::cout, top, predicate, *format); -#ifdef DEBUG - delete top; - delete format; -#endif - } -#endif + else if (command == "E") { + show_expanded = true; } - else if (! sort_order && ! show_related && - (command == "balance" || command == "bal")) { - if (ledger::item_t * top - = ledger::walk_accounts(journal->master, predicate, show_subtotals)) { - ledger::format_t * format - = new ledger::format_t(format_string.empty() ? - ledger::bal_fmt : format_string); - ledger::balance_report(std::cout, top, predicate, sort_order, *format, - show_expanded, show_subtotals); -#ifdef DEBUG - delete format; - delete top; -#endif - } + else if (show_related && command == "r") { + show_inverted = true; } - else if (command == "balance" || command == "bal") { - if (ledger::item_t * list - = ledger::walk_entries(journal->entries.begin(), - journal->entries.end(), predicate, - show_related, show_inverted)) - if (ledger::item_t * top - = ledger::walk_items(list, journal->master, predicate, - show_subtotals)) { - ledger::format_t * format - = new ledger::format_t(format_string.empty() ? - ledger::bal_fmt : format_string); - ledger::balance_report(std::cout, top, predicate, sort_order, *format, - show_expanded, show_subtotals); -#ifdef DEBUG - delete format; - delete top; - delete list; -#endif - } + + std::auto_ptr<ledger::item_t> top; + std::auto_ptr<ledger::item_t> list; + + if (command == "e") { + top.reset(new ledger::item_t); + ledger::item_t * item = new ledger::item_t(new_entry.get()); + for (ledger::transactions_list::const_iterator i + = new_entry->transactions.begin(); + i != new_entry->transactions.end(); + i++) + item->add_item(new ledger::item_t(*i)); + top->add_item(item); + } + else if ((! show_related || ! predicate.get()) && + (command == "b" || command == "E")) { + top.reset(ledger::walk_accounts(NULL, journal->master, predicate.get(), + command != "E" && show_subtotals, + command == "E")); } - else if (command == "register" || command == "reg") { - if (show_related) - show_inverted = true; - - if (ledger::item_t * top - = ledger::walk_entries(journal->entries.begin(), - journal->entries.end(), predicate, - show_related, show_inverted)) { + else { + top.reset(ledger::walk_entries(journal->entries.begin(), + journal->entries.end(), predicate.get(), + show_related, show_inverted)); + if (top.get() && command == "b" || command == "E") { + list.reset(top.release()); + top.reset(ledger::walk_accounts(list.get(), journal->master, + predicate.get(), + command != "E" && show_subtotals, + command == "E")); + } + } + + if (top.get()) { + const char * f; + if (! format_string.empty()) + f = format_string.c_str(); + else if (command == "b") + f = ledger::bal_fmt.c_str(); + else if (command == "r") + f = ledger::reg_fmt.c_str(); + else + f = ledger::print_fmt.c_str(); + + if (command == "b") { + std::auto_ptr<ledger::format_t> format(new ledger::format_t(f)); + ledger::balance_report(std::cout, top.get(), display_predicate.get(), + sort_order.get(), *format, show_expanded, + show_subtotals); + } else { std::string first_line_format; std::string next_lines_format; - const char * f = (format_string.empty() ? - ledger::reg_fmt.c_str() : format_string.c_str()); if (const char * p = std::strstr(f, "%/")) { first_line_format = std::string(f, 0, p - f); next_lines_format = std::string(p + 2); } else { - first_line_format = format_string; - next_lines_format = format_string; + first_line_format = next_lines_format = f; } - ledger::format_t * format = new ledger::format_t(first_line_format); - ledger::format_t * nformat = new ledger::format_t(next_lines_format); + std::auto_ptr<ledger::format_t> + format(new ledger::format_t(first_line_format)); + std::auto_ptr<ledger::format_t> + nformat(new ledger::format_t(next_lines_format)); - ledger::register_report(std::cout, top, predicate, sort_order, - *format, *nformat, show_expanded); -#ifdef DEBUG - delete nformat; - delete format; - delete top; -#endif + ledger::register_report(std::cout, top.get(), display_predicate.get(), + sort_order.get(), *format, *nformat, + show_expanded); } } - else { - std::cerr << "Error: Unrecognized command '" << command << "'." - << std::endl; - return 1; - } // Save the cache, if need be @@ -958,31 +871,10 @@ int main(int argc, char * argv[]) if (const char * p = std::getenv("LEDGER_CACHE")) { std::ofstream outstr(p); assert(std::getenv("LEDGER")); - ledger::write_binary_ledger(outstr, journal, std::getenv("LEDGER")); + ledger::write_binary_ledger(outstr, journal.get(), + std::getenv("LEDGER")); } -#ifdef DEBUG - delete journal; - - if (predicate) - delete predicate; - if (sort_order) - delete sort_order; - - if (ledger::format_t::value_expr) - delete ledger::format_t::value_expr; - if (ledger::format_t::total_expr) - delete ledger::format_t::total_expr; - - // jww (2004-07-30): This should be moved into some kind of - // "ledger::shutdown" function. - for (ledger::commodities_map::iterator i - = ledger::commodity_t::commodities.begin(); - i != ledger::commodity_t::commodities.end(); - i++) - delete (*i).second; -#endif - return 0; } @@ -294,7 +294,8 @@ void automated_transaction_t::extend_entry(entry_t * entry) amt = (*t)->amount; transaction_t * xact - = new transaction_t(entry, (*t)->account, amt, amt, (*t)->flags); + = new transaction_t(entry, (*t)->account, amt, amt, + (*t)->flags | TRANSACTION_AUTO); entry->add_transaction(xact); } } @@ -603,8 +604,9 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal, amt.parse(buf); time_commodity = amt.commodity; - transaction_t * xact = new transaction_t(curr, last_account, amt, amt, - TRANSACTION_VIRTUAL); + transaction_t * xact + = new transaction_t(curr, last_account, amt, amt, + TRANSACTION_VIRTUAL); curr->add_transaction(xact); if (! finalize_entry(curr) || ! journal->add_entry(curr)) @@ -777,123 +779,4 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal, return count; } -////////////////////////////////////////////////////////////////////// -// -// Textual ledger printing code -// - -void print_transaction(std::ostream& out, transaction_t * xact, - bool display_amount, bool display_cost) -{ - std::ostringstream s; - s << *(xact->account); - std::string acct_name = s.str(); - - if (xact->flags & TRANSACTION_VIRTUAL) { - if (xact->flags & TRANSACTION_BALANCE) - acct_name = std::string("[") + acct_name + "]"; - else - acct_name = std::string("(") + acct_name + ")"; - } - - out.width(30); - out.fill(' '); - out << std::left << acct_name; - - if (xact->amount && display_amount) { - out << " "; - out.width(12); - out.fill(' '); - std::ostringstream s; - s << xact->amount; - out << std::right << s.str(); - } - - if (xact->amount && display_cost && - xact->amount != xact->cost) { - out << " @ "; - out << xact->cost / xact->amount; - } - - if (! xact->note.empty()) - out << " ; " << xact->note; - - out << std::endl; -} - -void print_textual_entry(std::ostream& out, entry_t * entry, bool shortcut) -{ - char buf[32]; - std::strftime(buf, 31, "%Y/%m/%d ", std::gmtime(&entry->date)); - out << buf; - - if (entry->state == entry_t::CLEARED) - out << "* "; - if (! entry->code.empty()) - out << '(' << entry->code << ") "; - if (! entry->payee.empty()) - out << entry->payee; - - out << std::endl; - - const commodity_t * comm = NULL; - int size = 0; - - for (transactions_list::const_iterator x - = entry->transactions.begin(); - x != entry->transactions.end(); - x++) { - if ((*x)->flags & TRANSACTION_VIRTUAL && - ! ((*x)->flags & TRANSACTION_BALANCE)) - continue; - - if (! comm) - comm = (*x)->amount.commodity; - else if (comm != (*x)->amount.commodity) - shortcut = false; - - size++; - } - - if (shortcut && size != 2) - shortcut = false; - - for (transactions_list::const_iterator x - = entry->transactions.begin(); - x != entry->transactions.end(); - x++) { - out << " "; - print_transaction(out, *x, - (! shortcut || x == entry->transactions.begin() || - ((*x)->flags & TRANSACTION_VIRTUAL && - ! ((*x)->flags & TRANSACTION_BALANCE))), - size != 2); - } - - out << std::endl; -} - -void print_textual_ledger(std::ostream& out, ledger_t * journal, - bool shortcut) -{ - for (entries_list::const_iterator i = journal->entries.begin(); - i != journal->entries.end(); - i++) - print_textual_entry(out, *i, shortcut); -} - } // namespace ledger - -#ifdef PARSE_TEST - -int main(int argc, char *argv[]) -{ - journal.sources.push_back(argv[1]); - std::ifstream stream(argv[1]); - ledger::ledger_t journal; - int count = parse_textual_ledger(stream, &journal, journal.master); - std::cout << "Read " << count << " entries." << std::endl; - print_textual_ledger(std::cout, &journal, true); -} - -#endif // PARSE_TEST @@ -13,12 +13,6 @@ extern bool parse_date_mask(const char * date_str, struct std::tm * result); extern bool parse_date(const char * date_str, std::time_t * result, const int year = -1); -extern void print_textual_ledger(std::ostream& out, ledger_t * ledger, - bool shortcut = true); - -extern void print_textual_entry(std::ostream& out, entry_t * entry, - bool shortcut = true); - } // namespace ledger #endif // _TEXTUAL_H |