summaryrefslogtreecommitdiff
path: root/ledger.cc
diff options
context:
space:
mode:
Diffstat (limited to 'ledger.cc')
-rw-r--r--ledger.cc686
1 files changed, 58 insertions, 628 deletions
diff --git a/ledger.cc b/ledger.cc
index 4985fba6..7c782336 100644
--- a/ledger.cc
+++ b/ledger.cc
@@ -1,674 +1,104 @@
#include "ledger.h"
+#include "report.h"
+#include "textual.h"
+#include "binary.h"
#include <fstream>
-#include <unistd.h>
+#include <deque>
namespace ledger {
-book * main_ledger;
+const std::string version = "2.0b";
-extern int linenum;
-
-commodity::~commodity()
+ledger_t::~ledger_t()
{
- if (conversion)
- delete conversion;
+ delete master;
- for (price_map::iterator i = history.begin();
- i != history.end();
+ for (commodities_map::iterator i = commodities.begin();
+ i != commodities.end();
i++)
delete (*i).second;
-}
-
-void commodity::set_price(amount * price, std::time_t * when)
-{
- assert(price);
- if (when)
- history.insert(price_map_pair(*when, price));
- else
- conversion = price;
-}
-amount * commodity::price(std::time_t * when,
- bool use_history, bool download) const
-{
- if (conversion || ! when || ! use_history)
- return conversion;
-
- std::time_t age;
- amount * price = NULL;
-
- for (price_map::reverse_iterator i = history.rbegin();
- i != history.rend();
+ // Don't bother unhooking each entry's transactions from the
+ // accounts they refer to, because all accounts are about to
+ // be deleted.
+ for (entries_list::iterator i = entries.begin();
+ i != entries.end();
i++)
- if (std::difftime(*when, (*i).first) >= 0) {
- age = (*i).first;
- price = (*i).second;
- break;
- }
-
- extern long pricing_leeway;
- time_t now = time(NULL); // the time of the query
-
- if (download && ! sought &&
- std::difftime(now, *when) < pricing_leeway &&
- (! price || std::difftime(*when, age) > pricing_leeway)) {
- using namespace std;
-
- // Only consult the Internet once for any commodity
- sought = true;
-
- char buf[256];
- buf[0] = '\0';
-
- if (FILE * fp = popen((string("getquote ") + symbol).c_str(), "r")) {
- if (feof(fp) || ! fgets(buf, 255, fp)) {
- fclose(fp);
- return price;
- }
- fclose(fp);
- }
-
- if (buf[0]) {
- char * p = strchr(buf, '\n');
- if (p) *p = '\0';
-
- price = create_amount(buf);
- const_cast<commodity *>(this)->set_price(price, &now);
-
- extern string price_db;
- if (! price_db.empty()) {
- char buf[128];
- strftime(buf, 127, "%Y/%m/%d %H:%M:%S", localtime(&now));
- ofstream database(price_db.c_str(), ios_base::out | ios_base::app);
- database << "P " << buf << " " << symbol << " "
- << price->as_str() << endl;
- }
- }
- }
-
- return price;
-}
-
-const std::string transaction::acct_as_str() const
-{
- char * begin = NULL;
- char * end = NULL;
-
- if (is_virtual) {
- if (must_balance) {
- begin = "[";
- end = "]";
- } else {
- begin = "(";
- end = ")";
- }
- }
-
- if (begin)
- return std::string(begin) + acct->as_str() + end;
- else
- return acct->as_str();
-}
-
-void transaction::print(std::ostream& out, bool display_quantity,
- bool display_price) const
-{
- out.width(30);
- out << std::left << acct_as_str();
-
- if (cost && display_quantity) {
- out << " ";
- out.width(12);
-
- std::string value = cost->as_str(true);
- if (! display_price) {
- int index = value.find('@');
- if (index != -1)
- value = std::string(value, 0, index - 1);
- }
- out << std::right << value;
- }
-
- if (! note.empty())
- out << " ; " << note;
-
- out << std::endl;
+ delete *i;
}
-void entry::print(std::ostream& out, bool shortcut) const
+commodity_t * ledger_t::find_commodity(const std::string& symbol,
+ bool auto_create)
{
- 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;
-
- commodity * comm = NULL;
- int size = 0;
-
- for (std::list<transaction *>::const_iterator x = xacts.begin();
- x != xacts.end();
- x++) {
- if ((*x)->is_virtual && ! (*x)->must_balance)
- continue;
-
- if (! comm)
- comm = (*x)->cost->commdty();
- else if (comm != (*x)->cost->commdty())
- shortcut = false;
-
- size++;
- }
-
- if (shortcut && size != 2)
- shortcut = false;
-
- for (std::list<transaction *>::const_iterator x = xacts.begin();
- x != xacts.end();
- x++) {
- if ((*x)->is_virtual && ! (*x)->specified)
- continue;
-
- out << " ";
+ commodities_map::const_iterator i = commodities.find(symbol);
+ if (i != commodities.end())
+ return (*i).second;
- (*x)->print(out, (! shortcut || x == xacts.begin() ||
- ((*x)->is_virtual && ! (*x)->must_balance)),
- size != 2);
+ if (auto_create) {
+ commodity_t * commodity = new commodity_t(symbol);
+ add_commodity(commodity);
+ return commodity;
}
- out << std::endl;
-}
-
-bool entry::validate(bool show_unaccounted) const
-{
- totals balance;
-
- for (std::list<transaction *>::const_iterator x = xacts.begin();
- x != xacts.end();
- x++)
- if ((*x)->cost && (*x)->must_balance) {
- amount * value = (*x)->cost->value();
- balance.credit(value);
- delete value;
- }
-
- if (show_unaccounted && ! balance.is_zero()) {
- std::cerr << "Unaccounted-for balances are:" << std::endl;
- balance.print(std::cerr, 20);
- std::cerr << std::endl << std::endl;
- }
- return balance.is_zero(); // must balance to 0.0
+ return NULL;
}
-bool entry::finalize(bool do_compute)
+bool ledger_t::add_entry(entry_t * entry)
{
- // Scan through and compute the total balance for the entry. This
- // is used for auto-calculating the value of entries with no cost,
- // and the per-unit price of unpriced commodities.
-
- totals balance;
-
- for (std::list<transaction *>::iterator x = xacts.begin();
- x != xacts.end();
- x++)
- if ((*x)->cost && ! (*x)->is_virtual) {
- amount * value = (*x)->cost->value();
- balance.credit(value);
- delete value;
- }
-
- // If one transaction of a two-line transaction is of a different
- // commodity than the others, and it has no per-unit price,
- // determine its price by dividing the unit count into the value of
- // the balance. This is done for the last eligible commodity.
-
- if (! balance.amounts.empty() && balance.amounts.size() == 2) {
- for (std::list<transaction *>::iterator x = xacts.begin();
- x != xacts.end();
- x++) {
- if ((*x)->is_virtual || (*x)->cost->has_price())
- continue;
-
- for (totals::iterator i = balance.amounts.begin();
- i != balance.amounts.end();
- i++)
- if ((*i).second->commdty() != (*x)->cost->commdty()) {
- (*x)->cost->set_value((*i).second);
- assert((*x)->cost->has_price());
- (*x)->cost->commdty()->set_price((*x)->cost->per_item_price(),
- &date);
- break;
- }
-
- break;
- }
- }
-
- // Walk through each of the transactions, fixing up any that we
- // can, and performing any on-the-fly calculations.
-
- bool empty_allowed = true;
-
- for (std::list<transaction *>::iterator x = xacts.begin();
- x != xacts.end();
- x++) {
- if ((*x)->is_virtual || (*x)->cost)
- continue;
-
- if (! empty_allowed || balance.amounts.empty() ||
- balance.amounts.size() != 1) {
- std::cerr << "Error, line " << linenum
- << ": Transaction entry is lacking an amount."
- << std::endl;
- return false;
- }
- empty_allowed = false;
-
- // If one transaction gives no value at all -- and all the
- // rest are of the same commodity -- then its value is the
- // inverse of the computed value of the others.
-
- totals::iterator i = balance.amounts.begin();
- (*x)->cost = (*i).second->value();
- (*x)->cost->negate();
+ entries.push_back(entry);
- if (do_compute)
- (*x)->acct->balance.credit((*x)->cost);
- }
-
- // If automated transactions are being used, walk through the
- // current transaction lines and create new transactions for all
- // that match.
-
- for (book::virtual_map_iterator m = ledger->virtual_mapping.begin();
- m != ledger->virtual_mapping.end();
- m++) {
- std::list<transaction *> new_xacts;
-
- for (std::list<transaction *>::iterator x = xacts.begin();
- x != xacts.end();
- x++) {
- if ((*x)->is_virtual ||
- ! ledger::matches(*((*m).first), (*x)->acct->as_str()))
- continue;
-
- for (std::list<transaction *>::iterator i = (*m).second->begin();
- i != (*m).second->end();
- i++) {
- transaction * t;
-
- if ((*i)->cost->commdty()) {
- t = new transaction((*i)->acct, (*i)->cost);
- } else {
- amount * temp = (*x)->cost->value();
- t = new transaction((*i)->acct, temp->value((*i)->cost));
- delete temp;
- }
-
- t->is_virtual = (*i)->is_virtual;
- t->must_balance = (*i)->must_balance;
-
- new_xacts.push_back(t);
- }
- }
-
- // Add to the current entry any virtual transactions which were
- // created. We have to do this afterward, otherwise the
- // iteration above is screwed up if we try adding new
- // transactions during the traversal.
-
- for (std::list<transaction *>::iterator x = new_xacts.begin();
- x != new_xacts.end();
- x++) {
- xacts.push_back(*x);
+ for (transactions_list::const_iterator i = entry->transactions.begin();
+ i != entry->transactions.end();
+ i++) {
+ (*i)->account->add_transaction(*i);
- if (do_compute)
- (*x)->acct->balance.credit((*x)->cost);
+ if ((*i)->amount != (*i)->cost) {
+ assert((*i)->amount.commodity);
+ (*i)->amount.commodity->add_price(entry->date, (*i)->cost / (*i)->amount);
}
}
- // Compute the balances again, just to make sure it all comes out
- // right (i.e., zero for every commodity).
-
- if (! validate()) {
- std::cerr << "Error, line " << (linenum - 1)
- << ": Failed to balance the following transaction:"
- << std::endl;
- validate(true);
- return false;
- }
-
return true;
}
-bool entry::matches(const regexps_list& regexps) const
-{
- if (regexps.empty() || (ledger::matches(regexps, code) ||
- ledger::matches(regexps, desc))) {
- return true;
- }
- else {
- bool match = false;
-
- for (std::list<transaction *>::const_iterator x = xacts.begin();
- x != xacts.end();
- x++) {
- if (ledger::matches(regexps, (*x)->acct->as_str()) ||
- ledger::matches(regexps, (*x)->note)) {
- match = true;
- break;
- }
- }
- return match;
- }
-}
-
-totals::~totals()
-{
- for (iterator i = amounts.begin(); i != amounts.end(); i++)
- delete (*i).second;
-}
-
-void totals::credit(const amount * val)
+bool ledger_t::remove_entry(entry_t * entry)
{
- iterator i = amounts.find(val->commdty());
- if (i != amounts.end())
- (*i).second->credit(val);
-#ifndef DEBUG
- else
- amounts.insert(pair(val->commdty(), val->copy()));
-#else
- else {
- std::pair<iterator, bool> result =
- amounts.insert(pair(val->commdty(), val->copy()));
- assert(result.second);
- }
-#endif
-}
+ entries.remove(entry);
-void totals::credit(const totals& other)
-{
- for (const_iterator i = other.amounts.begin();
- i != other.amounts.end();
+ for (transactions_list::const_iterator i
+ = entry->transactions.begin();
+ i != entry->transactions.end();
i++)
- credit((*i).second);
-}
+ (*i)->account->remove_transaction(*i);
-void totals::negate()
-{
- for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
- (*i).second->negate();
-}
-
-bool totals::is_zero() const
-{
- for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
- if (! (*i).second->is_zero())
- return false;
return true;
}
-bool totals::is_negative() const
-{
- bool all_negative = true;
- bool some_negative = false;
- for (const_iterator i = amounts.begin(); i != amounts.end(); i++) {
- if ((*i).second->is_negative())
- some_negative = true;
- else if (! (*i).second->is_zero())
- all_negative = false;
- }
- return some_negative && all_negative;
-}
-
-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->is_zero())
- continue;
-
- if (first)
- first = false;
- else
- out << std::endl;
-
- out.width(width);
- out << std::right << (*i).second->as_str();
- }
-}
-
-totals * totals::value() const
-{
- totals * cost_basis = new totals;
-
- for (const_iterator i = amounts.begin(); i != amounts.end(); i++) {
- if ((*i).second->is_zero())
- continue;
-
- amount * value = (*i).second->value();
- cost_basis->credit(value);
- delete value;
- }
-
- return cost_basis;
-}
-
-totals * totals::street(std::time_t * when, bool use_history,
- bool download) const
+int parse_ledger_file(char * p, ledger_t * book)
{
- totals * street_balance = new totals;
-
- for (const_iterator i = amounts.begin(); i != amounts.end(); i++) {
- if ((*i).second->is_zero())
- continue;
-
- amount * street = (*i).second->street(when, use_history, download);
- street_balance->credit(street);
- delete street;
- }
+ char * sep = std::strrchr(p, '=');
+ if (sep) *sep++ = '\0';
- return street_balance;
-}
-
-account::~account()
-{
- for (accounts_map_iterator i = children.begin();
- i != children.end();
- i++)
- delete (*i).second;
-}
+ std::ifstream stream(p);
-const std::string account::as_str(const account * stop) const
-{
- if (! parent || this == stop)
- return name;
- else if (stop)
- return parent->as_str(stop) + ":" + name;
- else if (full_name.empty())
- full_name = parent->as_str() + ":" + name;
-
- return full_name;
-}
-
-mask::mask(const std::string& pat) : exclude(false)
-{
- const char * p = pat.c_str();
- if (*p == '-') {
- exclude = true;
- p++;
- while (std::isspace(*p))
- p++;
- }
- else if (*p == '+') {
- p++;
- while (std::isspace(*p))
- p++;
- }
- pattern = p;
-
- const char *error;
- int erroffset;
- regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
- &error, &erroffset, NULL);
- if (! regexp)
- std::cerr << "Warning: Failed to compile regexp: " << pattern
- << std::endl;
-}
-
-mask::mask(const mask& m) : exclude(m.exclude), pattern(m.pattern)
-{
- const char *error;
- int erroffset;
- regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
- &error, &erroffset, NULL);
- assert(regexp);
-}
-
-bool mask::match(const std::string& str) const
-{
- static int ovec[30];
- int result = pcre_exec(regexp, NULL, str.c_str(), str.length(),
- 0, 0, ovec, 30);
- return result >= 0 && ! exclude;
-}
-
-bool matches(const regexps_list& regexps, const std::string& str,
- bool * by_exclusion)
-{
- if (regexps.empty())
- return false;
-
- bool match = false;
- bool definite = false;
-
- for (regexps_list_const_iterator r = regexps.begin();
- r != regexps.end();
- r++) {
- static int ovec[30];
- int result = pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
- 0, 0, ovec, 30);
- if (result >= 0) {
- match = ! (*r).exclude;
- definite = true;
- }
- else if ((*r).exclude) {
- if (! match)
- match = ! definite;
- }
- else {
- definite = true;
- }
- }
-
- if (by_exclusion)
- *by_exclusion = match && ! definite && by_exclusion;
-
- return match;
-}
-
-book::~book()
-{
- for (commodities_map_iterator i = commodities.begin();
- i != commodities.end();
- i++)
- delete (*i).second;
-
- for (accounts_map_iterator i = accounts.begin();
- i != accounts.end();
- i++)
- delete (*i).second;
-
- for (virtual_map_iterator i = virtual_mapping.begin();
- i != virtual_mapping.end();
- i++) {
- delete (*i).first;
-
- for (std::list<transaction *>::iterator j = (*i).second->begin();
- j != (*i).second->end();
- j++) {
- delete *j;
- }
- delete (*i).second;
- }
-
- for (entries_list_iterator i = entries.begin();
- i != entries.end();
- i++)
- delete *i;
-}
-
-account * book::re_find_account(const std::string& regex)
-{
- mask acct_regex(regex);
-
- for (entries_list_reverse_iterator i = entries.rbegin();
- i != entries.rend();
- i++)
- for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
- x != (*i)->xacts.end();
- x++)
- if (acct_regex.match((*x)->acct->as_str()))
- return (*x)->acct;
-
- return NULL;
-}
-
-account * book::find_account(const std::string& name, bool create)
-{
- accounts_map_iterator i = accounts_cache.find(name);
- if (i != accounts_cache.end())
- return (*i).second;
-
- char * buf = new char[name.length() + 1];
- std::strcpy(buf, name.c_str());
-
- account * current = NULL;
- for (char * tok = std::strtok(buf, ":");
- tok;
- tok = std::strtok(NULL, ":")) {
- if (! current) {
- accounts_map_iterator i = accounts.find(tok);
- if (i == accounts.end()) {
- if (! create) {
- delete[] buf;
- return NULL;
- }
- current = new account(tok);
- accounts.insert(accounts_map_pair(tok, current));
- } else {
- current = (*i).second;
- }
- } else {
- accounts_map_iterator i = current->children.find(tok);
- if (i == current->children.end()) {
- if (! create) {
- delete[] buf;
- return NULL;
- }
- current = new account(tok, current);
- current->parent->children.insert(accounts_map_pair(tok, current));
- } else {
- current = (*i).second;
- }
- }
- }
+ account_t * master;
+ if (sep)
+ master = book->find_account(sep);
+ else
+ master = book->master;
- delete[] buf;
+ book->sources.push_back(p);
- if (current)
- accounts_cache.insert(accounts_map_pair(name, current));
+ unsigned long magic;
+ std::istream::pos_type start = stream.tellg();
+ stream.read((char *)&magic, sizeof(magic));
+ stream.seekg(start);
- return current;
+ if (magic == magic_number)
+ return read_binary_ledger(stream, "", book, master);
+ else
+ return parse_textual_ledger(stream, book, master);
}
} // namespace ledger