summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2003-10-08 23:05:15 +0000
committerJohn Wiegley <johnw@newartisans.com>2003-10-08 23:05:15 +0000
commit072d5131b49ff64de40d1c9e44a0abcf3b0f1d73 (patch)
treeaa3425496ac09539bacb15991dca36657bdcbcfb
parent331f389cc68b9ca54c50dddb58cb2e7bb3523cf6 (diff)
downloadfork-ledger-072d5131b49ff64de40d1c9e44a0abcf3b0f1d73.tar.gz
fork-ledger-072d5131b49ff64de40d1c9e44a0abcf3b0f1d73.tar.bz2
fork-ledger-072d5131b49ff64de40d1c9e44a0abcf3b0f1d73.zip
*** empty log message ***
-rw-r--r--Makefile8
-rw-r--r--balance.cc128
-rw-r--r--equity.cc68
-rw-r--r--ledger.cc65
-rw-r--r--ledger.h12
-rw-r--r--main.cc34
-rw-r--r--parse.cc14
-rw-r--r--register.cc138
-rw-r--r--reports.cc636
9 files changed, 723 insertions, 380 deletions
diff --git a/Makefile b/Makefile
index d5636a92..0679f6da 100644
--- a/Makefile
+++ b/Makefile
@@ -2,13 +2,7 @@ define GNUCASH
true
endef
-CODE = amount.cc \
- ledger.cc \
- parse.cc \
- balance.cc \
- register.cc \
- equity.cc \
- main.cc
+CODE = amount.cc ledger.cc parse.cc reports.cc
OBJS = $(patsubst %.cc,%.o,$(CODE))
diff --git a/balance.cc b/balance.cc
deleted file mode 100644
index 0cf5305f..00000000
--- a/balance.cc
+++ /dev/null
@@ -1,128 +0,0 @@
-#include "ledger.h"
-
-#include <unistd.h>
-
-namespace ledger {
-
-extern bool show_cleared;
-extern bool show_virtual;
-extern bool show_children;
-extern bool show_empty;
-extern bool show_subtotals;
-extern bool full_names;
-extern bool get_quotes;
-
-extern std::time_t begin_date;
-extern bool have_beginning;
-extern std::time_t end_date;
-extern bool have_ending;
-
-static void display_total(std::ostream& out, totals& balance,
- account * acct, bool top_level)
-{
- bool displayed = false;
-
- if (acct->checked == 1 &&
- (show_empty || ! acct->balance.is_zero())) {
- displayed = true;
-
- acct->balance.print(out, 20);
- if (show_subtotals && top_level)
- balance.credit(acct->balance);
-
- if (acct->parent && ! full_names && ! top_level) {
- for (const account * a = acct; a; a = a->parent)
- out << " ";
- out << acct->name << std::endl;
- } else {
- out << " " << acct->as_str() << std::endl;
- }
- }
-
- // Display balances for all child accounts
-
- for (accounts_map_iterator i = acct->children.begin();
- i != acct->children.end();
- i++)
- display_total(out, balance, (*i).second, ! displayed);
-}
-
-//////////////////////////////////////////////////////////////////////
-//
-// Balance reporting code
-//
-
-void report_balances(std::ostream& out, regexps_map& regexps)
-{
- // Walk through all of the ledger entries, computing the account
- // totals
-
- for (entries_list_iterator i = main_ledger->entries.begin();
- i != main_ledger->entries.end();
- i++) {
- if ((have_beginning && difftime((*i)->date, begin_date) < 0) ||
- (have_ending && difftime((*i)->date, end_date) >= 0) ||
- (show_cleared && ! (*i)->cleared))
- continue;
-
- for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
- x != (*i)->xacts.end();
- x++) {
- if (! show_virtual && (*x)->is_virtual)
- continue;
-
- for (account * acct = (*x)->acct;
- acct;
- acct = show_subtotals ? acct->parent : NULL) {
- if (acct->checked == 0) {
- if (regexps.empty()) {
- if (! (show_children || ! acct->parent))
- acct->checked = 2;
- else
- acct->checked = 1;
- }
- else {
- 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 == 1)
- acct->balance.credit((*x)->cost->street(get_quotes));
- }
- }
- }
-
- // Walk through all the top-level accounts, giving the balance
- // report for each, and then for each of their children.
-
- totals balance;
-
- for (accounts_map_iterator i = main_ledger->accounts.begin();
- i != main_ledger->accounts.end();
- i++)
- display_total(out, balance, (*i).second, true);
-
- // Print the total of all the balances shown
-
- if (show_subtotals && ! balance.is_zero()) {
- out << "--------------------" << std::endl;
- balance.print(out, 20);
- out << std::endl;
- }
-}
-
-} // namespace ledger
diff --git a/equity.cc b/equity.cc
deleted file mode 100644
index 2f3246aa..00000000
--- a/equity.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-#include "ledger.h"
-
-namespace ledger {
-
-extern bool get_quotes;
-
-static void equity_entry(account * acct, regexps_map& regexps,
- std::ostream& out)
-{
- if (! acct->balance.is_zero() &&
- (regexps.empty() || matches(regexps, acct->as_str()))) {
- entry opening;
-
- opening.date = std::time(NULL);
- opening.cleared = true;
- opening.desc = "Opening Balance";
-
- transaction * xact;
- for (totals::const_iterator i = acct->balance.amounts.begin();
- i != acct->balance.amounts.end();
- i++) {
- // Skip it, if there is a zero balance for the commodity
- if ((*i).second->is_zero())
- continue;
-
- xact = new transaction();
- xact->acct = const_cast<account *>(acct);
- xact->cost = (*i).second->street(get_quotes);
- opening.xacts.push_back(xact);
-
- xact = new transaction();
- xact->acct = main_ledger->find_account("Equity:Opening Balances");
- xact->cost = (*i).second->street(get_quotes);
- xact->cost->negate();
- opening.xacts.push_back(xact);
- }
-
- opening.print(out);
- }
-
- // Display balances for all child accounts
-
- for (accounts_map_iterator i = acct->children.begin();
- i != acct->children.end();
- i++)
- equity_entry((*i).second, regexps, out);
-}
-
-//////////////////////////////////////////////////////////////////////
-//
-// Create an Equity file based on a ledger. This is used for
-// archiving past years, and starting out a new year with compiled
-// balances.
-//
-
-void equity_ledger(std::ostream& out, regexps_map& regexps)
-{
- // The account have their current totals already generated as a
- // result of parsing. We just have to output those values.
- // totals
-
- for (accounts_map_iterator i = main_ledger->accounts.begin();
- i != main_ledger->accounts.end();
- i++)
- equity_entry((*i).second, regexps, out);
-}
-
-} // namespace ledger
diff --git a/ledger.cc b/ledger.cc
index 30ffacf9..3b96e20a 100644
--- a/ledger.cc
+++ b/ledger.cc
@@ -107,7 +107,7 @@ bool entry::validate(bool show_unaccounted) const
return balance.is_zero(); // must balance to 0.0
}
-bool entry::matches(const std::list<mask>& regexps) const
+bool entry::matches(const regexps_map& regexps) const
{
if (regexps.empty() || (ledger::matches(regexps, code) ||
ledger::matches(regexps, desc))) {
@@ -216,7 +216,7 @@ void read_regexps(const std::string& path, regexps_map& regexps)
char buf[80];
file.getline(buf, 79);
if (*buf && ! std::isspace(*buf))
- regexps.push_back(mask(buf));
+ regexps.push_back(new mask(buf));
}
}
}
@@ -226,28 +226,59 @@ bool matches(const regexps_map& regexps, const std::string& str,
{
assert(! regexps.empty());
- // If the first pattern is an exclude, we assume all patterns match
- // if they don't match the exclude -- and by_exclusion will be set
- // 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 = false;
+ bool definite = false;
- bool match = (*regexps.begin()).exclude;
- if (match && by_exclusion)
- *by_exclusion = true;
+// std::ofstream out("regex.out", std::ios_base::app);
+// out << "Matching against: " << str << std::endl;
- for (std::list<mask>::const_iterator r = regexps.begin();
+ for (regexps_map_const_iterator r = regexps.begin();
r != regexps.end();
r++) {
- int ovec[3];
- if (pcre_exec((*r).regexp, NULL, str.c_str(), str.length(),
- 0, 0, ovec, 3) >= 0) {
- if (by_exclusion)
- *by_exclusion = (*r).exclude;
- match = ! (*r).exclude;
+// out << " Trying: " << (*r)->pattern << std::endl;
+
+ static int ovec[30];
+ int result = pcre_exec((*r)->regexp, NULL, str.c_str(), str.length(),
+ 0, 0, ovec, 30);
+ if (result >= 0) {
+// out << " Definite ";
+
+ match = ! (*r)->exclude;
+// if (match)
+// out << "match";
+// else
+// out << "unmatch";
+
+ definite = true;
+ } else {
+ assert(result == -1);
+
+// out << " failure code = " << result << std::endl;
+
+ if ((*r)->exclude) {
+ if (! match) {
+ match = ! definite;
+// if (match)
+// out << " indefinite match by exclusion" << std::endl;
+ }
+ } else {
+ definite = true;
+ }
}
+
+// out << " Current status: "
+// << (definite ? "definite " : "")
+// << (match ? "match" : "not match") << std::endl;
}
+ if (match && ! definite && by_exclusion) {
+// out << " Note: Matched by exclusion rule" << std::endl;
+ *by_exclusion = true;
+ }
+
+// out << " Final result: " << (match ? "match" : "not match")
+// << std::endl;
+
return match;
}
diff --git a/ledger.h b/ledger.h
index 4e75bfe0..d2cd6e4c 100644
--- a/ledger.h
+++ b/ledger.h
@@ -1,5 +1,5 @@
#ifndef _LEDGER_H
-#define _LEDGER_H "$Revision: 1.20 $"
+#define _LEDGER_H "$Revision: 1.21 $"
//////////////////////////////////////////////////////////////////////
//
@@ -96,7 +96,9 @@ struct mask
}
};
-typedef std::list<mask> regexps_map;
+typedef std::list<mask *> regexps_map;
+typedef std::list<mask *>::iterator regexps_map_iterator;
+typedef std::list<mask *>::const_iterator regexps_map_const_iterator;
void record_regexp(const std::string& pattern, regexps_map& regexps);
void read_regexps(const std::string& path, regexps_map& regexps);
@@ -156,7 +158,7 @@ struct entry
}
}
- bool matches(const std::list<mask>& regexps) const;
+ bool matches(const regexps_map& regexps) const;
bool validate(bool show_unaccounted = false) const;
void print(std::ostream& out, bool shortcut = true) const;
@@ -245,10 +247,10 @@ struct book
entries_list entries;
int current_year;
- typedef std::map<std::list<mask> *,
+ typedef std::map<regexps_map *,
std::list<transaction *> *> virtual_map;
- typedef std::pair<std::list<mask> *,
+ typedef std::pair<regexps_map *,
std::list<transaction *> *> virtual_map_pair;
typedef virtual_map::const_iterator virtual_map_iterator;
diff --git a/main.cc b/main.cc
index f5021acc..8fca0150 100644
--- a/main.cc
+++ b/main.cc
@@ -82,8 +82,8 @@ int main(int argc, char * argv[])
{
std::istream * file = NULL;
std::string prices;
-
- regexps_map regexps;
+ regexps_map regexps;
+ int index;
have_beginning = false;
have_ending = false;
@@ -217,6 +217,8 @@ int main(int argc, char * argv[])
return 1;
}
+ index = optind;
+
if (use_warnings && (have_beginning || have_ending)) {
std::cout << "Reporting";
@@ -252,18 +254,24 @@ int main(int argc, char * argv[])
// Read the command word
- const std::string command = argv[optind++];
+ const std::string command = argv[index++];
- int optind_begin = optind;
+ int name_index = index;
if (command == "register") {
if (optind == argc) {
std::cerr << ("Error: Must specify an account name "
"after the 'register' command.") << std::endl;
return 1;
}
- optind++;
+ index++;
}
+ // Compile the list of specified regular expressions, which can be
+ // specified after the command, or using the '-i FILE' option
+
+ for (; index < argc; index++)
+ regexps.push_back(new mask(argv[index]));
+
// Parse the ledger
#ifdef READ_GNUCASH
@@ -282,12 +290,6 @@ int main(int argc, char * argv[])
if (! main_ledger)
std::exit(1);
- // Compile the list of specified regular expressions, which can be
- // specified after the command, or using the '-i FILE' option
-
- for (; optind < argc; optind++)
- regexps.push_back(mask(argv[optind]));
-
// Record any prices specified by the user
if (! prices.empty()) {
@@ -310,7 +312,7 @@ int main(int argc, char * argv[])
report_balances(std::cout, regexps);
}
else if (command == "register") {
- print_register(argv[optind_begin], std::cout, regexps);
+ print_register(argv[name_index], std::cout, regexps);
}
else if (command == "print") {
main_ledger->sort(cmp_entry_date());
@@ -325,6 +327,14 @@ int main(int argc, char * argv[])
// this point.
delete main_ledger;
+
+ // Delete the known regexp maps.
+
+ for (regexps_map_iterator r = regexps.begin();
+ r != regexps.end();
+ r++) {
+ delete *r;
+ }
#endif
}
diff --git a/parse.cc b/parse.cc
index 88042828..538b6ff4 100644
--- a/parse.cc
+++ b/parse.cc
@@ -24,7 +24,11 @@ static inline char * next_element(char * buf, bool variable = false)
*p = '\0';
return skip_ws(p + 1);
}
- else if (*(p + 1) == ' ' || *(p + 1) == '\t') {
+ else if (*p == '\t') {
+ *p = '\0';
+ return skip_ws(p + 1);
+ }
+ else if (*(p + 1) == ' ') {
*p = '\0';
return skip_ws(p + 2);
}
@@ -407,7 +411,7 @@ void parse_automated_transactions(std::istream& in, book * ledger)
{
static char line[MAX_LINE + 1];
- std::list<mask> * masks = NULL;
+ regexps_map * masks = NULL;
while (! in.eof() && in.peek() == '=') {
in.getline(line, MAX_LINE);
@@ -417,8 +421,8 @@ void parse_automated_transactions(std::istream& in, book * ledger)
p = skip_ws(p);
if (! masks)
- masks = new std::list<mask>;
- masks->push_back(mask(p));
+ masks = new regexps_map;
+ masks->push_back(new mask(p));
}
std::list<transaction *> * xacts = NULL;
@@ -490,7 +494,7 @@ book * parse_ledger(std::istream& in, regexps_map& regexps,
linenum++;
// Add the regexp to whatever masks currently exist
- regexps.push_back(mask(line));
+ regexps.push_back(new mask(line));
break;
case '=': // automated transactions
diff --git a/register.cc b/register.cc
deleted file mode 100644
index db9b4849..00000000
--- a/register.cc
+++ /dev/null
@@ -1,138 +0,0 @@
-#include "ledger.h"
-
-#include <unistd.h>
-
-namespace ledger {
-
-extern bool show_cleared;
-extern bool get_quotes;
-
-extern std::time_t begin_date;
-extern bool have_beginning;
-extern std::time_t end_date;
-extern bool have_ending;
-
-static std::string truncated(const std::string& str, int width)
-{
- char buf[256];
- memset(buf, '\0', 255);
- std::strncpy(buf, str.c_str(), width);
- if (buf[width - 1])
- std::strcpy(&buf[width - 3], "...");
- else
- buf[width] = '\0';
- return buf;
-}
-
-//////////////////////////////////////////////////////////////////////
-//
-// Register printing code
-//
-
-void print_register(const std::string& acct_name, std::ostream& out,
- regexps_map& regexps)
-{
- account * acct = main_ledger->find_account(acct_name, false);
- if (! acct) {
- std::cerr << "Error: Unknown account name: " << acct_name
- << std::endl;
- return;
- }
-
- // Walk through all of the ledger entries, printing their register
- // formatted equivalent
-
- totals balance;
-
- for (entries_list_iterator i = main_ledger->entries.begin();
- i != main_ledger->entries.end();
- i++) {
- if (! (*i)->matches(regexps))
- continue;
-
- for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
- x != (*i)->xacts.end();
- x++) {
- if ((*x)->acct != acct || ! show_cleared && (*i)->cleared)
- continue;
-
- char buf[32];
- std::strftime(buf, 31, "%m.%d ", std::localtime(&(*i)->date));
- out << buf;
-
-#if 0
- if ((*i)->cleared)
- out << "* ";
- else
- out << " ";
-
- out.width(4);
- if ((*i)->code.empty())
- out << " ";
- else
- out << std::left << (*i)->code;
-#endif
- out << " ";
-
- out.width(30);
- if ((*i)->desc.empty())
- out << " ";
- else
- out << std::left << truncated((*i)->desc, 30);
- out << " ";
-
- // Always display the street value, if prices have been
- // specified
-
- amount * street = (*x)->cost->street(get_quotes);
- balance.credit(street);
-
- // If there are two transactions, use the one which does not
- // refer to this account. If there are more than two, we will
- // just have to print all of the splits, like gnucash does.
-
- transaction * xact;
- if ((*i)->xacts.size() == 2) {
- if (*x == (*i)->xacts.front())
- xact = (*i)->xacts.back();
- else
- xact = (*i)->xacts.front();
- } else {
- xact = *x;
- }
-
- out.width(22);
- out << std::left << truncated(xact->acct_as_str(), 22) << " ";
-
- out.width(12);
- out << std::right << street->as_str(true);
- delete street;
-
- balance.print(out, 12);
-
- out << std::endl;
-
- if (xact != *x)
- continue;
-
- for (std::list<transaction *>::iterator y = (*i)->xacts.begin();
- y != (*i)->xacts.end();
- y++) {
- if (*x == *y)
- continue;
-
- out << " ";
-
- out.width(22);
- out << std::left << truncated((*y)->acct_as_str(), 22) << " ";
-
- out.width(12);
- street = (*y)->cost->street(get_quotes);
- out << std::right << street->as_str(true) << std::endl;
- delete street;
- }
- }
- }
-}
-
-} // namespace ledger
diff --git a/reports.cc b/reports.cc
new file mode 100644
index 00000000..6b4d697d
--- /dev/null
+++ b/reports.cc
@@ -0,0 +1,636 @@
+#include "ledger.h"
+
+#define LEDGER_VERSION "1.1"
+
+#include <fstream>
+#include <unistd.h>
+
+namespace ledger {
+
+extern book * parse_ledger(std::istream& in, regexps_map& regexps,
+ bool compute_balances);
+#ifdef READ_GNUCASH
+extern book * parse_gnucash(std::istream& in, bool compute_balances);
+#endif
+
+extern bool parse_date(const char * date_str, std::time_t * result,
+ const int year = -1);
+extern void parse_price_setting(const std::string& setting);
+
+
+static bool show_cleared;
+static bool show_virtual;
+static bool get_quotes;
+static bool show_children;
+static bool show_empty;
+static bool show_subtotals;
+static bool full_names;
+
+static std::time_t begin_date;
+static bool have_beginning;
+static std::time_t end_date;
+static bool have_ending;
+
+
+//////////////////////////////////////////////////////////////////////
+//
+// Balance reporting code
+//
+
+static void display_total(std::ostream& out, totals& balance,
+ account * acct, bool top_level,
+ int * headlines)
+{
+ bool displayed = false;
+
+ if (acct->checked == 1 &&
+ (show_empty || ! acct->balance.is_zero())) {
+ displayed = true;
+
+ acct->balance.print(out, 20);
+ if (show_subtotals && top_level)
+ balance.credit(acct->balance);
+
+ if (acct->parent && ! full_names && ! top_level) {
+ for (const account * a = acct; a; a = a->parent)
+ out << " ";
+ out << acct->name << std::endl;
+ } else {
+ out << " " << acct->as_str() << std::endl;
+ (*headlines)++;
+ }
+ }
+
+ // Display balances for all child accounts
+
+ for (accounts_map_iterator i = acct->children.begin();
+ i != acct->children.end();
+ i++)
+ display_total(out, balance, (*i).second, ! displayed, headlines);
+}
+
+void report_balances(std::ostream& out, regexps_map& regexps)
+{
+ // Walk through all of the ledger entries, computing the account
+ // totals
+
+ for (entries_list_iterator i = main_ledger->entries.begin();
+ i != main_ledger->entries.end();
+ i++) {
+ if ((have_beginning && difftime((*i)->date, begin_date) < 0) ||
+ (have_ending && difftime((*i)->date, end_date) >= 0) ||
+ (show_cleared && ! (*i)->cleared))
+ continue;
+
+ for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
+ x != (*i)->xacts.end();
+ x++) {
+ if (! show_virtual && (*x)->is_virtual)
+ continue;
+
+ for (account * acct = (*x)->acct;
+ acct;
+ acct = show_subtotals ? acct->parent : NULL) {
+ if (acct->checked == 0) {
+ if (regexps.empty()) {
+ if (! (show_children || ! acct->parent))
+ acct->checked = 2;
+ else
+ acct->checked = 1;
+ }
+ else {
+ 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 == 1)
+ acct->balance.credit((*x)->cost->street(get_quotes));
+ }
+ }
+ }
+
+ // Walk through all the top-level accounts, giving the balance
+ // report for each, and then for each of their children.
+
+ totals balance;
+ int headlines = 0;
+
+ for (accounts_map_iterator i = main_ledger->accounts.begin();
+ i != main_ledger->accounts.end();
+ i++)
+ display_total(out, balance, (*i).second, true, &headlines);
+
+ // Print the total of all the balances shown
+
+ if (show_subtotals && headlines > 1) {
+ out << "--------------------" << std::endl;
+ balance.print(out, 20);
+ out << std::endl;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+//
+// Register printing code
+//
+
+static std::string truncated(const std::string& str, int width)
+{
+ char buf[256];
+ memset(buf, '\0', 255);
+ std::strncpy(buf, str.c_str(), width);
+ if (buf[width - 1])
+ std::strcpy(&buf[width - 3], "...");
+ else
+ buf[width] = '\0';
+ return buf;
+}
+
+void print_register(const std::string& acct_name, std::ostream& out,
+ regexps_map& regexps)
+{
+ account * acct = main_ledger->find_account(acct_name, false);
+ if (! acct) {
+ std::cerr << "Error: Unknown account name: " << acct_name
+ << std::endl;
+ return;
+ }
+
+ // Walk through all of the ledger entries, printing their register
+ // formatted equivalent
+
+ totals balance;
+
+ for (entries_list_iterator i = main_ledger->entries.begin();
+ i != main_ledger->entries.end();
+ i++) {
+ if (! (*i)->matches(regexps))
+ continue;
+
+ for (std::list<transaction *>::iterator x = (*i)->xacts.begin();
+ x != (*i)->xacts.end();
+ x++) {
+ if ((*x)->acct != acct || ! show_cleared && (*i)->cleared)
+ continue;
+
+ char buf[32];
+ std::strftime(buf, 31, "%m.%d ", std::localtime(&(*i)->date));
+ out << buf;
+
+#if 0
+ if ((*i)->cleared)
+ out << "* ";
+ else
+ out << " ";
+
+ out.width(4);
+ if ((*i)->code.empty())
+ out << " ";
+ else
+ out << std::left << (*i)->code;
+#endif
+ out << " ";
+
+ out.width(30);
+ if ((*i)->desc.empty())
+ out << " ";
+ else
+ out << std::left << truncated((*i)->desc, 30);
+ out << " ";
+
+ // Always display the street value, if prices have been
+ // specified
+
+ amount * street = (*x)->cost->street(get_quotes);
+ balance.credit(street);
+
+ // If there are two transactions, use the one which does not
+ // refer to this account. If there are more than two, we will
+ // just have to print all of the splits, like gnucash does.
+
+ transaction * xact;
+ if ((*i)->xacts.size() == 2) {
+ if (*x == (*i)->xacts.front())
+ xact = (*i)->xacts.back();
+ else
+ xact = (*i)->xacts.front();
+ } else {
+ xact = *x;
+ }
+
+ out.width(22);
+ out << std::left << truncated(xact->acct_as_str(), 22) << " ";
+
+ out.width(12);
+ out << std::right << street->as_str(true);
+ delete street;
+
+ balance.print(out, 12);
+
+ out << std::endl;
+
+ if (! show_subtotals || xact != *x)
+ continue;
+
+ for (std::list<transaction *>::iterator y = (*i)->xacts.begin();
+ y != (*i)->xacts.end();
+ y++) {
+ if (*x == *y)
+ continue;
+
+ out << " ";
+
+ out.width(22);
+ out << std::left << truncated((*y)->acct_as_str(), 22) << " ";
+
+ out.width(12);
+ street = (*y)->cost->street(get_quotes);
+ out << std::right << street->as_str(true) << std::endl;
+ delete street;
+ }
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+//
+// Create an Equity file based on a ledger. This is used for
+// archiving past years, and starting out a new year with compiled
+// balances.
+//
+
+static void equity_entry(account * acct, regexps_map& regexps,
+ std::ostream& out)
+{
+ if (! acct->balance.is_zero() &&
+ (regexps.empty() || matches(regexps, acct->as_str()))) {
+ entry opening;
+
+ opening.date = std::time(NULL);
+ opening.cleared = true;
+ opening.desc = "Opening Balance";
+
+ transaction * xact;
+ for (totals::const_iterator i = acct->balance.amounts.begin();
+ i != acct->balance.amounts.end();
+ i++) {
+ // Skip it, if there is a zero balance for the commodity
+ if ((*i).second->is_zero())
+ continue;
+
+ xact = new transaction();
+ xact->acct = const_cast<account *>(acct);
+ xact->cost = (*i).second->street(get_quotes);
+ opening.xacts.push_back(xact);
+
+ xact = new transaction();
+ xact->acct = main_ledger->find_account("Equity:Opening Balances");
+ xact->cost = (*i).second->street(get_quotes);
+ xact->cost->negate();
+ opening.xacts.push_back(xact);
+ }
+
+ opening.print(out);
+ }
+
+ // Display balances for all child accounts
+
+ for (accounts_map_iterator i = acct->children.begin();
+ i != acct->children.end();
+ i++)
+ equity_entry((*i).second, regexps, out);
+}
+
+void equity_ledger(std::ostream& out, regexps_map& regexps)
+{
+ // The account have their current totals already generated as a
+ // result of parsing. We just have to output those values.
+ // totals
+
+ for (accounts_map_iterator i = main_ledger->accounts.begin();
+ i != main_ledger->accounts.end();
+ i++)
+ equity_entry((*i).second, regexps, out);
+}
+
+} // namespace ledger
+
+using namespace ledger;
+
+static void show_help(std::ostream& out)
+{
+ std::cerr
+ << "usage: ledger [options] COMMAND [options] [REGEXPS]" << std::endl
+ << std::endl
+ << "ledger options:" << std::endl
+ << " -C also show cleared transactions" << std::endl
+ << " -d DATE specify an implicit date range (e.g., -d april)"
+ << std::endl
+ << " -b DATE specify a beginning date" << std::endl
+ << " -e DATE specify an ending date" << std::endl
+ << " -c do not show future entries (same as -e TODAY)" << std::endl
+ << " -f FILE specify pathname of ledger data file" << std::endl
+ << " -h display this help text" << std::endl
+ << " -R do not factor any virtual transactions" << std::endl
+ << " -V FILE use virtual mappings listed in FILE" << std::endl
+ << " -i FILE read the list of inclusion regexps from FILE" << std::endl
+ << " -p FILE read the list of prices from FILE" << std::endl
+ << " -P download price quotes from the Internet" << std::endl
+ << " (works by running the command \"getquote SYMBOL\")"
+ << std::endl
+ << " -v display version information" << std::endl
+ << " -w print out warnings where applicable" << std::endl
+ << std::endl
+ << "commands:" << std::endl
+ << " balance show balance totals" << std::endl
+ << " register display a register for ACCOUNT" << std::endl
+ << " print print all ledger entries" << std::endl
+ << " equity generate equity ledger for all entries" << std::endl
+ << std::endl
+ << "`balance' options:" << std::endl
+ << " -F print each account's full name" << std::endl
+ << " -n do not generate totals for parent accounts" << std::endl
+ << " -s show sub-accounts in balance totals" << std::endl
+ << " -S show empty accounts in balance totals" << std::endl;
+}
+
+//////////////////////////////////////////////////////////////////////
+//
+// Command-line parser and top-level logic.
+//
+
+int main(int argc, char * argv[])
+{
+ std::istream * file = NULL;
+ std::string prices;
+ regexps_map regexps;
+ int index;
+
+ have_beginning = false;
+ have_ending = false;
+ show_cleared = false;
+ show_virtual = true;
+ show_children = false;
+ show_empty = false;
+ show_subtotals = true;
+ full_names = false;
+
+ // Parse the command-line options
+
+ int c;
+ while (-1 != (c = getopt(argc, argv, "+b:e:d:cChRV:wf:i:p:PvsSnF"))) {
+ switch (char(c)) {
+ case 'b':
+ case 'e': {
+ std::time_t when;
+ if (! parse_date(optarg, &when)) {
+ std::cerr << "Error: Bad date string: " << optarg << std::endl;
+ return 1;
+ }
+
+ if (c == 'b') {
+ begin_date = when;
+ have_beginning = true;
+ } else {
+ end_date = when;
+ have_ending = true;
+ }
+ break;
+ }
+
+#if 0
+ case 'd': {
+ if (! parse_date(optarg, &begin_date)) {
+ std::cerr << "Error: Bad date string: " << optarg << std::endl;
+ return 1;
+ }
+ have_beginning = true;
+
+ struct std::tm when, then;
+ std::memset(&then, 0, sizeof(struct std::tm));
+
+ std::time_t now = std::time(NULL);
+ struct std::tm * now_tm = std::localtime(&now);
+
+ for (const char ** f = formats; *f; f++) {
+ memset(&when, INT_MAX, sizeof(struct std::tm));
+ if (strptime(optarg, *f, &when)) {
+ then.tm_hour = 0;
+ then.tm_min = 0;
+ then.tm_sec = 0;
+
+ if (when.tm_year != -1)
+ then.tm_year = when.tm_year + 1;
+ else
+ then.tm_year = now_tm->tm_year;
+
+ if (std::strcmp(*f, "%Y") == 0) {
+ then.tm_mon = 0;
+ then.tm_mday = 1;
+ } else {
+ if (when.tm_mon != -1)
+ then.tm_mon = when.tm_mon + 1;
+ else
+ then.tm_mon = now_tm->tm_mon;
+
+ if (when.tm_mday != -1)
+ then.tm_mday = when.tm_mday + 1;
+ else
+ then.tm_mday = now_tm->tm_mday;
+ }
+
+ end_date = std::mktime(&then);
+ have_ending = true;
+ break;
+ }
+ }
+ break;
+ }
+#endif
+
+ case 'c':
+ end_date = std::time(NULL);
+ have_ending = true;
+ break;
+
+ case 'h': show_help(std::cout); break;
+ case 'f': file = new std::ifstream(optarg); break;
+
+ case 'C': show_cleared = true; break;
+ case 'R': show_virtual = false; break;
+ case 'w': use_warnings = true; break;
+ case 's': show_children = true; break;
+ case 'S': show_empty = true; break;
+ case 'n': show_subtotals = false; break;
+ case 'F': full_names = true; break;
+
+ // -i path-to-file-of-regexps
+ case 'i':
+ read_regexps(optarg, regexps);
+ break;
+
+ // -p "COMMODITY=PRICE"
+ // -p path-to-price-database
+ case 'p':
+ prices = optarg;
+ break;
+
+ case 'P':
+ get_quotes = true;
+ break;
+
+ case 'v':
+ std::cout
+ << "Ledger Accouting Tool " LEDGER_VERSION << std::endl
+ << " Copyright (c) 2003 John Wiegley <johnw@newartisans.com>"
+ << std::endl << std::endl
+ << "This program is made available under the terms of the BSD"
+ << std::endl
+ << "Public License. See the LICENSE file included with the"
+ << std::endl
+ << "distribution for details and disclaimer." << std::endl;
+ return 0;
+ }
+ }
+
+ if (optind == argc) {
+ show_help(std::cout);
+ return 1;
+ }
+
+ index = optind;
+
+ if (use_warnings && (have_beginning || have_ending)) {
+ std::cout << "Reporting";
+
+ if (have_beginning) {
+ char buf[32];
+ std::strftime(buf, 31, "%Y.%m.%d", std::localtime(&begin_date));
+ std::cout << " from " << buf;
+ }
+
+ if (have_ending) {
+ char buf[32];
+ std::strftime(buf, 31, "%Y.%m.%d", std::localtime(&end_date));
+ std::cout << " until " << buf;
+ }
+
+ std::cout << std::endl;
+ }
+
+ // A ledger data file must be specified
+
+ if (! file) {
+ const char * p = std::getenv("LEDGER");
+ if (p)
+ file = new std::ifstream(p);
+
+ if (! file || ! *file) {
+ std::cerr << ("Please specify ledger file using -f option "
+ "or LEDGER environment variable.")
+ << std::endl;
+ return 1;
+ }
+ }
+
+ // Read the command word
+
+ const std::string command = argv[index++];
+
+ int name_index = index;
+ if (command == "register") {
+ if (optind == argc) {
+ std::cerr << ("Error: Must specify an account name "
+ "after the 'register' command.") << std::endl;
+ return 1;
+ }
+ index++;
+ }
+
+ // Compile the list of specified regular expressions, which can be
+ // specified after the command, or using the '-i FILE' option
+
+ for (; index < argc; index++)
+ regexps.push_back(new mask(argv[index]));
+
+ // Parse the ledger
+
+#ifdef READ_GNUCASH
+ char buf[32];
+ file->get(buf, 31);
+ file->seekg(0);
+
+ if (std::strncmp(buf, "<?xml version=\"1.0\"?>", 21) == 0)
+ main_ledger = parse_gnucash(*file, command == "equity");
+ else
+#endif
+ main_ledger = parse_ledger(*file, regexps, command == "equity");
+
+ delete file;
+
+ if (! main_ledger)
+ std::exit(1);
+
+ // Record any prices specified by the user
+
+ if (! prices.empty()) {
+ if (access(prices.c_str(), R_OK) != -1) {
+ std::ifstream pricedb(prices.c_str());
+ while (! pricedb.eof()) {
+ char buf[80];
+ pricedb.getline(buf, 79);
+ if (*buf && ! std::isspace(*buf))
+ parse_price_setting(buf);
+ }
+ } else {
+ parse_price_setting(prices);
+ }
+ }
+
+ // Process the command
+
+ if (command == "balance") {
+ report_balances(std::cout, regexps);
+ }
+ else if (command == "register") {
+ print_register(argv[name_index], std::cout, regexps);
+ }
+ else if (command == "print") {
+ main_ledger->sort(cmp_entry_date());
+ main_ledger->print(std::cout, regexps, true);
+ }
+ else if (command == "equity") {
+ equity_ledger(std::cout, regexps);
+ }
+
+#ifdef DEBUG
+ // Ordinarily, deleting the main ledger just isn't necessary at
+ // this point.
+
+ delete main_ledger;
+
+ // Delete the known regexp maps.
+
+ for (regexps_map_iterator r = regexps.begin();
+ r != regexps.end();
+ r++) {
+ delete *r;
+ }
+#endif
+}
+
+// reports.cc ends here.