summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile28
-rw-r--r--amount.cc129
-rw-r--r--balance.cc27
-rw-r--r--gnucash.cc9
-rw-r--r--ledger.cc63
-rw-r--r--ledger.h57
-rw-r--r--main.cc254
-rw-r--r--parse.cc170
-rw-r--r--register.cc85
-rwxr-xr-xreport4
10 files changed, 564 insertions, 262 deletions
diff --git a/Makefile b/Makefile
index df54748c..13b71ac9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,14 @@
+define GNUCASH
+true
+endef
+define HUQUQ
+true
+endef
+
+#
+# Example build: make GNUCASH=1 COMMODITY=EUR
+#
+
CODE = amount.cc \
ledger.cc \
parse.cc \
@@ -8,16 +19,23 @@ CODE = amount.cc \
OBJS = $(patsubst %.cc,%.o,$(CODE))
-CFLAGS = -Wall -ansi -pedantic -DDEFAULT_COMMODITY="\"\$$\""
-CFLAGS := $(CFLAGS) -DHUQUQULLAH=1
+ifndef COMMODITY
+COMMODITY = \$$
+endif
-DFLAGS = -O3 -fomit-frame-pointer
-#DFLAGS = -g -O2 # -pg
+CFLAGS = -Wall -ansi -pedantic
+CFLAGS := $(CFLAGS) -DDEFAULT_COMMODITY="\"$(COMMODITY)\""
-INCS = -I/usr/include/xmltok
+#DFLAGS = -O3 -fomit-frame-pointer
+DFLAGS = -g # -O2 # -pg
+INCS = -I/usr/include/xmltok
LIBS = -lgmpxx -lgmp -lpcre
+ifdef HUQUQ
+CFLAGS := $(CFLAGS) -DHUQUQULLAH=1
+endif
+
ifdef GNUCASH
CODE := $(CODE) gnucash.cc
CFLAGS := $(CFLAGS) -DREAD_GNUCASH=1
diff --git a/amount.cc b/amount.cc
index e0aca70b..41837767 100644
--- a/amount.cc
+++ b/amount.cc
@@ -52,6 +52,10 @@ class gmp_amount : public amount
virtual amount * copy() const;
virtual amount * value(amount *) const;
virtual amount * street() const;
+ virtual bool has_price() const {
+ return priced;
+ }
+ virtual void set_value(const amount * val);
virtual operator bool() const;
@@ -72,56 +76,15 @@ class gmp_amount : public amount
return as_str(false);
}
- friend amount * create_amount(const char * value, const amount * price);
+ friend amount * create_amount(const char * value, const amount * cost);
};
-amount * create_amount(const char * value, const amount * price)
+amount * create_amount(const char * value, const amount * cost)
{
gmp_amount * a = new gmp_amount();
a->parse(value);
-
- // If a price was specified, it refers to a total price for the
- // whole `value', meaning we must divide to determine the
- // per-commodity price.
-
- if (price) {
- assert(! a->priced); // don't specify price twice!
-
- const gmp_amount * p = dynamic_cast<const gmp_amount *>(price);
- assert(p);
-
- // There is no need for per-commodity pricing when the total
- // price is in the same commodity as the quantity! In that case,
- // the two will always be identical.
- if (a->quantity_comm == p->quantity_comm) {
- assert(mpz_cmp(a->quantity, p->quantity) == 0);
- return a;
- }
-
- mpz_t quotient;
- mpz_t remainder;
- mpz_t addend;
-
- mpz_init(quotient);
- mpz_init(remainder);
- mpz_init(addend);
-
- mpz_ui_pow_ui(addend, 10, MAX_PRECISION);
-
- mpz_tdiv_qr(quotient, remainder, p->quantity, a->quantity);
- mpz_mul(remainder, remainder, addend);
- mpz_tdiv_q(remainder, remainder, a->quantity);
- mpz_mul(quotient, quotient, addend);
- mpz_add(quotient, quotient, remainder);
-
- a->priced = true;
- mpz_set(a->price, quotient);
- a->price_comm = p->quantity_comm;
-
- mpz_clear(quotient);
- mpz_clear(remainder);
- mpz_clear(addend);
- }
+ if (cost)
+ a->set_value(cost);
return a;
}
@@ -137,15 +100,28 @@ static void round(mpz_t out, const mpz_t val, int prec)
mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec);
mpz_tdiv_qr(quotient, remainder, val, divisor);
-
mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec - 1);
mpz_mul_ui(divisor, divisor, 5);
- if (mpz_cmp(remainder, divisor) >= 0) {
- mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec);
- mpz_sub(remainder, divisor, remainder);
- mpz_add(out, val, remainder);
+
+ if (mpz_sgn(remainder) < 0) {
+ mpz_ui_sub(divisor, 0, divisor);
+
+ if (mpz_cmp(remainder, divisor) < 0) {
+ mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec);
+ mpz_add(remainder, divisor, remainder);
+ mpz_ui_sub(remainder, 0, remainder);
+ mpz_add(out, val, remainder);
+ } else {
+ mpz_sub(out, val, remainder);
+ }
} else {
- mpz_sub(out, val, remainder);
+ if (mpz_cmp(remainder, divisor) >= 0) {
+ mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec);
+ mpz_sub(remainder, divisor, remainder);
+ mpz_add(out, val, remainder);
+ } else {
+ mpz_sub(out, val, remainder);
+ }
}
mpz_clear(divisor);
@@ -204,6 +180,10 @@ amount * gmp_amount::value(amount * pr) const
else
new_amt->quantity_comm = quantity_comm;
+ if (new_amt->quantity_comm->precision < MAX_PRECISION)
+ round(new_amt->quantity, new_amt->quantity,
+ new_amt->quantity_comm->precision);
+
return new_amt;
}
else if (! priced) {
@@ -211,8 +191,14 @@ amount * gmp_amount::value(amount * pr) const
}
else {
gmp_amount * new_amt = new gmp_amount();
+
multiply(new_amt->quantity, quantity, price);
+
new_amt->quantity_comm = price_comm;
+ if (new_amt->quantity_comm->precision < MAX_PRECISION)
+ round(new_amt->quantity, new_amt->quantity,
+ new_amt->quantity_comm->precision);
+
return new_amt;
}
}
@@ -265,6 +251,39 @@ amount * gmp_amount::street() const
return cost ? cost : copy();
}
+void gmp_amount::set_value(const amount * val)
+{
+ assert(! priced); // don't specify the pricing twice!
+
+ const gmp_amount * v = dynamic_cast<const gmp_amount *>(val);
+ assert(v);
+
+ mpz_t quotient;
+ mpz_t remainder;
+ mpz_t addend;
+
+ mpz_init(quotient);
+ mpz_init(remainder);
+ mpz_init(addend);
+
+ mpz_ui_pow_ui(addend, 10, MAX_PRECISION);
+
+ mpz_tdiv_qr(quotient, remainder, v->quantity, quantity);
+ mpz_mul(remainder, remainder, addend);
+ mpz_tdiv_q(remainder, remainder, quantity);
+ mpz_mul(quotient, quotient, addend);
+ mpz_add(quotient, quotient, remainder);
+ mpz_abs(quotient, quotient);
+
+ priced = true;
+ mpz_set(price, quotient);
+ price_comm = v->quantity_comm;
+
+ mpz_clear(quotient);
+ mpz_clear(remainder);
+ mpz_clear(addend);
+}
+
gmp_amount::operator bool() const
{
mpz_t copy;
@@ -535,16 +554,10 @@ static commodity * parse_amount(mpz_t out, const char * num,
thousands, european, precision);
} else {
comm = (*item).second;
-#if 0
- // If a finer precision was used than the commodity allows,
- // increase the precision.
- if (precision > comm->precision)
- comm->precision = precision;
-#else
+
if (use_warnings && precision > comm->precision)
std::cerr << "Warning: Use of higher precision than expected: "
<< value_str << std::endl;
-#endif
}
}
diff --git a/balance.cc b/balance.cc
index fe8dbe16..e27ff872 100644
--- a/balance.cc
+++ b/balance.cc
@@ -51,7 +51,7 @@ static void display_total(std::ostream& out, totals& balance,
displayed = true;
out << acct->balance;
- if (top_level)
+ if (! no_subtotals && top_level)
balance.credit(acct->balance);
if (acct->parent && ! no_subtotals && ! full_names) {
@@ -126,14 +126,23 @@ void report_balances(int argc, char **argv, std::ostream& out)
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
x != (*i)->xacts.end();
x++) {
- account * acct = (*x)->acct;
-
- for (; acct; acct = no_subtotals ? NULL : acct->parent) {
- bool true_match = false;
- if (! (regexps.empty() ||
- account_matches(acct, regexps, &true_match)))
+ for (account * acct = (*x)->acct;
+ acct;
+ acct = no_subtotals ? NULL : acct->parent) {
+ if (acct->checked == 0) {
+ bool true_match = false;
+ if (! (regexps.empty() ||
+ account_matches(acct, regexps, &true_match)))
+ acct->checked = 2;
+ else if (! (true_match || show_children || ! acct->parent))
+ acct->checked = 3;
+ else
+ acct->checked = 1;
+ }
+
+ if (acct->checked == 2)
break;
- else if (! (true_match || show_children || ! acct->parent))
+ else if (acct->checked == 3)
continue;
acct->display = true;
@@ -154,7 +163,7 @@ void report_balances(int argc, char **argv, std::ostream& out)
// Print the total of all the balances shown
- if (! no_subtotals)
+ if (! no_subtotals && balance)
out << "--------------------" << std::endl
<< balance << std::endl;
}
diff --git a/gnucash.cc b/gnucash.cc
index 7866f9c0..7400bdbe 100644
--- a/gnucash.cc
+++ b/gnucash.cc
@@ -204,8 +204,17 @@ static void dataHandler(void *userData, const char *s, int len)
xact->acct = (*i).second;
std::string value = curr_quant + " " + (*i).second->comm->symbol;
+
+ if (curr_value->comm() == (*i).second->comm) {
+ // assert: value must be equal to curr_value.
+ delete curr_value;
+ curr_value = NULL;
+ }
xact->cost = create_amount(value.c_str(), curr_value);
+ if (curr_value)
+ delete curr_value;
+
if (do_compute)
xact->acct->balance.credit(xact->cost);
break;
diff --git a/ledger.cc b/ledger.cc
index a3c82fbb..98383511 100644
--- a/ledger.cc
+++ b/ledger.cc
@@ -10,12 +10,7 @@ state main_ledger;
std::list<mask> regexps;
-#ifdef HUQUQULLAH
-bool compute_huquq;
-std::list<mask> huquq_categories;
-#endif
-
-void entry::print(std::ostream& out) const
+void entry::print(std::ostream& out, bool shortcut) const
{
char buf[32];
std::strftime(buf, 31, "%Y.%m.%d ", std::localtime(&date));
@@ -30,46 +25,48 @@ void entry::print(std::ostream& out) const
out << std::endl;
- bool shortcut = xacts.size() == 2;
if (shortcut &&
- xacts.front()->cost->comm() != xacts.back()->cost->comm())
+ (xacts.size() != 2 ||
+ xacts.front()->cost->comm() != xacts.back()->cost->comm())) {
shortcut = false;
+ }
- for (std::list<transaction *>::const_iterator i = xacts.begin();
- i != xacts.end();
- i++) {
+ for (std::list<transaction *>::const_iterator x = xacts.begin();
+ x != xacts.end();
+ x++) {
out << " ";
out.width(30);
- out << std::left << (*i)->acct->as_str();
+ out << std::left << (*x)->acct->as_str();
- if (! shortcut || i == xacts.begin()) {
+ if ((*x)->cost && (! shortcut || x == xacts.begin())) {
out << " ";
out.width(10);
- out << std::right << (*i)->cost->as_str(true);
+ out << std::right << (*x)->cost->as_str(true);
}
- if (! (*i)->note.empty())
- out << " ; " << (*i)->note;
+ if (! (*x)->note.empty())
+ out << " ; " << (*x)->note;
out << std::endl;
}
out << std::endl;
}
-bool entry::validate() const
+bool entry::validate(bool show_unaccounted) const
{
totals balance;
- for (std::list<transaction *>::const_iterator i = xacts.begin();
- i != xacts.end();
- i++)
- balance.credit((*i)->cost->value());
+ for (std::list<transaction *>::const_iterator x = xacts.begin();
+ x != xacts.end();
+ x++)
+ if ((*x)->cost)
+ balance.credit((*x)->cost->value());
- if (balance) {
- std::cerr << "Totals are:" << std::endl;
+ if (show_unaccounted && balance) {
+ std::cerr << "Unaccounted-for balances are:" << std::endl;
balance.print(std::cerr, 20);
- std::cerr << std::endl;
+ std::cerr << std::endl << std::endl;
}
return ! balance; // must balance to 0.0
}
@@ -155,8 +152,17 @@ amount * totals::value(const std::string& commodity) const
void print_ledger(int argc, char *argv[], std::ostream& out)
{
+ bool use_shortcuts = true;
+
optind = 1;
+ int c;
+ while (-1 != (c = getopt(argc, argv, "n"))) {
+ switch (char(c)) {
+ case 'n': use_shortcuts = false; break;
+ }
+ }
+
// Compile the list of specified regular expressions, which can be
// specified on the command line, or using an include/exclude file
@@ -172,7 +178,7 @@ void print_ledger(int argc, char *argv[], std::ostream& out)
i != main_ledger.entries.end();
i++)
if ((*i)->matches(regexps))
- (*i)->print(out);
+ (*i)->print(out, use_shortcuts);
}
void record_regexp(char * pattern, std::list<mask>& regexps)
@@ -317,9 +323,14 @@ account * state::find_account(const char * name, bool create)
delete[] buf;
- if (current)
+ if (current) {
accounts_cache.insert(accounts_entry(name, current));
+#ifdef HUQUQULLAH
+ if (matches(main_ledger.huquq_categories, name))
+ current->exempt_or_necessary = true;
+#endif
+ }
return current;
}
diff --git a/ledger.h b/ledger.h
index dc689536..bb619800 100644
--- a/ledger.h
+++ b/ledger.h
@@ -1,5 +1,5 @@
#ifndef _LEDGER_H
-#define _LEDGER_H "$Revision: 1.12 $"
+#define _LEDGER_H "$Revision: 1.13 $"
//////////////////////////////////////////////////////////////////////
//
@@ -15,7 +15,12 @@
#include <list>
#include <map>
#include <ctime>
+
+#ifdef DEBUG
#include <cassert>
+#else
+#define assert(x)
+#endif
#include <pcre.h> // Perl regular expression library
@@ -119,6 +124,9 @@ class amount
virtual amount * value(amount * pr = NULL) const = 0;
virtual amount * street() const = 0;
+ virtual bool has_price() const = 0;
+ virtual void set_value(const amount * pr) = 0;
+
// Test if non-zero
virtual operator bool() const = 0;
@@ -143,8 +151,8 @@ operator<<(std::basic_ostream<char, Traits>& out, const amount& a) {
return (out << std::string(a));
}
-extern amount * create_amount(const char * value, const amount * price = NULL);
-
+extern amount * create_amount(const char * value,
+ const amount * cost = NULL);
struct mask
{
@@ -169,8 +177,16 @@ struct transaction
amount * cost;
std::string note;
+#ifdef HUQUQULLAH
+ bool exempt_or_necessary;
+#endif
- transaction() : acct(NULL), cost(NULL) {}
+ transaction(account * _acct = NULL, amount * _cost = NULL)
+ : acct(_acct), cost(_cost) {
+#ifdef HUQUQULLAH
+ exempt_or_necessary = false;
+#endif
+ }
#ifdef DO_CLEANUP
~transaction() {
@@ -207,8 +223,8 @@ struct entry
#endif
bool matches(const std::list<mask>& regexps) const;
- void print(std::ostream& out) const;
- bool validate() const;
+ void print(std::ostream& out, bool shortcut = true) const;
+ bool validate(bool show_unaccounted = false) const;
};
struct cmp_entry_date {
@@ -268,7 +284,12 @@ struct account
std::string name;
commodity * comm; // default commodity for this account
totals balance;
- bool display;
+
+ bool display;
+ int checked;
+#ifdef HUQUQULLAH
+ bool exempt_or_necessary;
+#endif
typedef std::map<const std::string, struct account *> map;
typedef map::iterator iterator;
@@ -278,7 +299,11 @@ struct account
map children;
account(const std::string& _name, struct account * _parent = NULL)
- : parent(_parent), name(_name), display(false) {}
+ : parent(_parent), name(_name), display(false), checked(0) {
+#ifdef HUQUQULLAH
+ exempt_or_necessary = false;
+#endif
+ }
const std::string as_str() const {
if (! parent)
@@ -300,11 +325,6 @@ typedef accounts_t::iterator accounts_iterator;
typedef std::pair<const std::string, account *> accounts_entry;
-#ifdef HUQUQULLAH
-extern bool compute_huquq;
-extern std::list<mask> huquq_categories;
-#endif
-
struct state
{
commodities_t commodities;
@@ -313,6 +333,17 @@ struct state
entries_t entries;
totals prices;
+#ifdef HUQUQULLAH
+ bool compute_huquq;
+ std::list<mask> huquq_categories;
+ amount * huquq;
+ commodity * huquq_commodity;
+ account * huquq_account;
+ account * huquq_expenses_account;
+
+ state() : compute_huquq(false) {}
+#endif
+
#ifdef DO_CLEANUP
~state();
#endif
diff --git a/main.cc b/main.cc
index 78feb961..81d46f0f 100644
--- a/main.cc
+++ b/main.cc
@@ -24,16 +24,85 @@ namespace ledger {
using namespace ledger;
-void show_help(std::ostream& out)
+static void show_help(std::ostream& out)
{
- out << "usage: ledger [options] DATA_FILE COMMAND [ARGS]"
- << std::endl
- << "options:" << std::endl
- << " -s show sub-accounts in balance totals" << std::endl
- << " -S show empty accounts in balance totals" << std::endl
- << "commands:" << std::endl
- << " balance show balance totals" << std::endl
- << " print print all ledger entries" << std::endl;
+ std::cerr
+ << "usage: ledger [options] COMMAND [options] [REGEXPS]" << std::endl
+ << std::endl
+ << "ledger options:" << std::endl
+ << " -C also show cleared transactions" << std::endl
+ << " -b DATE specify a beginning date" << std::endl
+ << " -c do not show future entries (same as -e TODAY)" << std::endl
+ << " -e DATE specify an ending date" << std::endl
+ << " -f FILE specify pathname of ledger data file" << std::endl
+ << " -h display this help text" << std::endl
+#ifdef HUQUQULLAH
+ << " -H do not auto-compute Huququ'llah" << std::endl
+#endif
+ << " -i FILE read the list of inclusion regexps from FILE" << std::endl
+ << " -p FILE read the list of prices from FILE" << std::endl
+ << " -P download price quotes from the Internet" << std::endl
+ << " (this works by running the command \"getquote SYMBOL\")"
+ << std::endl
+ << " -w print out warnings where applicable" << std::endl
+ << std::endl
+ << "commands:" << std::endl
+ << " balance show balance totals" << std::endl
+ << " register display a register for ACCOUNT" << std::endl
+ << " print print all ledger entries" << std::endl
+ << " equity generate equity ledger for all entries" << std::endl
+ << std::endl
+ << "`balance' options:" << std::endl
+ << " -F print each account's full name" << std::endl
+ << " -n do not generate totals for parent accounts" << std::endl
+ << " -s show sub-accounts in balance totals" << std::endl
+ << " -S show empty accounts in balance totals" << std::endl;
+}
+
+static const char *formats[] = {
+ "%Y/%m/%d",
+ "%m/%d",
+ "%Y.%m.%d",
+ "%m.%d",
+ "%a",
+ "%A",
+ "%b",
+ "%B",
+ "%Y",
+ NULL
+};
+
+static bool parse_date(const char * date_str, std::time_t * result)
+{
+ struct std::tm when;
+
+ std::time_t now = std::time(NULL);
+ struct std::tm * now_tm = std::localtime(&now);
+
+ for (const char ** f = formats; *f; f++) {
+ memset(&when, INT_MAX, sizeof(struct std::tm));
+ if (strptime(date_str, *f, &when)) {
+ when.tm_hour = 0;
+ when.tm_min = 0;
+ when.tm_sec = 0;
+
+ if (when.tm_year == -1)
+ when.tm_year = now_tm->tm_year;
+
+ if (std::strcmp(*f, "%Y") == 0) {
+ when.tm_mon = 0;
+ when.tm_mday = 1;
+ } else {
+ if (when.tm_mon == -1)
+ when.tm_mon = now_tm->tm_mon;
+ if (when.tm_mday == -1)
+ when.tm_mday = now_tm->tm_mday;
+ }
+ *result = std::mktime(&when);
+ return true;
+ }
+ }
+ return false;
}
//////////////////////////////////////////////////////////////////////
@@ -53,37 +122,81 @@ int main(int argc, char *argv[])
std::istream * file = NULL;
#ifdef HUQUQULLAH
- compute_huquq = true;
+ bool compute_huquq = true;
#endif
have_beginning = false;
have_ending = false;
show_cleared = false;
int c;
- while (-1 != (c = getopt(argc, argv, "+b:e:cChHwf:i:p:P"))) {
+ while (-1 != (c = getopt(argc, argv, "+b:e:d:cChHwf:i:p:P"))) {
switch (char(c)) {
- case 'b': {
- struct tm * when = getdate(optarg);
- if (! when) {
- std::cerr << "Error: Bad begin date string: " << optarg
- << std::endl;
- } else {
- begin_date = std::mktime(when);
+ case 'b':
+ case 'e': {
+ std::time_t when;
+ if (! parse_date(optarg, &when)) {
+ std::cerr << "Error: Bad date string: " << optarg << std::endl;
+ return 1;
+ }
+
+ if (c == 'b') {
+ begin_date = when;
have_beginning = true;
+ } else {
+ end_date = when;
+ have_ending = true;
}
break;
}
- case 'e': {
- struct tm * when = getdate(optarg);
- if (! when) {
- std::cerr << "Error: Bad end date string: " << optarg
- << std::endl;
- } else {
- end_date = std::mktime(when);
- have_ending = true;
+
+ case 'd': {
+ if (! parse_date(optarg, &begin_date)) {
+ std::cerr << "Error: Bad date string: " << optarg << std::endl;
+ return 1;
+ }
+ have_beginning = true;
+
+ struct std::tm when, then;
+ std::memset(&then, 0, sizeof(struct std::tm));
+
+ std::time_t now = std::time(NULL);
+ struct std::tm * now_tm = std::localtime(&now);
+
+ for (const char ** f = formats; *f; f++) {
+ memset(&when, INT_MAX, sizeof(struct std::tm));
+ if (strptime(optarg, *f, &when)) {
+ then.tm_hour = 0;
+ then.tm_min = 0;
+ then.tm_sec = 0;
+
+ if (when.tm_year != -1)
+ then.tm_year = when.tm_year + 1;
+ else
+ then.tm_year = now_tm->tm_year;
+
+ if (std::strcmp(*f, "%Y") == 0) {
+ then.tm_mon = 0;
+ then.tm_mday = 1;
+ } else {
+ if (when.tm_mon != -1)
+ then.tm_mon = when.tm_mon + 1;
+ else
+ then.tm_mon = now_tm->tm_mon;
+
+ if (when.tm_mday != -1)
+ then.tm_mday = when.tm_mday + 1;
+ else
+ then.tm_mday = now_tm->tm_mday;
+ }
+
+ end_date = std::mktime(&then);
+ have_ending = true;
+ break;
+ }
}
break;
}
+
case 'c':
end_date = std::time(NULL);
have_ending = true;
@@ -127,46 +240,40 @@ int main(int argc, char *argv[])
}
if (optind == argc) {
- std::cerr
- << "usage: ledger [options] COMMAND [options] [REGEXPS]" << std::endl
- << std::endl
- << "ledger options:" << std::endl
- << " -C also show cleared transactions" << std::endl
- << " -b DATE specify a beginning date" << std::endl
- << " -c do not show future entries (same as -e TODAY)" << std::endl
- << " -e DATE specify an ending date" << std::endl
- << " -f FILE specify pathname of ledger data file" << std::endl
- << " -h display this help text" << std::endl
-#ifdef HUQUQULLAH
- << " -H do not auto-compute Huququ'llah" << std::endl
-#endif
- << " -i FILE read the list of inclusion regexps from FILE" << std::endl
- << " -p FILE read the list of prices from FILE" << std::endl
- << " -P download price quotes from the Internet" << std::endl
- << " (this works by running the command \"getquote SYMBOL\")"
- << std::endl
- << " -w print out warnings where applicable" << std::endl
- << std::endl
- << "commands:" << std::endl
- << " balance show balance totals" << std::endl
- << " register display a register for ACCOUNT" << std::endl
- << " print print all ledger entries" << std::endl
- << " equity generate equity ledger for all entries" << std::endl
- << std::endl
- << "`balance' options:" << std::endl
- << " -F print each account's full name" << std::endl
- << " -n do not generate totals for parent accounts" << std::endl
- << " -s show sub-accounts in balance totals" << std::endl
- << " -S show empty accounts in balance totals" << std::endl;
+ show_help(std::cout);
return 1;
}
- // The -f option is required
+ if (use_warnings && (have_beginning || have_ending)) {
+ std::cout << "Reporting";
- if (! file || ! *file) {
- std::cerr << "Please specify the ledger file using the -f option."
- << std::endl;
- return 1;
+ if (have_beginning) {
+ char buf[32];
+ std::strftime(buf, 31, "%Y.%m.%d", std::localtime(&begin_date));
+ std::cout << " from " << buf;
+ }
+
+ if (have_ending) {
+ char buf[32];
+ std::strftime(buf, 31, "%Y.%m.%d", std::localtime(&end_date));
+ std::cout << " until " << buf;
+ }
+
+ std::cout << std::endl;
+ }
+
+ // A ledger data file must be specified
+
+ if (! file) {
+ const char * p = std::getenv("LEDGER");
+ if (p)
+ file = new std::ifstream(p);
+
+ if (! file || ! *file) {
+ std::cerr << "Please specify the ledger file using the -f option."
+ << std::endl;
+ return 1;
+ }
}
// Read the command word
@@ -174,17 +281,34 @@ int main(int argc, char *argv[])
const std::string command = argv[optind];
#ifdef HUQUQULLAH
- if (command == "register")
+ if (command == "register" && argv[optind + 1] &&
+ std::string(argv[optind + 1]) != "Huququ'llah" &&
+ std::string(argv[optind + 1]) != "Expenses:Huququ'llah")
compute_huquq = false;
if (compute_huquq) {
- new commodity("H", true, true, true, false, 2);
+ main_ledger.compute_huquq = true;
+ main_ledger.huquq_commodity = new commodity("H", true, true,
+ true, false, 2);
+
+ // The allocation causes it to be inserted into the
+ // main_ledger.commodities mapping.
new commodity("mithqal", false, true, true, false, 1);
- read_regexps(".huquq", huquq_categories);
+ read_regexps(".huquq", main_ledger.huquq_categories);
main_ledger.record_price("H=" DEFAULT_COMMODITY "0.19");
+
+ bool save_use_warnings = use_warnings;
+ use_warnings = false;
main_ledger.record_price("troy=8.5410148523 mithqal");
+ use_warnings = save_use_warnings;
+
+ main_ledger.huquq = create_amount("H 1.00");
+
+ main_ledger.huquq_account = main_ledger.find_account("Huququ'llah");
+ main_ledger.huquq_expenses_account =
+ main_ledger.find_account("Expenses:Huququ'llah");
}
#endif
diff --git a/parse.cc b/parse.cc
index 309b90a4..7a4c5b07 100644
--- a/parse.cc
+++ b/parse.cc
@@ -32,17 +32,101 @@ static char * next_element(char * buf, bool variable = false)
static int linenum = 0;
-static inline void finalize_entry(entry * curr)
+static void finalize_entry(entry * curr, bool compute_balances)
{
- if (curr) {
- if (! curr->validate()) {
- std::cerr << "Failed to balance the following transaction, "
- << "ending on line " << (linenum - 1) << std::endl;
- curr->print(std::cerr);
- } else {
- main_ledger.entries.push_back(curr);
+ assert(curr);
+
+ // Certain shorcuts are allowed in the case of exactly two
+ // transactions.
+
+ if (! curr->xacts.empty() && curr->xacts.size() == 2) {
+ transaction * first = curr->xacts.front();
+ transaction * second = curr->xacts.back();
+
+ // If one transaction gives no value at all, then its value is
+ // the inverse of the computed value of the other.
+
+ if (! first->cost && second->cost) {
+ first->cost = second->cost->value();
+ first->cost->negate();
+
+ if (compute_balances)
+ first->acct->balance.credit(first->cost);
+ }
+ else if (! second->cost && first->cost) {
+ second->cost = first->cost->value();
+ second->cost->negate();
+
+ if (compute_balances)
+ second->acct->balance.credit(second->cost);
+ }
+ else if (first->cost && second->cost) {
+ // If one transaction is of a different commodity than the
+ // other, and it has no per-unit price, and its not of the
+ // default commodity, then determine its price by dividing the
+ // unit count into the total, to balance the transaction.
+
+ if (first->cost->comm() != second->cost->comm()) {
+ if (! second->cost->has_price() &&
+ second->cost->comm_symbol() != DEFAULT_COMMODITY) {
+ second->cost->set_value(first->cost);
+ }
+ else if (! first->cost->has_price() &&
+ first->cost->comm_symbol() != DEFAULT_COMMODITY) {
+ first->cost->set_value(second->cost);
+ }
+ }
}
}
+
+ if (! curr->validate()) {
+ std::cerr << "Error, line " << (linenum - 1)
+ << ": Failed to balance the following transaction:"
+ << std::endl;
+ curr->print(std::cerr);
+ curr->validate(true);
+ return;
+ }
+
+#ifdef HUQUQULLAH
+ if (main_ledger.compute_huquq) {
+ for (std::list<transaction *>::iterator x = curr->xacts.begin();
+ x != curr->xacts.end();
+ x++) {
+ if (! (*x)->exempt_or_necessary || ! (*x)->cost)
+ continue;
+
+ // Reflect the exempt or necessary transaction in the
+ // Huququ'llah account, using the H commodity, which is 19% of
+ // whichever DEFAULT_COMMODITY ledger was compiled with.
+
+ amount * temp = (*x)->cost->value();
+
+ transaction * t
+ = new transaction(main_ledger.huquq_account,
+ temp->value(main_ledger.huquq));
+ curr->xacts.push_back(t);
+
+ if (compute_balances)
+ t->acct->balance.credit(t->cost);
+
+ // Balance the above transaction by recording the inverse in
+ // Expenses:Huququ'llah.
+
+ t = new transaction(main_ledger.huquq_expenses_account,
+ temp->value(main_ledger.huquq));
+ t->cost->negate();
+ curr->xacts.push_back(t);
+
+ if (compute_balances)
+ t->acct->balance.credit(t->cost);
+
+ delete temp;
+ }
+ }
+#endif
+
+ main_ledger.entries.push_back(curr);
}
//////////////////////////////////////////////////////////////////////
@@ -59,7 +143,6 @@ bool parse_ledger(std::istream& in, bool compute_balances)
char line[1024];
struct std::tm moment;
- memset(&moment, 0, sizeof(struct std::tm));
entry * curr = NULL;
@@ -86,15 +169,15 @@ bool parse_ledger(std::istream& in, bool compute_balances)
int matched = pcre_exec(entry_re, NULL, line, std::strlen(line),
0, 0, ovector, 60);
if (! matched) {
- std::cerr << "Failed to parse, line " << linenum << ": "
- << line << std::endl;
+ std::cerr << "Error, line " << linenum
+ << ": Failed to parse: " << line << std::endl;
continue;
}
// If we haven't finished with the last entry yet, do so now
if (curr)
- finalize_entry(curr);
+ finalize_entry(curr, compute_balances);
curr = new entry;
@@ -114,6 +197,8 @@ bool parse_ledger(std::istream& in, bool compute_balances)
pcre_copy_substring(line, ovector, matched, 4, buf, 255);
int mday = std::atoi(buf);
+ memset(&moment, 0, sizeof(struct std::tm));
+
moment.tm_mday = mday;
moment.tm_mon = mon - 1;
moment.tm_year = year - 1900;
@@ -131,12 +216,11 @@ bool parse_ledger(std::istream& in, bool compute_balances)
}
if (ovector[8 * 2] >= 0) {
- int result = pcre_copy_substring(line, ovector, matched, 8, buf, 255);
- assert(result >= 0);
+ pcre_copy_substring(line, ovector, matched, 8, buf, 255);
curr->desc = buf;
}
}
- else if (std::isspace(line[0])) {
+ else if (curr && std::isspace(line[0])) {
transaction * xact = new transaction();
char * p = line;
@@ -162,8 +246,7 @@ bool parse_ledger(std::istream& in, bool compute_balances)
xact->note = cost_str;
}
- xact->cost = curr->xacts.front()->cost->copy();
- xact->cost->negate();
+ xact->cost = NULL;
}
else {
note_str = std::strchr(cost_str, ';');
@@ -183,56 +266,21 @@ bool parse_ledger(std::istream& in, bool compute_balances)
}
#ifdef HUQUQULLAH
- bool exempt_or_necessary = false;
- if (compute_huquq) {
- if (*p == '!') {
- exempt_or_necessary = true;
- p++;
- }
- else if (matches(huquq_categories, p)) {
- exempt_or_necessary = true;
- }
+ if (*p == '!') {
+ xact->exempt_or_necessary = true;
+ p++;
}
#endif
xact->acct = main_ledger.find_account(p);
- if (compute_balances)
+#ifdef HUQUQULLAH
+ if (xact->acct->exempt_or_necessary)
+ xact->exempt_or_necessary = true;
+#endif
+ if (compute_balances && xact->cost)
xact->acct->balance.credit(xact->cost);
curr->xacts.push_back(xact);
-
-#ifdef HUQUQULLAH
- if (exempt_or_necessary) {
- static amount * huquq = create_amount("H 1.00");
- amount * temp;
-
- // Reflect the exempt or necessary transaction in the
- // Huququ'llah account, using the H commodity, which is 19%
- // of whichever DEFAULT_COMMODITY ledger was compiled with.
- transaction * t = new transaction();
- t->acct = main_ledger.find_account("Huququ'llah");
- temp = xact->cost->value();
- t->cost = temp->value(huquq);
- delete temp;
- curr->xacts.push_back(t);
-
- if (compute_balances)
- t->acct->balance.credit(t->cost);
-
- // Balance the above transaction by recording the inverse in
- // Expenses:Huququ'llah.
- t = new transaction();
- t->acct = main_ledger.find_account("Expenses:Huququ'llah");
- temp = xact->cost->value();
- t->cost = temp->value(huquq);
- delete temp;
- t->cost->negate();
- curr->xacts.push_back(t);
-
- if (compute_balances)
- t->acct->balance.credit(t->cost);
- }
-#endif
}
else if (line[0] == 'Y') {
current_year = std::atoi(line + 2);
@@ -240,7 +288,7 @@ bool parse_ledger(std::istream& in, bool compute_balances)
}
if (curr)
- finalize_entry(curr);
+ finalize_entry(curr, compute_balances);
return true;
}
diff --git a/register.cc b/register.cc
index 5b5ea1dd..e0128d10 100644
--- a/register.cc
+++ b/register.cc
@@ -11,6 +11,18 @@ extern bool have_beginning;
extern std::time_t end_date;
extern bool have_ending;
+static std::string truncated(const std::string& str, int width)
+{
+ char buf[256];
+ memset(buf, '\0', 255);
+ std::strncpy(buf, str.c_str(), width);
+ if (buf[width - 1])
+ std::strcpy(&buf[width - 3], "...");
+ else
+ buf[width] = '\0';
+ return buf;
+}
+
//////////////////////////////////////////////////////////////////////
//
// Register printing code
@@ -19,16 +31,15 @@ extern bool have_ending;
void print_register(int argc, char **argv, std::ostream& out)
{
optind = 1;
-#if 0
- int c;
- while (-1 != (c = getopt(argc, argv, ""))) {
- switch (char(c)) {
- }
- }
-#endif
// Find out which account this register is to be printed for
+ if (optind == argc) {
+ std::cerr << ("Error: Must specify an account name "
+ "after the 'register' command.") << std::endl;
+ return;
+ }
+
account * acct = main_ledger.find_account(argv[optind++], false);
if (! acct) {
std::cerr << "Error: Unknown account name: " << argv[optind - 1]
@@ -50,17 +61,7 @@ void print_register(int argc, char **argv, std::ostream& out)
for (entries_iterator i = main_ledger.entries.begin();
i != main_ledger.entries.end();
i++) {
- bool applies = false;
- for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
- x != (*i)->xacts.end();
- x++) {
- if ((*x)->acct == acct) {
- applies = true;
- break;
- }
- }
-
- if (! applies || ! (*i)->matches(regexps))
+ if (! (*i)->matches(regexps))
continue;
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
@@ -91,21 +92,59 @@ void print_register(int argc, char **argv, std::ostream& out)
if ((*i)->desc.empty())
out << " ";
else
- out << std::left << (*i)->desc;
+ out << std::left << truncated((*i)->desc, 30);
out << " ";
- transaction * xact = (*i)->xacts.front();
+ // Always display the street value, if prices have been
+ // specified
+
+ amount * street = (*x)->cost->street();
+ balance.credit(street);
+
+ // If there are two transactions, use the one which does not
+ // refer to this account. If there are more than two, we will
+ // just have to print all of the splits, like gnucash does.
+
+ transaction * xact;
+ if ((*i)->xacts.size() == 2) {
+ if (*x == (*i)->xacts.front())
+ xact = (*i)->xacts.back();
+ else
+ xact = (*i)->xacts.front();
+ } else {
+ xact = *x;
+ }
out.width(22);
- out << std::left << xact->acct->as_str() << " ";
+ out << std::left << truncated(xact->acct->as_str(), 22) << " ";
out.width(12);
- out << std::right << (*x)->cost->as_str(true);
+ out << std::right << street->as_str(true);
+ delete street;
- balance.credit((*x)->cost);
balance.print(out, 12);
out << std::endl;
+
+ if (xact != *x)
+ continue;
+
+ for (std::list<transaction *>::iterator y = (*i)->xacts.begin();
+ y != (*i)->xacts.end();
+ y++) {
+ if (*x == *y)
+ continue;
+
+ out << " ";
+
+ out.width(22);
+ out << std::left << truncated((*y)->acct->as_str(), 22) << " ";
+
+ out.width(12);
+ street = (*y)->cost->street();
+ out << std::right << street->as_str(true) << std::endl;
+ delete street;
+ }
}
}
}
diff --git a/report b/report
index 5f53b737..9e67850d 100755
--- a/report
+++ b/report
@@ -1,7 +1,7 @@
#!/bin/bash
-binary=./ledger
-ledger=ledger.dat
+binary=ledger
+LEDGER=$HOME/doc/finance/ledger.dat
line="$binary -f $ledger"