*** empty log message ***
This commit is contained in:
parent
bfff951c31
commit
3cfae27947
11 changed files with 421 additions and 288 deletions
2
Makefile
2
Makefile
|
|
@ -43,7 +43,7 @@ ledger.info: ledger.texi
|
||||||
g++ $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $<
|
g++ $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f ledger *.o
|
rm -f ledger ledger.info *.o *~ .\#*
|
||||||
|
|
||||||
rebuild: clean deps all
|
rebuild: clean deps all
|
||||||
|
|
||||||
|
|
|
||||||
57
amount.cc
57
amount.cc
|
|
@ -1,20 +1,24 @@
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include <gmp.h> // GNU multi-precision library
|
|
||||||
|
|
||||||
#include "ledger.h"
|
#include "ledger.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <gmp.h> // GNU multi-precision library
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
#define MAX_PRECISION 10 // must be 2 or higher
|
#define MAX_PRECISION 10 // must be 2 or higher
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// The `amount' structure. Every transaction has an associated amount,
|
// The `amount' structure. Every transaction has an associated
|
||||||
// which is represented by this structure. `amount' uses the GNU
|
// amount, which is represented by this structure. `amount' uses the
|
||||||
// multi-precision library, allowing for arbitrarily large amounts.
|
// GNU multi-precision library, allowing for arbitrarily large
|
||||||
// Each amount is a quantity of commodity at a certain price; the
|
// amounts. Each amount is a quantity of a certain commodity, with
|
||||||
// default commodity is the US dollar, with a price of 1.00.
|
// 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
|
class gmp_amount : public amount
|
||||||
|
|
@ -57,20 +61,21 @@ class gmp_amount : public amount
|
||||||
}
|
}
|
||||||
virtual void set_value(const amount * val);
|
virtual void set_value(const amount * val);
|
||||||
|
|
||||||
virtual operator bool() const;
|
virtual bool is_zero() const;
|
||||||
|
|
||||||
virtual void negate() {
|
virtual void negate() {
|
||||||
mpz_ui_sub(quantity, 0, quantity);
|
mpz_ui_sub(quantity, 0, quantity);
|
||||||
}
|
}
|
||||||
virtual void credit(const amount * other);
|
virtual void credit(const amount * other);
|
||||||
|
|
||||||
virtual void parse(const char * num);
|
virtual void parse(const std::string& num);
|
||||||
virtual std::string as_str(bool full_prec) const;
|
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();
|
gmp_amount * a = new gmp_amount();
|
||||||
a->parse(value);
|
a->parse(value);
|
||||||
|
|
@ -277,7 +282,7 @@ void gmp_amount::set_value(const amount * val)
|
||||||
mpz_clear(addend);
|
mpz_clear(addend);
|
||||||
}
|
}
|
||||||
|
|
||||||
gmp_amount::operator bool() const
|
bool gmp_amount::is_zero() const
|
||||||
{
|
{
|
||||||
mpz_t copy;
|
mpz_t copy;
|
||||||
mpz_init_set(copy, quantity);
|
mpz_init_set(copy, quantity);
|
||||||
|
|
@ -286,7 +291,7 @@ gmp_amount::operator bool() const
|
||||||
round(copy, copy, quantity_comm->precision);
|
round(copy, copy, quantity_comm->precision);
|
||||||
bool zero = mpz_sgn(copy) == 0;
|
bool zero = mpz_sgn(copy) == 0;
|
||||||
mpz_clear(copy);
|
mpz_clear(copy);
|
||||||
return ! zero;
|
return zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string amount_to_str(const commodity * comm, const mpz_t val,
|
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();
|
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;
|
std::ostringstream s;
|
||||||
|
|
||||||
|
|
@ -430,8 +435,11 @@ std::string gmp_amount::as_str(bool full_prec) const
|
||||||
return s.str();
|
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, '/')) {
|
if (char * p = std::strchr(num, '/')) {
|
||||||
mpz_t numer;
|
mpz_t numer;
|
||||||
mpz_t val;
|
mpz_t val;
|
||||||
|
|
@ -559,7 +567,7 @@ static commodity * parse_amount(mpz_t out, const char * num,
|
||||||
return comm;
|
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
|
// Compile the regular expression used for parsing amounts
|
||||||
static pcre * re = NULL;
|
static pcre * re = NULL;
|
||||||
|
|
@ -576,17 +584,20 @@ void gmp_amount::parse(const char * num)
|
||||||
int ovector[60];
|
int ovector[60];
|
||||||
int matched;
|
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) {
|
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 the following succeeded, then we have a price
|
||||||
if (ovector[8 * 2] >= 0) {
|
if (ovector[8 * 2] >= 0) {
|
||||||
priced = true;
|
priced = true;
|
||||||
price_comm = parse_amount(price, num, matched, ovector, 9);
|
price_comm = parse_amount(price, number.c_str(), matched,
|
||||||
|
ovector, 9);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "Failed to parse amount: " << num << std::endl;
|
std::cerr << "Failed to parse amount: " << number << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
92
balance.cc
92
balance.cc
|
|
@ -16,51 +16,25 @@ static bool show_empty;
|
||||||
static bool no_subtotals;
|
static bool no_subtotals;
|
||||||
static bool full_names;
|
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,
|
static void display_total(std::ostream& out, totals& balance,
|
||||||
account * acct, bool top_level,
|
account * acct, bool top_level)
|
||||||
const std::list<mask>& regexps)
|
|
||||||
{
|
{
|
||||||
bool displayed = false;
|
bool displayed = false;
|
||||||
|
|
||||||
if (acct->checked == 1 && (show_empty || acct->balance)) {
|
if (acct->checked == 1 &&
|
||||||
|
(show_empty || ! acct->balance.is_zero())) {
|
||||||
displayed = true;
|
displayed = true;
|
||||||
|
|
||||||
out << acct->balance;
|
acct->balance.print(out, 20);
|
||||||
if (! no_subtotals && top_level)
|
if (! no_subtotals && top_level)
|
||||||
balance.credit(acct->balance);
|
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)
|
for (const account * a = acct; a; a = a->parent)
|
||||||
out << " ";
|
out << " ";
|
||||||
out << acct->name << std::endl;
|
out << acct->name << std::endl;
|
||||||
} else {
|
} 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();
|
for (accounts_iterator i = acct->children.begin();
|
||||||
i != acct->children.end();
|
i != acct->children.end();
|
||||||
i++)
|
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
|
// 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_children = false;
|
||||||
show_empty = false;
|
show_empty = false;
|
||||||
|
|
@ -120,22 +95,33 @@ void report_balances(int argc, char **argv, std::ostream& out)
|
||||||
acct;
|
acct;
|
||||||
acct = no_subtotals ? NULL : acct->parent) {
|
acct = no_subtotals ? NULL : acct->parent) {
|
||||||
if (acct->checked == 0) {
|
if (acct->checked == 0) {
|
||||||
bool true_match = false;
|
if (regexps.empty()) {
|
||||||
if (! (regexps.empty() ||
|
if (! (show_children || ! acct->parent))
|
||||||
account_matches(acct, regexps, &true_match)))
|
acct->checked = 2;
|
||||||
acct->checked = 2;
|
else
|
||||||
else if (! (true_match || show_children || ! acct->parent))
|
acct->checked = 1;
|
||||||
acct->checked = 3;
|
}
|
||||||
else
|
else {
|
||||||
acct->checked = 1;
|
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)
|
if (acct->checked == 1)
|
||||||
break;
|
acct->balance.credit((*x)->cost->street());
|
||||||
else if (acct->checked == 3)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
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();
|
for (accounts_iterator i = main_ledger.accounts.begin();
|
||||||
i != main_ledger.accounts.end();
|
i != main_ledger.accounts.end();
|
||||||
i++)
|
i++)
|
||||||
display_total(out, balance, (*i).second, true, regexps);
|
display_total(out, balance, (*i).second, true);
|
||||||
|
|
||||||
// Print the total of all the balances shown
|
// Print the total of all the balances shown
|
||||||
|
|
||||||
if (! no_subtotals && balance)
|
if (! no_subtotals && ! balance.is_zero()) {
|
||||||
out << "--------------------" << std::endl
|
out << "--------------------" << std::endl;
|
||||||
<< balance << std::endl;
|
balance.print(out, 20);
|
||||||
|
out << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ledger
|
} // namespace ledger
|
||||||
|
|
|
||||||
16
equity.cc
16
equity.cc
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
static void equity_entry(std::ostream& out, account * acct,
|
static void equity_entry(account * acct, regexps_t& regexps,
|
||||||
const std::list<mask>& regexps)
|
std::ostream& out)
|
||||||
{
|
{
|
||||||
if (acct->balance &&
|
if (! acct->balance.is_zero() &&
|
||||||
(regexps.empty() || matches(regexps, acct->as_str()))) {
|
(regexps.empty() || matches(regexps, acct->as_str()))) {
|
||||||
entry opening;
|
entry opening;
|
||||||
|
|
||||||
|
|
@ -17,7 +17,8 @@ static void equity_entry(std::ostream& out, account * acct,
|
||||||
for (totals::const_iterator i = acct->balance.amounts.begin();
|
for (totals::const_iterator i = acct->balance.amounts.begin();
|
||||||
i != acct->balance.amounts.end();
|
i != acct->balance.amounts.end();
|
||||||
i++) {
|
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;
|
continue;
|
||||||
|
|
||||||
xact = new transaction();
|
xact = new transaction();
|
||||||
|
|
@ -40,7 +41,7 @@ static void equity_entry(std::ostream& out, account * acct,
|
||||||
for (accounts_iterator i = acct->children.begin();
|
for (accounts_iterator i = acct->children.begin();
|
||||||
i != acct->children.end();
|
i != acct->children.end();
|
||||||
i++)
|
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.
|
// 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;
|
optind = 1;
|
||||||
|
|
||||||
|
|
@ -67,7 +69,7 @@ void equity_ledger(int argc, char **argv, std::ostream& out)
|
||||||
for (accounts_iterator i = main_ledger.accounts.begin();
|
for (accounts_iterator i = main_ledger.accounts.begin();
|
||||||
i != main_ledger.accounts.end();
|
i != main_ledger.accounts.end();
|
||||||
i++)
|
i++)
|
||||||
equity_entry(out, (*i).second, regexps);
|
equity_entry((*i).second, regexps, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ledger
|
} // namespace ledger
|
||||||
|
|
|
||||||
120
ledger.cc
120
ledger.cc
|
|
@ -34,9 +34,8 @@ void entry::print(std::ostream& out, bool shortcut) const
|
||||||
x != xacts.end();
|
x != xacts.end();
|
||||||
x++) {
|
x++) {
|
||||||
#ifdef HUQUQULLAH
|
#ifdef HUQUQULLAH
|
||||||
if ((*x)->acct->exempt_or_necessary &&
|
if ((*x)->exempt_or_necessary ||
|
||||||
(! shortcut || ! ledger::matches(main_ledger.huquq_categories,
|
(! shortcut && (*x)->acct->exempt_or_necessary))
|
||||||
(*x)->acct->as_str())))
|
|
||||||
out << " !";
|
out << " !";
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -69,12 +68,12 @@ bool entry::validate(bool show_unaccounted) const
|
||||||
if ((*x)->cost)
|
if ((*x)->cost)
|
||||||
balance.credit((*x)->cost->value());
|
balance.credit((*x)->cost->value());
|
||||||
|
|
||||||
if (show_unaccounted && balance) {
|
if (show_unaccounted && ! balance.is_zero()) {
|
||||||
std::cerr << "Unaccounted-for balances are:" << std::endl;
|
std::cerr << "Unaccounted-for balances are:" << std::endl;
|
||||||
balance.print(std::cerr, 20);
|
balance.print(std::cerr, 20);
|
||||||
std::cerr << std::endl << std::endl;
|
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
|
bool entry::matches(const std::list<mask>& regexps) const
|
||||||
|
|
@ -117,33 +116,37 @@ void totals::credit(const totals& other)
|
||||||
credit((*i).second);
|
credit((*i).second);
|
||||||
}
|
}
|
||||||
|
|
||||||
totals::operator bool() const
|
bool totals::is_zero() const
|
||||||
{
|
{
|
||||||
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
|
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
|
||||||
if (*((*i).second))
|
if (! (*i).second->is_zero())
|
||||||
return true;
|
return false;
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void totals::print(std::ostream& out, int width) const
|
void totals::print(std::ostream& out, int width) const
|
||||||
{
|
{
|
||||||
bool first = true;
|
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);
|
for (const_iterator i = amounts.begin(); i != amounts.end(); i++) {
|
||||||
out << std::right << (*i).second->as_str();
|
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.
|
// Print out the entire ledger that was read in, sorted by date.
|
||||||
// This can be used to "wash" ugly ledger files.
|
// 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;
|
bool use_shortcuts = true;
|
||||||
|
|
||||||
|
|
@ -174,37 +177,40 @@ void print_ledger(int argc, char *argv[], std::ostream& out)
|
||||||
(*i)->print(out, use_shortcuts);
|
(*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;
|
const char * p = pat.c_str();
|
||||||
|
if (*p == '-') {
|
||||||
char * pat = pattern;
|
|
||||||
if (*pat == '-') {
|
|
||||||
exclude = true;
|
exclude = true;
|
||||||
pat++;
|
p++;
|
||||||
while (std::isspace(*pat))
|
while (std::isspace(*p))
|
||||||
pat++;
|
p++;
|
||||||
}
|
}
|
||||||
else if (*pat == '+') {
|
else if (*p == '+') {
|
||||||
pat++;
|
p++;
|
||||||
while (std::isspace(*pat))
|
while (std::isspace(*p))
|
||||||
pat++;
|
p++;
|
||||||
}
|
}
|
||||||
|
pattern = p;
|
||||||
|
|
||||||
const char *error;
|
const char *error;
|
||||||
int erroffset;
|
int erroffset;
|
||||||
pcre * re = pcre_compile(pat, PCRE_CASELESS, &error, &erroffset, NULL);
|
regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
|
||||||
if (! re)
|
&error, &erroffset, NULL);
|
||||||
|
if (! regexp)
|
||||||
std::cerr << "Warning: Failed to compile regexp: " << pattern
|
std::cerr << "Warning: Failed to compile regexp: " << pattern
|
||||||
<< std::endl;
|
<< 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) {
|
regexps.push_back(mask(pattern));
|
||||||
std::ifstream file(path);
|
}
|
||||||
|
|
||||||
|
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()) {
|
while (! file.eof()) {
|
||||||
char buf[80];
|
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 matches(const regexps_t& regexps, const std::string& str,
|
||||||
bool * exclude)
|
bool * by_exclusion)
|
||||||
{
|
{
|
||||||
|
assert(! regexps.empty());
|
||||||
|
|
||||||
// If the first pattern is an exclude, we assume all patterns match
|
// 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
|
// if they don't match the exclude -- and by_exclusion will be set
|
||||||
// include, then only accounts matching the include will match.
|
// 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;
|
bool match = (*regexps.begin()).exclude;
|
||||||
|
if (match && by_exclusion)
|
||||||
|
*by_exclusion = true;
|
||||||
|
|
||||||
for (std::list<mask>::const_iterator r = regexps.begin();
|
for (std::list<mask>::const_iterator r = regexps.begin();
|
||||||
r != regexps.end();
|
r != regexps.end();
|
||||||
|
|
@ -230,8 +242,8 @@ bool matches(const std::list<mask>& regexps, const std::string& str,
|
||||||
int ovec[3];
|
int ovec[3];
|
||||||
if (pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
|
if (pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
|
||||||
0, 0, ovec, 3) >= 0) {
|
0, 0, ovec, 3) >= 0) {
|
||||||
if (exclude)
|
if (by_exclusion)
|
||||||
*exclude = (*r).exclude;
|
*by_exclusion = (*r).exclude;
|
||||||
match = ! (*r).exclude;
|
match = ! (*r).exclude;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -261,12 +273,12 @@ state::~state()
|
||||||
|
|
||||||
#endif // DO_CLEANUP
|
#endif // DO_CLEANUP
|
||||||
|
|
||||||
void state::record_price(const char * setting)
|
void state::record_price(const std::string& setting)
|
||||||
{
|
{
|
||||||
char buf[128];
|
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 * c = buf;
|
||||||
char * p = std::strchr(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);
|
accounts_iterator i = accounts_cache.find(name);
|
||||||
if (i != accounts_cache.end())
|
if (i != accounts_cache.end())
|
||||||
return (*i).second;
|
return (*i).second;
|
||||||
|
|
||||||
char * buf = new char[std::strlen(name) + 1];
|
char * buf = new char[name.length() + 1];
|
||||||
std::strcpy(buf, name);
|
std::strcpy(buf, name.c_str());
|
||||||
|
|
||||||
account * current = NULL;
|
account * current = NULL;
|
||||||
for (char * tok = std::strtok(buf, ":");
|
for (char * tok = std::strtok(buf, ":");
|
||||||
|
|
@ -294,8 +306,10 @@ account * state::find_account(const char * name, bool create)
|
||||||
if (! current) {
|
if (! current) {
|
||||||
accounts_iterator i = accounts.find(tok);
|
accounts_iterator i = accounts.find(tok);
|
||||||
if (i == accounts.end()) {
|
if (i == accounts.end()) {
|
||||||
if (! create)
|
if (! create) {
|
||||||
|
delete[] buf;
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
current = new account(tok);
|
current = new account(tok);
|
||||||
accounts.insert(accounts_entry(tok, current));
|
accounts.insert(accounts_entry(tok, current));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -304,8 +318,10 @@ account * state::find_account(const char * name, bool create)
|
||||||
} else {
|
} else {
|
||||||
accounts_iterator i = current->children.find(tok);
|
accounts_iterator i = current->children.find(tok);
|
||||||
if (i == current->children.end()) {
|
if (i == current->children.end()) {
|
||||||
if (! create)
|
if (! create) {
|
||||||
|
delete[] buf;
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
current = new account(tok, current);
|
current = new account(tok, current);
|
||||||
current->parent->children.insert(accounts_entry(tok, current));
|
current->parent->children.insert(accounts_entry(tok, current));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
137
ledger.h
137
ledger.h
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef _LEDGER_H
|
#ifndef _LEDGER_H
|
||||||
#define _LEDGER_H "$Revision: 1.16 $"
|
#define _LEDGER_H "$Revision: 1.17 $"
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
|
|
@ -26,70 +26,6 @@
|
||||||
|
|
||||||
namespace ledger {
|
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
|
struct commodity
|
||||||
{
|
{
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
@ -127,9 +63,9 @@ class amount
|
||||||
virtual bool has_price() const = 0;
|
virtual bool has_price() const = 0;
|
||||||
virtual void set_value(const amount * pr) = 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
|
// Assignment
|
||||||
|
|
||||||
|
|
@ -138,27 +74,28 @@ class amount
|
||||||
|
|
||||||
// String conversion routines
|
// String conversion routines
|
||||||
|
|
||||||
virtual void parse(const char * num) = 0;
|
virtual void parse(const std::string& num) = 0;
|
||||||
virtual std::string as_str(bool full_prec = false) const = 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);
|
const amount * cost = NULL);
|
||||||
|
|
||||||
struct mask
|
struct mask
|
||||||
{
|
{
|
||||||
bool exclude;
|
bool exclude;
|
||||||
pcre * regexp;
|
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);
|
void record_regexp(const std::string& pattern, regexps_t& regexps);
|
||||||
extern void read_regexps(const char * path, std::list<mask>& regexps);
|
void read_regexps(const std::string& path, regexps_t& regexps);
|
||||||
extern bool matches(const std::list<mask>& regexps,
|
bool matches(const regexps_t& regexps, const std::string& str,
|
||||||
const std::string& str, bool * exclude = NULL);
|
bool * by_exclusion = NULL);
|
||||||
|
|
||||||
|
|
||||||
struct account;
|
struct account;
|
||||||
|
|
@ -187,6 +124,7 @@ struct transaction
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct entry
|
struct entry
|
||||||
{
|
{
|
||||||
std::time_t date;
|
std::time_t date;
|
||||||
|
|
@ -249,7 +187,7 @@ struct totals
|
||||||
}
|
}
|
||||||
void credit(const totals& other);
|
void credit(const totals& other);
|
||||||
|
|
||||||
operator bool() const;
|
bool is_zero() const;
|
||||||
|
|
||||||
void print(std::ostream& out, int width) 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 std::map<const std::string, account *> accounts_t;
|
||||||
typedef accounts_t::iterator accounts_iterator;
|
typedef accounts_t::iterator accounts_iterator;
|
||||||
|
|
@ -276,10 +207,14 @@ struct account
|
||||||
account * parent;
|
account * parent;
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
|
#ifdef READ_GNUCASH
|
||||||
commodity * comm; // default commodity for this account
|
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
|
#ifdef HUQUQULLAH
|
||||||
bool exempt_or_necessary;
|
bool exempt_or_necessary;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -301,25 +236,21 @@ struct account
|
||||||
const std::string as_str() const {
|
const std::string as_str() const {
|
||||||
if (! parent)
|
if (! parent)
|
||||||
return name;
|
return name;
|
||||||
else
|
else if (full_name.empty())
|
||||||
return parent->as_str() + ":" + name;
|
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
|
struct state
|
||||||
{
|
{
|
||||||
commodities_t commodities;
|
commodities_t commodities;
|
||||||
accounts_t accounts;
|
accounts_t accounts;
|
||||||
accounts_t accounts_cache; // maps full names to accounts
|
accounts_t accounts_cache; // maps full names to accounts
|
||||||
entries_t entries;
|
entries_t entries;
|
||||||
totals prices;
|
totals prices;
|
||||||
|
|
||||||
#ifdef HUQUQULLAH
|
#ifdef HUQUQULLAH
|
||||||
bool compute_huquq;
|
bool compute_huquq;
|
||||||
|
|
@ -336,8 +267,8 @@ struct state
|
||||||
~state();
|
~state();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void record_price(const char * setting);
|
void record_price(const std::string& setting);
|
||||||
account * find_account(const char * name, bool create = true);
|
account * find_account(const std::string& name, bool create = true);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern state main_ledger;
|
extern state main_ledger;
|
||||||
|
|
|
||||||
218
ledger.texi
218
ledger.texi
|
|
@ -1,5 +1,5 @@
|
||||||
\input texinfo @c -*-texinfo-*-
|
\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
|
@comment %**start of header
|
||||||
|
|
||||||
@setfilename ledger.info
|
@setfilename ledger.info
|
||||||
|
|
@ -24,17 +24,24 @@
|
||||||
@contents
|
@contents
|
||||||
|
|
||||||
@ifnottex
|
@ifnottex
|
||||||
@node Top
|
@node Top, Introduction, (dir), (dir)
|
||||||
@top Ledger Accouting Tool
|
@top Ledger Accouting Tool
|
||||||
|
|
||||||
@c @insertcopying
|
@c @insertcopying
|
||||||
@end ifnottex
|
@end ifnottex
|
||||||
|
|
||||||
|
@menu
|
||||||
|
* Introduction::
|
||||||
|
* Keeping a ledger::
|
||||||
|
* Computing Huqúqu'lláh::
|
||||||
|
@end menu
|
||||||
|
|
||||||
|
@node Introduction, Keeping a ledger, Top, Top
|
||||||
@chapter Introduction
|
@chapter Introduction
|
||||||
|
|
||||||
@code{ledger} is an accouting tool that has the chutzpah to exist. It
|
@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
|
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.
|
father's CRT.
|
||||||
|
|
||||||
What it does do is provide a double-entry accouting ledger with all of
|
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
|
and using the @code{ledger} tool to provide you with information
|
||||||
summaries derived from your ledger's entries.
|
summaries derived from your ledger's entries.
|
||||||
|
|
||||||
|
@node Keeping a ledger, Computing Huqúqu'lláh, Introduction, Top
|
||||||
@chapter Keeping a ledger
|
@chapter Keeping a ledger
|
||||||
|
|
||||||
The most important part of accounting is keeping a good ledger. If
|
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
|
For this entry, @code{ledger} will figure out that $-23.00 must come
|
||||||
from ``Assets:Checking'' in order to balance the entry.
|
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
|
@code{ledger} makes no assumptions about the commodities you use; it
|
||||||
only requires that you specify a commodity. The commodity may be any
|
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
|
This is a roundabout way of reporting AAPL shares in their Deutsch
|
||||||
Mark equivalent.
|
Mark equivalent.
|
||||||
|
|
||||||
|
@node Accounts and Inventories, Understanding Equity, Commodities and Currencies, Keeping a ledger
|
||||||
@section Accounts and Inventories
|
@section Accounts and Inventories
|
||||||
|
|
||||||
Since @code{ledger}'s accounts and commodity system is so flexible,
|
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,
|
Now you've turned 2 steaks into 15 gold, courtesy of your customer,
|
||||||
Sturm Brightblade.
|
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
|
@chapter Computing Huqúqu'lláh
|
||||||
|
|
||||||
As a Bahá'í, I need to compute Huqúqu'lláh on some of my assets. The
|
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:
|
ledger data, type:
|
||||||
|
|
||||||
@example
|
@example
|
||||||
/home/johnw $ ledger -f ledger.dat balance huquq
|
/home/johnw $ ledger -f ledger.dat balance ^huquq
|
||||||
@end example
|
@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
|
@bye
|
||||||
|
|
|
||||||
32
main.cc
32
main.cc
|
|
@ -8,10 +8,14 @@ namespace ledger {
|
||||||
extern bool parse_gnucash(std::istream& in, bool compute_balances);
|
extern bool parse_gnucash(std::istream& in, bool compute_balances);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern void report_balances(int argc, char **argv, std::ostream& out);
|
extern void report_balances(int argc, char ** argv, regexps_t& regexps,
|
||||||
extern void print_register(int argc, char **argv, std::ostream& out);
|
std::ostream& out);
|
||||||
extern void print_ledger(int argc, char *argv[], std::ostream& out);
|
extern void print_register(int argc, char ** argv, regexps_t& regexps,
|
||||||
extern void equity_ledger(int argc, char **argv, std::ostream& out);
|
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 show_cleared;
|
||||||
bool get_quotes;
|
bool get_quotes;
|
||||||
|
|
@ -75,7 +79,7 @@ static const char *formats[] = {
|
||||||
NULL
|
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;
|
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++) {
|
for (const char ** f = formats; *f; f++) {
|
||||||
memset(&when, INT_MAX, sizeof(struct std::tm));
|
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_hour = 0;
|
||||||
when.tm_min = 0;
|
when.tm_min = 0;
|
||||||
when.tm_sec = 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.
|
// 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;
|
std::istream * file = NULL;
|
||||||
|
|
||||||
|
regexps_t regexps;
|
||||||
|
|
||||||
#ifdef HUQUQULLAH
|
#ifdef HUQUQULLAH
|
||||||
bool compute_huquq = true;
|
bool compute_huquq = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -126,6 +130,8 @@ int main(int argc, char *argv[])
|
||||||
have_ending = false;
|
have_ending = false;
|
||||||
show_cleared = false;
|
show_cleared = false;
|
||||||
|
|
||||||
|
// Parse the command-line options
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
while (-1 != (c = getopt(argc, argv, "+b:e:d:cChHwf:i:p:Pv"))) {
|
while (-1 != (c = getopt(argc, argv, "+b:e:d:cChHwf:i:p:Pv"))) {
|
||||||
switch (char(c)) {
|
switch (char(c)) {
|
||||||
|
|
@ -328,13 +334,13 @@ int main(int argc, char *argv[])
|
||||||
// Process the command
|
// Process the command
|
||||||
|
|
||||||
if (command == "balance")
|
if (command == "balance")
|
||||||
report_balances(argc - optind, &argv[optind], std::cout);
|
report_balances(argc - optind, &argv[optind], regexps, std::cout);
|
||||||
else if (command == "register")
|
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")
|
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")
|
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.
|
// main.cc ends here.
|
||||||
|
|
|
||||||
8
parse.cc
8
parse.cc
|
|
@ -114,7 +114,9 @@ static void finalize_entry(entry * curr, bool compute_balances)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HUQUQULLAH
|
#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;
|
continue;
|
||||||
|
|
||||||
// Reflect 19% of the exempt or necessary transaction in the
|
// Reflect 19% of the exempt or necessary transaction in the
|
||||||
|
|
@ -307,10 +309,6 @@ bool parse_ledger(std::istream& in, bool compute_balances)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
xact->acct = main_ledger.find_account(p);
|
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)
|
if (compute_balances && xact->cost)
|
||||||
xact->acct->balance.credit(xact->cost);
|
xact->acct->balance.credit(xact->cost);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ static std::string truncated(const std::string& str, int width)
|
||||||
// Register printing code
|
// 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;
|
optind = 1;
|
||||||
|
|
||||||
|
|
|
||||||
24
report
24
report
|
|
@ -1,20 +1,20 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
binary=ledger
|
|
||||||
LEDGER=$HOME/doc/finance/ledger.dat
|
|
||||||
|
|
||||||
line="$binary -f $ledger"
|
|
||||||
|
|
||||||
command=$1
|
command=$1
|
||||||
shift
|
shift
|
||||||
|
|
||||||
case "$command" in
|
case "$command" in
|
||||||
balance) $line "$@" balance -s -- -Equity -Income -Expenses -Retirement ;;
|
balance) ledger "$@" balance -- -Equity -Income -Expenses -Retirement ;;
|
||||||
worth) $line "$@" balance assets liabilities ;;
|
worth) ledger "$@" balance assets liabilities ;;
|
||||||
profit) $line "$@" balance income expense ;;
|
profit) ledger "$@" balance income expense ;;
|
||||||
spending) $line "$@" balance -F food movies gas tips \
|
spending) ledger "$@" balance -F food movies gas tips \
|
||||||
health supplies -insurance -vacation ;;
|
health supplies -insurance -vacation ;;
|
||||||
huquq) $line "$@" balance ^huquq ;;
|
monthly_spending)
|
||||||
gold) $line "$@" balance -G $1 ^huquq ;;
|
for i in jan feb mar apr may jun jul aug sep oct nov dec
|
||||||
equity) $line "$@" equity -- -^Income -^Expenses -^Equity ;;
|
do
|
||||||
|
echo $i:
|
||||||
|
$0 spending -d $i
|
||||||
|
done ;;
|
||||||
|
huquq) ledger "$@" balance -n ^huquq ;;
|
||||||
|
equity) ledger "$@" equity -- -^Income -^Expenses -^Equity ;;
|
||||||
esac
|
esac
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue