summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--autoxact.cc2
-rw-r--r--binary.cc39
-rw-r--r--config.cc15
-rw-r--r--config.h3
-rw-r--r--format.h1
-rw-r--r--gnucash.cc6
-rw-r--r--ledger.cc39
-rw-r--r--ledger.h28
-rw-r--r--main.cc38
-rw-r--r--qif.cc160
-rw-r--r--textual.cc9
-rw-r--r--walk.cc13
13 files changed, 269 insertions, 85 deletions
diff --git a/Makefile b/Makefile
index fa060364..cb927a66 100644
--- a/Makefile
+++ b/Makefile
@@ -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);
}
diff --git a/binary.cc b/binary.cc
index 4df0d0eb..40e5d684 100644
--- a/binary.cc
+++ b/binary.cc
@@ -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);
diff --git a/config.cc b/config.cc
index 56b56761..81c9dbd3 100644
--- a/config.cc
+++ b/config.cc
@@ -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
diff --git a/config.h b/config.h
index 8aa9eca0..799e1d30 100644
--- a/config.h
+++ b/config.h
@@ -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;
diff --git a/format.h b/format.h
index dd578dbd..9eaf53bc 100644
--- a/format.h
+++ b/format.h
@@ -127,6 +127,7 @@ class format_transactions : public item_handler<transaction_t>
}
xact->dflags |= TRANSACTION_DISPLAYED;
}
+ flush();
}
};
diff --git a/gnucash.cc b/gnucash.cc
index 011889fd..c7635f11 100644
--- a/gnucash.cc
+++ b/gnucash.cc
@@ -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;
diff --git a/ledger.cc b/ledger.cc
index 7432cb78..ff7c7502 100644
--- a/ledger.cc
+++ b/ledger.cc
@@ -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);
diff --git a/ledger.h b/ledger.h
index 02418981..219a7d6c 100644
--- a/ledger.h
+++ b/ledger.h
@@ -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
diff --git a/main.cc b/main.cc
index a3ac20c9..3580b622 100644
--- a/main.cc
+++ b/main.cc
@@ -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;
diff --git a/qif.cc b/qif.cc
new file mode 100644
index 00000000..e0bc262b
--- /dev/null
+++ b/qif.cc
@@ -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
diff --git a/textual.cc b/textual.cc
index 963cf78a..fde735c2 100644
--- a/textual.cc
+++ b/textual.cc
@@ -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()) ||
diff --git a/walk.cc b/walk.cc
index 8b9f3874..7422aad3 100644
--- a/walk.cc
+++ b/walk.cc
@@ -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;