diff options
Diffstat (limited to 'reports.cc')
-rw-r--r-- | reports.cc | 1225 |
1 files changed, 1225 insertions, 0 deletions
diff --git a/reports.cc b/reports.cc new file mode 100644 index 00000000..2708c1d0 --- /dev/null +++ b/reports.cc @@ -0,0 +1,1225 @@ +#include "ledger.h" + +#define LEDGER_VERSION "1.7" + +#include <cstring> +#include <unistd.h> + +namespace ledger { + +static bool cleared_only = false; +static bool uncleared_only = false; +static bool show_virtual = true; +static bool show_children = false; +static bool show_sorted = false; +static bool show_empty = false; +static bool show_subtotals = true; +static bool full_names = false; +static bool print_monthly = false; +static bool gnuplot_safe = false; + +static bool cost_basis = false; +static bool use_history = false; +static bool net_gain = false; +static bool get_quotes = false; + long pricing_leeway = 24 * 3600; + std::string price_db; + +static amount * lower_limit = NULL; + +static mask * negonly_regexp = NULL; + +static std::time_t begin_date; +static bool have_beginning = false; + +static std::time_t end_date; +static bool have_ending = false; + +static struct std::tm date_mask; +static bool have_date_mask = false; + +static bool matches_date_range(entry * ent) +{ + if (have_beginning && difftime(ent->date, begin_date) < 0) + return false; + + if (have_ending && difftime(ent->date, end_date) >= 0) + return false; + + if (have_date_mask) { + struct std::tm * then = std::localtime(&ent->date); + + if (date_mask.tm_mon != -1 && + date_mask.tm_mon != then->tm_mon) + return false; + + if (date_mask.tm_mday != -1 && + date_mask.tm_mday != then->tm_mday) + return false; + +#if 0 + // jww (2003-10-10): This causes only certain days of the week to + // print, even when it was not included in the mask. + if (date_mask.tm_wday != -1 && + date_mask.tm_wday != then->tm_wday) + return false; +#endif + + if (date_mask.tm_year != -1 && + date_mask.tm_year != then->tm_year) + return false; + } + + return true; +} + +static amount * resolve_amount(amount * amt, + std::time_t * when = NULL, + totals * balance = NULL, + bool add_base_value = false, + bool free_memory = false) +{ + amount * value; + bool alloced = true; + + if (! use_history) { + value = amt; + alloced = false; + } + else if (cost_basis) { + value = amt->value(); + } + else if (net_gain) { + value = amt->street(when ? when : (have_ending ? &end_date : NULL), + use_history, get_quotes); + amount * basis = amt->value(); + if (value->commdty() == basis->commdty()) { + basis->negate(); + value->credit(basis); + } else { + // If the commodities do not match, ignore this amount by + // returning a zeroed value. + delete basis; + basis = value->copy(); + basis->negate(); + value->credit(basis); + delete basis; + } + } + else { + value = amt->street(when ? when : (have_ending ? &end_date : NULL), + use_history, get_quotes); + } + + if (balance) { + if (add_base_value) + balance->credit(cost_basis ? value : amt); + else + balance->credit(value); + } + + if (free_memory && alloced) { + delete value; + value = NULL; + } + else if (! free_memory && ! alloced) { + value = value->copy(); + } + + return value; +} + +static inline void print_resolved_balance(std::ostream& out, + std::time_t * when, + totals& balance, + bool added_base_value = false) +{ + if (! added_base_value || ! use_history || cost_basis) { + balance.print(out, 12); + } else { + totals * street = balance.street(when ? when : (have_ending ? + &end_date : NULL), + use_history, get_quotes); + street->print(out, 12); + delete street; + } +} + +////////////////////////////////////////////////////////////////////// +// +// Balance reporting code +// + +static bool satisfies_limit(totals& balance) +{ + bool satisfies = true; + bool invert = false; + + assert(lower_limit); + + if (balance.is_negative()) + invert = true; + else + lower_limit->negate(); + + balance.credit(lower_limit); + if (balance.is_negative()) + satisfies = invert; + else + satisfies = ! invert; + + lower_limit->negate(); + balance.credit(lower_limit); + + if (invert) + lower_limit->negate(); + + return satisfies; +} + +static bool satisfies_limit(amount * balance) +{ + bool satisfies = true; + bool invert = false; + + assert(lower_limit); + + if (balance->is_negative()) + invert = true; + else + lower_limit->negate(); + + balance->credit(lower_limit); + if (balance->is_negative()) + satisfies = invert; + else + satisfies = ! invert; + + lower_limit->negate(); + balance->credit(lower_limit); + + if (invert) + lower_limit->negate(); + + return satisfies; +} + +static void adjust_total(account * acct) +{ + for (accounts_map_iterator i = acct->children.begin(); + i != acct->children.end(); + i++) + adjust_total((*i).second); + + if (acct->checked == 1) { + if (! show_empty && acct->balance.is_zero()) + acct->checked = 2; + else if (lower_limit && ! satisfies_limit(acct->balance)) + acct->checked = 2; + else if (negonly_regexp && negonly_regexp->match(acct->as_str()) && + ! acct->balance.is_negative()) + acct->checked = 2; + + if (acct->checked == 2) { + acct->balance.negate(); + for (account * a = acct->parent; a; a = a->parent) + a->balance.credit(acct->balance); + } + } +} + +static int acct_visible_children(account * acct) +{ + int count = 0; + for (accounts_map_iterator i = acct->children.begin(); + i != acct->children.end(); + i++) { + if ((*i).second->checked == 1) { + if ((*i).second->children.size() == 0) + count++; + else + count += acct_visible_children((*i).second); + } + } + return count; +} + +static void display_total(std::ostream& out, totals& balance, + account * acct, int level, int * headlines) +{ + // If the number of visible children is exactly one, do not print + // the parent account, but just the one child (whose name will + // output with sufficiently qualification). + + if (acct->checked == 1 && acct_visible_children(acct) != 1) { + if (acct->balance.is_zero()) { + out.width(20); + out << " "; + } else { + acct->balance.print(out, 20); + } + + if (level == 0 || full_names || ! show_subtotals) { + if (show_subtotals) { + balance.credit(acct->balance); + (*headlines)++; + } + + out << " " << acct->as_str() << std::endl; + } else { + out << " "; + for (int i = 0; i < level; i++) + out << " "; + + assert(acct->parent); + if (acct_visible_children(acct->parent) == 1) { + /* If the account has no other siblings, instead of printing: + Parent + Child + print: + Parent:Child */ + const account * parent; + for (parent = acct->parent; + parent->parent && acct_visible_children(parent->parent) == 1; + parent = parent->parent) {} + + out << acct->as_str(parent) << std::endl; + } else { + out << acct->name << std::endl; + } + } + + level++; + } + + // Display balances for all child accounts + + for (accounts_map_iterator i = acct->children.begin(); + i != acct->children.end(); + i++) + display_total(out, balance, (*i).second, level, headlines); +} + +void report_balances(std::ostream& out, regexps_list& regexps) +{ + // Walk through all of the ledger entries, computing the account + // totals + + for (entries_list_iterator i = main_ledger->entries.begin(); + i != main_ledger->entries.end(); + i++) { + if ((cleared_only && ! (*i)->cleared) || + (uncleared_only && (*i)->cleared) || ! matches_date_range(*i)) + continue; + + for (std::list<transaction *>::iterator x = (*i)->xacts.begin(); + x != (*i)->xacts.end(); + x++) { + if (! show_virtual && (*x)->is_virtual) + continue; + + for (account * acct = (*x)->acct; + acct; + acct = show_subtotals ? acct->parent : NULL) { + bool by_exclusion = false; + bool match = false; + + if (acct->checked == 0) { + if (regexps.empty()) { + if (! (show_children || ! acct->parent)) + acct->checked = 2; + else + acct->checked = 1; + } else { + match = matches(regexps, acct->as_str(), &by_exclusion); + if (! match) { + acct->checked = 2; + } + else if (by_exclusion) { + if (! (show_children || ! acct->parent)) + acct->checked = 2; + else + acct->checked = 1; + } + else { + acct->checked = 1; + } + } + } + + if (acct->checked == 1) { + resolve_amount((*x)->cost, NULL, &acct->balance, false, true); + } + else if (show_subtotals) { + if (! regexps.empty() && ! match) { + for (account * a = acct->parent; a; a = a->parent) { + if (matches(regexps, a->as_str(), &by_exclusion) && + ! by_exclusion) { + match = true; + break; + } + } + if (! match) break; + } + } + } + } + } + + // Walk through all the top-level accounts, giving the balance + // report for each, and then for each of their children. + + totals balance; + int headlines = 0; + + for (accounts_map_iterator i = main_ledger->accounts.begin(); + i != main_ledger->accounts.end(); + i++) { + adjust_total((*i).second); + display_total(out, balance, (*i).second, 0, &headlines); + } + + // Print the total of all the balances shown + + if (show_subtotals && headlines > 1 && ! balance.is_zero()) { + out << "--------------------" << std::endl; + balance.print(out, 20); + out << std::endl; + } +} + +////////////////////////////////////////////////////////////////////// +// +// Register printing code +// + +static std::string truncated(const std::string& str, int width) +{ + char buf[256]; + memset(buf, '\0', 255); + std::strncpy(buf, str.c_str(), width); + if (buf[width - 1]) + std::strcpy(&buf[width - 3], "..."); + else + buf[width] = '\0'; + return buf; +} + +enum periodicity_t { + PERIOD_NONE, + PERIOD_MONTHLY, + PERIOD_WEEKLY_SUN, + PERIOD_WEEKLY_MON +}; + +static totals * prev_balance = NULL; +static std::time_t prev_date; + +void print_register_transaction(std::ostream& out, entry * ent, + transaction * xact, totals& balance); + +static void report_change_in_asset_value(std::ostream& out, std::time_t date, + account * acct, totals& balance) +{ + totals * prev_street_balance = + prev_balance->street(&prev_date, use_history, get_quotes); + totals * curr_street_balance = + prev_balance->street(&date, use_history, get_quotes); + + delete prev_balance; + prev_balance = NULL; + + prev_street_balance->negate(); + curr_street_balance->credit(*prev_street_balance); + + if (! curr_street_balance->is_zero()) { + for (totals::const_iterator i = curr_street_balance->amounts.begin(); + i != curr_street_balance->amounts.end(); + i++) { + if ((*i).second->is_zero()) + continue; + + entry change(main_ledger); + + change.date = date; + change.cleared = true; + change.desc = "Assets revalued"; + + transaction * trans = new transaction(); + trans->acct = const_cast<account *>(acct); + trans->cost = (*i).second->copy(); + change.xacts.push_back(trans); + + transaction * trans2 = new transaction(); + trans2->acct = main_ledger->find_account("Equity:Asset Gain"); + trans2->cost = (*i).second->copy(); + trans2->cost->negate(); + change.xacts.push_back(trans2); + + balance.credit(trans2->cost); + + print_register_transaction(out, &change, trans, balance); + + delete prev_balance; + prev_balance = NULL; + } + } + + delete prev_street_balance; + delete curr_street_balance; +} + +void print_register_transaction(std::ostream& out, entry * ent, + transaction * xact, totals& balance) +{ + if (prev_balance) + report_change_in_asset_value(out, ent->date, xact->acct, balance); + + char buf[32]; + std::strftime(buf, 31, "%Y/%m/%d ", std::localtime(&ent->date)); + out << buf; + + out.width(20); + if (ent->desc.empty()) + out << " "; + else + out << std::left << truncated(ent->desc, 20); + out << " "; + + // Always display the street value, if prices have been + // specified + + amount * street = resolve_amount(xact->cost, &ent->date, &balance, true); + + // If there are two transactions, use the one which does not + // refer to this account. If there are more than two, print + // "<Splits...>", unless the -s option is being used (show + // children), in which case print all of the splits, like + // gnucash does. + + transaction * xp; + if (ent->xacts.size() == 2) { + if (xact == ent->xacts.front()) + xp = ent->xacts.back(); + else + xp = ent->xacts.front(); + } else { + xp = xact; + } + std::string xact_str = xp->acct_as_str(); + + if (xp == xact && ! show_subtotals) + xact_str = "<Splits...>"; + + out.width(22); + out << std::left << truncated(xact_str, 22) << " "; + + out.width(12); + out << std::right << street->as_str(true); + delete street; + + print_resolved_balance(out, &ent->date, balance, true); + + out << std::endl; + + assert(! prev_balance); + prev_balance = new totals; + prev_balance->credit(balance); + prev_date = ent->date; + + if (! show_children || xp != xact) + return; + + for (std::list<transaction *>::iterator y = ent->xacts.begin(); + y != ent->xacts.end(); + y++) { + if (xact == *y) + continue; + + out << " "; + + out.width(22); + out << std::left << truncated((*y)->acct_as_str(), 22) << " "; + out.width(12); + + street = resolve_amount((*y)->cost, &ent->date); + out << std::right << street->as_str(true) << std::endl; + delete street; + } +} + +void print_register_period(std::ostream& out, std::time_t date, + account * acct, amount& sum, totals& balance) +{ + if (! gnuplot_safe && prev_balance) { + sum.negate(); + balance.credit(&sum); + report_change_in_asset_value(out, date, acct, balance); + sum.negate(); + balance.credit(&sum); + } + + char buf[32]; + std::strftime(buf, 31, "%Y/%m/%d ", std::localtime(&date)); + out << buf; + + if (! gnuplot_safe) { + out.width(20); + std::strftime(buf, 31, "%B", std::localtime(&date)); + out << std::left << truncated(buf, 20); + out << " "; + + out.width(22); + out << std::left << truncated(acct->as_str(), 22) << " "; + } else { + commodity * cmdty = sum.commdty(); + cmdty->symbol = ""; + cmdty->separate = false; + cmdty->thousands = false; + cmdty->european = false; + } + + out.width(12); + out << std::right << sum.as_str(); + + if (! gnuplot_safe) { + print_resolved_balance(out, &date, balance, true); + + assert(! prev_balance); + prev_balance = new totals; + prev_balance->credit(balance); + prev_date = date; + } + + out << std::endl; +} + +void print_register(std::ostream& out, const std::string& acct_name, + regexps_list& regexps, periodicity_t period = PERIOD_NONE) +{ + mask acct_regex(acct_name); + + // Walk through all of the ledger entries, printing their register + // formatted equivalent + + totals balance; + amount * period_sum = NULL; // jww (2004-04-27): should be 'totals' type + std::time_t last_date; + account * last_acct; + int last_mon = -1; + + for (entries_list_iterator i = main_ledger->entries.begin(); + i != main_ledger->entries.end(); + i++) { + if ((cleared_only && ! (*i)->cleared) || + (uncleared_only && (*i)->cleared) || + ! matches_date_range(*i) || ! (*i)->matches(regexps)) + continue; + + int entry_mon = std::localtime(&(*i)->date)->tm_mon; + + if (period_sum && period == PERIOD_MONTHLY && + last_mon != -1 && entry_mon != last_mon) { + assert(last_acct); + print_register_period(out, last_date, last_acct, *period_sum, balance); + delete period_sum; + period_sum = NULL; + } + + for (std::list<transaction *>::iterator x = (*i)->xacts.begin(); + x != (*i)->xacts.end(); + x++) { + if (! acct_regex.match((*x)->acct->as_str()) || + (lower_limit && ! satisfies_limit((*x)->cost))) + continue; + + if (period == PERIOD_NONE) { + print_register_transaction(out, *i, *x, balance); + } else { + amount * street = resolve_amount((*x)->cost, &(*i)->date, &balance, + true); + if (period_sum) { + period_sum->credit(street); + delete street; + } else { + period_sum = street; + } + + last_mon = entry_mon; + } + + last_date = (*i)->date; + last_acct = (*x)->acct; + } + } + + if (period_sum) { + if (last_acct) + print_register_period(out, last_date, last_acct, *period_sum, balance); + delete period_sum; + } + + if (! gnuplot_safe && prev_balance) { + report_change_in_asset_value(out, have_ending ? end_date : std::time(NULL), + last_acct, balance); + } +} + +////////////////////////////////////////////////////////////////////// +// +// Create an Equity file based on a ledger. This is used for +// archiving past years, and starting out a new year with compiled +// balances. +// + +static void equity_entry(account * acct, regexps_list& regexps, + std::ostream& out) +{ + if (! acct->balance.is_zero() && + (regexps.empty() || matches(regexps, acct->as_str()))) { + entry opening(main_ledger); + + opening.date = have_ending ? end_date : std::time(NULL); + opening.cleared = true; + opening.desc = "Opening Balance"; + + for (totals::const_iterator i = acct->balance.amounts.begin(); + i != acct->balance.amounts.end(); + i++) { + // Skip it, if there is a zero balance for the commodity + if ((*i).second->is_zero()) + continue; + + transaction * xact = new transaction(); + xact->acct = const_cast<account *>(acct); + xact->cost = (*i).second->copy(); + opening.xacts.push_back(xact); + + xact = new transaction(); + xact->acct = main_ledger->find_account("Equity:Opening Balances"); + xact->cost = (*i).second->copy(); + xact->cost->negate(); + opening.xacts.push_back(xact); + } + + opening.print(out); + } + + // Display balances for all child accounts + + for (accounts_map_iterator i = acct->children.begin(); + i != acct->children.end(); + i++) + equity_entry((*i).second, regexps, out); +} + +void equity_ledger(std::ostream& out, regexps_list& regexps) +{ + // The account have their current totals already generated as a + // result of parsing. We just have to output those values. + // totals + + for (accounts_map_iterator i = main_ledger->accounts.begin(); + i != main_ledger->accounts.end(); + i++) + equity_entry((*i).second, regexps, out); +} + +////////////////////////////////////////////////////////////////////// +// +// Report on the price of any commodities matching REGEXPS. This can +// be used to see what something was worth at a specific time. +// + +void price_report(std::ostream& out, regexps_list& regexps) +{ + if (! have_ending) { + end_date = std::time(NULL); + have_ending = true; + } + + for (commodities_map_iterator i = main_ledger->commodities.begin(); + i != main_ledger->commodities.end(); + i++) + if (regexps.empty() || matches(regexps, (*i).first)) { + amount * price = (*i).second->price(have_ending ? &end_date : NULL, + use_history, get_quotes); + if (price && ! price->is_zero()) { + out.width(20); + out << std::right << price->as_str() << " " << (*i).first + << std::endl; + } + } +} + +////////////////////////////////////////////////////////////////////// +// +// Add a new entry, using hueristic logic to simplify the entry +// requirements +// + +void add_new_entry(int index, int argc, char **argv) +{ + regexps_list regexps; + entry added(main_ledger); + entry * matching = NULL; + + assert(index < argc); + + if (! parse_date(argv[index++], &added.date)) { + std::cerr << "Error: Bad entry date: " << argv[index - 1] + << std::endl; + std::exit(1); + } + + added.cleared = cleared_only; + + if (index == argc) { + std::cerr << "Error: Too few arguments to 'entry'." << std::endl; + std::exit(1); + } + + regexps.clear(); + regexps.push_back(mask(argv[index++])); + + for (entries_list_reverse_iterator i = main_ledger->entries.rbegin(); + i != main_ledger->entries.rend(); + i++) { + if ((*i)->matches(regexps)) { + matching = *i; + break; + } + } + + added.desc = matching ? matching->desc : regexps.front().pattern; + + if (index == argc) { + std::cerr << "Error: Too few arguments to 'entry'." << std::endl; + std::exit(1); + } + + if (argv[index][0] == '-' || std::isdigit(argv[index][0])) { + if (! matching) { + std::cerr << "Error: Missing account name for non-matching entry." + << std::endl; + std::exit(1); + } + + transaction * m_xact, * xact, * first; + + m_xact = matching->xacts.front(); + + first = xact = new transaction(); + xact->acct = m_xact->acct; + xact->cost = create_amount(argv[index++]); + xact->cost->set_commdty(m_xact->cost->commdty()); + + added.xacts.push_back(xact); + + m_xact = matching->xacts.back(); + + xact = new transaction(); + xact->acct = m_xact->acct; + xact->cost = first->cost->copy(); + xact->cost->negate(); + + added.xacts.push_back(xact); + + if ((index + 1) < argc && std::string(argv[index]) == "-from") + if (account * acct = main_ledger->re_find_account(argv[++index])) + added.xacts.back()->acct = acct; + } else { + while (index < argc && std::string(argv[index]) != "-from") { + transaction * xact = new transaction(); + + mask acct_regex(argv[index++]); + + account * acct = NULL; + commodity * cmdty = NULL; + + if (matching) { + for (std::list<transaction *>::iterator x = matching->xacts.begin(); + x != matching->xacts.end(); + x++) { + if (acct_regex.match((*x)->acct->as_str())) { + acct = (*x)->acct; + cmdty = (*x)->cost->commdty(); + break; + } + } + } + + if (acct) + xact->acct = acct; + else + xact->acct = main_ledger->re_find_account(acct_regex.pattern); + + if (! xact->acct) { + std::cerr << "Error: Could not find account name '" + << acct_regex.pattern << "'." << std::endl; + std::exit(1); + } + + if (index == argc) { + std::cerr << "Error: Too few arguments to 'entry'." << std::endl; + std::exit(1); + } + + xact->cost = create_amount(argv[index++]); + if (! xact->cost->commdty()) + xact->cost->set_commdty(cmdty); + + added.xacts.push_back(xact); + } + + if ((index + 1) < argc && std::string(argv[index]) == "-from") { + if (account * acct = main_ledger->re_find_account(argv[++index])) { + transaction * xact = new transaction(); + xact->acct = acct; + xact->cost = NULL; + + added.xacts.push_back(xact); + } + } else { + transaction * xact = new transaction(); + if (! matching) { + std::cerr << "Error: Could not figure out the account to draw from." + << std::endl; + std::exit(1); + } + xact->acct = matching->xacts.back()->acct; + xact->cost = NULL; + added.xacts.push_back(xact); + } + } + + if (added.finalize()) + added.print(std::cout); +} + +// Print out the entire ledger that was read in. This can be used to +// "wash" ugly ledger files. It's written here, instead of ledger.cc, +// in order to access the static globals above. + +void book::print(std::ostream& out, regexps_list& regexps, + bool shortcut) const +{ + for (entries_list_const_iterator i = entries.begin(); + i != entries.end(); + i++) { + if (! matches_date_range(*i) || ! (*i)->matches(regexps)) + continue; + + (*i)->print(out, shortcut); + } +} + +} // namespace ledger + +using namespace ledger; + +static void show_help(std::ostream& out) +{ + std::cerr + << "usage: ledger [options] COMMAND [options] [REGEXPS]" << std::endl + << std::endl + << "Basic options:" << std::endl + << " -h display this help text" << std::endl + << " -v display version information" << std::endl + << " -f FILE specify pathname of ledger data file" << std::endl + << " -i FILE read list of inclusion regexps from FILE" << std::endl + << std::endl + << "Report filtering:" << std::endl + << " -b DATE specify a beginning date" << std::endl + << " -e DATE specify an ending date" << std::endl + << " -c do not show future entries (same as -e TODAY)" << std::endl + << " -d DATE specify a date mask ('-d mon', for all mondays)" << std::endl + << " -C show only cleared transactions and balances" << std::endl + << " -U show only uncleared transactions and balances" << std::endl + << " -l AMT don't print balance totals whose abs value is <AMT" << std::endl + << " -N REGEX accounts matching REGEXP only display if negative" << std::endl + << " -R do not consider virtual transactions: real only" << std::endl + << std::endl + << "Customizing output:" << std::endl + << " -n do not calculate parent account totals" << std::endl + << " -s show sub-accounts in balance totals" << std::endl + << " -S sort the output of \"print\" by date" << std::endl + << " -E show accounts that total to zero" << std::endl + << " -F print each account's full name" << std::endl + << " -M print register using monthly sub-totals" << std::endl + << " -G use with -M to produce gnuplot-friendly output" << std::endl + << std::endl + << "Commodity prices:" << std::endl + << " -P FILE sets the price database, for reading/writing price info" << std::endl + << " -T report commodity totals, not their market value" << std::endl + << " -V report the market value of commodities" << std::endl + << " -B report cost basis of commodities" << std::endl + << " -Q download new price information (when needed) from the Internet" << std::endl + << " (works by running \"getquote SYMBOL\")" << std::endl + << " -L MINS with -Q, fetch quotes only if data is older than MINS" << std::endl + << " -p STR specifies a direct commodity conversion: COMM=AMOUNT" << std::endl + << std::endl + << "commands:" << std::endl + << " 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; +} + +////////////////////////////////////////////////////////////////////// +// +// Command-line parser and top-level logic. +// + +int main(int argc, char * argv[]) +{ + int index; + std::string prices; + std::string limit; + regexps_list regexps; + + std::vector<std::string> files; + + main_ledger = new book; + + // Initialize some variables based on environment variable settings + + if (char * p = std::getenv("PRICE_HIST")) + price_db = p; + + if (char * p = std::getenv("PRICE_EXP")) + pricing_leeway = std::atol(p) * 60; + + // Parse the command-line options + + int c; + while (-1 != (c = getopt(argc, argv, + "+ABb:Ccd:Ee:Ff:Ghi:L:l:MN:nP:p:QRSsTUVv"))) { + switch (char(c)) { + case 'b': + have_beginning = true; + if (! parse_date(optarg, &begin_date)) { + std::cerr << "Error: Bad begin date: " << optarg << std::endl; + return 1; + } + break; + + case 'e': + have_ending = true; + if (! parse_date(optarg, &end_date)) { + std::cerr << "Error: Bad end date: " << optarg << std::endl; + return 1; + } + break; + + case 'c': + end_date = std::time(NULL); + have_ending = true; + break; + + case 'd': + have_date_mask = true; + if (! parse_date_mask(optarg, &date_mask)) { + std::cerr << "Error: Bad date mask: " << optarg << std::endl; + return 1; + } + break; + + case 'h': show_help(std::cout); break; + case 'f': files.push_back(optarg); break; + case 'C': cleared_only = true; break; + case 'U': uncleared_only = true; break; + case 'R': show_virtual = false; break; + case 's': show_children = true; break; + case 'S': show_sorted = true; break; + case 'E': show_empty = true; break; + case 'n': show_subtotals = false; break; + case 'F': full_names = true; break; + case 'M': print_monthly = true; break; + case 'G': gnuplot_safe = true; break; + + case 'N': + negonly_regexp = new mask(optarg); + break; + + // -i path-to-file-of-regexps + case 'i': + if (access(optarg, R_OK) != -1) + read_regexps(optarg, regexps); + break; + + // -p "COMMODITY=PRICE" + case 'p': + parse_price_setting(optarg); + break; + + case 'P': + price_db = optarg; + break; + + case 'Q': + get_quotes = true; + break; + + case 'V': + use_history = true; + break; + + case 'B': + cost_basis = true; + use_history = true; + break; + + case 'A': + net_gain = true; + use_history = true; + break; + + case 'T': + cost_basis = false; + use_history = false; + break; + + case 'L': + pricing_leeway = std::atol(optarg) * 60; + break; + + case 'l': + lower_limit = create_amount(optarg); + break; + + case 'v': + std::cout + << "Ledger Accouting Tool " LEDGER_VERSION << std::endl + << " Copyright (c) 2003 John Wiegley <johnw@newartisans.com>" + << std::endl << std::endl + << "This program is made available under the terms of the BSD" + << std::endl + << "Public License. See the LICENSE file included with the" + << std::endl + << "distribution for details and disclaimer." << std::endl; + return 0; + } + } + + if (optind == argc) { + show_help(std::cout); + return 1; + } + + index = optind; + + // Read the command word + + const std::string command = argv[index++]; + + int name_index = index; + if (command == "register" || command == "reg") { + if (net_gain) { + std::cerr << ("Reporting the asset gain makes " + "no sense for the register report.") + << std::endl; + return 1; + } + + if (name_index == argc) { + std::cerr << ("Error: Must specify an account name " + "after the 'register' command.") << std::endl; + return 1; + } + index++; + } + + // Compile the list of specified regular expressions, which can be + // specified after the command, or using the '-i FILE' option + + if (command != "entry") + for (; index < argc; index++) + regexps.push_back(mask(argv[index])); + + // A ledger data file must be specified + + int entry_count = 0; + + if (files.empty()) { + if (char * p = std::getenv("LEDGER")) { + for (p = std::strtok(p, ":"); p; p = std::strtok(NULL, ":")) { + char * sep = std::strrchr(p, '='); + if (sep) *sep++ = '\0'; + entry_count += parse_ledger_file(main_ledger, std::string(p), regexps, + command == "equity", sep); + } + } + } else { + for (std::vector<std::string>::iterator i = files.begin(); + i != files.end(); i++) { + char buf[4096]; + char * p = buf; + std::strcpy(p, (*i).c_str()); + char * sep = std::strrchr(p, '='); + if (sep) *sep++ = '\0'; + entry_count += parse_ledger_file(main_ledger, std::string(p), regexps, + command == "equity", sep); + } + } + + if (use_history && ! cost_basis && ! price_db.empty()) + entry_count += parse_ledger_file(main_ledger, price_db, regexps, + command == "equity"); + + if (entry_count == 0) { + std::cerr << ("Please specify ledger file(s) using -f option " + "or LEDGER environment variable.") << std::endl; + return 1; + } + + // Process the command + + if (command == "balance" || command == "bal") { + report_balances(std::cout, regexps); + } + else if (command == "register" || command == "reg") { + if (show_sorted || print_monthly) + main_ledger->sort(cmp_entry_date()); + print_register(std::cout, argv[name_index], regexps, + print_monthly ? PERIOD_MONTHLY : PERIOD_NONE); + } + else if (command == "print") { + if (show_sorted) + main_ledger->sort(cmp_entry_date()); + main_ledger->print(std::cout, regexps, ! full_names); + } + else if (command == "equity") { + equity_ledger(std::cout, regexps); + } + else if (command == "price" || command == "prices") { + price_report(std::cout, regexps); + } + else if (command == "entry") { + add_new_entry(index, argc, argv); + } + else { + std::cerr << "Error: Unrecognized command '" << command << "'." + << std::endl; + return 1; + } + +#ifdef DEBUG + // Ordinarily, deleting the main ledger isn't necessary, since the + // process is about to give back its heap to the OS. + + delete main_ledger; + + if (lower_limit) + delete lower_limit; + + if (negonly_regexp) + delete negonly_regexp; +#endif + + return 0; +} + +// reports.cc ends here. |