*** empty log message ***
This commit is contained in:
parent
3667f06594
commit
7bf86bc48a
8 changed files with 393 additions and 181 deletions
|
|
@ -1,5 +1,4 @@
|
|||
#include <sstream>
|
||||
#include <cassert>
|
||||
|
||||
#include <gmp.h> // GNU multi-precision library
|
||||
#include <pcre.h> // Perl regular expression library
|
||||
|
|
@ -337,9 +336,6 @@ amount& gmp_amount::operator=(const char * num)
|
|||
commodities_iterator item = commodities.find(symbol.c_str());
|
||||
if (item == commodities.end()) {
|
||||
quantity_comm = new commodity(symbol, prefix, separate, precision);
|
||||
std::pair<commodities_iterator, bool> insert_result =
|
||||
commodities.insert(commodities_entry(symbol, quantity_comm));
|
||||
assert(insert_result.second);
|
||||
} else {
|
||||
quantity_comm = (*item).second;
|
||||
|
||||
|
|
@ -396,9 +392,6 @@ amount& gmp_amount::operator=(const char * num)
|
|||
commodities_iterator item = commodities.find(symbol.c_str());
|
||||
if (item == commodities.end()) {
|
||||
price_comm = new commodity(symbol, prefix, separate, precision);
|
||||
std::pair<commodities_iterator, bool> insert_result =
|
||||
commodities.insert(commodities_entry(symbol, price_comm));
|
||||
assert(insert_result.second);
|
||||
} else {
|
||||
price_comm = (*item).second;
|
||||
|
||||
|
|
|
|||
198
balance.cc
198
balance.cc
|
|
@ -1,10 +1,7 @@
|
|||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "ledger.h"
|
||||
|
||||
#include <pcre.h> // Perl regular expression library
|
||||
|
||||
#include "ledger.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -12,10 +9,38 @@ namespace ledger {
|
|||
// Balance report.
|
||||
//
|
||||
|
||||
void report_balances(std::ostream& out, std::vector<entry *>& ledger,
|
||||
bool show_children, bool show_empty)
|
||||
static inline bool matches(const std::list<pcre *>& regexps,
|
||||
const std::string& str) {
|
||||
for (std::list<pcre *>::const_iterator r = regexps.begin();
|
||||
r != regexps.end();
|
||||
r++) {
|
||||
int ovec[3];
|
||||
if (pcre_exec(*r, NULL, str.c_str(), str.length(), 0, 0, ovec, 3) >= 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void report_balances(int argc, char *argv[], std::ostream& out)
|
||||
{
|
||||
#if 0
|
||||
bool show_current = false;
|
||||
bool show_cleared = false;
|
||||
bool show_children = false;
|
||||
bool show_empty = false;
|
||||
bool no_subtotals = false;
|
||||
|
||||
int c;
|
||||
while (-1 != (c = getopt(argc, argv, "cCsSn"))) {
|
||||
switch (char(c)) {
|
||||
case 'c': show_current = true; break;
|
||||
case 'C': show_cleared = true; break;
|
||||
case 's': show_children = true; break;
|
||||
case 'S': show_empty = true; break;
|
||||
case 'n': no_subtotals = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Compile the list of specified regular expressions, which can be
|
||||
// specified on the command line, or using an include/exclude file.
|
||||
|
||||
|
|
@ -26,97 +51,118 @@ void report_balances(std::ostream& out, std::vector<entry *>& ledger,
|
|||
int erroffset;
|
||||
pcre * re = pcre_compile(argv[optind], PCRE_CASELESS,
|
||||
&error, &erroffset, NULL);
|
||||
assert(re);
|
||||
regexps.push_back(re);
|
||||
if (! re)
|
||||
std::cerr << "Warning: Failed to compile regexp: " << argv[optind]
|
||||
<< std::endl;
|
||||
else
|
||||
regexps.push_back(re);
|
||||
}
|
||||
#endif
|
||||
|
||||
// The balance of all accounts must equal zero
|
||||
totals future_balance;
|
||||
totals current_balance;
|
||||
totals cleared_balance;
|
||||
// Walk through all of the ledger entries, computing the account
|
||||
// totals
|
||||
|
||||
std::map<account *, totals *> balances;
|
||||
|
||||
std::time_t now = std::time(NULL);
|
||||
|
||||
for (ledger_iterator i = ledger.begin(); i != ledger.end(); i++) {
|
||||
for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
|
||||
x != (*i)->xacts.end();
|
||||
x++) {
|
||||
account * acct = (*x)->acct;
|
||||
if (! regexps.empty() && ! matches(regexps, acct->name))
|
||||
continue;
|
||||
|
||||
while (acct) {
|
||||
totals * balance = NULL;
|
||||
|
||||
std::map<account *, totals *>::iterator t = balances.find(acct);
|
||||
if (t == balances.end()) {
|
||||
balance = new totals;
|
||||
balances.insert(std::pair<account *, totals *>(acct, balance));
|
||||
} else {
|
||||
balance = (*t).second;
|
||||
}
|
||||
|
||||
if (show_current) {
|
||||
if (difftime((*i)->date, now) < 0)
|
||||
balance->credit((*x)->cost);
|
||||
}
|
||||
else if (show_cleared) {
|
||||
if ((*i)->cleared)
|
||||
balance->credit((*x)->cost);
|
||||
}
|
||||
else {
|
||||
balance->credit((*x)->cost);
|
||||
}
|
||||
|
||||
if (no_subtotals)
|
||||
acct = NULL;
|
||||
else
|
||||
acct = acct->parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print out the balance report header
|
||||
|
||||
std::string which = "Future";
|
||||
if (show_current)
|
||||
which = "Current";
|
||||
else if (show_cleared)
|
||||
which = "Cleared";
|
||||
|
||||
std::cout.width(10);
|
||||
std::cout << std::right << "Future" << " ";
|
||||
std::cout.width(10);
|
||||
std::cout << std::right << "Current" << " ";
|
||||
std::cout.width(10);
|
||||
std::cout << std::right << "Cleared" << std::endl;
|
||||
std::cout << std::right << which << std::endl;
|
||||
|
||||
for (accounts_iterator i = accounts.begin();
|
||||
i != accounts.end();
|
||||
i++) {
|
||||
if (! show_empty && ! (*i).second->future)
|
||||
// Walk through the accounts, given the balance report for each
|
||||
|
||||
totals total_balance;
|
||||
|
||||
for (accounts_iterator i = accounts.begin(); i != accounts.end(); i++) {
|
||||
account * acct = (*i).second;
|
||||
|
||||
if (! regexps.empty() && ! matches(regexps, acct->name))
|
||||
continue;
|
||||
|
||||
int depth = 0;
|
||||
account * acct = (*i).second;
|
||||
while (acct->parent) {
|
||||
for (account * a = acct; a; a = a->parent)
|
||||
depth++;
|
||||
acct = acct->parent;
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (! regexps.empty()) {
|
||||
bool matches = false;
|
||||
for (std::list<pcre *>::iterator r = regexps.begin();
|
||||
r != regexps.end();
|
||||
r++) {
|
||||
int ovector[30];
|
||||
if (pcre_exec(*r, NULL, (*i).first.c_str(), (*i).first.length(),
|
||||
0, 0, ovector, 30) >= 0) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! matches)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (! show_children && depth) {
|
||||
if (! show_children && depth)
|
||||
continue;
|
||||
|
||||
totals * balance = balances[acct];
|
||||
|
||||
if (! show_empty && ! *balance)
|
||||
continue;
|
||||
}
|
||||
|
||||
std::cout.width(10);
|
||||
std::cout << (*i).second->future << " ";
|
||||
std::cout.width(10);
|
||||
std::cout << (*i).second->current << " ";
|
||||
std::cout.width(10);
|
||||
std::cout << (*i).second->cleared << " ";
|
||||
std::cout << *balance << " ";
|
||||
|
||||
total_balance.credit(*balance);
|
||||
|
||||
if (depth) {
|
||||
while (--depth >= 0)
|
||||
std::cout << " ";
|
||||
std::cout << (*i).second->name << std::endl;
|
||||
std::cout << acct->name << std::endl;
|
||||
} else {
|
||||
std::cout << (*i).first << std::endl;
|
||||
|
||||
#if 0
|
||||
if (regexps.empty()) {
|
||||
#endif
|
||||
future_balance.credit((*i).second->future);
|
||||
current_balance.credit((*i).second->current);
|
||||
cleared_balance.credit((*i).second->cleared);
|
||||
#if 0
|
||||
}
|
||||
#endif
|
||||
std::cout << *acct << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (regexps.empty()) {
|
||||
#endif
|
||||
std::cout.width(10);
|
||||
std::cout << std::right << future_balance << " ";
|
||||
std::cout.width(10);
|
||||
std::cout << std::right << current_balance << " ";
|
||||
std::cout.width(10);
|
||||
std::cout << std::right << cleared_balance << std::endl;
|
||||
#if 0
|
||||
// Print the total of all the balances shown
|
||||
|
||||
std::cout.width(10);
|
||||
std::cout << std::right << total_balance << std::endl;
|
||||
|
||||
// Free up temporary variables created on the heap
|
||||
|
||||
for (std::map<account *, totals *>::iterator i = balances.begin();
|
||||
i != balances.end();
|
||||
i++) {
|
||||
delete (*i).second;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
41
gnucash.cc
41
gnucash.cc
|
|
@ -1,14 +1,12 @@
|
|||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
|
||||
#include "ledger.h"
|
||||
|
||||
extern "C" {
|
||||
#include <xmlparse.h> // expat XML parser
|
||||
}
|
||||
|
||||
#include "ledger.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
static account * curr_account;
|
||||
|
|
@ -20,8 +18,6 @@ static amount * curr_value;
|
|||
static std::string curr_quant;
|
||||
static XML_Parser current_parser;
|
||||
|
||||
static std::vector<entry *> * current_ledger;
|
||||
|
||||
enum {
|
||||
NO_ACTION,
|
||||
ACCOUNT_NAME,
|
||||
|
|
@ -116,7 +112,7 @@ static void endElement(void *userData, const char *name)
|
|||
<< XML_GetCurrentLineNumber(current_parser) << std::endl;
|
||||
curr_entry->print(std::cerr);
|
||||
} else {
|
||||
current_ledger->push_back(curr_entry);
|
||||
ledger.push_back(curr_entry);
|
||||
}
|
||||
curr_entry = NULL;
|
||||
}
|
||||
|
|
@ -192,11 +188,18 @@ static void dataHandler(void *userData, const char *s, int len)
|
|||
|
||||
case XACT_ACCOUNT: {
|
||||
accounts_iterator i = accounts.find(std::string(s, len));
|
||||
assert(i != accounts.end());
|
||||
curr_entry->xacts.back()->acct = (*i).second;
|
||||
if (i == accounts.end()) {
|
||||
std::cerr << "Could not find account " << std::string(s, len)
|
||||
<< " at line " << XML_GetCurrentLineNumber(current_parser)
|
||||
<< std::endl;
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
transaction * xact = curr_entry->xacts.back();
|
||||
xact->acct = (*i).second;
|
||||
|
||||
std::string value = curr_quant + " " + (*i).second->comm->symbol;
|
||||
curr_entry->xacts.back()->cost = create_amount(value.c_str(), curr_value);
|
||||
xact->cost = create_amount(value.c_str(), curr_value);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -215,27 +218,25 @@ static void dataHandler(void *userData, const char *s, int len)
|
|||
}
|
||||
}
|
||||
|
||||
bool parse_gnucash(std::istream& in, std::vector<entry *>& ledger)
|
||||
bool parse_gnucash(std::istream& in)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
|
||||
XML_Parser parser = XML_ParserCreate(NULL);
|
||||
current_parser = parser;
|
||||
|
||||
//XML_SetUserData(parser, &depth);
|
||||
XML_SetElementHandler(parser, startElement, endElement);
|
||||
XML_SetCharacterDataHandler(parser, dataHandler);
|
||||
|
||||
current_ledger = &ledger;
|
||||
|
||||
curr_account = NULL;
|
||||
curr_entry = NULL;
|
||||
curr_comm = NULL;
|
||||
|
||||
action = NO_ACTION;
|
||||
|
||||
XML_Parser parser = XML_ParserCreate(NULL);
|
||||
current_parser = parser;
|
||||
|
||||
XML_SetElementHandler(parser, startElement, endElement);
|
||||
XML_SetCharacterDataHandler(parser, dataHandler);
|
||||
|
||||
while (! in.eof()) {
|
||||
in.getline(buf, BUFSIZ - 1);
|
||||
|
||||
if (! XML_Parse(parser, buf, std::strlen(buf), in.eof())) {
|
||||
std::cerr << XML_ErrorString(XML_GetErrorCode(parser))
|
||||
<< " at line " << XML_GetCurrentLineNumber(parser)
|
||||
|
|
|
|||
10
ledger.cc
10
ledger.cc
|
|
@ -1,13 +1,11 @@
|
|||
#include <vector>
|
||||
|
||||
#include "ledger.h"
|
||||
|
||||
namespace ledger {
|
||||
|
||||
commodities_t commodities;
|
||||
commodity * commodity_usd;
|
||||
|
||||
accounts_t accounts;
|
||||
accounts_t accounts;
|
||||
ledger_t ledger;
|
||||
|
||||
void entry::print(std::ostream& out) const
|
||||
{
|
||||
|
|
@ -105,10 +103,10 @@ amount * totals::value(const std::string& commodity)
|
|||
return total;
|
||||
}
|
||||
|
||||
// Print out the entire ledger that was read in, but now sorted.
|
||||
// Print out the entire ledger that was read in, sorted by date.
|
||||
// This can be used to "wash" ugly ledger files.
|
||||
|
||||
void print_ledger(std::ostream& out, std::vector<entry *>& ledger)
|
||||
void print_ledger(int argc, char *argv[], std::ostream& out)
|
||||
{
|
||||
// Sort the list of entries by date, then print them in order.
|
||||
|
||||
|
|
|
|||
46
ledger.h
46
ledger.h
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef _LEDGER_H
|
||||
#define _LEDGER_H "$Revision: 1.2 $"
|
||||
#define _LEDGER_H "$Revision: 1.3 $"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
@ -11,9 +11,11 @@
|
|||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <ctime>
|
||||
#include <cassert>
|
||||
|
||||
namespace ledger {
|
||||
|
||||
|
|
@ -47,7 +49,7 @@ namespace ledger {
|
|||
// commodity is converted into the first by computing which the price
|
||||
// must have been in order to balance the transaction. Example:
|
||||
//
|
||||
// 2004.06.18 c (BUY) Apple Computer
|
||||
// 2004.06.18 * (BUY) Apple Computer
|
||||
// Assets:Brokerage $-200.00
|
||||
// Assets:Brokerage 100 AAPL
|
||||
//
|
||||
|
|
@ -64,7 +66,7 @@ namespace ledger {
|
|||
// stock, and it will read this transaction as if it had been
|
||||
// written:
|
||||
//
|
||||
// 2004.06.18 c (BUY) Apple Computer
|
||||
// 2004.06.18 * (BUY) Apple Computer
|
||||
// Assets:Brokerage $-200
|
||||
// Assets:Brokerage 100 AAPL @ $2
|
||||
//
|
||||
|
|
@ -72,7 +74,7 @@ namespace ledger {
|
|||
// exchange for services rendered, use the regular single-commodity
|
||||
// form of transaction:
|
||||
//
|
||||
// 2004.07.11 c A kick-back for the broker
|
||||
// 2004.07.11 * A kick-back for the broker
|
||||
// Assets:Brokerage -10 AAPL
|
||||
// Expenses:Broker's Fees 10 AAPL
|
||||
//
|
||||
|
|
@ -92,8 +94,7 @@ struct commodity
|
|||
int precision;
|
||||
|
||||
commodity() : prefix(false), separate(true) {}
|
||||
commodity(const std::string& sym, bool pre, bool sep, int prec)
|
||||
: symbol(sym), prefix(pre), separate(sep), precision(prec) {}
|
||||
commodity(const std::string& sym, bool pre, bool sep, int prec);
|
||||
};
|
||||
|
||||
typedef std::map<const std::string, commodity *> commodities_t;
|
||||
|
|
@ -103,6 +104,15 @@ typedef std::pair<const std::string, commodity *> commodities_entry;
|
|||
extern commodities_t commodities;
|
||||
extern commodity * commodity_usd;
|
||||
|
||||
inline commodity::commodity(const std::string& sym,
|
||||
bool pre, bool sep, int prec)
|
||||
: symbol(sym), prefix(pre), separate(sep), precision(prec) {
|
||||
std::pair<commodities_iterator, bool> result =
|
||||
commodities.insert(commodities_entry(sym, this));
|
||||
assert(result.second);
|
||||
}
|
||||
|
||||
|
||||
class amount
|
||||
{
|
||||
public:
|
||||
|
|
@ -181,6 +191,12 @@ struct cmp_entry_date {
|
|||
}
|
||||
};
|
||||
|
||||
typedef std::vector<entry *> ledger_t;
|
||||
typedef ledger_t::iterator ledger_iterator;
|
||||
|
||||
extern ledger_t ledger;
|
||||
|
||||
|
||||
class totals
|
||||
{
|
||||
typedef std::map<const std::string, amount *> map_t;
|
||||
|
|
@ -217,6 +233,7 @@ operator<<(std::basic_ostream<char, Traits>& out, const totals& t) {
|
|||
return out;
|
||||
}
|
||||
|
||||
|
||||
struct account
|
||||
{
|
||||
std::string name;
|
||||
|
|
@ -231,26 +248,9 @@ struct account
|
|||
|
||||
map children;
|
||||
|
||||
// Balance totals, by commodity
|
||||
totals future;
|
||||
totals current;
|
||||
totals cleared;
|
||||
|
||||
account(const std::string& _name, struct account * _parent = NULL)
|
||||
: name(_name), parent(_parent) {}
|
||||
|
||||
void credit(const entry * ent, const amount * amt) {
|
||||
for (account * acct = this; acct; acct = acct->parent) {
|
||||
acct->future.credit(amt);
|
||||
|
||||
if (difftime(ent->date, std::time(NULL)) < 0)
|
||||
acct->current.credit(amt);
|
||||
|
||||
if (ent->cleared)
|
||||
acct->cleared.credit(amt);
|
||||
}
|
||||
}
|
||||
|
||||
operator std::string() const {
|
||||
if (! parent)
|
||||
return name;
|
||||
|
|
|
|||
186
ledger.texi
Normal file
186
ledger.texi
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
\input texinfo @c -*-texinfo-*-
|
||||
@comment $Id: ledger.texi,v 1.1 2003/09/30 00:09:43 johnw Exp $
|
||||
@comment %**start of header
|
||||
|
||||
@setfilename ledger.info
|
||||
|
||||
@settitle Ledger Accouting Tool
|
||||
@syncodeindex pg cp
|
||||
@comment %**end of header
|
||||
|
||||
@dircategory Ledger Accouting Tool
|
||||
@direntry
|
||||
* ledger: (ledger)The Ledger Accouting Tool.
|
||||
@end direntry
|
||||
|
||||
@titlepage
|
||||
@title Ledger Accouting Tool
|
||||
@author John Wiegley <@email{johnw@@newartisans.com}>
|
||||
@page
|
||||
@vskip 0pt plus 1filll
|
||||
@c @insertcopying
|
||||
@end titlepage
|
||||
|
||||
@contents
|
||||
|
||||
@ifnottex
|
||||
@node Top
|
||||
@top Ledger Accouting Tool
|
||||
|
||||
@c @insertcopying
|
||||
@end ifnottex
|
||||
|
||||
@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
|
||||
father's CRT.
|
||||
|
||||
What it does do is provide a double-entry accouting ledger with all of
|
||||
the flexibility and muscle of its modern day cousins---without any of
|
||||
the fat. Think of it as the bran muffin of accouting tools.
|
||||
|
||||
To begin with, you need to start keeping a ledger. This is the basis
|
||||
of all accouting, and if you don't know how to do it, now is the time
|
||||
to learn. The little booklet that comes with your checkbook is a
|
||||
ledger, so we'll describe double-entry accouting in terms of that.
|
||||
|
||||
A checkbook ledger records debits (subtractions, or withdrawals) and
|
||||
credits (additions, or deposits) with reference to a single account:
|
||||
your checking account. Where the money comes from, and where it goes
|
||||
to, are simply described in the memo field where you write the person
|
||||
or the company's name. The ultimate aim of keeping a checkbook ledger
|
||||
is so you know how much money is available to spend at all times.
|
||||
That is really the aim of all ledgers.
|
||||
|
||||
What computers add is the ability to walk through all of those
|
||||
transactions and tell you things about your spending habits; let you
|
||||
devise budgets to get control over your spending; squirrel away money
|
||||
into virtual savings account without having to physically move the
|
||||
money around; etc. As you keep your checkbook ledger, you are
|
||||
recording a lot of information about your life and your habits, and
|
||||
sometimes that information can tell you things you aren't even aware
|
||||
of. That is the aim of all good accouting tools.
|
||||
|
||||
The next step up from a checkbook ledger is a ledger that covers all
|
||||
of your accounts, not just your checking account. In this ledger, you
|
||||
write not only who the money goes to---in the case of a debit---but
|
||||
where the money is coming from. In the checkbook ledger, its assumed
|
||||
that all of the money is coming from your checking account. But in a
|
||||
general ledger, you have to write two-lines: The source and target.
|
||||
There must always be a debit from some account for any credit made to
|
||||
anyone else. This is what is meant by ``double-entry'' accouting.
|
||||
|
||||
For example, let's say you have a checking account and a brokerage
|
||||
account, and that you can write checks from both of them. Rather than
|
||||
keeping two checkbooks, you decide to use one ledger for both. Once
|
||||
you get the hang of this, you'll be ready to use one ledger for all of
|
||||
your accouting needs, which gets you to the point of this
|
||||
introduction.
|
||||
|
||||
So in your general ledger, you need to pay Pacific Bell Telephone for
|
||||
your monthly phone bill. The cost is $23.00. In a checkbook ledger,
|
||||
you would write out a line that credits your account with Pacific Bell
|
||||
by $23 as follows:
|
||||
|
||||
@example
|
||||
9/29 100 Pacific Bell $23.00 $77.00
|
||||
@end example
|
||||
|
||||
Very simple: You've written check #100 for $23 to Pacific Bell, which
|
||||
leaves your balance in checking at $77.
|
||||
|
||||
But in a general ledger, you need to say where the money is coming
|
||||
from. A general ledger entry would look like this:
|
||||
|
||||
@example
|
||||
9/29 100 Pacific Bell $23.00 $223.00
|
||||
Checking $-23.00 $77.00
|
||||
@end example
|
||||
|
||||
What does all of this mean? The first line shows a credit (or
|
||||
payment) to Pacific Bell to the tune of $23.00. Then, because there
|
||||
is no one ``balance'' in a general ledger, we've written in the total
|
||||
balance of your payments to the account ``Pacific Bell''. This was
|
||||
done by looking at the last entry for ``Pacific Bell'' in the general
|
||||
ledger, adding $23.00 to that amount, and writing in the total in the
|
||||
balance column.
|
||||
|
||||
Secondly, the money is coming from your ``Checking'' account, which
|
||||
means a debit (or withdrawal) of $23.00, which will leave the ending
|
||||
balance in your ``Checking'' account at $77.00.
|
||||
|
||||
The transaction itself must balance to $0: $23 goes to Pacific Bell,
|
||||
$23 comes from Checking: there is nothing left over to be accounted
|
||||
for. The money has in fact moved from one account to another. This
|
||||
is basis of double-entry accounting: That money never pops out of
|
||||
existence, it is always described as a transaction between
|
||||
accounts---as a flow from one place to another.
|
||||
|
||||
Keeping a general ledger is the same as keeping two separate ledgers:
|
||||
One for Pacific Bell and one for Checking. In that case, each time
|
||||
you write a credit into one, you write a corresponding debit into the
|
||||
other. This makes it much easier to write in the running balance,
|
||||
since you don't have to go looking back for the last time an account
|
||||
was referenced, but it also means having a lot of ledger books if you
|
||||
deal with multiple accounts.
|
||||
|
||||
Enter the beauty of a computerized accouting tool. The purpose of
|
||||
@code{ledger} is to make general ledger accouting simple by keeping
|
||||
track of the balances for you. Your only job is to enter credit/debit
|
||||
pairs and make sure they balance. If a transaction does not balance,
|
||||
@code{ledger} will display an error and ignore the
|
||||
transaction.@footnote{In some special cases, it will automatically
|
||||
balance the entry for you.}
|
||||
|
||||
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.
|
||||
|
||||
@chapter Keeping a ledger
|
||||
|
||||
The most important part of accounting is keeping a good ledger. If
|
||||
you have a good ledger, tools can be written to work whatever
|
||||
mathematically tricks you need to better understand your spending
|
||||
patterns. Without a good ledger, no tool, however smart, can help
|
||||
you.
|
||||
|
||||
The @code{ledger} program aims at making ledger entry as simple as
|
||||
possible. Since it is a command-line tool, it does not provide a user
|
||||
interface for keeping a ledger. If you like, you may use
|
||||
@code{gnucash} to maintain your ledger, in which case the
|
||||
@code{ledger} program will read @code{gnucash}'s data files directly.
|
||||
In that case, read the @code{gnucash} manual now, and skip to the next
|
||||
chapter.
|
||||
|
||||
If you are not using @code{gnucash}, but a text editor to maintain
|
||||
your ledger, read on. @code{ledger} has been designed to make data
|
||||
entry as simple as possible, by keeping the ledger format easy, and
|
||||
also by automagically determining as much information as possible
|
||||
based on the nature of your entries.
|
||||
|
||||
For example, you do not need to tell @code{ledger} about the accounts
|
||||
you use. Any time @code{ledger} sees a debit or a credit to an
|
||||
account it knows nothing about, it will create it. If you use a
|
||||
commodity that is new to @code{ledger}, it will create that commodity,
|
||||
and determine its display characteristics (placement of the symbol
|
||||
before or after the amount, display precision, etc) based on how you
|
||||
used the commodity in the transaction.
|
||||
|
||||
Here is the Pacific Bell example from above, given as a @code{ledger}
|
||||
transaction:
|
||||
|
||||
@example
|
||||
9/29 (100) Pacific Bell
|
||||
Expenses:Utilities:Telephone $23.00
|
||||
Assets:Checking $-23.00
|
||||
@end example
|
||||
|
||||
As you can see, it is very similar to what would be written on paper,
|
||||
minus the computed balance totals, and adding in account names that
|
||||
work better with @code{ledger}'s scheme of things.
|
||||
|
||||
@chapter Using @code{ledger}
|
||||
|
||||
@bye
|
||||
65
main.cc
65
main.cc
|
|
@ -1,44 +1,49 @@
|
|||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
#include <pcre.h> // Perl regular expression library
|
||||
|
||||
#include "ledger.h"
|
||||
|
||||
#include <pcre.h> // Perl regular expression library
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Command-line parser and top-level logic.
|
||||
//
|
||||
|
||||
namespace ledger {
|
||||
extern bool parse_ledger(std::istream& in, std::vector<entry *>& ledger);
|
||||
extern bool parse_gnucash(std::istream& in, std::vector<entry *>& ledger);
|
||||
extern void report_balances(std::ostream& out, std::vector<entry *>& ledger,
|
||||
bool show_children, bool show_empty);
|
||||
extern void print_ledger(std::ostream& out, std::vector<entry *>& ledger);
|
||||
extern bool parse_ledger(std::istream& in);
|
||||
extern bool parse_gnucash(std::istream& in);
|
||||
|
||||
extern void report_balances(int argc, char *argv[], std::ostream& out);
|
||||
extern void print_ledger(int argc, char *argv[], std::ostream& out);
|
||||
}
|
||||
|
||||
using namespace ledger;
|
||||
|
||||
void show_help(std::ostream& out)
|
||||
{
|
||||
out << "usage: ledger [options] DATA_FILE COMMAND [ARGS]"
|
||||
<< std::endl
|
||||
<< "options:" << std::endl
|
||||
<< " -s show sub-accounts in balance totals" << std::endl
|
||||
<< " -S show empty accounts in balance totals" << std::endl
|
||||
<< "commands:" << std::endl
|
||||
<< " balance show balance totals" << std::endl
|
||||
<< " print print all ledger entries" << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// Setup global defaults
|
||||
// Global defaults
|
||||
|
||||
commodity_usd = new commodity("$", true, false, 2);
|
||||
commodities.insert(commodities_entry("$", commodity_usd));
|
||||
commodities.insert(commodities_entry("USD", commodity_usd));
|
||||
|
||||
// Parse the command-line options
|
||||
|
||||
bool show_children = false;
|
||||
bool show_empty = false;
|
||||
|
||||
int c;
|
||||
while (-1 != (c = getopt(argc, argv, "sS"))) {
|
||||
while (-1 != (c = getopt(argc, argv, "+h"))) {
|
||||
switch (char(c)) {
|
||||
case 's': show_children = true; break;
|
||||
case 'S': show_empty = true; break;
|
||||
case 'h': show_help(std::cout); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,36 +56,28 @@ int main(int argc, char *argv[])
|
|||
<< "commands:" << std::endl
|
||||
<< " balance show balance totals" << std::endl
|
||||
<< " print print all ledger entries" << std::endl;
|
||||
std::exit(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Parse the ledger
|
||||
|
||||
std::ifstream file(argv[optind++]);
|
||||
std::vector<entry *> ledger;
|
||||
|
||||
char buf[256];
|
||||
file.get(buf, 255);
|
||||
char buf[32];
|
||||
file.get(buf, 31);
|
||||
file.seekg(0);
|
||||
|
||||
if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0)
|
||||
parse_gnucash(file, ledger);
|
||||
parse_gnucash(file);
|
||||
else
|
||||
parse_ledger(file, ledger);
|
||||
|
||||
// Read the command word
|
||||
|
||||
if (optind == argc) {
|
||||
std::cerr << "Command word missing" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string command = argv[optind++];
|
||||
parse_ledger(file);
|
||||
|
||||
// Process the command
|
||||
|
||||
const std::string command = argv[optind];
|
||||
|
||||
if (command == "balance")
|
||||
report_balances(std::cout, ledger, show_children, show_empty);
|
||||
report_balances(argc - optind, &argv[optind], std::cout);
|
||||
else if (command == "print")
|
||||
print_ledger(std::cout, ledger);
|
||||
print_ledger(argc - optind, &argv[optind], std::cout);
|
||||
}
|
||||
|
|
|
|||
21
parse.cc
21
parse.cc
|
|
@ -1,14 +1,11 @@
|
|||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <cctype>
|
||||
#include <cassert>
|
||||
|
||||
#include <pcre.h> // Perl regular expression library
|
||||
|
||||
#include "ledger.h"
|
||||
|
||||
#include <pcre.h> // Perl regular expression library
|
||||
|
||||
namespace ledger {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -37,8 +34,7 @@ char * next_element(char * buf, bool variable = false)
|
|||
|
||||
static int linenum = 0;
|
||||
|
||||
void finalize_entry(entry * curr, std::vector<entry *>& ledger)
|
||||
{
|
||||
inline void finalize_entry(entry * curr) {
|
||||
if (curr) {
|
||||
if (! curr->validate()) {
|
||||
std::cerr << "Failed to balance the following transaction, "
|
||||
|
|
@ -50,7 +46,7 @@ void finalize_entry(entry * curr, std::vector<entry *>& ledger)
|
|||
}
|
||||
}
|
||||
|
||||
bool parse_ledger(std::istream& in, std::vector<entry *>& ledger)
|
||||
bool parse_ledger(std::istream& in)
|
||||
{
|
||||
static std::time_t now = std::time(NULL);
|
||||
static struct std::tm * now_tm = std::localtime(&now);
|
||||
|
|
@ -94,7 +90,7 @@ bool parse_ledger(std::istream& in, std::vector<entry *>& ledger)
|
|||
}
|
||||
|
||||
if (curr)
|
||||
finalize_entry(curr, ledger);
|
||||
finalize_entry(curr);
|
||||
curr = new entry;
|
||||
|
||||
// Parse the date
|
||||
|
|
@ -165,11 +161,6 @@ bool parse_ledger(std::istream& in, std::vector<entry *>& ledger)
|
|||
current = (*i).second;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply transaction to account (and all parent accounts)
|
||||
|
||||
assert(current);
|
||||
current->credit(curr, xact->cost);
|
||||
}
|
||||
xact->acct = current;
|
||||
|
||||
|
|
@ -181,7 +172,7 @@ bool parse_ledger(std::istream& in, std::vector<entry *>& ledger)
|
|||
}
|
||||
|
||||
if (curr)
|
||||
finalize_entry(curr, ledger);
|
||||
finalize_entry(curr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue