*** empty log message ***

This commit is contained in:
John Wiegley 2003-10-01 21:55:40 +00:00
parent c7e1cf34b3
commit 7ce12f8cfe
8 changed files with 184 additions and 150 deletions

View file

@ -17,7 +17,7 @@ OBJS = $(patsubst %.cc,%.o,$(CODE))
CFLAGS = -Wall -ansi -pedantic
#DFLAGS = -O3 -fomit-frame-pointer
DFLAGS = -g # -O2 # -pg
DFLAGS = -g -O2 # -pg
INCS = -I/usr/include/xmltok
LIBS = -lgmpxx -lgmp -lpcre

View file

@ -59,13 +59,10 @@ class gmp_amount : public amount
virtual operator bool() const;
virtual void credit(const amount * other) {
*this += *other;
}
virtual void negate() {
mpz_ui_sub(quantity, 0, quantity);
}
virtual void operator+=(const amount& other);
virtual void credit(const amount * other);
virtual void parse(const char * num) {
*this = num;
@ -211,30 +208,32 @@ amount * gmp_amount::street() const
extern bool get_quotes;
for (int cycles = 0; cycles < 10; cycles++) {
totals::iterator pi = main_ledger.prices.amounts.find(amt->comm_symbol());
totals::iterator pi =
main_ledger.prices.amounts.find(amt->comm_symbol());
if (pi == main_ledger.prices.amounts.end()) {
if (get_quotes && amt->comm_symbol() != DEFAULT_COMMODITY) {
using namespace std;
using namespace std;
char buf[256];
buf[0] = '\0';
if (! get_quotes)
break;
if (FILE * fp = popen((std::string("getquote ") +
amt->comm_symbol()).c_str(), "r")) {
if (feof(fp) || ! fgets(buf , 255, fp)) {
fclose(fp);
break;
}
char buf[256];
buf[0] = '\0';
if (FILE * fp = popen((std::string("getquote ") +
amt->comm_symbol()).c_str(), "r")) {
if (feof(fp) || ! fgets(buf , 255, fp)) {
fclose(fp);
break;
}
fclose(fp);
}
if (buf[0]) {
char * p = strchr(buf, '\n');
if (p) *p = '\0';
if (buf[0]) {
char * p = strchr(buf, '\n');
if (p) *p = '\0';
main_ledger.record_price((amt->comm_symbol() + "=" + buf).c_str());
continue;
}
main_ledger.record_price((amt->comm_symbol() + "=" + buf).c_str());
continue;
}
break;
} else {
@ -598,11 +597,11 @@ amount& gmp_amount::operator=(const char * num)
return *this;
}
void gmp_amount::operator+=(const amount& _other)
void gmp_amount::credit(const amount * value)
{
const gmp_amount& other = dynamic_cast<const gmp_amount&>(_other);
assert(quantity_comm == other.quantity_comm);
mpz_add(quantity, quantity, other.quantity);
const gmp_amount * val = dynamic_cast<const gmp_amount *>(value);
assert(quantity_comm == val->quantity_comm);
mpz_add(quantity, quantity, val->quantity);
}
} // namespace ledger

View file

@ -86,23 +86,12 @@ void report_balances(int argc, char **argv, std::ostream& out)
optind = 1;
int c;
while (-1 != (c = getopt(argc, argv, "sSnFG:"))) {
while (-1 != (c = getopt(argc, argv, "sSnF"))) {
switch (char(c)) {
case 's': show_children = true; break;
case 'S': show_empty = true; break;
case 'n': no_subtotals = true; break;
case 'F': full_names = true; break;
#ifdef HUQUQULLAH
case 'G': {
double gold = std::atof(optarg);
gold = 1 / gold;
char buf[256];
std::sprintf(buf, DEFAULT_COMMODITY "=%f troy", gold);
main_ledger.record_price(buf);
break;
}
#endif
}
}

View file

@ -27,14 +27,20 @@ void entry::print(std::ostream& out, bool shortcut) const
if (shortcut &&
(xacts.size() != 2 ||
xacts.front()->cost->comm() != xacts.back()->cost->comm())) {
xacts.front()->cost->comm() != xacts.back()->cost->comm()))
shortcut = false;
}
for (std::list<transaction *>::const_iterator x = xacts.begin();
x != xacts.end();
x++) {
out << " ";
#ifdef HUQUQULLAH
if ((*x)->acct->exempt_or_necessary &&
(! shortcut || ! ledger::matches(main_ledger.huquq_categories,
(*x)->acct->as_str())))
out << " !";
else
#endif
out << " ";
out.width(30);
out << std::left << (*x)->acct->as_str();
@ -134,19 +140,6 @@ void totals::print(std::ostream& out, int width) const
}
}
amount * totals::value(const std::string& commodity) const
{
// Render all of the amounts into the given commodity. This
// requires known prices for each commodity.
amount * total = create_amount((commodity + " 0.00").c_str());
for (const_iterator i = amounts.begin(); i != amounts.end(); i++)
*total += *((*i).second);
return total;
}
// Print out the entire ledger that was read in, sorted by date.
// This can be used to "wash" ugly ledger files.

View file

@ -1,5 +1,5 @@
#ifndef _LEDGER_H
#define _LEDGER_H "$Revision: 1.13 $"
#define _LEDGER_H "$Revision: 1.14 $"
//////////////////////////////////////////////////////////////////////
//
@ -104,8 +104,8 @@ struct commodity
commodity() : prefix(false), separate(true),
thousands(false), european(false) {}
commodity(const std::string& sym, bool pre, bool sep,
bool thou, bool euro, int prec);
commodity(const std::string& sym, bool pre = false, bool sep = true,
bool thou = true, bool euro = false, int prec = 2);
};
typedef std::map<const std::string, commodity *> commodities_t;
@ -135,7 +135,6 @@ class amount
virtual void credit(const amount * other) = 0;
virtual void negate() = 0;
virtual void operator+=(const amount& other) = 0;
// String conversion routines
@ -263,7 +262,6 @@ struct totals
void print(std::ostream& out, int width) const;
// Returns an allocated entity
amount * value(const std::string& comm) const;
amount * sum(const std::string& comm) {
return amounts[comm];
}

View file

@ -1,5 +1,5 @@
\input texinfo @c -*-texinfo-*-
@comment $Id: ledger.texi,v 1.4 2003/10/01 04:42:13 johnw Exp $
@comment $Id: ledger.texi,v 1.5 2003/10/01 21:55:40 johnw Exp $
@comment %**start of header
@setfilename ledger.info
@ -287,6 +287,47 @@ Euro=DM 0.75
This is a roundabout way of reporting AAPL shares in their Deutsch
Mark equivalent.
@section Accounts and Inventories
Since @code{ledger}'s accounts and commodity system is so flexible,
you can have accounts that don't really exist, and use commodities
that no one else recognizes. For example, let's say you are buying
and selling various items in EverQuest, and want to keep track of them
using a ledger. Just add items of whatever quantity you wish into
your EverQuest account:
@example
9/29 Get some stuff at the Inn
Places:Black's Tavern -3 Apples
Places:Black's Tavern -5 Steaks
EverQuest:Inventory
@end example
Now your EverQuest:Inventory has 3 apples and 5 steaks in it. The
amounts are negative, because you are taking @emph{from} Black's
Tavern in order to credit your Inventory account. Note that you don't
have to use ``Places:Black's Tavern'' as the source account. You
could use ``EverQuest:System'' to represent the fact that you acquired
them online. The only purpose for choosing one kind of source account
over another is for generate more informative reports later on. The
more you know, the better analysis you can perform.
If you later sell some of these items to another player, the entry
would look like:
@example
10/2 Sturm Brightblade
EverQuest:Inventory -2 Steaks
EverQuest:Inventory 15 Gold
@end example
Now you've turned 2 steaks into 15 gold, courtesy of your customer,
Sturm Brightblade.
Note that if you're playing on a system where ``Gold'' is the standard
currency, you should use the @samp{-D} flag to tell @code{ledger} that
that is the default commodity.
@chapter Using @code{ledger}
@chapter Computing Huqúqu'lláh

22
main.cc
View file

@ -112,11 +112,6 @@ static bool parse_date(const char * date_str, std::time_t * result)
int main(int argc, char *argv[])
{
// Global defaults
commodity * usd = new commodity("$", true, false, true, false, 2);
main_ledger.commodities.insert(commodities_entry("USD", usd));
// Parse the command-line options
std::istream * file = NULL;
@ -129,7 +124,7 @@ int main(int argc, char *argv[])
show_cleared = false;
int c;
while (-1 != (c = getopt(argc, argv, "+b:e:d:cChHwf:i:p:P"))) {
while (-1 != (c = getopt(argc, argv, "+b:e:d:D:cChHwf:i:p:P"))) {
switch (char(c)) {
case 'b':
case 'e': {
@ -288,24 +283,9 @@ int main(int argc, char *argv[])
if (compute_huquq) {
main_ledger.compute_huquq = true;
main_ledger.huquq_commodity = new commodity("H", true, true,
true, false, 2);
// The allocation causes it to be inserted into the
// main_ledger.commodities mapping.
new commodity("mithqal", false, true, true, false, 1);
read_regexps(".huquq", main_ledger.huquq_categories);
main_ledger.record_price("H=" DEFAULT_COMMODITY "0.19");
bool save_use_warnings = use_warnings;
use_warnings = false;
main_ledger.record_price("troy=8.5410148523 mithqal");
use_warnings = save_use_warnings;
main_ledger.huquq = create_amount("H 1.00");
main_ledger.huquq_account = main_ledger.find_account("Huququ'llah");
main_ledger.huquq_expenses_account =
main_ledger.find_account("Expenses:Huququ'llah");

172
parse.cc
View file

@ -36,49 +36,119 @@ static void finalize_entry(entry * curr, bool compute_balances)
{
assert(curr);
// Certain shorcuts are allowed in the case of exactly two
// transactions.
// If there were no transactions, it's definitely an error!
if (! curr->xacts.empty() && curr->xacts.size() == 2) {
transaction * first = curr->xacts.front();
transaction * second = curr->xacts.back();
if (curr->xacts.empty()) {
std::cerr << "Error, line " << (linenum - 1)
<< ": Entry has no transactions!" << std::endl;
return;
}
// If one transaction gives no value at all, then its value is
// the inverse of the computed value of the other.
// Scan through and compute the total balance for the entry.
if (! first->cost && second->cost) {
first->cost = second->cost->value();
first->cost->negate();
totals balance;
if (compute_balances)
first->acct->balance.credit(first->cost);
}
else if (! second->cost && first->cost) {
second->cost = first->cost->value();
second->cost->negate();
for (std::list<transaction *>::iterator x = curr->xacts.begin();
x != curr->xacts.end();
x++)
if ((*x)->cost)
balance.credit((*x)->cost->value());
if (compute_balances)
second->acct->balance.credit(second->cost);
}
else if (first->cost && second->cost) {
// If one transaction is of a different commodity than the
// other, and it has no per-unit price, and its not of the
// default commodity, then determine its price by dividing the
// unit count into the total, to balance the transaction.
// If one transaction is of a different commodity than the others,
// and it has no per-unit price, determine its price by dividing
// the unit count into the value of the balance.
//
// NOTE: We don't do this for prefix-style or joined-symbol
// commodities. Also, do it for the last eligible commodity first,
// if it meets the criteria.
if (first->cost->comm() != second->cost->comm()) {
if (! second->cost->has_price() &&
second->cost->comm_symbol() != DEFAULT_COMMODITY) {
second->cost->set_value(first->cost);
}
else if (! first->cost->has_price() &&
first->cost->comm_symbol() != DEFAULT_COMMODITY) {
first->cost->set_value(second->cost);
if (! balance.amounts.empty() && balance.amounts.size() == 2) {
for (std::list<transaction *>::iterator x = curr->xacts.begin();
x != curr->xacts.end();
x++) {
if (! (*x)->cost->has_price() &&
! (*x)->cost->comm()->prefix &&
(*x)->cost->comm()->separate) {
for (totals::iterator i = balance.amounts.begin();
i != balance.amounts.end();
i++) {
if ((*i).second->comm() != (*x)->cost->comm()) {
(*x)->cost->set_value((*i).second);
break;
}
}
break;
}
}
}
// Walk through each of the transactions, fixing up any that we
// can, and performing any on-the-fly calculations.
bool empty_allowed = true;
for (std::list<transaction *>::iterator x = curr->xacts.begin();
x != curr->xacts.end();
x++) {
if (! (*x)->cost) {
if (empty_allowed && ! balance.amounts.empty() &&
balance.amounts.size() == 1) {
empty_allowed = false;
// If one transaction gives no value at all -- and all the
// rest are of the same commodity -- then its value is the
// inverse of the computed value of the others.
totals::iterator i = balance.amounts.begin();
(*x)->cost = (*i).second->value();
(*x)->cost->negate();
if (compute_balances)
(*x)->acct->balance.credit((*x)->cost);
} else {
std::cerr << "Error, line " << (linenum - 1)
<< ": Transaction entry is lacking an amount."
<< std::endl;
return;
}
}
#ifdef HUQUQULLAH
if (! main_ledger.compute_huquq || ! (*x)->exempt_or_necessary)
continue;
// Reflect 19% of the exempt or necessary transaction in the
// Huququ'llah account.
amount * divisor = create_amount("0.19");
amount * temp = (*x)->cost->value();
transaction * t = new transaction(main_ledger.huquq_account,
temp->value(divisor));
curr->xacts.push_back(t);
if (compute_balances)
t->acct->balance.credit(t->cost);
// Balance the above transaction by recording the inverse in
// Expenses:Huququ'llah.
t = new transaction(main_ledger.huquq_expenses_account,
temp->value(divisor));
t->cost->negate();
curr->xacts.push_back(t);
if (compute_balances)
t->acct->balance.credit(t->cost);
delete temp;
delete divisor;
#endif
}
// Compute the balances again, just to make sure it all comes out
// right (i.e., to zero for every commodity).
if (! curr->validate()) {
std::cerr << "Error, line " << (linenum - 1)
<< ": Failed to balance the following transaction:"
@ -88,43 +158,7 @@ static void finalize_entry(entry * curr, bool compute_balances)
return;
}
#ifdef HUQUQULLAH
if (main_ledger.compute_huquq) {
for (std::list<transaction *>::iterator x = curr->xacts.begin();
x != curr->xacts.end();
x++) {
if (! (*x)->exempt_or_necessary || ! (*x)->cost)
continue;
// Reflect the exempt or necessary transaction in the
// Huququ'llah account, using the H commodity, which is 19% of
// whichever DEFAULT_COMMODITY ledger was compiled with.
amount * temp = (*x)->cost->value();
transaction * t
= new transaction(main_ledger.huquq_account,
temp->value(main_ledger.huquq));
curr->xacts.push_back(t);
if (compute_balances)
t->acct->balance.credit(t->cost);
// Balance the above transaction by recording the inverse in
// Expenses:Huququ'llah.
t = new transaction(main_ledger.huquq_expenses_account,
temp->value(main_ledger.huquq));
t->cost->negate();
curr->xacts.push_back(t);
if (compute_balances)
t->acct->balance.credit(t->cost);
delete temp;
}
}
#endif
// If it's OK, add it to the general ledger's list of entries.
main_ledger.entries.push_back(curr);
}