diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | autoxact.cc | 2 | ||||
-rw-r--r-- | binary.cc | 39 | ||||
-rw-r--r-- | config.cc | 15 | ||||
-rw-r--r-- | config.h | 3 | ||||
-rw-r--r-- | format.h | 1 | ||||
-rw-r--r-- | gnucash.cc | 6 | ||||
-rw-r--r-- | ledger.cc | 39 | ||||
-rw-r--r-- | ledger.h | 28 | ||||
-rw-r--r-- | main.cc | 38 | ||||
-rw-r--r-- | qif.cc | 160 | ||||
-rw-r--r-- | textual.cc | 9 | ||||
-rw-r--r-- | walk.cc | 13 |
13 files changed, 269 insertions, 85 deletions
@@ -10,6 +10,7 @@ CODE = account.cc \ format.cc \ ledger.cc \ option.cc \ + qif.cc \ quotes.cc \ textual.cc \ valexpr.cc \ diff --git a/autoxact.cc b/autoxact.cc index 7dac4512..01007d11 100644 --- a/autoxact.cc +++ b/autoxact.cc @@ -21,7 +21,7 @@ void automated_transaction_t::extend_entry(entry_t * entry) amt = (*t)->amount; transaction_t * xact - = new transaction_t(entry, (*t)->account, amt, amt, + = new transaction_t((*t)->account, amt, amt, (*t)->flags | TRANSACTION_AUTO); entry->add_transaction(xact); } @@ -92,7 +92,7 @@ void read_binary_amount(std::istream& in, amount_t& amt) transaction_t * read_binary_transaction(std::istream& in, entry_t * entry) { - transaction_t * xact = new transaction_t(entry, NULL); + transaction_t * xact = new transaction_t(NULL); xact->account = accounts[read_binary_number<account_t::ident_t>(in)]; xact->account->add_transaction(xact); @@ -193,9 +193,10 @@ account_t * read_binary_account(std::istream& in, account_t * master = NULL) return acct; } -unsigned int read_binary_journal(std::istream& in, - journal_t * journal, - account_t * master) +unsigned int read_binary_journal(std::istream& in, + const std::string& file, + journal_t * journal, + account_t * master) { ident = 0; c_ident = 0; @@ -204,18 +205,24 @@ unsigned int read_binary_journal(std::istream& in, read_binary_number<unsigned long>(in) != format_version) return 0; - 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; - stat(path.c_str(), &info); - if (std::difftime(info.st_mtime, old_mtime) > 0) - return 0; - journal->sources.push_back(path); + if (! file.empty()) { + for (unsigned short i = 0, + count = read_binary_number<unsigned short>(in); + i < count; + i++) { + std::string path = read_binary_string(in); + if (i == 0 && path != file) + return 0; + + std::time_t old_mtime; + read_binary_number(in, old_mtime); + struct stat info; + 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); @@ -59,7 +59,8 @@ Basic options:\n\ -i, --init FILE initialize ledger by loading FILE (def: ~/.ledgerrc)\n\ -f, --file FILE read ledger data from FILE\n\ -o, --output FILE write output to FILE\n\ - -p, --set-price CONV specify a commodity conversion: \"COMM=AMOUNT\"\n\n\ + -p, --set-price CONV specify a commodity conversion: \"COMM=AMOUNT\"\n\ + -a, --account NAME specify the default account (useful with QIF files)\n\n\ Report filtering:\n\ -b, --begin-date DATE set report begin date\n\ -e, --end-date DATE set report end date\n\ @@ -132,13 +133,7 @@ OPT_BEGIN(init, "i:") { } OPT_END(init); OPT_BEGIN(file, "f:") { - char * buf = new char[std::strlen(optarg) + 1]; - std::strcpy(buf, optarg); - for (char * p = std::strtok(buf, ":"); - p; - p = std::strtok(NULL, ":")) - config->files.push_back(p); - delete[] buf; + config->data_file = optarg; } OPT_END(file); OPT_BEGIN(cache, ":") { @@ -157,6 +152,10 @@ OPT_BEGIN(set_price, "p:") { std::cerr << "Error: Invalid price setting: " << optarg << std::endl; } OPT_END(set_price); +OPT_BEGIN(account, "a:") { + config->account = optarg; +} OPT_END(account); + ////////////////////////////////////////////////////////////////////// // // Report filtering @@ -17,12 +17,13 @@ extern const std::string equity_fmt; struct config_t { - strings_list files; strings_list price_settings; std::string init_file; + std::string data_file; std::string cache_file; std::string price_db; std::string output_file; + std::string account; std::string predicate; std::string display_predicate; std::string interval_text; @@ -127,6 +127,7 @@ class format_transactions : public item_handler<transaction_t> } xact->dflags |= TRANSACTION_DISPLAYED; } + flush(); } }; @@ -1,8 +1,8 @@ +#include "ledger.h" + #include <sstream> #include <cstring> -#include "ledger.h" - extern "C" { #include <xmlparse.h> // expat XML parser } @@ -85,7 +85,7 @@ static void startElement(void *userData, const char *name, const char **atts) action = ENTRY_DESC; else if (std::strcmp(name, "trn:split") == 0) { assert(curr_entry); - curr_entry->add_transaction(new transaction_t(curr_entry, curr_account)); + curr_entry->add_transaction(new transaction_t(curr_account)); } else if (std::strcmp(name, "split:reconciled-state") == 0) action = XACT_STATE; @@ -98,7 +98,7 @@ entry_t * journal_t::derive_entry(strings_list::iterator i, m_xact = matching->transactions.front(); amount_t amt(*i++); - first = xact = new transaction_t(added.get(), m_xact->account, amt, amt); + first = xact = new transaction_t(m_xact->account, amt, amt); added->add_transaction(xact); if (xact->amount.commodity->symbol.empty()) { @@ -108,8 +108,7 @@ entry_t * journal_t::derive_entry(strings_list::iterator i, m_xact = matching->transactions.back(); - xact = new transaction_t(added.get(), m_xact->account, - - first->amount, - first->amount); + xact = new transaction_t(m_xact->account, - first->amount, - first->amount); added->add_transaction(xact); if (i != end && std::string(*i++) == "-from" && i != end) @@ -150,7 +149,7 @@ entry_t * journal_t::derive_entry(strings_list::iterator i, } amount_t amt(*i++); - transaction_t * xact = new transaction_t(added.get(), acct, amt, amt); + transaction_t * xact = new transaction_t(acct, amt, amt); added->add_transaction(xact); if (! xact->amount.commodity) @@ -158,10 +157,8 @@ entry_t * journal_t::derive_entry(strings_list::iterator i, } if (i != end && std::string(*i++) == "-from" && i != end) { - if (account_t * acct = find_account(*i++)) { - transaction_t * xact = new transaction_t(NULL, acct); - added->add_transaction(xact); - } + if (account_t * acct = find_account(*i++)) + added->add_transaction(new transaction_t(acct)); } else { if (! matching) { std::cerr << "Error: Could not figure out the account to draw from." @@ -169,8 +166,7 @@ entry_t * journal_t::derive_entry(strings_list::iterator i, std::exit(1); } transaction_t * xact - = new transaction_t(added.get(), - matching->transactions.back()->account); + = new transaction_t(matching->transactions.back()->account); added->add_transaction(xact); } } @@ -178,9 +174,10 @@ entry_t * journal_t::derive_entry(strings_list::iterator i, return added.release(); } -int parse_journal_file(const std::string& path, - journal_t * journal, - account_t * master) +int parse_journal_file(const std::string& path, + journal_t * journal, + account_t * master, + const std::string * original_file) { journal->sources.push_back(path); @@ -189,13 +186,19 @@ int parse_journal_file(const std::string& path, std::ifstream stream(path.c_str()); - unsigned long magic; - stream.read((char *)&magic, sizeof(magic)); + char magic[sizeof(unsigned int) + 1]; + stream.read(magic, sizeof(unsigned int)); + magic[sizeof(unsigned int)] = '\0'; stream.seekg(0); - if (magic == binary_magic_number) - return read_binary_journal(stream, journal, - master ? master : journal->master); + if (*((unsigned int *) magic) == binary_magic_number) + return read_binary_journal(stream, original_file ? *original_file : "", + journal, master ? master : journal->master); + else if (std::strcmp(magic, "!Typ") == 0 || + std::strcmp(magic, "\n!Ty") == 0 || + std::strcmp(magic, "\r\n!T") == 0) + return parse_qif_file(stream, journal, master ? master : journal->master, + commodity_t::find_commodity("$", true)); else return parse_textual_journal(stream, journal, master ? master : journal->master); @@ -57,17 +57,16 @@ class transaction_t mutable unsigned int index; mutable unsigned short dflags; - transaction_t(entry_t * _entry, account_t * _account) - : entry(_entry), account(_account), flags(TRANSACTION_NORMAL), + transaction_t(account_t * _account) + : entry(NULL), account(_account), flags(TRANSACTION_NORMAL), index(0), dflags(0) {} - transaction_t(entry_t * _entry, - account_t * _account, + transaction_t(account_t * _account, const amount_t& _amount, const amount_t& _cost, unsigned int _flags = TRANSACTION_NORMAL, const std::string& _note = "") - : entry(_entry), account(_account), amount(_amount), + : entry(NULL), account(_account), amount(_amount), cost(_cost), flags(_flags), note(_note), index(0), dflags(0) {} }; @@ -97,6 +96,7 @@ class entry_t } void add_transaction(transaction_t * xact) { + xact->entry = this; transactions.push_back(xact); } bool remove_transaction(transaction_t * xact) { @@ -223,9 +223,10 @@ class journal_t strings_list::iterator end) const; }; -int parse_journal_file(const std::string& path, - journal_t * journal, - account_t * master = NULL); +int parse_journal_file(const std::string& path, + journal_t * journal, + account_t * master = NULL, + const std::string * original_file = NULL); unsigned int parse_textual_journal(std::istream& in, journal_t * ledger, @@ -233,14 +234,19 @@ unsigned int parse_textual_journal(std::istream& in, extern const unsigned long binary_magic_number; -unsigned int read_binary_journal(std::istream& in, - journal_t * journal, - account_t * master = NULL); +unsigned int read_binary_journal(std::istream& in, + const std::string& file, + journal_t * journal, + account_t * master = NULL); void write_binary_journal(std::ostream& out, journal_t * journal, strings_list * files = NULL); +unsigned int parse_qif_file(std::istream& in, journal_t * journal, + account_t * master, + commodity_t * def_commodity = NULL); + extern const std::string version; } // namespace ledger @@ -153,7 +153,7 @@ int main(int argc, char * argv[], char * envp[]) TIMER_STOP(process_args); - bool use_cache = config->files.empty(); + bool use_cache = config->data_file.empty(); // Process options from the environment @@ -185,8 +185,12 @@ int main(int argc, char * argv[], char * envp[]) if (parse_journal_file(config->init_file, journal.get())) throw error("Entries not allowed in initialization file"); - if (use_cache && ! config->cache_file.empty()) { - entry_count += parse_journal_file(config->cache_file, journal.get()); + if (use_cache && ! config->cache_file.empty() && + ! config->data_file.empty()) { + entry_count += parse_journal_file(config->cache_file, journal.get(), + NULL, &config->data_file); + journal->sources.pop_front(); // remove cache_file + if (entry_count == 0) { journal.reset(new journal_t); cache_dirty = true; @@ -195,17 +199,18 @@ int main(int argc, char * argv[], char * envp[]) } } - if (entry_count == 0) { - for (strings_list::iterator i = config->files.begin(); - i != config->files.end(); - i++) - if (*i == "-") { - use_cache = false; - entry_count += parse_textual_journal(std::cin, journal.get(), - journal->master); - } else { - entry_count += parse_journal_file(*i, journal.get()); - } + if (entry_count == 0 && ! config->data_file.empty()) { + account_t * account = NULL; + if (! config->account.empty()) + account = journal->find_account(config->account); + + if (config->data_file == "-") { + use_cache = false; + entry_count += parse_textual_journal(std::cin, journal.get(), account); + } else { + entry_count += parse_journal_file(config->data_file, journal.get(), + account); + } if (! config->price_db.empty()) if (parse_journal_file(config->price_db, journal.get())) @@ -265,9 +270,8 @@ int main(int argc, char * argv[], char * envp[]) bool show_all_related = false; if (command == "p" || command == "e") { - config->show_related = - show_all_related = - config->show_subtotal = true; + config->show_related = + show_all_related = true; } else if (command == "E") { config->show_subtotal = true; @@ -0,0 +1,160 @@ +#include "ledger.h" +#include "datetime.h" +#include "error.h" +#include "util.h" + +#include <cstring> + +namespace ledger { + +#define MAX_LINE 1024 + +static char line[MAX_LINE + 1]; +static std::string path; +static unsigned int linenum; + +static inline char * get_line(std::istream& in) { + in.getline(line, MAX_LINE); + int len = std::strlen(line); + if (line[len - 1] == '\r') + line[len - 1] = '\0'; + linenum++; + return line; +} + +unsigned int parse_qif_file(std::istream& in, journal_t * journal, + account_t * master, commodity_t * def_commodity) +{ + std::auto_ptr<entry_t> entry; + std::auto_ptr<amount_t> amount; + transaction_t * xact; + account_t * misc = journal->find_account("Miscellaneous"); + unsigned int count; + + path = journal->sources.back(); + linenum = 1; + + while (! in.eof()) { + char c; + in.get(c); + switch (c) { + case ' ': + case '\t': + if (peek_next_nonws(in) != '\n') { + get_line(in); + throw parse_error(path, linenum, "Line begins with whitespace"); + } + // fall through... + + case '\n': + linenum++; + case '\r': // skip blank lines + break; + + case '!': + in >> line; + + // jww (2004-08-19): these types are not supported yet + assert(std::strcmp(line, "Type:Invst") != 0 && + std::strcmp(line, "Account") != 0 && + std::strcmp(line, "Type:Cat") != 0 && + std::strcmp(line, "Type:Class") != 0 && + std::strcmp(line, "Type:Memorized") != 0); + + get_line(in); + break; + + case 'D': + entry.reset(new entry_t); + xact = new transaction_t(master); + entry->add_transaction(xact); + + in >> line; + if (! parse_date(line, &entry->date)) + throw parse_error(path, linenum, "Failed to parse date"); + break; + + case 'T': + case '$': + in >> line; + xact->amount.parse(line); + if (def_commodity) + xact->amount.commodity = def_commodity; + if (c == '$') + xact->amount.negate(); + xact->cost = xact->amount; + break; + + case 'C': + if (in.peek() == '*') { + in.get(c); + entry->state = entry_t::CLEARED; + } + break; + + case 'N': + if (std::isdigit(in.peek())) { + in >> c >> line; + entry->code = line; + } + break; + + case 'P': + case 'M': + case 'L': + case 'S': + case 'E': { + char b = c; + int len; + c = in.peek(); + if (! std::isspace(c) && c != '\n') { + get_line(in); + + switch (b) { + case 'P': + entry->payee = line; + break; + + case 'S': + xact = new transaction_t(NULL); + entry->add_transaction(xact); + // fall through... + case 'L': + len = std::strlen(line); + if (line[len - 1] == ']') + line[len - 1] = '\0'; + xact->account = journal->find_account(line[0] == '[' ? + line + 1 : line); + break; + + case 'M': + case 'E': + xact->note = line; + break; + } + } + break; + } + + case 'A': + // jww (2004-08-19): these are ignored right now + get_line(in); + break; + + case '^': + if (xact->account == master) { + transaction_t * nxact = new transaction_t(misc); + entry->add_transaction(nxact); + nxact->amount = nxact->cost = - xact->amount; + } + + if (journal->add_entry(entry.release())) + count++; + break; + } + } + + return count; +} + +} // namespace ledger @@ -18,8 +18,8 @@ namespace ledger { #define MAX_LINE 1024 -std::string path; -unsigned int linenum; +static std::string path; +static unsigned int linenum; #ifdef TIMELOG_SUPPORT static std::time_t time_in; @@ -54,7 +54,7 @@ transaction_t * parse_transaction_text(char * line, account_t * account, { // The account will be determined later... - std::auto_ptr<transaction_t> xact(new transaction_t(entry, NULL)); + std::auto_ptr<transaction_t> xact(new transaction_t(NULL)); // The call to `next_element' will skip past the account name, // and return a pointer to the beginning of the amount. Once @@ -392,8 +392,7 @@ unsigned int parse_textual_journal(std::istream& in, journal_t * journal, time_commodity = amt.commodity; transaction_t * xact - = new transaction_t(curr.get(), last_account, amt, amt, - TRANSACTION_VIRTUAL); + = new transaction_t(last_account, amt, amt, TRANSACTION_VIRTUAL); curr->add_transaction(xact); if (! finalize_entry(curr.get()) || @@ -59,10 +59,10 @@ void collapse_transactions::report_cumulative_subtotal() for (amounts_map::const_iterator i = result.amounts.begin(); i != result.amounts.end(); i++) { - transaction_t * total_xact - = new transaction_t(last_entry, totals_account); + transaction_t * total_xact = new transaction_t(totals_account); xact_temps.push_back(total_xact); + total_xact->entry = last_entry; total_xact->amount = (*i).second; total_xact->cost = (*i).second; @@ -98,9 +98,10 @@ void changed_value_transactions::operator()(transaction_t * xact) for (amounts_map::const_iterator i = diff.amounts.begin(); i != diff.amounts.end(); i++) { - transaction_t * temp_xact = new transaction_t(entry, NULL); + transaction_t * temp_xact = new transaction_t(NULL); xact_temps.push_back(temp_xact); + temp_xact->entry = entry; temp_xact->amount = (*i).second; temp_xact->dflags |= TRANSACTION_NO_TOTAL; @@ -145,7 +146,8 @@ void subtotal_transactions::flush(const char * spec_fmt) i != balances.end(); i++) { entry->date = finish; - transaction_t temp(entry, (*i).first); + transaction_t temp((*i).first); + temp.entry = entry; temp.total = (*i).second; balance_t result; format_t::compute_total(result, details_t(&temp)); @@ -154,9 +156,10 @@ void subtotal_transactions::flush(const char * spec_fmt) for (amounts_map::const_iterator j = result.amounts.begin(); j != result.amounts.end(); j++) { - transaction_t * xact = new transaction_t(entry, (*i).first); + transaction_t * xact = new transaction_t((*i).first); xact_temps.push_back(xact); + xact->entry = entry; xact->amount = (*j).second; xact->cost = (*j).second; |