diff options
-rw-r--r-- | account.cc | 2 | ||||
-rw-r--r-- | amount.h | 4 | ||||
-rw-r--r-- | binary.cc | 606 | ||||
-rw-r--r-- | ledger.cc | 23 | ||||
-rw-r--r-- | ledger.h | 30 | ||||
-rw-r--r-- | main.cc | 341 | ||||
-rw-r--r-- | textual.cc | 28 |
7 files changed, 391 insertions, 643 deletions
@@ -5,7 +5,7 @@ namespace ledger { -unsigned long account_t::next_ident; +account_t::ident_t account_t::next_ident; account_t::~account_t() { @@ -188,6 +188,8 @@ typedef std::pair<const std::string, commodity_t *> commodities_pair; class commodity_t { public: + typedef unsigned short ident_t; + std::string symbol; std::string name; std::string note; @@ -195,7 +197,7 @@ class commodity_t unsigned int flags; history_map history; amount_t conversion; - unsigned long ident; + ident_t ident; // If set, this global function pointer is called to determine // whether prices have been updated in the meanwhile. @@ -1,4 +1,5 @@ #include "ledger.h" +#include "debug.h" #include <vector> #include <fstream> @@ -13,85 +14,97 @@ namespace ledger { - unsigned long binary_magic_number = 0xFFEED765; -static unsigned long format_version = 0x00020009; +static char buf[4096]; -static char buf[4096]; + unsigned long binary_magic_number = 0xFFEED765; +static unsigned long format_version = 0x0002000a; static std::vector<account_t *> accounts; +static account_t::ident_t ident; static std::vector<commodity_t *> commodities; +static commodity_t::ident_t c_ident; -static unsigned long ident; -static unsigned long c_ident; +#ifdef RELEASE_LEVEL >= ALPHA +#define read_binary_guard(in, id) { \ + unsigned short guard; \ + in.read((char *)&guard, sizeof(guard)); \ + assert(guard == id); \ +} +#else +#define read_binary_guard(in, id) +#endif -void read_binary_amount(std::istream& in, amount_t& amt) +template <typename T> +inline void read_binary_number(std::istream& in, T& num) { + in.read((char *)&num, sizeof(num)); +} + +template <typename T> +inline T read_binary_number(std::istream& in) { + T num; + in.read((char *)&num, sizeof(num)); + return num; +} + +inline void read_binary_string(std::istream& in, std::string& str) { - unsigned long id; + read_binary_guard(in, 0x3001); -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1001); + unsigned char len; + read_binary_number(in, len); + if (len) { + in.read(buf, len); + buf[len] = '\0'; + str = buf; } -#endif - in.read((char *)&id, sizeof(id)); - if (id == 0xffffffff) + read_binary_guard(in, 0x3002); +} + +inline std::string read_binary_string(std::istream& in) +{ + read_binary_guard(in, 0x3001); + + unsigned char len; + read_binary_number(in, len); + if (len) { + in.read(buf, len); + buf[len] = '\0'; + read_binary_guard(in, 0x3002); + return buf; + } + + read_binary_guard(in, 0x3002); + + return ""; +} + +void read_binary_amount(std::istream& in, amount_t& amt) +{ + commodity_t::ident_t id; + read_binary_number(in, id); + if (id == 0xffff) amt.commodity = NULL; else amt.commodity = commodities[id]; amt.read_quantity(in); - -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1002); - } -#endif } transaction_t * read_binary_transaction(std::istream& in, entry_t * entry) { transaction_t * xact = new transaction_t(entry, NULL); - unsigned long id; - -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1003); - } -#endif - - in.read((char *)&id, sizeof(id)); + account_t::ident_t id; + read_binary_number(in, id); xact->account = accounts[id]; xact->account->add_transaction(xact); read_binary_amount(in, xact->amount); read_binary_amount(in, xact->cost); - - in.read((char *)&xact->flags, sizeof(xact->flags)); - - unsigned short len; - in.read((char *)&len, sizeof(len)); - if (len) { - in.read(buf, len); - buf[len] = '\0'; - xact->note = buf; - } - -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1004); - } -#endif + read_binary_number(in, xact->flags); + read_binary_string(in, xact->note); return xact; } @@ -100,101 +113,42 @@ entry_t * read_binary_entry(std::istream& in, journal_t * journal) { entry_t * entry = new entry_t; -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1005); - } -#endif - - in.read((char *)&entry->date, sizeof(entry->date)); - in.read((char *)&entry->state, sizeof(entry->state)); - - unsigned short len; - in.read((char *)&len, sizeof(len)); - if (len) { - in.read(buf, len); - buf[len] = '\0'; - entry->code = buf; - } - - in.read((char *)&len, sizeof(len)); - if (len) { - in.read(buf, len); - buf[len] = '\0'; - entry->payee = buf; - } - - unsigned long count; - in.read((char *)&count, sizeof(count)); + read_binary_number(in, entry->date); + read_binary_number(in, entry->state); + read_binary_string(in, entry->code); + read_binary_string(in, entry->payee); - for (int i = count; --i >= 0; ) { + for (unsigned long i = 0, count = read_binary_number<unsigned long>(in); + i < count; + i++) { transaction_t * xact = read_binary_transaction(in, entry); entry->transactions.push_back(xact); } -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1006); - } -#endif - return entry; } commodity_t * read_binary_commodity(std::istream& in) { - unsigned long id; - commodity_t * commodity = new commodity_t; commodities.push_back(commodity); -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1007); - } -#endif - - in.read((char *)&id, sizeof(id)); + commodity_t::ident_t id; + read_binary_number(in, id); commodity->ident = id; assert(id == commodities.size() - 1); - unsigned short len; - in.read((char *)&len, sizeof(len)); - if (len) { - in.read(buf, len); - buf[len] = '\0'; - commodity->symbol = buf; - } - - in.read((char *)&len, sizeof(len)); - if (len) { - in.read(buf, len); - buf[len] = '\0'; - commodity->name = buf; - } - - in.read((char *)&len, sizeof(len)); - if (len) { - in.read(buf, len); - buf[len] = '\0'; - commodity->note = buf; - } + read_binary_string(in, commodity->symbol); + read_binary_string(in, commodity->name); + read_binary_string(in, commodity->note); + read_binary_number(in, commodity->precision); + read_binary_number(in, commodity->flags); - in.read((char *)&commodity->precision, sizeof(commodity->precision)); - in.read((char *)&commodity->flags, sizeof(commodity->flags)); - - unsigned long count; - in.read((char *)&count, sizeof(count)); - - for (int i = count; --i >= 0; ) { + for (unsigned long i = 0, count = read_binary_number<unsigned long>(in); + i < count; + i++) { std::time_t when; - in.read((char *)&when, sizeof(std::time_t)); + read_binary_number(in, when); amount_t amt; read_binary_amount(in, amt); commodity->history.insert(history_pair(when, amt)); @@ -202,60 +156,28 @@ commodity_t * read_binary_commodity(std::istream& in) read_binary_amount(in, commodity->conversion); -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1008); - } -#endif - return commodity; } account_t * read_binary_account(std::istream& in, account_t * master = NULL) { - unsigned long id; - account_t * acct = new account_t(NULL); accounts.push_back(acct); -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1009); - } -#endif - - in.read((char *)&id, sizeof(id)); + account_t::ident_t id; + read_binary_number(in, id); acct->ident = id; assert(id == accounts.size() - 1); - in.read((char *)&id, sizeof(id)); - if (id == 0xffffffff) + read_binary_number(in, id); // parent id + if (id == 0xffff) acct->parent = NULL; else acct->parent = accounts[id]; - unsigned short len; - in.read((char *)&len, sizeof(len)); - if (len) { - in.read(buf, len); - buf[len] = '\0'; - acct->name = buf; - } - - in.read((char *)&len, sizeof(len)); - if (len) { - in.read(buf, len); - buf[len] = '\0'; - acct->note = buf; - } - - in.read((char *)&acct->depth, sizeof(acct->depth)); - - in.read((char *)&len, sizeof(len)); + read_binary_string(in, acct->name); + read_binary_string(in, acct->note); + read_binary_number(in, acct->depth); // If all of the subaccounts will be added to a different master // account, throw away what we've learned about the recorded @@ -266,82 +188,49 @@ account_t * read_binary_account(std::istream& in, account_t * master = NULL) acct = master; } - for (int i = 0; i < len; i++) { + for (account_t::ident_t i = 0, + count = read_binary_number<account_t::ident_t>(in); + i < count; + i++) { account_t * child = read_binary_account(in); child->parent = acct; acct->add_account(child); } -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1010); - } -#endif - return acct; } -unsigned int read_binary_journal(std::istream& in, - const std::string& leader, - journal_t * journal, - account_t * master) +unsigned int read_binary_journal(std::istream& in, + journal_t * journal, + account_t * master) { - ident = 0; + ident = 0; c_ident = 0; - unsigned long magic; - in.read((char *)&magic, sizeof(magic)); - if (magic != binary_magic_number) + if (read_binary_number<unsigned long>(in) != binary_magic_number || + read_binary_number<unsigned long>(in) != format_version) return 0; -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1011); - } -#endif - - unsigned long this_ver; - in.read((char *)&this_ver, sizeof(this_ver)); - if (this_ver != format_version) - return 0; - - unsigned short len; - in.read((char *)&len, sizeof(len)); - if (! len) - return 0; - in.read(buf, len); - - if (leader != buf) - return 0; - - in.read((char *)&len, sizeof(len)); - - for (int i = len; --i >= 0; ) { - in.read((char *)&len, sizeof(len)); - assert(len); - in.read(buf, len); - buf[len] = '\0'; - - journal->sources.push_back(buf); - + for (unsigned short i = 0, + count = read_binary_number<unsigned short>(in); + i < count; + i++) { + std::string path = read_binary_string(in); std::time_t old_mtime; + read_binary_number(in, old_mtime); struct stat info; - in.read((char *)&old_mtime, sizeof(old_mtime)); - stat(buf, &info); - if (info.st_mtime > old_mtime) + stat(path.c_str(), &info); + if (std::difftime(info.st_mtime, old_mtime) > 0) return 0; + journal->sources.push_back(path); } journal->master = read_binary_account(in, master); - unsigned long count; - in.read((char *)&count, sizeof(count)); - - for (int i = count; --i >= 0; ) { + for (account_t::ident_t i = 0, + count = read_binary_number<account_t::ident_t>(in); + i < count; + i++) { commodity_t * commodity = read_binary_commodity(in); std::pair<commodities_map::iterator, bool> result = commodity_t::commodities.insert(commodities_pair(commodity->symbol, @@ -349,21 +238,14 @@ unsigned int read_binary_journal(std::istream& in, assert(result.second || master); } - in.read((char *)&count, sizeof(count)); - - for (int i = count; --i >= 0; ) { + unsigned int count = read_binary_number<unsigned long>(in); + for (unsigned long i = 0; + i < count; + i++) { entry_t * entry = read_binary_entry(in, journal); journal->entries.push_back(entry); } -#ifdef DEBUG_ENABLED - { - unsigned short guard; - in.read((char *)&guard, sizeof(guard)); - assert(guard == 0x1012); - } -#endif - accounts.clear(); commodities.clear(); @@ -371,254 +253,144 @@ unsigned int read_binary_journal(std::istream& in, } -void write_binary_amount(std::ostream& out, const amount_t& amt) -{ -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1001; - out.write((char *)&guard, sizeof(guard)); - } +#ifdef RELEASE_LEVEL >= ALPHA +#define write_binary_guard(in, id) { \ + unsigned short guard = id; \ + out.write((char *)&guard, sizeof(guard)); \ +} +#else +#define write_binary_guard(in, id) #endif - if (amt.commodity) { - out.write((char *)&amt.commodity->ident, sizeof(amt.commodity->ident)); - } else { - unsigned long end = 0xffffffff; - out.write((char *)&end, sizeof(end)); - } - - amt.write_quantity(out); - -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1002; - out.write((char *)&guard, sizeof(guard)); - } -#endif +template <typename T> +inline void write_binary_number(std::ostream& out, T num) { + out.write((char *)&num, sizeof(num)); } -void write_binary_transaction(std::ostream& out, transaction_t * xact) +inline void write_binary_string(std::ostream& out, const std::string& str) { -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1003; - out.write((char *)&guard, sizeof(guard)); - } -#endif - - out.write((char *)&xact->account->ident, sizeof(xact->account->ident)); - write_binary_amount(out, xact->amount); - write_binary_amount(out, xact->cost); - out.write((char *)&xact->flags, sizeof(xact->flags)); + write_binary_guard(out, 0x3001); - unsigned short len = xact->note.length(); - out.write((char *)&len, sizeof(len)); + unsigned char len = str.length(); + write_binary_number(out, len); if (len) - out.write(xact->note.c_str(), len); + out.write(str.c_str(), len); -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1004; - out.write((char *)&guard, sizeof(guard)); - } -#endif + write_binary_guard(out, 0x3002); } -void write_binary_entry(std::ostream& out, entry_t * entry) +void write_binary_amount(std::ostream& out, const amount_t& amt) { -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1005; - out.write((char *)&guard, sizeof(guard)); - } -#endif - - out.write((char *)&entry->date, sizeof(entry->date)); - out.write((char *)&entry->state, sizeof(entry->state)); + if (amt.commodity) + write_binary_number(out, amt.commodity->ident); + else + write_binary_number<commodity_t::ident_t>(out, 0xffff); - unsigned short len = entry->code.length(); - out.write((char *)&len, sizeof(len)); - if (len) - out.write(entry->code.c_str(), len); + amt.write_quantity(out); +} - len = entry->payee.length(); - out.write((char *)&len, sizeof(len)); - if (len) - out.write(entry->payee.c_str(), len); +void write_binary_transaction(std::ostream& out, transaction_t * xact) +{ + write_binary_number(out, xact->account->ident); + write_binary_amount(out, xact->amount); + write_binary_amount(out, xact->cost); + write_binary_number(out, xact->flags); + write_binary_string(out, xact->note); +} - unsigned long count = entry->transactions.size(); - out.write((char *)&count, sizeof(count)); +void write_binary_entry(std::ostream& out, entry_t * entry) +{ + write_binary_number(out, entry->date); + write_binary_number(out, entry->state); + write_binary_string(out, entry->code); + write_binary_string(out, entry->payee); + write_binary_number<unsigned long>(out, entry->transactions.size()); for (transactions_list::const_iterator i = entry->transactions.begin(); i != entry->transactions.end(); i++) write_binary_transaction(out, *i); - -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1006; - out.write((char *)&guard, sizeof(guard)); - } -#endif } void write_binary_commodity(std::ostream& out, commodity_t * commodity) { -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1007; - out.write((char *)&guard, sizeof(guard)); - } -#endif - - out.write((char *)&c_ident, sizeof(c_ident)); + write_binary_number(out, c_ident); commodity->ident = c_ident; ++c_ident; - unsigned short len = commodity->symbol.length(); - out.write((char *)&len, sizeof(len)); - out.write(commodity->symbol.c_str(), len); - - len = commodity->name.length(); - out.write((char *)&len, sizeof(len)); - if (len) - out.write(commodity->name.c_str(), len); - - len = commodity->note.length(); - out.write((char *)&len, sizeof(len)); - if (len) - out.write(commodity->note.c_str(), len); - - out.write((char *)&commodity->precision, sizeof(commodity->precision)); - out.write((char *)&commodity->flags, sizeof(commodity->flags)); - - unsigned long count = commodity->history.size(); - out.write((char *)&count, sizeof(count)); + write_binary_string(out, commodity->symbol); + write_binary_string(out, commodity->name); + write_binary_string(out, commodity->note); + write_binary_number(out, commodity->precision); + write_binary_number(out, commodity->flags); + write_binary_number<unsigned long>(out, commodity->history.size()); for (history_map::const_iterator i = commodity->history.begin(); i != commodity->history.end(); i++) { - out.write((char *)&((*i).first), sizeof(std::time_t)); + write_binary_number(out, (*i).first); write_binary_amount(out, (*i).second); } write_binary_amount(out, commodity->conversion); - -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1008; - out.write((char *)&guard, sizeof(guard)); - } -#endif } void write_binary_account(std::ostream& out, account_t * account) { -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1009; - out.write((char *)&guard, sizeof(guard)); - } -#endif - - out.write((char *)&ident, sizeof(ident)); + write_binary_number(out, ident); account->ident = ident; ++ident; - if (account->parent) { - out.write((char *)&account->parent->ident, sizeof(account->parent->ident)); - } else { - unsigned long end = 0xffffffff; - out.write((char *)&end, sizeof(end)); - } - - unsigned short len = account->name.length(); - out.write((char *)&len, sizeof(len)); - if (len) - out.write(account->name.c_str(), len); - - len = account->note.length(); - out.write((char *)&len, sizeof(len)); - if (len) - out.write(account->note.c_str(), len); - - out.write((char *)&account->depth, sizeof(account->depth)); + if (account->parent) + write_binary_number(out, account->parent->ident); + else + write_binary_number<account_t::ident_t>(out, 0xffff); - len = account->accounts.size(); - out.write((char *)&len, sizeof(len)); + write_binary_string(out, account->name); + write_binary_string(out, account->note); + write_binary_number(out, account->depth); + write_binary_number<account_t::ident_t>(out, account->accounts.size()); for (accounts_map::iterator i = account->accounts.begin(); i != account->accounts.end(); i++) write_binary_account(out, (*i).second); - -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1010; - out.write((char *)&guard, sizeof(guard)); - } -#endif } void write_binary_journal(std::ostream& out, journal_t * journal, - const std::string& leader) + strings_list * files) { - out.write((char *)&binary_magic_number, sizeof(binary_magic_number)); + write_binary_number(out, binary_magic_number); + write_binary_number(out, format_version); -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1011; - out.write((char *)&guard, sizeof(guard)); - } -#endif - - out.write((char *)&format_version, sizeof(format_version)); - - unsigned short len = leader.length(); - assert(len > 0); - out.write((char *)&len, sizeof(len)); - out.write(leader.c_str(), len); - - len = journal->sources.size(); - out.write((char *)&len, sizeof(len)); - - for (std::list<std::string>::const_iterator i = journal->sources.begin(); - i != journal->sources.end(); - i++) { - len = (*i).length(); - out.write((char *)&len, sizeof(len)); - assert(len); - out.write((*i).c_str(), len); - struct stat info; - stat((*i).c_str(), &info); - out.write((char *)&info.st_mtime, sizeof(info.st_mtime)); + if (! files) { + write_binary_number<unsigned short>(out, 0); + } else { + write_binary_number<unsigned short>(out, files->size()); + for (strings_list::const_iterator i = files->begin(); + i != files->end(); + i++) { + write_binary_string(out, *i); + struct stat info; + stat((*i).c_str(), &info); + write_binary_number(out, std::time_t(info.st_mtime)); + } } write_binary_account(out, journal->master); - unsigned long count = commodity_t::commodities.size() - 1; - out.write((char *)&count, sizeof(count)); - + write_binary_number<commodity_t::ident_t>(out, commodity_t::commodities.size() - 1); for (commodities_map::const_iterator i = commodity_t::commodities.begin(); i != commodity_t::commodities.end(); i++) if (! (*i).first.empty()) write_binary_commodity(out, (*i).second); - count = journal->entries.size(); - out.write((char *)&count, sizeof(count)); - + write_binary_number<unsigned long>(out, journal->entries.size()); for (entries_list::const_iterator i = journal->entries.begin(); i != journal->entries.end(); i++) write_binary_entry(out, *i); - -#ifdef DEBUG_ENABLED - { - unsigned short guard = 0x1012; - out.write((char *)&guard, sizeof(guard)); - } -#endif } } // namespace ledger @@ -177,30 +177,23 @@ entry_t * journal_t::derive_entry(strings_list::iterator i, return added; } -int parse_journal_file(char * p, journal_t * journal) +int parse_journal_file(const std::string& path, journal_t * journal) { - char * sep = std::strrchr(p, '='); - if (sep) *sep++ = '\0'; + journal->sources.push_back(path); - std::ifstream stream(p); + if (access(path.c_str(), R_OK) == -1) + return 0; - account_t * master; - if (sep) - master = journal->find_account(sep); - else - master = journal->master; - - journal->sources.push_back(p); + std::ifstream stream(path.c_str()); unsigned long magic; - std::istream::pos_type start = stream.tellg(); stream.read((char *)&magic, sizeof(magic)); - stream.seekg(start); + stream.seekg(0); if (magic == binary_magic_number) - return read_binary_journal(stream, "", journal, master); + return read_binary_journal(stream, journal, journal->master); else - return parse_textual_journal(stream, journal, master); + return parse_textual_journal(stream, journal, journal->master); } } // namespace ledger @@ -110,20 +110,22 @@ typedef std::pair<const std::string, account_t *> accounts_pair; class account_t { public: + typedef unsigned short ident_t; + account_t * parent; std::string name; std::string note; - unsigned int depth; + unsigned char depth; accounts_map accounts; transactions_list transactions; mutable balance_pair_t value; mutable balance_pair_t total; - mutable unsigned long ident; - mutable unsigned long dflags; + mutable ident_t ident; + mutable unsigned short dflags; mutable std::string _fullname; - static unsigned long next_ident; + static ident_t next_ident; account_t(account_t * _parent, const std::string& _name = "", @@ -214,21 +216,21 @@ class journal_t strings_list::iterator end) const; }; -int parse_journal_file(char * p, journal_t * journal); +int parse_journal_file(const std::string& path, journal_t * journal); -unsigned int parse_textual_journal(std::istream& in, journal_t * ledger, - account_t * master = NULL); +unsigned int parse_textual_journal(std::istream& in, + journal_t * ledger, + account_t * master = NULL); extern unsigned long binary_magic_number; -unsigned int read_binary_journal(std::istream& in, - const std::string& leader, - journal_t * journal, - account_t * master = NULL); +unsigned int read_binary_journal(std::istream& in, + journal_t * journal, + account_t * master = NULL); -void write_binary_journal(std::ostream& out, - journal_t * journal, - const std::string& leader); +void write_binary_journal(std::ostream& out, + journal_t * journal, + strings_list * files = NULL); extern const std::string version; @@ -11,23 +11,51 @@ #include <unistd.h> #include <ctime> -namespace ledger { +namespace { + +using namespace ledger; -static const std::string bal_fmt = "%20T %2_%-n\n"; -static const std::string reg_fmt +const std::string bal_fmt = "%20T %2_%-n\n"; +const std::string reg_fmt = "%D %-.20P %-.22N %12.66t %12.80T\n\ %/ %-.22N %12.66t %12.80T\n"; -static const std::string plot_value_fmt = "%D %t\n"; -static const std::string plot_total_fmt = "%D %T\n"; -static const std::string print_fmt +const std::string plot_value_fmt = "%D %t\n"; +const std::string plot_total_fmt = "%D %T\n"; +const std::string print_fmt = "\n%D %X%C%P\n %-34N %12o\n%/ %-34N %12o\n"; -static const std::string equity_fmt +const std::string equity_fmt = "\n%D %X%C%P\n%/ %-34N %12t\n"; +std::auto_ptr<journal_t> journal(new journal_t); +std::list<std::string> files; +std::auto_ptr<value_expr_t> sort_order; +std::auto_ptr<std::ostream> output_stream; +std::auto_ptr<interval_t> report_interval; + +#define OUT() (output_stream.get() ? *output_stream.get() : std::cout) -static long pricing_leeway = 24 * 3600; -static std::string price_db; -static bool cache_dirty = false; +std::string init_file; +std::string cache_file; +std::string price_db; +std::string predicate; +std::string display_predicate; +std::string format_string; +std::string sort_string; +std::string value_expr = "a"; +std::string total_expr = "T"; +std::time_t interval_begin = 0; +unsigned long pricing_leeway = 24 * 3600; + +bool cache_dirty = true; +bool show_subtotals = true; +bool show_expanded = false; +bool show_related = false; +bool show_all_related = false; +bool show_inverted = false; +bool show_empty = false; +bool days_of_the_week = false; +bool show_revalued = false; +bool show_revalued_only = false; void download_price_quote(commodity_t * commodity, const std::time_t age, @@ -77,55 +105,6 @@ void download_price_quote(commodity_t * commodity, } } -} // namespace ledger - - -namespace { - using namespace ledger; - - std::auto_ptr<journal_t> journal(new journal_t); - std::list<std::string> files; - std::auto_ptr<value_expr_t> sort_order; - std::auto_ptr<std::ostream> output_stream; - std::auto_ptr<interval_t> report_interval; - -#define OUT() (output_stream.get() ? *output_stream.get() : std::cout) - - std::string predicate; - std::string display_predicate; - std::string format_string; - std::string sort_string; - std::string value_expr = "a"; - std::string total_expr = "T"; - std::time_t interval_begin = 0; - - bool show_subtotals = true; - bool show_expanded = false; - bool show_related = false; - bool show_all_related = false; - bool show_inverted = false; - bool show_empty = false; - bool days_of_the_week = false; - bool show_revalued = false; - bool show_revalued_only = false; - bool use_cache = false; -} - -// jww (2004-08-13): fix this -static std::string ledger_cache_file() -{ - std::string cache_file; - - if (const char * p = std::getenv("LEDGER_CACHE")) { - cache_file = p; - } - else if (const char * p = std::getenv("HOME")) { - cache_file = p; - cache_file += "/.ledger"; - } - return cache_file; -} - static void show_version(std::ostream& out) { out @@ -139,42 +118,59 @@ static void show_version(std::ostream& out) static void show_help(std::ostream& out) { out - << "usage: ledger [options] COMMAND [options] [REGEXPS]\n\n" - << "Basic options:\n" - << " -h display this help text\n" - << " -v display version information\n" - << " -f FILE specify pathname of ledger data file\n\n" - << "Report filtering:\n" - << " -a REGEX specify an account regex for \"print\"\n" - << " -b DATE specify a beginning date\n" - << " -e DATE specify an ending date\n" - << " -c do not show future entries (same as -e TODAY)\n" - << " -d DATE specify a date mask ('-d mon', for all mondays)\n" - << " -C show only cleared transactions and balances\n" - << " -U show only uncleared transactions and balances\n" - << " -R do not consider virtual transactions: real only\n" - << " -l EXPR don't print entries for which EXPR yields 0\n\n" - << "Output customization:\n" - << " -n do not calculate parent account totals\n" - << " -s show sub-accounts in balance, and splits in register\n" - << " -M print register using monthly sub-totals\n" - << " -E show accounts that total to zero\n" - << " -S EXPR sort entry output based on EXPR\n\n" - << "Commodity reporting:\n" - << " -T report commodity totals, not their market value\n" - << " -B report cost basis of commodities\n" - << " -V report the market value of commodities\n" - << " -P FILE sets the price database, for reading/writing price info\n" - << " -Q download price information from the Internet\n" - << " (works by running \"getquote SYMBOL\")\n" - << " -L MINS with -Q, fetch quotes only if data is older than MINS\n" - << " -p STR specifies a direct commodity conversion: COMM=AMOUNT\n\n" - << "Commands:\n" - << " balance show balance totals\n" - << " register display a register for ACCOUNT\n" - << " print print all ledger entries\n" - << " entry output a newly formed entry, based on arguments\n" - << " equity output equity entries for specified accounts\n"; + << "usage: ledger [options] COMMAND [options] [REGEXPS]\n\n\ +Basic options:\n\ + -h, --help display this help text\n\ + -v, --version display version information\n\ + -i, --init FILE initialize ledger by loading FILE\n\ + -f, --file FILE specify pathname of ledger data file\n\ + -o, --output FILE write all output to FILE\n\ + -p, --set-price CONV specifies commodity conversion: COMM=AMOUNT\n\n\ +Report filtering:\n\ + -b, --begin-date DATE specify a beginning date\n\ + -e, --end-date DATE specify an ending date\n\ + -c, --current do not show future entries (same as -e TODAY)\n\ + -C, --cleared show only cleared transactions and balances\n\ + -U, --uncleared show only uncleared transactions and balances\n\ + -R, --real do not consider virtual transactions: real only\n\n\ +Output customization:\n\ + -F, --format STR \n\ + -y, --date-format STR \n\ + -E, --empty balance: also show accounts that total to zero\n\ + -n, --collapse balance: no parent account totals; register: collapse\n\ + -s, --show-all balance: show sub-accounts; register: show subtotals\n\ + -S, --sort EXPR sort report according to value EXPR\n\ + -r, --related \n\ + -z, --interval EXPR \n\ + -w, --dow print register using day of week sub-totals\n\ + -W, --weekly \" \" weekly sub-totals\n\ + -M, --monthly \" \" monthly sub-totals\n\ + -Y, --yearly \" \" yearly sub-totals\n\ + -l, --limit EXPR don't calculate entries for which EXPR yields 0\n\ + -d, --display EXPR don't print entries for which EXPR yields 0\n\ + -t, --value EXPR \n\ + -T, --total EXPR \n\ + -j, --value-data \n\ + -J, --total-data \n\n\ +Commodity reporting:\n\ + -P, --price-db FILE sets the price database\n\ + -L, --price-exp MINS with -Q, fetch quotes only if data is older than MINS\n\ + -Q, --download download price information from the Internet\n\ + (works by running \"getquote SYMBOL\")\n\ + -O, --quantity \n\ + -B, --basis report cost basis of commodities\n\ + -V, --market report the market value of commodities\n\ + -G, --gain \n\ + -A, --average \n\ + -D, --deviation \n\ + -X, --trend \n\ + -Z, --weighted-trend \n\n\ +Commands:\n\ + balance show balance totals\n\ + register display a register for ACCOUNT\n\ + print print all ledger entries\n\ + entry output a newly formed entry, based on arguments\n\ + equity output equity entries for specified accounts\n"; } @@ -195,19 +191,22 @@ OPT_BEGIN(version, "v", false) { } OPT_END(version); OPT_BEGIN(init, "i:", true) { - std::ifstream stream(optarg); - parse_textual_journal(stream, journal.get(), journal->master); + init_file = optarg; } OPT_END(init); OPT_BEGIN(file, "f:", true) { - files.push_back(optarg); - use_cache = false; + char * buf = new char[std::strlen(optarg) + 1]; + std::strcpy(buf, optarg); + for (char * p = std::strtok(buf, ":"); + p; + p = std::strtok(NULL, ":")) + files.push_back(p); + delete[] buf; } OPT_END(file); -#if 0 -OPT_BEGIN(cache, ":") { +OPT_BEGIN(cache, ":", false) { + cache_file = optarg; } OPT_END(cache); -#endif OPT_BEGIN(output, "o:", false) { if (std::string(optarg) != "-") @@ -215,6 +214,7 @@ OPT_BEGIN(output, "o:", false) { } OPT_END(output); OPT_BEGIN(set_price, "p:", true) { + // jww (2004-08-14): fix this relative to the other file settings if (char * p = std::strchr(optarg, '=')) { *p = ' '; std::string conversion = "C "; @@ -440,52 +440,30 @@ TIMER_DEF(process_env, "processing environment"); TIMER_DEF(process_args, "processing command-line arguments"); TIMER_DEF(read_cache, "reading cache file"); +} // namespace + int main(int argc, char * argv[], char * envp[]) { #ifdef DEBUG_ENABLED - if (char * p = std::getenv("DEBUG_FILE")) { + // If debugging is enabled, and DEBUG_FILE is set, then all + // debugging output goes to that file. + + if (const char * p = std::getenv("DEBUG_FILE")) { debug_stream = new std::ofstream(p); free_debug_stream = true; } #endif - // A ledger data file must be specified - - TIMER_START(read_cache); - - // jww (2004-08-13): use LEDGER_FILE and LEDGER_CACHE - use_cache = std::getenv("LEDGER") != NULL; + // Initialize default paths - if (use_cache) { - // jww (2004-08-13): fix this - for (int i = 0; i < argc; i++) - if (std::strcmp(argv[i], "-f") == 0 || - std::strcmp(argv[i], "--file") == 0) { - use_cache = false; - break; - } - - cache_dirty = true; - - if (use_cache) { - std::string cache_file = ledger_cache_file(); - if (! cache_file.empty() && - access(cache_file.c_str(), R_OK) != -1) { - std::ifstream stream(cache_file.c_str()); - if (! read_binary_journal(stream, std::getenv("LEDGER"), - journal.get())) { - // Throw away what's been read, and create a new journal - journal.reset(new journal_t); - } else { - cache_dirty = false; - } - } - } - } + if (const char * p = std::getenv("HOME")) + init_file = cache_file = price_db = p; - TIMER_STOP(read_cache); + init_file += "/.ledgerrc"; + cache_file += "/.ledger"; + price_db += "/.pricedb"; - // Parse the command-line options + // Parse command-line arguments TIMER_START(process_args); @@ -501,62 +479,69 @@ int main(int argc, char * argv[], char * envp[]) TIMER_STOP(process_args); + const bool use_cache = files.empty(); + // Process options from the environment TIMER_START(process_env); process_environment(envp, "LEDGER_"); - if (char * p = std::getenv("PRICE_HIST")) + if (const char * p = std::getenv("LEDGER")) + process_option("file", p); + if (const char * p = std::getenv("PRICE_HIST")) process_option("price-db", p); - if (char * p = std::getenv("PRICE_EXP")) + if (const char * p = std::getenv("PRICE_EXP")) process_option("price-exp", p); TIMER_STOP(process_env); - // Read the ledger file, unless we already read it from the cache + // Parse ledger files TIMER_START(parse_files); - if (! use_cache || cache_dirty) { - int entry_count = 0; + int entry_count = 0; - try { - if (files.empty()) { - if (char * p = std::getenv("LEDGER")) - for (p = std::strtok(p, ":"); p; p = std::strtok(NULL, ":")) - entry_count += parse_journal_file(p, journal.get()); + try { + if (! init_file.empty()) + if (parse_journal_file(init_file, journal.get())) + throw error("Entries not allowed in initialization file"); + + if (use_cache && ! cache_file.empty()) { + journal->sources.clear(); // remove init_file + entry_count += parse_journal_file(cache_file, journal.get()); + journal->sources.pop_front(); // remove cache_file + + std::list<std::string> exceptions; + std::set_difference(journal->sources.begin(), journal->sources.end(), + files.begin(), files.end(), exceptions.begin()); + + if (entry_count == 0 || exceptions.size() > 0) { + journal.reset(new journal_t); + entry_count = 0; } 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_journal_file(p, journal.get()); - } + cache_dirty = false; } + } - // Read prices from their own ledger file, after all others have - // been read. + if (entry_count == 0) + for (std::list<std::string>::iterator i = files.begin(); + i != files.end(); i++) + entry_count += parse_journal_file(*i, journal.get()); - if (! price_db.empty()) { - const char * path = price_db.c_str(); - std::ifstream db(path); - journal->sources.push_back(path); - entry_count += parse_textual_journal(db, journal.get(), - journal->master); - } - } - catch (error& err) { - std::cerr << "Fatal: " << err.what() << std::endl; - return 1; - } + if (! price_db.empty()) + if (parse_journal_file(price_db, journal.get())) + throw error("Entries not allowed in price history file"); + } + catch (error& err) { + std::cerr << "Fatal: " << err.what() << std::endl; + return 1; + } - if (entry_count == 0) { - std::cerr << ("Please specify ledger file(s) using -f option " - "or LEDGER environment variable.") << std::endl; - return 1; - } + if (entry_count == 0) { + std::cerr << ("Please specify ledger file(s) using -f option " + "or LEDGER environment variable.") << std::endl; + return 1; } TIMER_STOP(parse_files); @@ -849,13 +834,9 @@ int main(int argc, char * argv[], char * envp[]) TIMER_START(write_cache); - if (use_cache && cache_dirty) { - std::string cache_file = ledger_cache_file(); - if (! cache_file.empty()) { - assert(std::getenv("LEDGER")); - std::ofstream stream(cache_file.c_str()); - write_binary_journal(stream, journal.get(), std::getenv("LEDGER")); - } + if (use_cache && cache_dirty && ! cache_file.empty()) { + std::ofstream stream(cache_file.c_str()); + write_binary_journal(stream, journal.get(), &journal->sources); } TIMER_STOP(write_cache); @@ -296,6 +296,14 @@ entry_t * parse_entry(std::istream& in, account_t * master) return curr; } +template <typename T> +struct push_var { + T& var; + T prev; + push_var(T& _var) : var(_var), prev(var) {} + ~push_var() { var = prev; } +}; + unsigned int parse_textual_journal(std::istream& in, journal_t * journal, account_t * master) { @@ -496,9 +504,9 @@ unsigned int parse_textual_journal(std::istream& in, journal_t * journal, linenum++; break; - case ':': { // option setting + case '-': { // option setting std::string opt; - in >> c; + in >> c >> c; in >> opt; in.getline(line, MAX_LINE); linenum++; @@ -533,19 +541,9 @@ unsigned int parse_textual_journal(std::istream& in, journal_t * journal, in.getline(line, MAX_LINE); linenum++; - char * p = skip_ws(line); - std::ifstream stream(p); - - journal->sources.push_back(p); - - unsigned int curr_linenum = linenum; - std::string curr_path = path; - - count += parse_textual_journal(stream, journal, - account_stack.front()); - - linenum = curr_linenum; - path = curr_path; + push_var<unsigned int> save_linenum(linenum); + push_var<std::string> save_path(path); + count += parse_journal_file(skip_ws(line), journal); } break; |