summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2003-10-02 05:04:38 +0000
committerJohn Wiegley <johnw@newartisans.com>2003-10-02 05:04:38 +0000
commit3cfae2794784c1629dd98c0b600b2731e27a3f57 (patch)
tree5339db0e7df4b6f925788270d8c805111e729ae1
parentbfff951c310b0b1b4f2ddaf3d69549bd3bac2717 (diff)
downloadfork-ledger-3cfae2794784c1629dd98c0b600b2731e27a3f57.tar.gz
fork-ledger-3cfae2794784c1629dd98c0b600b2731e27a3f57.tar.bz2
fork-ledger-3cfae2794784c1629dd98c0b600b2731e27a3f57.zip
*** empty log message ***
-rw-r--r--Makefile2
-rw-r--r--amount.cc55
-rw-r--r--balance.cc92
-rw-r--r--equity.cc16
-rw-r--r--ledger.cc122
-rw-r--r--ledger.h137
-rw-r--r--ledger.texi218
-rw-r--r--main.cc32
-rw-r--r--parse.cc8
-rw-r--r--register.cc3
-rwxr-xr-xreport24
11 files changed, 421 insertions, 288 deletions
diff --git a/Makefile b/Makefile
index 2b1b7703..a64800ef 100644
--- a/Makefile
+++ b/Makefile
@@ -43,7 +43,7 @@ ledger.info: ledger.texi
g++ $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $<
clean:
- rm -f ledger *.o
+ rm -f ledger ledger.info *.o *~ .\#*
rebuild: clean deps all
diff --git a/amount.cc b/amount.cc
index 6098ebde..8d1752a0 100644
--- a/amount.cc
+++ b/amount.cc
@@ -1,20 +1,24 @@
-#include <sstream>
+#include "ledger.h"
+#include <sstream>
#include <gmp.h> // GNU multi-precision library
-#include "ledger.h"
-
namespace ledger {
#define MAX_PRECISION 10 // must be 2 or higher
//////////////////////////////////////////////////////////////////////
//
-// The `amount' structure. Every transaction has an associated amount,
-// which is represented by this structure. `amount' uses the GNU
-// multi-precision library, allowing for arbitrarily large amounts.
-// Each amount is a quantity of commodity at a certain price; the
-// default commodity is the US dollar, with a price of 1.00.
+// The `amount' structure. Every transaction has an associated
+// amount, which is represented by this structure. `amount' uses the
+// GNU multi-precision library, allowing for arbitrarily large
+// amounts. Each amount is a quantity of a certain commodity, with
+// an optional price per-unit for that commodity at the time the
+// amount was stated.
+//
+// To create an amount, for example:
+//
+// amount * cost = create_amount("50.2 MSFT @ $100.50");
//
class gmp_amount : public amount
@@ -57,20 +61,21 @@ class gmp_amount : public amount
}
virtual void set_value(const amount * val);
- virtual operator bool() const;
+ virtual bool is_zero() const;
virtual void negate() {
mpz_ui_sub(quantity, 0, quantity);
}
virtual void credit(const amount * other);
- virtual void parse(const char * num);
- virtual std::string as_str(bool full_prec) const;
+ virtual void parse(const std::string& num);
+ virtual const std::string as_str(bool full_prec) const;
- friend amount * create_amount(const char * value, const amount * cost);
+ friend amount * create_amount(const std::string& value,
+ const amount * cost);
};
-amount * create_amount(const char * value, const amount * cost)
+amount * create_amount(const std::string& value, const amount * cost)
{
gmp_amount * a = new gmp_amount();
a->parse(value);
@@ -277,7 +282,7 @@ void gmp_amount::set_value(const amount * val)
mpz_clear(addend);
}
-gmp_amount::operator bool() const
+bool gmp_amount::is_zero() const
{
mpz_t copy;
mpz_init_set(copy, quantity);
@@ -286,7 +291,7 @@ gmp_amount::operator bool() const
round(copy, copy, quantity_comm->precision);
bool zero = mpz_sgn(copy) == 0;
mpz_clear(copy);
- return ! zero;
+ return zero;
}
static std::string amount_to_str(const commodity * comm, const mpz_t val,
@@ -416,7 +421,7 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val,
return s.str();
}
-std::string gmp_amount::as_str(bool full_prec) const
+const std::string gmp_amount::as_str(bool full_prec) const
{
std::ostringstream s;
@@ -430,8 +435,11 @@ std::string gmp_amount::as_str(bool full_prec) const
return s.str();
}
-static void parse_number(mpz_t out, const char * num, commodity * comm)
+static void parse_number(mpz_t out, const std::string& number,
+ commodity * comm)
{
+ const char * num = number.c_str();
+
if (char * p = std::strchr(num, '/')) {
mpz_t numer;
mpz_t val;
@@ -559,7 +567,7 @@ static commodity * parse_amount(mpz_t out, const char * num,
return comm;
}
-void gmp_amount::parse(const char * num)
+void gmp_amount::parse(const std::string& number)
{
// Compile the regular expression used for parsing amounts
static pcre * re = NULL;
@@ -576,17 +584,20 @@ void gmp_amount::parse(const char * num)
int ovector[60];
int matched;
- matched = pcre_exec(re, NULL, num, std::strlen(num), 0, 0, ovector, 60);
+ matched = pcre_exec(re, NULL, number.c_str(), number.length(),
+ 0, 0, ovector, 60);
if (matched > 0) {
- quantity_comm = parse_amount(quantity, num, matched, ovector, 1);
+ quantity_comm = parse_amount(quantity, number.c_str(), matched,
+ ovector, 1);
// If the following succeeded, then we have a price
if (ovector[8 * 2] >= 0) {
priced = true;
- price_comm = parse_amount(price, num, matched, ovector, 9);
+ price_comm = parse_amount(price, number.c_str(), matched,
+ ovector, 9);
}
} else {
- std::cerr << "Failed to parse amount: " << num << std::endl;
+ std::cerr << "Failed to parse amount: " << number << std::endl;
}
}
diff --git a/balance.cc b/balance.cc
index dedfa5bb..7301fa96 100644
--- a/balance.cc
+++ b/balance.cc
@@ -16,51 +16,25 @@ static bool show_empty;
static bool no_subtotals;
static bool full_names;
-static bool account_matches(const account * acct,
- const std::list<mask>& regexps,
- bool * true_match)
-{
- bool match = false;
- *true_match = false;
-
- if (show_children) {
- for (const account * a = acct; a; a = a->parent) {
- bool exclude = false;
- if (matches(regexps, a->name, &exclude)) {
- match = true;
- *true_match = a == acct;
- break;
- }
- if (exclude)
- break;
- }
- } else {
- match = matches(regexps, acct->as_str());
- if (match)
- *true_match = matches(regexps, acct->name);
- }
- return match;
-}
-
static void display_total(std::ostream& out, totals& balance,
- account * acct, bool top_level,
- const std::list<mask>& regexps)
+ account * acct, bool top_level)
{
bool displayed = false;
- if (acct->checked == 1 && (show_empty || acct->balance)) {
+ if (acct->checked == 1 &&
+ (show_empty || ! acct->balance.is_zero())) {
displayed = true;
- out << acct->balance;
+ acct->balance.print(out, 20);
if (! no_subtotals && top_level)
balance.credit(acct->balance);
- if (acct->parent && ! no_subtotals && ! full_names) {
+ if (acct->parent && ! full_names && ! top_level) {
for (const account * a = acct; a; a = a->parent)
out << " ";
out << acct->name << std::endl;
} else {
- out << " " << *acct << std::endl;
+ out << " " << acct->as_str() << std::endl;
}
}
@@ -69,7 +43,7 @@ static void display_total(std::ostream& out, totals& balance,
for (accounts_iterator i = acct->children.begin();
i != acct->children.end();
i++)
- display_total(out, balance, (*i).second, ! displayed, regexps);
+ display_total(out, balance, (*i).second, ! displayed);
}
//////////////////////////////////////////////////////////////////////
@@ -77,7 +51,8 @@ static void display_total(std::ostream& out, totals& balance,
// Balance reporting code
//
-void report_balances(int argc, char **argv, std::ostream& out)
+void report_balances(int argc, char ** argv, regexps_t& regexps,
+ std::ostream& out)
{
show_children = false;
show_empty = false;
@@ -120,22 +95,33 @@ void report_balances(int argc, char **argv, std::ostream& out)
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 (regexps.empty()) {
+ if (! (show_children || ! acct->parent))
+ acct->checked = 2;
+ else
+ acct->checked = 1;
+ }
+ else {
+ bool by_exclusion;
+ bool match = matches(regexps, acct->as_str(),
+ &by_exclusion);
+ if (! match) {
+ acct->checked = 2;
+ }
+ else if (by_exclusion) {
+ if (! (show_children || ! acct->parent))
+ acct->checked = 2;
+ else
+ acct->checked = 1;
+ }
+ else {
+ acct->checked = 1;
+ }
+ }
}
- if (acct->checked == 2)
- break;
- else if (acct->checked == 3)
- continue;
-
- acct->balance.credit((*x)->cost->street());
+ if (acct->checked == 1)
+ acct->balance.credit((*x)->cost->street());
}
}
}
@@ -148,13 +134,15 @@ void report_balances(int argc, char **argv, std::ostream& out)
for (accounts_iterator i = main_ledger.accounts.begin();
i != main_ledger.accounts.end();
i++)
- display_total(out, balance, (*i).second, true, regexps);
+ display_total(out, balance, (*i).second, true);
// Print the total of all the balances shown
- if (! no_subtotals && balance)
- out << "--------------------" << std::endl
- << balance << std::endl;
+ if (! no_subtotals && ! balance.is_zero()) {
+ out << "--------------------" << std::endl;
+ balance.print(out, 20);
+ out << std::endl;
+ }
}
} // namespace ledger
diff --git a/equity.cc b/equity.cc
index 16502ad1..1a4500b5 100644
--- a/equity.cc
+++ b/equity.cc
@@ -2,10 +2,10 @@
namespace ledger {
-static void equity_entry(std::ostream& out, account * acct,
- const std::list<mask>& regexps)
+static void equity_entry(account * acct, regexps_t& regexps,
+ std::ostream& out)
{
- if (acct->balance &&
+ if (! acct->balance.is_zero() &&
(regexps.empty() || matches(regexps, acct->as_str()))) {
entry opening;
@@ -17,7 +17,8 @@ static void equity_entry(std::ostream& out, account * acct,
for (totals::const_iterator i = acct->balance.amounts.begin();
i != acct->balance.amounts.end();
i++) {
- if (! *((*i).second)) // skip if zero balance for the commodity
+ // Skip it, if there is a zero balance for the commodity
+ if ((*i).second->is_zero())
continue;
xact = new transaction();
@@ -40,7 +41,7 @@ static void equity_entry(std::ostream& out, account * acct,
for (accounts_iterator i = acct->children.begin();
i != acct->children.end();
i++)
- equity_entry(out, (*i).second, regexps);
+ equity_entry((*i).second, regexps, out);
}
//////////////////////////////////////////////////////////////////////
@@ -50,7 +51,8 @@ static void equity_entry(std::ostream& out, account * acct,
// balances.
//
-void equity_ledger(int argc, char **argv, std::ostream& out)
+void equity_ledger(int argc, char ** argv, regexps_t& regexps,
+ std::ostream& out)
{
optind = 1;
@@ -67,7 +69,7 @@ void equity_ledger(int argc, char **argv, std::ostream& out)
for (accounts_iterator i = main_ledger.accounts.begin();
i != main_ledger.accounts.end();
i++)
- equity_entry(out, (*i).second, regexps);
+ equity_entry((*i).second, regexps, out);
}
} // namespace ledger
diff --git a/ledger.cc b/ledger.cc
index ef7a9d94..4f750202 100644
--- a/ledger.cc
+++ b/ledger.cc
@@ -34,9 +34,8 @@ void entry::print(std::ostream& out, bool shortcut) const
x != xacts.end();
x++) {
#ifdef HUQUQULLAH
- if ((*x)->acct->exempt_or_necessary &&
- (! shortcut || ! ledger::matches(main_ledger.huquq_categories,
- (*x)->acct->as_str())))
+ if ((*x)->exempt_or_necessary ||
+ (! shortcut && (*x)->acct->exempt_or_necessary))
out << " !";
else
#endif
@@ -69,12 +68,12 @@ bool entry::validate(bool show_unaccounted) const
if ((*x)->cost)
balance.credit((*x)->cost->value());
- if (show_unaccounted && balance) {
+ if (show_unaccounted && ! balance.is_zero()) {
std::cerr << "Unaccounted-for balances are:" << std::endl;
balance.print(std::cerr, 20);
std::cerr << std::endl << std::endl;
}
- return ! balance; // must balance to 0.0
+ return balance.is_zero(); // must balance to 0.0
}
bool entry::matches(const std::list<mask>& regexps) const
@@ -117,33 +116,37 @@ void totals::credit(const totals& other)
credit((*i).second);
}
-totals::operator bool() const
+bool totals::is_zero() const
{
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
- if (*((*i).second))
- return true;
- return false;
+ if (! (*i).second->is_zero())
+ return false;
+ return true;
}
void totals::print(std::ostream& out, int width) const
{
bool first = true;
- for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
- if (*((*i).second)) {
- if (first)
- first = false;
- else
- out << std::endl;
-
- out.width(width);
- out << std::right << (*i).second->as_str();
- }
+
+ for (const_iterator i = amounts.begin(); i != amounts.end(); i++) {
+ if ((*i).second->is_zero())
+ continue;
+
+ if (first)
+ first = false;
+ else
+ out << std::endl;
+
+ out.width(width);
+ out << std::right << (*i).second->as_str();
+ }
}
// Print out the entire ledger that was read in, sorted by date.
// This can be used to "wash" ugly ledger files.
-void print_ledger(int argc, char *argv[], std::ostream& out)
+void print_ledger(int argc, char ** argv, regexps_t& regexps,
+ std::ostream& out)
{
bool use_shortcuts = true;
@@ -174,37 +177,40 @@ void print_ledger(int argc, char *argv[], std::ostream& out)
(*i)->print(out, use_shortcuts);
}
-void record_regexp(char * pattern, std::list<mask>& regexps)
+mask::mask(const std::string& pat) : exclude(false)
{
- bool exclude = false;
-
- char * pat = pattern;
- if (*pat == '-') {
+ const char * p = pat.c_str();
+ if (*p == '-') {
exclude = true;
- pat++;
- while (std::isspace(*pat))
- pat++;
+ p++;
+ while (std::isspace(*p))
+ p++;
}
- else if (*pat == '+') {
- pat++;
- while (std::isspace(*pat))
- pat++;
+ else if (*p == '+') {
+ p++;
+ while (std::isspace(*p))
+ p++;
}
+ pattern = p;
const char *error;
int erroffset;
- pcre * re = pcre_compile(pat, PCRE_CASELESS, &error, &erroffset, NULL);
- if (! re)
+ regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
+ &error, &erroffset, NULL);
+ if (! regexp)
std::cerr << "Warning: Failed to compile regexp: " << pattern
<< std::endl;
- else
- regexps.push_back(mask(exclude, re));
}
-void read_regexps(const char * path, std::list<mask>& regexps)
+void record_regexp(const std::string& pattern, regexps_t& regexps)
+{
+ regexps.push_back(mask(pattern));
+}
+
+void read_regexps(const std::string& path, regexps_t& regexps)
{
- if (access(path, R_OK) != -1) {
- std::ifstream file(path);
+ if (access(path.c_str(), R_OK) != -1) {
+ std::ifstream file(path.c_str());
while (! file.eof()) {
char buf[80];
@@ -215,14 +221,20 @@ void read_regexps(const char * path, std::list<mask>& regexps)
}
}
-bool matches(const std::list<mask>& regexps, const std::string& str,
- bool * exclude)
+bool matches(const regexps_t& regexps, const std::string& str,
+ bool * by_exclusion)
{
+ assert(! regexps.empty());
+
// If the first pattern is an exclude, we assume all patterns match
- // if they don't match the exclude. If the first pattern is an
- // include, then only accounts matching the include will match.
+ // if they don't match the exclude -- and by_exclusion will be set
+ // to true to reflect this "by default" behavior. But if the first
+ // pattern is an include, only accounts matching the include will
+ // match, and these are a positive match.
bool match = (*regexps.begin()).exclude;
+ if (match && by_exclusion)
+ *by_exclusion = true;
for (std::list<mask>::const_iterator r = regexps.begin();
r != regexps.end();
@@ -230,8 +242,8 @@ bool matches(const std::list<mask>& regexps, const std::string& str,
int ovec[3];
if (pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
0, 0, ovec, 3) >= 0) {
- if (exclude)
- *exclude = (*r).exclude;
+ if (by_exclusion)
+ *by_exclusion = (*r).exclude;
match = ! (*r).exclude;
}
}
@@ -261,12 +273,12 @@ state::~state()
#endif // DO_CLEANUP
-void state::record_price(const char * setting)
+void state::record_price(const std::string& setting)
{
char buf[128];
- std::strcpy(buf, setting);
+ std::strcpy(buf, setting.c_str());
- assert(std::strlen(setting) < 128);
+ assert(setting.length() < 128);
char * c = buf;
char * p = std::strchr(buf, '=');
@@ -278,14 +290,14 @@ void state::record_price(const char * setting)
}
}
-account * state::find_account(const char * name, bool create)
+account * state::find_account(const std::string& name, bool create)
{
accounts_iterator i = accounts_cache.find(name);
if (i != accounts_cache.end())
return (*i).second;
- char * buf = new char[std::strlen(name) + 1];
- std::strcpy(buf, name);
+ char * buf = new char[name.length() + 1];
+ std::strcpy(buf, name.c_str());
account * current = NULL;
for (char * tok = std::strtok(buf, ":");
@@ -294,8 +306,10 @@ account * state::find_account(const char * name, bool create)
if (! current) {
accounts_iterator i = accounts.find(tok);
if (i == accounts.end()) {
- if (! create)
+ if (! create) {
+ delete[] buf;
return NULL;
+ }
current = new account(tok);
accounts.insert(accounts_entry(tok, current));
} else {
@@ -304,8 +318,10 @@ account * state::find_account(const char * name, bool create)
} else {
accounts_iterator i = current->children.find(tok);
if (i == current->children.end()) {
- if (! create)
+ if (! create) {
+ delete[] buf;
return NULL;
+ }
current = new account(tok, current);
current->parent->children.insert(accounts_entry(tok, current));
} else {
diff --git a/ledger.h b/ledger.h
index e074f82e..900ce8e8 100644
--- a/ledger.h
+++ b/ledger.h
@@ -1,5 +1,5 @@
#ifndef _LEDGER_H
-#define _LEDGER_H "$Revision: 1.16 $"
+#define _LEDGER_H "$Revision: 1.17 $"
//////////////////////////////////////////////////////////////////////
//
@@ -26,70 +26,6 @@
namespace ledger {
-// Format of a ledger entry (GNUcash account files are also supported):
-//
-// DATE [CLEARED] (CODE) DESCRIPTION
-// ACCOUNT AMOUNT [; NOTE]
-// ACCOUNT AMOUNT [; NOTE]
-// ...
-//
-// The DATE can be YYYY.MM.DD or YYYY/MM/DD or MM/DD.
-// The CLEARED bit is a '*' if the account has been cleared.
-// The CODE can be anything, but must be enclosed in parenthesis.
-// The DESCRIPTION can be anything, up to a newline.
-//
-// The ACCOUNT is a colon-separated string naming the account.
-// The AMOUNT follows the form:
-// [COMM][WS]QUANTITY[WS][COMM][[WS]@[WS][COMM]PRICE[COMM]]
-// For example:
-// 200 AAPL @ $40.00
-// $50.00
-// DM 12.54
-// DM 12.54 @ $1.20
-// The NOTE can be anything.
-//
-// All entries must balance to 0.0, in every commodity. This means
-// that a transaction with mixed commodities must balance by
-// converting one of those commodities to the other. As a
-// convenience, this is done automatically for you in the case where
-// exactly two commodities are referred to, in which case the second
-// commodity is converted into the first by computing which the price
-// must have been in order to balance the transaction. Example:
-//
-// 2004.06.18 * (BUY) Apple Computer
-// Assets:Brokerage $-200.00
-// Assets:Brokerage 100 AAPL
-//
-// What this transaction says is that $200 was paid from the
-// brokerage account to buy 100 shares of Apple stock, and then place
-// those same shares back in the brokerage account. From this point
-// forward, the account "Assets:Brokerage" will have two balance
-// totals: The number of dollars in the account, and the number of
-// apple shares.
-// In terms of the transaction, however, it must balance to zero,
-// otherwise it would mean that something had been lost without
-// accouting for it. So in this case what ledger will do is divide
-// 100 by $200, to arrive at a per-share price of $2 for the APPL
-// stock, and it will read this transaction as if it had been
-// written:
-//
-// 2004.06.18 * (BUY) Apple Computer
-// Assets:Brokerage $-200
-// Assets:Brokerage 100 AAPL @ $2
-//
-// If you then wanted to give some of the shares to someone, in
-// exchange for services rendered, use the regular single-commodity
-// form of transaction:
-//
-// 2004.07.11 * A kick-back for the broker
-// Assets:Brokerage -10 AAPL
-// Expenses:Broker's Fees 10 AAPL
-//
-// This transaction does not need to know the price of AAPL on the
-// given day, because none of the shares are being converted to
-// another commodity. It simply directly affects the total number of
-// AAPL shares held in "Assets:Brokerage".
-
struct commodity
{
std::string name;
@@ -127,9 +63,9 @@ class amount
virtual bool has_price() const = 0;
virtual void set_value(const amount * pr) = 0;
- // Test if non-zero
+ // Test if the quantity is zero
- virtual operator bool() const = 0;
+ virtual bool is_zero() const = 0;
// Assignment
@@ -138,27 +74,28 @@ class amount
// String conversion routines
- virtual void parse(const char * num) = 0;
- virtual std::string as_str(bool full_prec = false) const = 0;
+ virtual void parse(const std::string& num) = 0;
+ virtual const std::string as_str(bool full_prec = false) const = 0;
};
-extern amount * create_amount(const char * value,
+extern amount * create_amount(const std::string& value,
const amount * cost = NULL);
struct mask
{
- bool exclude;
- pcre * regexp;
+ bool exclude;
+ std::string pattern;
+ pcre * regexp;
- mask(bool exc, pcre * re) : exclude(exc), regexp(re) {}
+ mask(const std::string& pattern);
};
-extern std::list<mask> regexps;
+typedef std::list<mask> regexps_t;
-extern void record_regexp(char * pattern, std::list<mask>& regexps);
-extern void read_regexps(const char * path, std::list<mask>& regexps);
-extern bool matches(const std::list<mask>& regexps,
- const std::string& str, bool * exclude = NULL);
+void record_regexp(const std::string& pattern, regexps_t& regexps);
+void read_regexps(const std::string& path, regexps_t& regexps);
+bool matches(const regexps_t& regexps, const std::string& str,
+ bool * by_exclusion = NULL);
struct account;
@@ -187,6 +124,7 @@ struct transaction
#endif
};
+
struct entry
{
std::time_t date;
@@ -249,7 +187,7 @@ struct totals
}
void credit(const totals& other);
- operator bool() const;
+ bool is_zero() const;
void print(std::ostream& out, int width) const;
@@ -259,13 +197,6 @@ struct totals
}
};
-template<class Traits>
-std::basic_ostream<char, Traits> &
-operator<<(std::basic_ostream<char, Traits>& out, const totals& t) {
- t.print(out, 20);
- return out;
-}
-
typedef std::map<const std::string, account *> accounts_t;
typedef accounts_t::iterator accounts_iterator;
@@ -276,10 +207,14 @@ struct account
account * parent;
std::string name;
+#ifdef READ_GNUCASH
commodity * comm; // default commodity for this account
- totals balance;
+#endif
+ totals balance; // optional, parse-time computed balance
- int checked;
+ mutable std::string full_name;
+
+ int checked; // 'balance' uses this for speed's sake
#ifdef HUQUQULLAH
bool exempt_or_necessary;
#endif
@@ -301,25 +236,21 @@ struct account
const std::string as_str() const {
if (! parent)
return name;
- else
- return parent->as_str() + ":" + name;
+ else if (full_name.empty())
+ full_name = parent->as_str() + ":" + name;
+
+ return full_name;
}
};
-template<class Traits>
-std::basic_ostream<char, Traits> &
-operator<<(std::basic_ostream<char, Traits>& out, const account& a) {
- return (out << a.as_str());
-}
-
struct state
{
- commodities_t commodities;
- accounts_t accounts;
- accounts_t accounts_cache; // maps full names to accounts
- entries_t entries;
- totals prices;
+ commodities_t commodities;
+ accounts_t accounts;
+ accounts_t accounts_cache; // maps full names to accounts
+ entries_t entries;
+ totals prices;
#ifdef HUQUQULLAH
bool compute_huquq;
@@ -336,8 +267,8 @@ struct state
~state();
#endif
- void record_price(const char * setting);
- account * find_account(const char * name, bool create = true);
+ void record_price(const std::string& setting);
+ account * find_account(const std::string& name, bool create = true);
};
extern state main_ledger;
diff --git a/ledger.texi b/ledger.texi
index 722c63d1..aba24264 100644
--- a/ledger.texi
+++ b/ledger.texi
@@ -1,5 +1,5 @@
\input texinfo @c -*-texinfo-*-
-@comment $Id: ledger.texi,v 1.7 2003/10/02 00:07:14 johnw Exp $
+@comment $Id: ledger.texi,v 1.8 2003/10/02 05:04:38 johnw Exp $
@comment %**start of header
@setfilename ledger.info
@@ -24,17 +24,24 @@
@contents
@ifnottex
-@node Top
+@node Top, Introduction, (dir), (dir)
@top Ledger Accouting Tool
@c @insertcopying
@end ifnottex
+@menu
+* Introduction::
+* Keeping a ledger::
+* Computing Huqúqu'lláh::
+@end menu
+
+@node Introduction, Keeping a ledger, Top, Top
@chapter Introduction
@code{ledger} is an accouting tool that has the chutzpah to exist. It
provides not one bell or whistle for the money, and returns the user
-back to the days before user interfaces were even a twinkle on their
+to the days before user interfaces were even a twinkle in their
father's CRT.
What it does do is provide a double-entry accouting ledger with all of
@@ -138,6 +145,7 @@ Your usage of @code{ledger} will have two parts: Keeping the ledger,
and using the @code{ledger} tool to provide you with information
summaries derived from your ledger's entries.
+@node Keeping a ledger, Computing Huqúqu'lláh, Introduction, Top
@chapter Keeping a ledger
The most important part of accounting is keeping a good ledger. If
@@ -192,7 +200,79 @@ the balanced amount, if it is the same as the first line:
For this entry, @code{ledger} will figure out that $-23.00 must come
from ``Assets:Checking'' in order to balance the entry.
-@section Commodities and currencies
+@menu
+* Credits and Debits::
+* Commodities and Currencies::
+* Accounts and Inventories::
+* Understanding Equity::
+@end menu
+
+@node Credits and Debits, Commodities and Currencies, Keeping a ledger, Keeping a ledger
+@section Credits and Debits
+
+Credit and debit are simple enough terms in themselves, but the usages
+of the modern world have made them very hard to puzzle out.
+
+Basically, a credit means you add something to an account, and a debit
+means you take away. A debit card is correctly name: From your point
+of view, it debits your checking account every time you use it.
+
+The credit card is strangely named, because you have to look at it
+from the merchant's point of view: Every time you use it, it credit's
+@emph{his} account right away. This was a giant leap from the days of
+cash and checks, when the only other way to supply immediate credit
+was by a wire transfer. But a credit card does not credit you
+anything at all. In fact, from your point of view, it should be
+called a liability card, since it increases your liability to the
+issuing bank every time you use it.
+
+In @code{ledger}, credits and debits are given as they are, which
+means that sometimes you will see a minus sign where you don't expect
+one. For example, when you get paid, in order to credit your bank
+account, you need to debit an income account:
+
+@example
+9/29 My Employer
+ Assets:Checking $500.00
+ Income:Salary $-500.00
+@end example
+
+But wait, you say, why is the Income a negative figure? And when you
+look at the balance totals for your ledger, you will certainly be
+surprised to see Expenses as a positive figure, and Income as a
+negative figure. Isn't that the opposite of how it should look?
+
+It may take getting used to, but to properly use a general ledger you
+will need to think in terms of correct debits and credits. Rather
+than @code{ledger} ``fixing'' the minus signs, let's understand why
+they are there.
+
+When you earn money, the money has to come from somewhere. Let's call
+that somewhere ``society''. In order for society to give you an
+income, you must take money away from society (debit) in order to put
+it into your bank (credit). When you then spend that money, it leaves
+your bank account (debit) and goes back to society (credit). This is
+why Income will appear negative---it reflects the money you have drawn
+from society---and why Expenses will be positive---it is the amount
+you've given back. These credits and debits will always cancel each
+other out in the end, because you don't have the ability to create new
+money: It must always come from somewhere, and in the end must always
+leave. This is the beginning of economy, after which the explanation
+gets terribly difficult.
+
+Based on that explanation, here's another way to look at your balance
+report: Every negative figure means that that account or person or
+place has less money now than when you started your ledger; and every
+positive figure means that that account or person or place has more
+money now that when you started your ledger. Make sense?
+
+Alos, credit cards will have a negative value, because you are
+spending @emph{from} them (debit) in order pay someone else (credit).
+They are called credit cards because you are able to instantly credit
+that other person, by simply waving a card.
+
+@node Commodities and Currencies, Accounts and Inventories, Credits and Debits, Keeping a ledger
+@section Commodities and Currencies
@code{ledger} makes no assumptions about the commodities you use; it
only requires that you specify a commodity. The commodity may be any
@@ -292,6 +372,7 @@ Euro=DM 0.75
This is a roundabout way of reporting AAPL shares in their Deutsch
Mark equivalent.
+@node Accounts and Inventories, Understanding Equity, Commodities and Currencies, Keeping a ledger
@section Accounts and Inventories
Since @code{ledger}'s accounts and commodity system is so flexible,
@@ -329,8 +410,120 @@ would look like:
Now you've turned 2 steaks into 15 gold, courtesy of your customer,
Sturm Brightblade.
-@chapter Using @code{ledger}
+@node Understanding Equity, , Accounts and Inventories, Keeping a ledger
+@section Understanding Equity
+
+The most confusing entry in any ledger will be your equity
+account---because starting balances can't come out of nowhere.
+
+When you first start your ledger, you will likely already have money
+in some of your accounts. Let's say there's $100 in your checking
+account; then add an entry to your ledger to reflect this amount.
+Where will money come from? The answer: your equity.
+
+@example
+10/2 Opening Balance
+ Assets:Checking $100.00
+ Equity:Opening Balances $-100.00
+@end example
+
+But what is equity? You may have heard of equity when people talked
+about house mortgages, as ``the part of the house that you own''.
+Basically, equity is like the value of something. If you own a car
+worth $5000, then you have $5000 in equity in that car. In order to
+turn that car (a commodity) into a cash flow, or a credit to your bank
+account, you will have to debit the equity by selling it.
+
+When you start a ledger, you are probably already worth something.
+Your net worth is your current equity. By transferring the money in
+the ledger from your equity to your bank accounts, you are crediting
+the ledger account based on your prior equity value. That is why,
+when you look at the balance report, you will see a large negative
+number for Equity that never changes: Because that is what you were
+worth (what you debited from yourself in order to start the ledger)
+before the money started moving around. If the total positive value
+of your assets is greater than the absolute value of your starting
+equity, it means you are making money.
+
+Clear as mud? Keep thinking about it. Until you figure it out, put
+``-- -Equity'' at the end of your balance command, to remove the
+confusing figure from the totals.
+
+@chapter Using the @code{ledger} Tool
+
+Now that you have an orderly and well-organized general ledger, it's
+time to start generating some orderly and well-organized reports.
+This is where the @code{ledger} tool comes in. With it, you can
+balance your checkbook, see where your money is going, tell whether
+you've made a profit this year, and even compute the present day value
+of your retirement accounts. And all with the simplest of interfaces:
+the command-line.
+
+The most often used command will be the @code{balance} command:
+
+@example
+/home/johnw $ export LEDGER=/home/johnw/doc/finance/ledger.dat
+/home/johnw $ ledger balance
+@end example
+
+Here I've set my @code{LEDGER} environment variable to point to where
+my ledger file is hiding. Thereafter, I needn't specify it again.
+
+The balance command prints out the summarized balances of all my
+top-level accounts, excluding sub-accounts. In order to see the
+balances for a specific account, just specify a regular expression
+after the balance command:
+
+@example
+/home/johnw $ ledger balance expenses:food
+@end example
+
+This will show all the money that's been spent on food, since the
+beginning of the ledger. For food spending just this month
+(September), use:
+
+@example
+/home/johnw $ ledger -d sep balance expenses:food
+@end example
+
+Or maybe I want to see all of my assets, in which case the -s (show
+sub-accounts) option comes in handy:
+
+@example
+/home/johnw $ ledger balance -s
+@end example
+
+To exclude a particular account, use a regular expression with a
+leading minus sign. The following will show all expenses, but without
+food spending:
+
+@example
+/home/johnw $ ledger balance expenses -food
+@end example
+
+If you want to show all accounts but for one account, remember to use
+@samp{--} to separate the exclusion pattern from the options list:
+
+@example
+/home/johnw $ ledger balance -- -equity
+@end example
+
+@chapter Using GnuCash to Keep Your Ledger
+
+The @code{ledger} tool is fast and simple, but it gives you no special
+method of actually editing the ledger. It assumes you know how to use
+a text editor, and like doing so. Perhaps an Emacs mode will appear
+someday soon to make editing @code{ledger}'s data files much easier.
+
+Until then, you are free to use GnuCash to maintain your ledger, and
+the @code{ledger} program for querying and reporting on the contents
+of that ledger. It takes a little longer to parse the XML data format
+that GnuCash uses, but the end result is identical.
+
+Then again, why would anyone use a Gnome-centric, 35 megabyte behemoth
+to edit their data, and a 65 kilobyte executable to query it@dots{}
+@node Computing Huqúqu'lláh, , Keeping a ledger, Top
@chapter Computing Huqúqu'lláh
As a Bahá'í, I need to compute Huqúqu'lláh on some of my assets. The
@@ -374,20 +567,7 @@ That's it. To see how much Huqúq is currently owed based on your
ledger data, type:
@example
-/home/johnw $ ledger -f ledger.dat balance huquq
+/home/johnw $ ledger -f ledger.dat balance ^huquq
@end example
-Not sure if you should pay yet? Go to your newspaper, or look on the
-Web, and find the current price of gold per ounce. Then pass this
-figure to the @samp{-G} option. If it were $357.10, you would use:
-
-@example
-/home/johnw $ ledger -f ledger.dat balance -G 357.10 huquq
-@end example
-
-Now your balance report will be given in mi@underline{th}qáls of gold,
-not dollars. If the balance on your Huqúqu'lláh account is more than
--19 mi@underline{th}qáls (remember, it is a liability account, so
-amounts are negative), then get out your checkbook.
-
@bye
diff --git a/main.cc b/main.cc
index 2ecaaecb..ca2f4201 100644
--- a/main.cc
+++ b/main.cc
@@ -8,10 +8,14 @@ namespace ledger {
extern bool parse_gnucash(std::istream& in, bool compute_balances);
#endif
- extern void report_balances(int argc, char **argv, std::ostream& out);
- extern void print_register(int argc, char **argv, std::ostream& out);
- extern void print_ledger(int argc, char *argv[], std::ostream& out);
- extern void equity_ledger(int argc, char **argv, std::ostream& out);
+ extern void report_balances(int argc, char ** argv, regexps_t& regexps,
+ std::ostream& out);
+ extern void print_register(int argc, char ** argv, regexps_t& regexps,
+ std::ostream& out);
+ extern void print_ledger(int argc, char ** argv, regexps_t& regexps,
+ std::ostream& out);
+ extern void equity_ledger(int argc, char ** argv, regexps_t& regexps,
+ std::ostream& out);
bool show_cleared;
bool get_quotes;
@@ -75,7 +79,7 @@ static const char *formats[] = {
NULL
};
-static bool parse_date(const char * date_str, std::time_t * result)
+static bool parse_date(const std::string& date_str, std::time_t * result)
{
struct std::tm when;
@@ -84,7 +88,7 @@ static bool parse_date(const char * date_str, std::time_t * result)
for (const char ** f = formats; *f; f++) {
memset(&when, INT_MAX, sizeof(struct std::tm));
- if (strptime(date_str, *f, &when)) {
+ if (strptime(date_str.c_str(), *f, &when)) {
when.tm_hour = 0;
when.tm_min = 0;
when.tm_sec = 0;
@@ -113,12 +117,12 @@ static bool parse_date(const char * date_str, std::time_t * result)
// Command-line parser and top-level logic.
//
-int main(int argc, char *argv[])
+int main(int argc, char * argv[])
{
- // Parse the command-line options
-
std::istream * file = NULL;
+ regexps_t regexps;
+
#ifdef HUQUQULLAH
bool compute_huquq = true;
#endif
@@ -126,6 +130,8 @@ int main(int argc, char *argv[])
have_ending = false;
show_cleared = false;
+ // Parse the command-line options
+
int c;
while (-1 != (c = getopt(argc, argv, "+b:e:d:cChHwf:i:p:Pv"))) {
switch (char(c)) {
@@ -328,13 +334,13 @@ int main(int argc, char *argv[])
// Process the command
if (command == "balance")
- report_balances(argc - optind, &argv[optind], std::cout);
+ report_balances(argc - optind, &argv[optind], regexps, std::cout);
else if (command == "register")
- print_register(argc - optind, &argv[optind], std::cout);
+ print_register(argc - optind, &argv[optind], regexps, std::cout);
else if (command == "print")
- print_ledger(argc - optind, &argv[optind], std::cout);
+ print_ledger(argc - optind, &argv[optind], regexps, std::cout);
else if (command == "equity")
- equity_ledger(argc - optind, &argv[optind], std::cout);
+ equity_ledger(argc - optind, &argv[optind], regexps, std::cout);
}
// main.cc ends here.
diff --git a/parse.cc b/parse.cc
index 1c2cb556..f88eccc1 100644
--- a/parse.cc
+++ b/parse.cc
@@ -114,7 +114,9 @@ static void finalize_entry(entry * curr, bool compute_balances)
}
#ifdef HUQUQULLAH
- if (! main_ledger.compute_huquq || ! (*x)->exempt_or_necessary)
+ if (! main_ledger.compute_huquq ||
+ ! ((*x)->exempt_or_necessary ||
+ (*x)->acct->exempt_or_necessary))
continue;
// Reflect 19% of the exempt or necessary transaction in the
@@ -307,10 +309,6 @@ bool parse_ledger(std::istream& in, bool compute_balances)
#endif
xact->acct = main_ledger.find_account(p);
-#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);
diff --git a/register.cc b/register.cc
index e0128d10..bc7f6b06 100644
--- a/register.cc
+++ b/register.cc
@@ -28,7 +28,8 @@ static std::string truncated(const std::string& str, int width)
// Register printing code
//
-void print_register(int argc, char **argv, std::ostream& out)
+void print_register(int argc, char ** argv, regexps_t& regexps,
+ std::ostream& out)
{
optind = 1;
diff --git a/report b/report
index 9e67850d..ecd67e0d 100755
--- a/report
+++ b/report
@@ -1,20 +1,20 @@
#!/bin/bash
-binary=ledger
-LEDGER=$HOME/doc/finance/ledger.dat
-
-line="$binary -f $ledger"
-
command=$1
shift
case "$command" in
- balance) $line "$@" balance -s -- -Equity -Income -Expenses -Retirement ;;
- worth) $line "$@" balance assets liabilities ;;
- profit) $line "$@" balance income expense ;;
- spending) $line "$@" balance -F food movies gas tips \
+ balance) ledger "$@" balance -- -Equity -Income -Expenses -Retirement ;;
+ worth) ledger "$@" balance assets liabilities ;;
+ profit) ledger "$@" balance income expense ;;
+ spending) ledger "$@" balance -F food movies gas tips \
health supplies -insurance -vacation ;;
- huquq) $line "$@" balance ^huquq ;;
- gold) $line "$@" balance -G $1 ^huquq ;;
- equity) $line "$@" equity -- -^Income -^Expenses -^Equity ;;
+ monthly_spending)
+ for i in jan feb mar apr may jun jul aug sep oct nov dec
+ do
+ echo $i:
+ $0 spending -d $i
+ done ;;
+ huquq) ledger "$@" balance -n ^huquq ;;
+ equity) ledger "$@" equity -- -^Income -^Expenses -^Equity ;;
esac