#include "ledger.h" #include namespace ledger { bool use_warnings = false; state main_ledger; std::list regexps; #ifdef HUQUQULLAH bool compute_huquq; std::list huquq_categories; #endif void entry::print(std::ostream& out) const { char buf[32]; std::strftime(buf, 31, "%Y.%m.%d ", std::localtime(&date)); out << buf; if (cleared) out << "* "; if (! code.empty()) out << '(' << code << ") "; if (! desc.empty()) out << " " << desc; out << std::endl; bool shortcut = xacts.size() == 2; if (shortcut && xacts.front()->cost->comm() != xacts.back()->cost->comm()) shortcut = false; for (std::list::const_iterator i = xacts.begin(); i != xacts.end(); i++) { out << " "; out.width(30); out << std::left << (*i)->acct->as_str(); if (! shortcut || i == xacts.begin()) { out << " "; out.width(10); out << std::right << (*i)->cost->as_str(true); } if (! (*i)->note.empty()) out << " ; " << (*i)->note; out << std::endl; } out << std::endl; } bool entry::validate() const { totals balance; for (std::list::const_iterator i = xacts.begin(); i != xacts.end(); i++) balance.credit((*i)->cost->value()); if (balance) { std::cerr << "Totals are:" << std::endl; balance.print(std::cerr, 20); std::cerr << std::endl; } return ! balance; // must balance to 0.0 } bool entry::matches(const std::list& regexps) const { if (regexps.empty() || (ledger::matches(regexps, code) || ledger::matches(regexps, desc))) { return true; } else { bool match = false; for (std::list::const_iterator x = xacts.begin(); x != xacts.end(); x++) { if (ledger::matches(regexps, (*x)->acct->name) || ledger::matches(regexps, (*x)->note)) { match = true; break; } } return match; } } #ifdef DO_CLEANUP totals::~totals() { for (iterator i = amounts.begin(); i != amounts.end(); i++) delete (*i).second; } #endif // DO_CLEANUP void totals::credit(const totals& other) { for (const_iterator i = other.amounts.begin(); i != other.amounts.end(); i++) credit((*i).second); } totals::operator bool() const { for (const_iterator i = amounts.begin(); i != amounts.end(); i++) if (*((*i).second)) return true; return false; } void totals::print(std::ostream& out, int width) const { bool first = true; for (const_iterator i = amounts.begin(); i != amounts.end(); i++) if (*((*i).second)) { if (first) first = false; else out << std::endl; out.width(width); out << std::right << *((*i).second); } } amount * totals::value(const std::string& commodity) const { // Render all of the amounts into the given commodity. This // requires known prices for each commodity. amount * total = create_amount((commodity + " 0.00").c_str()); for (const_iterator i = amounts.begin(); i != amounts.end(); i++) *total += *((*i).second); return total; } // Print out the entire ledger that was read in, sorted by date. // This can be used to "wash" ugly ledger files. void print_ledger(int argc, char *argv[], std::ostream& out) { optind = 1; // 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); // Sort the list of entries by date, then print them in order. std::sort(main_ledger.entries.begin(), main_ledger.entries.end(), cmp_entry_date()); for (entries_iterator i = main_ledger.entries.begin(); i != main_ledger.entries.end(); i++) if ((*i)->matches(regexps)) (*i)->print(out); } 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)); } void read_regexps(const char * path, std::list& regexps) { if (access(path, R_OK) != -1) { std::ifstream file(path); while (! file.eof()) { char buf[80]; file.getline(buf, 79); if (*buf && ! std::isspace(*buf)) record_regexp(buf, regexps); } } } bool matches(const std::list& regexps, const std::string& str, bool * exclude) { // 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) { if (exclude) *exclude = (*r).exclude; match = ! (*r).exclude; } } return match; } #ifdef DO_CLEANUP state::~state() { for (commodities_iterator i = commodities.begin(); i != commodities.end(); i++) delete (*i).second; for (accounts_iterator i = accounts.begin(); i != accounts.end(); i++) delete (*i).second; for (entries_iterator i = entries.begin(); i != entries.end(); i++) delete *i; } #endif // DO_CLEANUP void state::record_price(const char * setting) { char buf[128]; std::strcpy(buf, setting); assert(std::strlen(setting) < 128); char * c = buf; char * p = std::strchr(buf, '='); if (! p) { std::cerr << "Warning: Invalid price setting: " << setting << std::endl; } else { *p++ = '\0'; prices.amounts.insert(totals::pair(c, create_amount(p))); } } account * state::find_account(const char * name, bool create) { accounts_iterator i = accounts_cache.find(name); if (i != accounts_cache.end()) return (*i).second; char * buf = new char[std::strlen(name) + 1]; std::strcpy(buf, name); account * current = NULL; for (char * tok = std::strtok(buf, ":"); tok; tok = std::strtok(NULL, ":")) { if (! current) { accounts_iterator i = accounts.find(tok); if (i == accounts.end()) { if (! create) return NULL; current = new account(tok); accounts.insert(accounts_entry(tok, current)); } else { current = (*i).second; } } else { account::iterator i = current->children.find(tok); if (i == current->children.end()) { if (! create) return NULL; current = new account(tok, current); current->parent->children.insert(accounts_entry(tok, current)); } else { current = (*i).second; } } } delete[] buf; if (current) accounts_cache.insert(accounts_entry(name, current)); return current; } } // namespace ledger