*** empty log message ***
This commit is contained in:
parent
c7e1cf34b3
commit
7ce12f8cfe
8 changed files with 184 additions and 150 deletions
2
Makefile
2
Makefile
|
|
@ -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
|
||||
|
||||
|
|
|
|||
49
amount.cc
49
amount.cc
|
|
@ -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
|
||||
|
|
|
|||
13
balance.cc
13
balance.cc
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
25
ledger.cc
25
ledger.cc
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
8
ledger.h
8
ledger.h
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
43
ledger.texi
43
ledger.texi
|
|
@ -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
22
main.cc
|
|
@ -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
172
parse.cc
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue