From 072d5131b49ff64de40d1c9e44a0abcf3b0f1d73 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 8 Oct 2003 23:05:15 +0000 Subject: *** empty log message *** --- Makefile | 8 +- balance.cc | 128 ------------ equity.cc | 68 ------- ledger.cc | 65 +++++-- ledger.h | 12 +- main.cc | 34 ++-- parse.cc | 14 +- register.cc | 138 ------------- reports.cc | 636 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 723 insertions(+), 380 deletions(-) delete mode 100644 balance.cc delete mode 100644 equity.cc delete mode 100644 register.cc create mode 100644 reports.cc 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 - -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::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(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& 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::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 regexps_map; +typedef std::list regexps_map; +typedef std::list::iterator regexps_map_iterator; +typedef std::list::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& 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 *, + typedef std::map *> virtual_map; - typedef std::pair *, + typedef std::pair *> 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 * 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; - masks->push_back(mask(p)); + masks = new regexps_map; + masks->push_back(new mask(p)); } std::list * 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 - -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::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::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 +#include + +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::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::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::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(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 " + << 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, "", 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. -- cgit v1.2.3