summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2003-09-30 07:02:31 +0000
committerJohn Wiegley <johnw@newartisans.com>2003-09-30 07:02:31 +0000
commit5bd2401bc7cde0e01cd5b9d18cf077e255c4bd45 (patch)
treebcc3d57e446c3c7b9c91888bda2fa02d61423e96
parentff57781f1fc80024ae6387f29b2f06278acadb3b (diff)
downloadfork-ledger-5bd2401bc7cde0e01cd5b9d18cf077e255c4bd45.tar.gz
fork-ledger-5bd2401bc7cde0e01cd5b9d18cf077e255c4bd45.tar.bz2
fork-ledger-5bd2401bc7cde0e01cd5b9d18cf077e255c4bd45.zip
*** empty log message ***
-rw-r--r--Makefile23
-rw-r--r--amount.cc30
-rw-r--r--balance.cc165
-rw-r--r--gnucash.cc7
-rw-r--r--ledger.h5
-rw-r--r--main.cc51
-rw-r--r--parse.cc16
7 files changed, 188 insertions, 109 deletions
diff --git a/Makefile b/Makefile
index a89f0586..acabc563 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,4 @@
-CODE = amount.cc ledger.cc parse.cc gnucash.cc balance.cc
-ifndef LIBRARY
-CODE := $(CODE) main.cc
-endif
+CODE = amount.cc ledger.cc parse.cc gnucash.cc balance.cc main.cc
OBJS = $(patsubst %.cc,%.o,$(CODE))
@@ -10,27 +7,11 @@ DFLAGS = -g
INCS = -I/usr/include/xmltok
LIBS = -lgmpxx -lgmp -lpcre -lxmlparse
-ifdef LIBRARY
-
-CFLAGS := $(CFLAGS) -fpic
-
-all: make.deps libledger.so ledger
-
-libledger.so: $(OBJS)
- g++ $(CFLAGS) $(INCS) $(DFLAGS) -shared -fpic -o $@ $(OBJS) $(LIBS)
-
-ledger: main.cc
- g++ $(INCS) $(DFLAGS) -o $@ main.cc -L. -lledger
-
-else # LIBRARY
-
all: make.deps ledger
ledger: $(OBJS)
g++ $(CFLAGS) $(INCS) $(DFLAGS) -o $@ $(OBJS) $(LIBS)
-endif # LIBRARY
-
%.o: %.cc
g++ $(CFLAGS) $(INCS) $(DFLAGS) -c -o $@ $<
@@ -42,6 +23,6 @@ rebuild: clean deps all
deps: make.deps
make.deps: Makefile
- cc -M $(INCS) $(CODE) main.cc > $@
+ cc -M $(INCS) $(CODE) > $@
include make.deps
diff --git a/amount.cc b/amount.cc
index a7d26256..8a880e5b 100644
--- a/amount.cc
+++ b/amount.cc
@@ -7,6 +7,8 @@
namespace ledger {
+#define MAX_PRECISION 10 // must be 2 or higher
+
//////////////////////////////////////////////////////////////////////
//
// The `amount' structure. Every transaction has an associated amount,
@@ -16,8 +18,6 @@ namespace ledger {
// default commodity is the US dollar, with a price of 1.00.
//
-#define MAX_PRECISION 10 // must be 2 or higher
-
class gmp_amount : public amount
{
bool priced;
@@ -42,13 +42,16 @@ class gmp_amount : public amount
mpz_clear(quantity);
}
+ virtual commodity * comm() const {
+ return quantity_comm;
+ }
virtual const std::string& comm_symbol() const {
assert(quantity_comm);
return quantity_comm->symbol;
}
virtual amount * copy() const;
- virtual amount * value() const;
+ virtual amount * value(amount *) const;
virtual operator bool() const;
@@ -184,13 +187,21 @@ amount * gmp_amount::copy() const
return new_amt;
}
-amount * gmp_amount::value() const
+amount * gmp_amount::value(amount * pr) const
{
- if (! priced) {
+ if (pr) {
+ gmp_amount * p = dynamic_cast<gmp_amount *>(pr);
+ assert(p);
+ gmp_amount * new_amt = new gmp_amount();
+ multiply(new_amt->quantity, quantity, p->quantity);
+ new_amt->quantity_comm = p->quantity_comm;
+ return new_amt;
+ }
+ else if (! priced) {
return copy();
- } else {
+ }
+ else {
gmp_amount * new_amt = new gmp_amount();
- new_amt->priced = false;
multiply(new_amt->quantity, quantity, price);
new_amt->quantity_comm = price_comm;
return new_amt;
@@ -455,8 +466,7 @@ static commodity * parse_amount(mpz_t out, const char * num,
commodity * comm = NULL;
if (! saw_commodity) {
- std::cerr << "Error: No commodity specified: " << value_str
- << std::endl;
+ std::cerr << "Error: No commodity specified: " << value_str << std::endl;
std::exit(1);
} else {
commodities_iterator item = commodities.find(symbol.c_str());
@@ -492,7 +502,7 @@ amount& gmp_amount::operator=(const char * num)
const char *error;
int erroffset;
static const std::string amount_re =
- "(([^-0-9/.,]+)(\\s*))?([-0-9/.,]+)((\\s*)([^-0-9/.,@]+))?";
+ "(([^-0-9/., ]+)(\\s*))?([-0-9/.,]+)((\\s*)([^-0-9/., @]+))?";
const std::string regexp =
"^" + amount_re + "(\\s*@\\s*" + amount_re + ")?$";
re = pcre_compile(regexp.c_str(), 0, &error, &erroffset, NULL);
diff --git a/balance.cc b/balance.cc
index 8154f466..dedbc4e1 100644
--- a/balance.cc
+++ b/balance.cc
@@ -1,14 +1,11 @@
#include "ledger.h"
+#include <fstream>
+#include <unistd.h>
#include <pcre.h> // Perl regular expression library
namespace ledger {
-//////////////////////////////////////////////////////////////////////
-//
-// Balance report.
-//
-
static bool show_current = false;
static bool show_cleared = false;
static bool show_children = false;
@@ -24,10 +21,14 @@ struct mask
};
static inline bool matches(const std::list<mask>& regexps,
- const std::string& str) {
- // If the first pattern is an exclude, then we assume that all
- // patterns match if they don't match the exclude.
+ const std::string& str)
+{
+ // If the first pattern is an exclude, we assume all patterns match
+ // if they don't match the exclude. If the first pattern is an
+ // include, then only accounts matching the include will match.
+
bool match = (*regexps.begin()).exclude;
+
for (std::list<mask>::const_iterator r = regexps.begin();
r != regexps.end();
r++) {
@@ -36,6 +37,7 @@ static inline bool matches(const std::list<mask>& regexps,
0, 0, ovec, 3) >= 0)
match = ! (*r).exclude;
}
+
return match;
}
@@ -82,48 +84,113 @@ static void display_total(std::ostream& out, totals& total_balance,
}
}
+ // Display balances for all child accounts
+
for (account::const_iterator i = acct->children.begin();
i != acct->children.end();
i++)
display_total(out, total_balance, (*i).second, balances, regexps);
}
+static void record_price(char * setting,
+ std::map<const std::string, amount *>& prices)
+{
+ char * c = setting;
+ char * p = std::strchr(setting, '=');
+ if (! p) {
+ std::cerr << "Warning: Invalid price setting: " << setting << std::endl;
+ } else {
+ *p++ = '\0';
+ amount * price = create_amount(p);
+ prices.insert(std::pair<const std::string, amount *>(c, price));
+ }
+}
+
+static void record_regexp(char * pattern, std::list<mask>& regexps)
+{
+ bool exclude = false;
+
+ char * pat = pattern;
+ if (*pat == '-') {
+ exclude = true;
+ pat++;
+ while (std::isspace(*pat))
+ pat++;
+ }
+ else if (*pat == '+') {
+ pat++;
+ while (std::isspace(*pat))
+ pat++;
+ }
+
+ const char *error;
+ int erroffset;
+ pcre * re = pcre_compile(pat, PCRE_CASELESS, &error, &erroffset, NULL);
+ if (! re)
+ std::cerr << "Warning: Failed to compile regexp: " << pattern
+ << std::endl;
+ else
+ regexps.push_back(mask(exclude, re));
+}
+
+//////////////////////////////////////////////////////////////////////
+//
+// Balance reporting code
+//
+
void report_balances(int argc, char **argv, std::ostream& out)
{
+ std::map<const std::string, amount *> prices;
+ std::list<mask> regexps;
+
int c;
optind = 1;
- while (-1 != (c = getopt(argc, argv, "cCsSn"))) {
+ while (-1 != (c = getopt(argc, argv, "cCsSni:p:"))) {
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;
+
+ // -i path-to-file-of-regexps
+ case 'i':
+ if (access(optarg, R_OK) != -1) {
+ std::ifstream include(optarg);
+
+ while (! include.eof()) {
+ char buf[80];
+ include.getline(buf, 79);
+ if (*buf && ! std::isspace(*buf))
+ record_regexp(buf, regexps);
+ }
+ }
+ break;
+
+ // -p "COMMODITY=PRICE"
+ // -p path-to-price-database
+ case 'p':
+ if (access(optarg, R_OK) != -1) {
+ std::ifstream pricedb(optarg);
+
+ while (! pricedb.eof()) {
+ char buf[80];
+ pricedb.getline(buf, 79);
+ if (*buf && ! std::isspace(*buf))
+ record_price(buf, prices);
+ }
+ } else {
+ record_price(optarg, prices);
+ }
+ break;
}
}
// Compile the list of specified regular expressions, which can be
- // specified on the command line, or using an include/exclude file.
+ // specified on the command line, or using an include/exclude file
- std::list<mask> regexps;
-
- for (; optind < argc; optind++) {
- bool exclude = false;
- char * pat = argv[optind];
- if (*pat == '-') {
- exclude = true;
- pat++;
- }
-
- const char *error;
- int erroffset;
- pcre * re = pcre_compile(pat, PCRE_CASELESS, &error, &erroffset, NULL);
- if (! re)
- std::cerr << "Warning: Failed to compile regexp: " << argv[optind]
- << std::endl;
- else
- regexps.push_back(mask(exclude, re));
- }
+ for (; optind < argc; optind++)
+ record_regexp(argv[optind], regexps);
// Walk through all of the ledger entries, computing the account
// totals
@@ -165,34 +232,30 @@ void report_balances(int argc, char **argv, std::ostream& out)
do_credit = true;
}
- if (do_credit)
+ if (! do_credit)
+ continue;
+
+ std::map<const std::string, amount *>::iterator pi
+ = prices.find((*x)->cost->comm_symbol());
+
+ if (pi == prices.end()) {
balance->credit((*x)->cost);
+ } else {
+ amount * value = (*x)->cost->value((*pi).second);
+ balance->credit(value);
+ delete value;
+ }
}
}
}
-#if 0
- // Print out the balance report header
-
- std::string which = "Future";
- if (show_current)
- which = "Current";
- else if (show_cleared)
- which = "Cleared";
-
- out.width(20);
- out << std::right << which << std::endl
- << "--------------------" << std::endl;
-#endif
-
- // Walk through all the top-level accounts, given the balance
+ // Walk through all the top-level accounts, giving the balance
// report for each, and then for each of their children.
totals total_balance;
for (accounts_iterator i = accounts.begin(); i != accounts.end(); i++)
- if (! (*i).second->parent)
- display_total(out, total_balance, (*i).second, balances, regexps);
+ display_total(out, total_balance, (*i).second, balances, regexps);
// Print the total of all the balances shown
@@ -204,9 +267,13 @@ void report_balances(int argc, char **argv, std::ostream& out)
for (std::map<account *, totals *>::iterator i = balances.begin();
i != balances.end();
- i++) {
+ i++)
+ delete (*i).second;
+
+ for (std::map<const std::string, amount *>::iterator i = prices.begin();
+ i != prices.end();
+ i++)
delete (*i).second;
- }
}
} // namespace ledger
diff --git a/gnucash.cc b/gnucash.cc
index f4c48042..fb768cd8 100644
--- a/gnucash.cc
+++ b/gnucash.cc
@@ -18,9 +18,9 @@ static amount * curr_value;
static std::string curr_quant;
static XML_Parser current_parser;
-accounts_t accounts_by_id;
+static accounts_t accounts_by_id;
-enum {
+static enum {
NO_ACTION,
ACCOUNT_NAME,
ACCOUNT_ID,
@@ -97,7 +97,8 @@ static void endElement(void *userData, const char *name)
{
if (std::strcmp(name, "gnc:account") == 0) {
assert(curr_account);
- accounts.insert(accounts_entry(curr_account->name, curr_account));
+ if (! curr_account->parent)
+ accounts.insert(accounts_entry(curr_account->name, curr_account));
accounts_by_id.insert(accounts_entry(curr_account_id, curr_account));
curr_account = NULL;
}
diff --git a/ledger.h b/ledger.h
index 7e9a48f5..d9524b4a 100644
--- a/ledger.h
+++ b/ledger.h
@@ -1,5 +1,5 @@
#ifndef _LEDGER_H
-#define _LEDGER_H "$Revision: 1.4 $"
+#define _LEDGER_H "$Revision: 1.5 $"
//////////////////////////////////////////////////////////////////////
//
@@ -122,9 +122,10 @@ class amount
public:
virtual ~amount() {}
+ virtual commodity * comm() const = 0;
virtual const std::string& comm_symbol() const = 0;
virtual amount * copy() const = 0;
- virtual amount * value() const = 0;
+ virtual amount * value(amount * pr = NULL) const = 0;
// Test if non-zero
diff --git a/main.cc b/main.cc
index 00d900d2..2d4f6725 100644
--- a/main.cc
+++ b/main.cc
@@ -4,11 +4,6 @@
#include <pcre.h> // Perl regular expression library
-//////////////////////////////////////////////////////////////////////
-//
-// Command-line parser and top-level logic.
-//
-
namespace ledger {
extern bool parse_ledger(std::istream& in);
extern bool parse_gnucash(std::istream& in);
@@ -31,6 +26,11 @@ void show_help(std::ostream& out)
<< " print print all ledger entries" << std::endl;
}
+//////////////////////////////////////////////////////////////////////
+//
+// Command-line parser and top-level logic.
+//
+
int main(int argc, char *argv[])
{
// Global defaults
@@ -40,38 +40,53 @@ int main(int argc, char *argv[])
// Parse the command-line options
+ std::istream * file = NULL;
+
int c;
- while (-1 != (c = getopt(argc, argv, "+hw"))) {
+ while (-1 != (c = getopt(argc, argv, "+hwf:"))) {
switch (char(c)) {
case 'h': show_help(std::cout); break;
case 'w': use_warnings = true; break;
+ case 'f': file = new std::ifstream(optarg); break;
}
}
if (optind == argc) {
- std::cerr << "usage: ledger [options] DATA_FILE COMMAND [ARGS]"
+ std::cerr << "usage: ledger [options] COMMAND [options] [ARGS]" << std::endl
+ << std::endl
+ << "ledger options:" << std::endl
+ << " -f FILE specify pathname of ledger data file" << std::endl
<< 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;
+ << " print print all ledger entries" << std::endl
+ << std::endl
+ << "`balance' command options:" << std::endl
+ << " -s show sub-accounts in balance totals" << std::endl
+ << " -S show empty accounts in balance totals" << std::endl;
return 1;
}
- // Parse the ledger
+ // The -f option is required
+
+ if (! file) {
+ std::cerr << "Please specify the ledger file using the -f option."
+ << std::endl;
+ return 1;
+ }
- std::ifstream file(argv[optind++]);
+ // Parse the ledger
char buf[32];
- file.get(buf, 31);
- file.seekg(0);
+ file->get(buf, 31);
+ file->seekg(0);
if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0)
- parse_gnucash(file);
+ parse_gnucash(*file);
else
- parse_ledger(file);
+ parse_ledger(*file);
+
+ delete file;
// Process the command
@@ -82,3 +97,5 @@ int main(int argc, char *argv[])
else if (command == "print")
print_ledger(argc - optind, &argv[optind], std::cout);
}
+
+// main.cc ends here.
diff --git a/parse.cc b/parse.cc
index 749e3cbd..579bc432 100644
--- a/parse.cc
+++ b/parse.cc
@@ -8,12 +8,7 @@
namespace ledger {
-//////////////////////////////////////////////////////////////////////
-//
-// Ledger parser
-//
-
-char * next_element(char * buf, bool variable = false)
+static char * next_element(char * buf, bool variable = false)
{
char * p;
@@ -34,7 +29,8 @@ char * next_element(char * buf, bool variable = false)
static int linenum = 0;
-inline void finalize_entry(entry * curr) {
+static inline void finalize_entry(entry * curr)
+{
if (curr) {
if (! curr->validate()) {
std::cerr << "Failed to balance the following transaction, "
@@ -46,6 +42,11 @@ inline void finalize_entry(entry * curr) {
}
}
+//////////////////////////////////////////////////////////////////////
+//
+// Ledger parser
+//
+
bool parse_ledger(std::istream& in)
{
static std::time_t now = std::time(NULL);
@@ -150,6 +151,7 @@ bool parse_ledger(std::istream& in)
// If there is no amount given, it is intended as an implicit
// amount; we must use the opposite of the value of the
// preceding transaction.
+
if (! cost_str || *cost_str == ';') {
if (cost_str) {
while (*cost_str == ';' || std::isspace(*cost_str))