#include "ledger.h" #include #include #include // Perl regular expression library namespace ledger { static bool show_current = false; static bool show_cleared = false; static bool show_children = false; static bool show_empty = false; static bool no_subtotals = false; struct mask { bool exclude; pcre * regexp; mask(bool exc, pcre * re) : exclude(exc), regexp(re) {} }; static inline bool matches(const std::list& regexps, const std::string& str) { // If the first pattern is an exclude, we assume all patterns match // if they don't match the exclude. If the first pattern is an // include, then only accounts matching the include will match. bool match = (*regexps.begin()).exclude; for (std::list::const_iterator r = regexps.begin(); r != regexps.end(); r++) { int ovec[3]; if (pcre_exec((*r).regexp, NULL, str.c_str(), str.length(), 0, 0, ovec, 3) >= 0) match = ! (*r).exclude; } return match; } static void display_total(std::ostream& out, totals& total_balance, const account * acct, const std::map& balances, const std::list& regexps) { std::map::const_iterator b = balances.find(const_cast(acct)); if (b != balances.end()) { totals * balance = (*b).second; if (balance && (show_empty || *balance)) { bool match = true; if (! regexps.empty()) { if (show_children) { match = false; for (const account * a = acct; a; a = a->parent) { if (matches(regexps, a->name)) { match = true; break; } } } else { match = matches(regexps, acct->name); } } if (match) { out << *balance; if (acct->parent) { for (const account * a = acct; a; a = a->parent) out << " "; out << acct->name << std::endl; } else { out << " " << *acct << std::endl; // For top-level accounts, update the total running balance total_balance.credit(*balance); } } } } // Display balances for all child accounts for (account::const_iterator i = acct->children.begin(); i != acct->children.end(); i++) display_total(out, total_balance, (*i).second, balances, regexps); } static void record_price(char * setting, std::map& prices) { char * c = setting; char * p = std::strchr(setting, '='); if (! p) { std::cerr << "Warning: Invalid price setting: " << setting << std::endl; } else { *p++ = '\0'; amount * price = create_amount(p); prices.insert(std::pair(c, price)); } } static void record_regexp(char * pattern, std::list& regexps) { bool exclude = false; char * pat = pattern; if (*pat == '-') { exclude = true; pat++; while (std::isspace(*pat)) pat++; } else if (*pat == '+') { pat++; while (std::isspace(*pat)) pat++; } const char *error; int erroffset; pcre * re = pcre_compile(pat, PCRE_CASELESS, &error, &erroffset, NULL); if (! re) std::cerr << "Warning: Failed to compile regexp: " << pattern << std::endl; else regexps.push_back(mask(exclude, re)); } ////////////////////////////////////////////////////////////////////// // // Balance reporting code // void report_balances(int argc, char **argv, std::ostream& out) { std::map prices; std::list regexps; int c; optind = 1; while (-1 != (c = getopt(argc, argv, "cCsSni:p:"))) { switch (char(c)) { case 'c': show_current = true; break; case 'C': show_cleared = true; break; case 's': show_children = true; break; case 'S': show_empty = true; break; case 'n': no_subtotals = true; break; // -i path-to-file-of-regexps case 'i': if (access(optarg, R_OK) != -1) { std::ifstream include(optarg); while (! include.eof()) { char buf[80]; include.getline(buf, 79); if (*buf && ! std::isspace(*buf)) record_regexp(buf, regexps); } } break; // -p "COMMODITY=PRICE" // -p path-to-price-database case 'p': if (access(optarg, R_OK) != -1) { std::ifstream pricedb(optarg); while (! pricedb.eof()) { char buf[80]; pricedb.getline(buf, 79); if (*buf && ! std::isspace(*buf)) record_price(buf, prices); } } else { record_price(optarg, prices); } break; } } // Compile the list of specified regular expressions, which can be // specified on the command line, or using an include/exclude file for (; optind < argc; optind++) record_regexp(argv[optind], regexps); // Walk through all of the ledger entries, computing the account // totals std::map balances; std::time_t now = std::time(NULL); for (ledger_iterator i = ledger.begin(); i != ledger.end(); i++) { for (std::list::iterator x = (*i)->xacts.begin(); x != (*i)->xacts.end(); x++) { account * acct = (*x)->acct; for (; acct; acct = no_subtotals ? NULL : acct->parent) { if (! show_children && acct->parent) continue; totals * balance = NULL; std::map::iterator t = balances.find(acct); if (t == balances.end()) { balance = new totals; balances.insert(std::pair(acct, balance)); } else { balance = (*t).second; } bool do_credit = false; if (show_current) { if (difftime((*i)->date, now) < 0) do_credit = true; } else if (show_cleared) { if ((*i)->cleared) do_credit = true; } else { do_credit = true; } if (! do_credit) continue; std::map::iterator pi = prices.find((*x)->cost->comm_symbol()); if (pi == prices.end()) { balance->credit((*x)->cost); } else { amount * value = (*x)->cost->value((*pi).second); balance->credit(value); delete value; } } } } // Walk through all the top-level accounts, giving the balance // report for each, and then for each of their children. totals total_balance; for (accounts_iterator i = accounts.begin(); i != accounts.end(); i++) display_total(out, total_balance, (*i).second, balances, regexps); // Print the total of all the balances shown if (! no_subtotals) out << "--------------------" << std::endl << total_balance << std::endl; // Free up temporary variables created on the heap for (std::map::iterator i = balances.begin(); i != balances.end(); i++) delete (*i).second; for (std::map::iterator i = prices.begin(); i != prices.end(); i++) delete (*i).second; } } // namespace ledger