*** empty log message ***

This commit is contained in:
John Wiegley 2003-10-02 05:04:38 +00:00
parent bfff951c31
commit 3cfae27947
11 changed files with 421 additions and 288 deletions

View file

@ -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

View file

@ -1,20 +1,24 @@
#include <sstream>
#include <gmp.h> // GNU multi-precision library
#include "ledger.h"
#include <sstream>
#include <gmp.h> // GNU multi-precision library
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;
}
}

View file

@ -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,21 +95,32 @@ 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)))
if (regexps.empty()) {
if (! (show_children || ! acct->parent))
acct->checked = 2;
else if (! (true_match || show_children || ! acct->parent))
acct->checked = 3;
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;
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

View file

@ -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

104
ledger.cc
View file

@ -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,19 +116,22 @@ 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;
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)) {
for (const_iterator i = amounts.begin(); i != amounts.end(); i++) {
if ((*i).second->is_zero())
continue;
if (first)
first = false;
else
@ -143,7 +145,8 @@ void totals::print(std::ostream& out, int width) const
// 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)
{
if (access(path, R_OK) != -1) {
std::ifstream file(path);
regexps.push_back(mask(pattern));
}
void read_regexps(const std::string& path, regexps_t& regexps)
{
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 {

123
ledger.h
View file

@ -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;
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,17 +236,13 @@ 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
{
@ -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;

View file

@ -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
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

32
main.cc
View file

@ -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.

View file

@ -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);

View file

@ -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;

24
report
View file

@ -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