diff options
-rw-r--r-- | amount.cc | 45 | ||||
-rw-r--r-- | binary.cc | 10 | ||||
-rw-r--r-- | binary.h | 2 | ||||
-rw-r--r-- | constraint.cc | 4 | ||||
-rw-r--r-- | constraint.h | 10 | ||||
-rw-r--r-- | error.h | 7 | ||||
-rw-r--r-- | expr.cc | 230 | ||||
-rw-r--r-- | expr.h | 106 | ||||
-rw-r--r-- | format.cc | 259 | ||||
-rw-r--r-- | format.h | 85 | ||||
-rw-r--r-- | gnucash.cc | 10 | ||||
-rw-r--r-- | item.cc | 4 | ||||
-rw-r--r-- | ledger.cc | 33 | ||||
-rw-r--r-- | ledger.h | 57 | ||||
-rw-r--r-- | main.cc | 275 | ||||
-rw-r--r-- | textual.cc | 73 |
16 files changed, 643 insertions, 567 deletions
@@ -10,8 +10,6 @@ namespace ledger { -commodity_t * amount_t::null_commodity = NULL; - static void mpz_round(mpz_t value, int precision) { mpz_t divisor; @@ -544,7 +542,7 @@ void parse_commodity(std::istream& in, std::string& symbol) } } -void amount_t::parse(std::istream& in, ledger_t * ledger) +void amount_t::parse(std::istream& in) { // The possible syntax for an amount is: // @@ -607,25 +605,10 @@ void amount_t::parse(std::istream& in, ledger_t * ledger) assert(precision <= MAX_PRECISION); // Create the commodity if has not already been seen. - if (ledger) { - commodity = ledger->find_commodity(symbol, true); - commodity->flags |= flags; - if (precision > commodity->precision) - commodity->precision = precision; - } - else if (symbol.empty()) { - if (! null_commodity) { - commodity = null_commodity = new commodity_t(symbol, precision, flags); - } else { - commodity = null_commodity; - commodity->flags |= flags; - if (precision > commodity->precision) - commodity->precision = precision; - } - } - else { - commodity = new commodity_t(symbol, precision, flags); - } + commodity = commodity_t::find_commodity(symbol, true); + commodity->flags |= flags; + if (precision > commodity->precision) + commodity->precision = precision; // The number is specified as the user desires, with the commodity // flags telling how to parse it. @@ -696,6 +679,24 @@ void (*commodity_t::updater)(commodity_t * commodity, const amount_t& price, const std::time_t moment) = NULL; +commodities_map commodity_t::commodities; + +commodity_t * commodity_t::find_commodity(const std::string& symbol, + bool auto_create) +{ + commodities_map::const_iterator i = commodities.find(symbol); + if (i != commodities.end()) + return (*i).second; + + if (auto_create) { + commodity_t * commodity = new commodity_t(symbol); + add_commodity(commodity); + return commodity; + } + + return NULL; +} + amount_t commodity_t::value(const std::time_t moment) { std::time_t age = 0; @@ -343,8 +343,8 @@ unsigned int read_binary_ledger(std::istream& in, for (int i = count; --i >= 0; ) { commodity_t * commodity = read_binary_commodity(in); std::pair<commodities_map::iterator, bool> result - = ledger->commodities.insert(commodities_pair(commodity->symbol, - commodity)); + = commodity_t::commodities.insert(commodities_pair(commodity->symbol, + commodity)); assert(result.second || master); } @@ -593,11 +593,11 @@ void write_binary_ledger(std::ostream& out, ledger_t * ledger, write_binary_account(out, ledger->master); - unsigned long count = ledger->commodities.size(); + unsigned long count = commodity_t::commodities.size(); out.write((char *)&count, sizeof(count)); - for (commodities_map::const_iterator i = ledger->commodities.begin(); - i != ledger->commodities.end(); + for (commodities_map::const_iterator i = commodity_t::commodities.begin(); + i != commodity_t::commodities.end(); i++) write_binary_commodity(out, (*i).second); @@ -9,7 +9,7 @@ extern unsigned long magic_number; extern unsigned int read_binary_ledger(std::istream& in, const std::string& leader, - ledger_t * book, + ledger_t * journal, account_t * master = NULL); extern void write_binary_ledger(std::ostream& out, diff --git a/constraint.cc b/constraint.cc index 4720e62b..fe00f4d2 100644 --- a/constraint.cc +++ b/constraint.cc @@ -93,10 +93,10 @@ bool matches(const masks_list& regexps, const std::string& str, bool constraints_t::matches_date_range(const std::time_t date) const { - if (have_beginning && difftime(date, begin_date) < 0) + if (begin_date != -1 && difftime(date, begin_date) < 0) return false; - if (have_ending && difftime(date, end_date) >= 0) + if (end_date != -1 && difftime(date, end_date) >= 0) return false; if (have_date_mask) { diff --git a/constraint.h b/constraint.h index f7ecef62..79e01fc6 100644 --- a/constraint.h +++ b/constraint.h @@ -104,9 +104,7 @@ class constraints_t bool show_empty; std::time_t begin_date; - bool have_beginning; std::time_t end_date; - bool have_ending; struct std::tm date_mask; bool have_date_mask; @@ -128,8 +126,8 @@ class constraints_t show_subtotals = true; show_empty = false; - have_beginning = false; - have_ending = false; + begin_date = -1; + end_date = -1; have_date_mask = false; period = PERIOD_NONE; @@ -140,11 +138,11 @@ class constraints_t ~constraints_t(); std::time_t begin() const { - return have_beginning ? begin_date : 0; + return begin_date == -1 ? 0 : begin_date; } std::time_t end() const { - return have_ending ? end_date : std::time(NULL); + return end_date == -1 ? std::time(NULL) : end_date; } bool matches_date_range(const std::time_t date) const; @@ -32,6 +32,13 @@ class expr_error : public error virtual ~expr_error() throw() {} }; +class format_error : public error +{ + public: + format_error(const std::string& reason) throw() : error(reason) {} + virtual ~format_error() throw() {} +}; + class parse_error : public error { unsigned int line; @@ -149,14 +149,14 @@ balance_t node_t::compute(const item_t * item, return temp; } -node_t * parse_term(std::istream& in, ledger_t * ledger); +node_t * parse_term(std::istream& in); -inline node_t * parse_term(const char * p, ledger_t * ledger) { +inline node_t * parse_term(const char * p) { std::istringstream stream(p); - return parse_term(stream, ledger); + return parse_term(stream); } -node_t * parse_term(std::istream& in, ledger_t * ledger) +node_t * parse_term(std::istream& in) { node_t * node = NULL; @@ -185,8 +185,8 @@ node_t * parse_term(std::istream& in, ledger_t * ledger) } if (! ident.empty()) { - node = new node_t(CONSTANT_A); - node->constant_a.parse(ident, ledger); + node = new node_t(node_t::CONSTANT_A); + node->constant_a.parse(ident); } return node; } @@ -194,62 +194,62 @@ node_t * parse_term(std::istream& in, ledger_t * ledger) in.get(c); switch (c) { // Basic terms - case 'a': node = new node_t(AMOUNT); break; - case 'c': node = new node_t(COST); break; - case 'd': node = new node_t(DATE); break; - case 'b': node = new node_t(BEGIN_DATE); break; - case 'e': node = new node_t(END_DATE); break; - case 'i': node = new node_t(INDEX); break; - case 'B': node = new node_t(BALANCE); break; - case 'T': node = new node_t(TOTAL); break; - case 'C': node = new node_t(COST_TOTAL); break; + case 'a': node = new node_t(node_t::AMOUNT); break; + case 'c': node = new node_t(node_t::COST); break; + case 'd': node = new node_t(node_t::DATE); break; + case 'b': node = new node_t(node_t::BEGIN_DATE); break; + case 'e': node = new node_t(node_t::END_DATE); break; + case 'i': node = new node_t(node_t::INDEX); break; + case 'B': node = new node_t(node_t::BALANCE); break; + case 'T': node = new node_t(node_t::TOTAL); break; + case 'C': node = new node_t(node_t::COST_TOTAL); break; // Compound terms - case 'v': node = parse_expr("P(a,d)", ledger); break; - case 'V': node = parse_term("P(T,d)", ledger); break; - case 'g': node = parse_expr("v-c", ledger); break; - case 'G': node = parse_expr("V-C", ledger); break; - case 'o': node = parse_expr("d-b", ledger); break; - case 'w': node = parse_expr("e-d", ledger); break; + case 'v': node = parse_expr("P(a,d)"); break; + case 'V': node = parse_term("P(T,d)"); break; + case 'g': node = parse_expr("v-c"); break; + case 'G': node = parse_expr("V-C"); break; + case 'o': node = parse_expr("d-b"); break; + case 'w': node = parse_expr("e-d"); break; // Functions case '-': - node = new node_t(F_NEG); - node->left = parse_term(in, ledger); + node = new node_t(node_t::F_NEG); + node->left = parse_term(in); break; - case 'A': // absolute value ("positive") - node = new node_t(F_ABS); - node->left = parse_term(in, ledger); + case 'A': + node = new node_t(node_t::F_ABS); + node->left = parse_term(in); break; case 'M': - node = new node_t(F_ARITH_MEAN); - node->left = parse_term(in, ledger); + node = new node_t(node_t::F_ARITH_MEAN); + node->left = parse_term(in); break; case 'D': { - node = new node_t(O_SUB); - node->left = parse_term("a", ledger); - node->right = parse_term(in, ledger); + node = new node_t(node_t::O_SUB); + node->left = parse_term("a"); + node->right = parse_term(in); break; } case 'P': - node = new node_t(F_VALUE); + node = new node_t(node_t::F_VALUE); if (in.peek() == '(') { in.get(c); - node->left = parse_expr(in, ledger); + node->left = parse_expr(in); if (in.peek() == ',') { in.get(c); - node->right = parse_expr(in, ledger); + node->right = parse_expr(in); } if (in.peek() == ')') in.get(c); else throw expr_error("Missing ')'"); } else { - node->left = parse_term(in, ledger); + node->left = parse_term(in); } break; @@ -267,7 +267,7 @@ node_t * parse_term(std::istream& in, ledger_t * ledger) } if (c == '/') { in.get(c); - node = new node_t(F_REGEXP); + node = new node_t(node_t::F_REGEXP); node->mask = new mask_t(ident); } else { throw expr_error("Missing closing '/'"); @@ -276,7 +276,7 @@ node_t * parse_term(std::istream& in, ledger_t * ledger) } case '(': - node = parse_expr(in, ledger); + node = parse_expr(in); if (in.peek() == ')') in.get(c); else @@ -294,7 +294,7 @@ node_t * parse_term(std::istream& in, ledger_t * ledger) } if (c == ']') { in.get(c); - node = new node_t(CONSTANT_T); + node = new node_t(node_t::CONSTANT_T); if (! parse_date(ident.c_str(), &node->constant_t)) throw expr_error("Failed to parse date"); } else { @@ -311,11 +311,11 @@ node_t * parse_term(std::istream& in, ledger_t * ledger) return node; } -node_t * parse_mul_expr(std::istream& in, ledger_t * ledger) +node_t * parse_mul_expr(std::istream& in) { node_t * node = NULL; - node = parse_term(in, ledger); + node = parse_term(in); if (node && ! in.eof()) { char c = in.peek(); @@ -324,17 +324,17 @@ node_t * parse_mul_expr(std::istream& in, ledger_t * ledger) switch (c) { case '*': { node_t * prev = node; - node = new node_t(O_MUL); + node = new node_t(node_t::O_MUL); node->left = prev; - node->right = parse_term(in, ledger); + node->right = parse_term(in); break; } case '/': { node_t * prev = node; - node = new node_t(O_DIV); + node = new node_t(node_t::O_DIV); node->left = prev; - node->right = parse_term(in, ledger); + node->right = parse_term(in); break; } } @@ -345,11 +345,11 @@ node_t * parse_mul_expr(std::istream& in, ledger_t * ledger) return node; } -node_t * parse_add_expr(std::istream& in, ledger_t * ledger) +node_t * parse_add_expr(std::istream& in) { node_t * node = NULL; - node = parse_mul_expr(in, ledger); + node = parse_mul_expr(in); if (node && ! in.eof()) { char c = in.peek(); @@ -358,17 +358,17 @@ node_t * parse_add_expr(std::istream& in, ledger_t * ledger) switch (c) { case '+': { node_t * prev = node; - node = new node_t(O_ADD); + node = new node_t(node_t::O_ADD); node->left = prev; - node->right = parse_mul_expr(in, ledger); + node->right = parse_mul_expr(in); break; } case '-': { node_t * prev = node; - node = new node_t(O_SUB); + node = new node_t(node_t::O_SUB); node->left = prev; - node->right = parse_mul_expr(in, ledger); + node->right = parse_mul_expr(in); break; } } @@ -379,19 +379,19 @@ node_t * parse_add_expr(std::istream& in, ledger_t * ledger) return node; } -node_t * parse_logic_expr(std::istream& in, ledger_t * ledger) +node_t * parse_logic_expr(std::istream& in) { node_t * node = NULL; if (in.peek() == '!') { char c; in.get(c); - node = new node_t(O_NOT); - node->left = parse_logic_expr(in, ledger); + node = new node_t(node_t::O_NOT); + node->left = parse_logic_expr(in); return node; } - node = parse_add_expr(in, ledger); + node = parse_add_expr(in); if (node && ! in.eof()) { char c = in.peek(); @@ -400,33 +400,33 @@ node_t * parse_logic_expr(std::istream& in, ledger_t * ledger) switch (c) { case '=': { node_t * prev = node; - node = new node_t(O_EQ); + node = new node_t(node_t::O_EQ); node->left = prev; - node->right = parse_add_expr(in, ledger); + node->right = parse_add_expr(in); break; } case '<': { node_t * prev = node; - node = new node_t(O_LT); + node = new node_t(node_t::O_LT); if (in.peek() == '=') { in.get(c); - node->type = O_LTE; + node->type = node_t::O_LTE; } node->left = prev; - node->right = parse_add_expr(in, ledger); + node->right = parse_add_expr(in); break; } case '>': { node_t * prev = node; - node = new node_t(O_GT); + node = new node_t(node_t::O_GT); if (in.peek() == '=') { in.get(c); - node->type = O_GTE; + node->type = node_t::O_GTE; } node->left = prev; - node->right = parse_add_expr(in, ledger); + node->right = parse_add_expr(in); break; } @@ -443,11 +443,11 @@ node_t * parse_logic_expr(std::istream& in, ledger_t * ledger) return node; } -node_t * parse_expr(std::istream& in, ledger_t * ledger) +node_t * parse_expr(std::istream& in) { node_t * node = NULL; - node = parse_logic_expr(in, ledger); + node = parse_logic_expr(in); if (node && ! in.eof()) { char c = in.peek(); @@ -456,27 +456,27 @@ node_t * parse_expr(std::istream& in, ledger_t * ledger) switch (c) { case '&': { node_t * prev = node; - node = new node_t(O_AND); + node = new node_t(node_t::O_AND); node->left = prev; - node->right = parse_logic_expr(in, ledger); + node->right = parse_logic_expr(in); break; } case '|': { node_t * prev = node; - node = new node_t(O_OR); + node = new node_t(node_t::O_OR); node->left = prev; - node->right = parse_logic_expr(in, ledger); + node->right = parse_logic_expr(in); break; } case '?': { node_t * prev = node; - node = new node_t(O_QUES); + node = new node_t(node_t::O_QUES); node->left = prev; - node_t * choices = new node_t(O_COL); + node_t * choices = new node_t(node_t::O_COL); node->right = choices; - choices->left = parse_logic_expr(in, ledger); + choices->left = parse_logic_expr(in); c = in.peek(); if (c != ':') { std::ostringstream err; @@ -484,7 +484,7 @@ node_t * parse_expr(std::istream& in, ledger_t * ledger) throw expr_error(err.str()); } in.get(c); - choices->right = parse_logic_expr(in, ledger); + choices->right = parse_logic_expr(in); break; } @@ -512,43 +512,43 @@ namespace ledger { static void dump_tree(std::ostream& out, node_t * node) { switch (node->type) { - case CONSTANT_A: out << "CONST[" << node->constant_a << "]"; break; - case CONSTANT_T: out << "DATE/TIME[" << node->constant_t << "]"; break; - case AMOUNT: out << "AMOUNT"; break; - case COST: out << "COST"; break; - case DATE: out << "DATE"; break; - case INDEX: out << "INDEX"; break; - case BALANCE: out << "BALANCE"; break; - case COST_BALANCE: out << "COST_BALANCE"; break; - case TOTAL: out << "TOTAL"; break; - case COST_TOTAL: out << "COST_TOTAL"; break; - case BEGIN_DATE: out << "BEGIN"; break; - case END_DATE: out << "END"; break; - - case F_ARITH_MEAN: + case node_t::CONSTANT_A: out << "CONST[" << node->constant_a << "]"; break; + case node_t::CONSTANT_T: out << "DATE/TIME[" << node->constant_t << "]"; break; + case node_t::AMOUNT: out << "AMOUNT"; break; + case node_t::COST: out << "COST"; break; + case node_t::DATE: out << "DATE"; break; + case node_t::INDEX: out << "INDEX"; break; + case node_t::BALANCE: out << "BALANCE"; break; + case node_t::COST_BALANCE: out << "COST_BALANCE"; break; + case node_t::TOTAL: out << "TOTAL"; break; + case node_t::COST_TOTAL: out << "COST_TOTAL"; break; + case node_t::BEGIN_DATE: out << "BEGIN"; break; + case node_t::END_DATE: out << "END"; break; + + case node_t::F_ARITH_MEAN: out << "MEAN("; dump_tree(out, node->left); out << ")"; break; - case F_NEG: + case node_t::F_NEG: out << "ABS("; dump_tree(out, node->left); out << ")"; break; - case F_ABS: + case node_t::F_ABS: out << "ABS("; dump_tree(out, node->left); out << ")"; break; - case F_REGEXP: + case node_t::F_REGEXP: assert(node->mask); out << "RE(" << node->mask->pattern << ")"; break; - case F_VALUE: + case node_t::F_VALUE: out << "VALUE("; dump_tree(out, node->left); if (node->right) { @@ -558,12 +558,12 @@ static void dump_tree(std::ostream& out, node_t * node) out << ")"; break; - case O_NOT: + case node_t::O_NOT: out << "!"; dump_tree(out, node->left); break; - case O_QUES: + case node_t::O_QUES: dump_tree(out, node->left); out << "?"; dump_tree(out, node->right->left); @@ -571,38 +571,38 @@ static void dump_tree(std::ostream& out, node_t * node) dump_tree(out, node->right->right); break; - case O_AND: - case O_OR: - case O_EQ: - case O_LT: - case O_LTE: - case O_GT: - case O_GTE: - case O_ADD: - case O_SUB: - case O_MUL: - case O_DIV: + case node_t::O_AND: + case node_t::O_OR: + case node_t::O_EQ: + case node_t::O_LT: + case node_t::O_LTE: + case node_t::O_GT: + case node_t::O_GTE: + case node_t::O_ADD: + case node_t::O_SUB: + case node_t::O_MUL: + case node_t::O_DIV: out << "("; dump_tree(out, node->left); switch (node->type) { - case O_AND: out << " & "; break; - case O_OR: out << " | "; break; - case O_EQ: out << "="; break; - case O_LT: out << "<"; break; - case O_LTE: out << "<="; break; - case O_GT: out << ">"; break; - case O_GTE: out << ">="; break; - case O_ADD: out << "+"; break; - case O_SUB: out << "-"; break; - case O_MUL: out << "*"; break; - case O_DIV: out << "/"; break; + case node_t::O_AND: out << " & "; break; + case node_t::O_OR: out << " | "; break; + case node_t::O_EQ: out << "="; break; + case node_t::O_LT: out << "<"; break; + case node_t::O_LTE: out << "<="; break; + case node_t::O_GT: out << ">"; break; + case node_t::O_GTE: out << ">="; break; + case node_t::O_ADD: out << "+"; break; + case node_t::O_SUB: out << "-"; break; + case node_t::O_MUL: out << "*"; break; + case node_t::O_DIV: out << "/"; break; default: assert(0); break; } dump_tree(out, node->right); out << ")"; break; - case LAST: + case node_t::LAST: default: assert(0); break; @@ -7,55 +7,55 @@ namespace ledger { -enum kind_t { - // Constants - CONSTANT_A, - CONSTANT_T, - - // Item details - AMOUNT, - COST, - DATE, - INDEX, - - // Item totals - BALANCE, - COST_BALANCE, - TOTAL, - COST_TOTAL, - - // Constraint details - BEGIN_DATE, - END_DATE, - - // Functions - F_ARITH_MEAN, - F_VALUE, - F_NEG, - F_ABS, - F_REGEXP, - - // Binary operators - O_ADD, - O_SUB, - O_MUL, - O_DIV, - O_EQ, - O_LT, - O_LTE, - O_GT, - O_GTE, - O_NOT, - O_AND, - O_OR, - O_QUES, - O_COL, - - LAST -}; - struct node_t { + enum kind_t { + // Constants + CONSTANT_A, + CONSTANT_T, + + // Item details + AMOUNT, + COST, + DATE, + INDEX, + + // Item totals + BALANCE, + COST_BALANCE, + TOTAL, + COST_TOTAL, + + // Constraint details + BEGIN_DATE, + END_DATE, + + // Functions + F_ARITH_MEAN, + F_VALUE, + F_NEG, + F_ABS, + F_REGEXP, + + // Binary operators + O_ADD, + O_SUB, + O_MUL, + O_DIV, + O_EQ, + O_LT, + O_LTE, + O_GT, + O_GTE, + O_NOT, + O_AND, + O_OR, + O_QUES, + O_COL, + + LAST + }; + kind_t type; node_t * left; node_t * right; @@ -78,18 +78,18 @@ struct node_t const std::time_t end = -1) const; }; -node_t * parse_expr(std::istream& in, ledger_t * ledger); +node_t * parse_expr(std::istream& in); -inline node_t * parse_expr(const char * p, ledger_t * ledger) { +inline node_t * parse_expr(const char * p) { std::istringstream stream(p); - return parse_expr(stream, ledger); + return parse_expr(stream); } -inline node_t * parse_expr(const std::string& str, ledger_t * ledger) { - return parse_expr(str.c_str(), ledger); +inline node_t * parse_expr(const std::string& str) { + return parse_expr(str.c_str()); } -inline node_t * find_node(node_t * node, kind_t type) { +inline node_t * find_node(node_t * node, node_t::kind_t type) { node_t * result = NULL; if (node->type == type) result = node; @@ -1,4 +1,5 @@ #include "format.h" +#include "error.h" namespace ledger { @@ -24,20 +25,36 @@ std::string maximal_account_name(const item_t * item, return name; } -std::string format_t::report_line(const item_t * item, - const item_t * displayed_parent) const +node_t * format_t::value_expr = NULL; +node_t * format_t::total_expr = NULL; + +element_t * format_t::parse_elements(const std::string& fmt) { - std::string result; + element_t * result = NULL; + element_t * current = NULL; + std::string str; - for (const char * p = format_string.c_str(); *p; p++) { + for (const char * p = fmt.c_str(); *p; p++) { if (*p == '%') { - bool leftalign = false; - int width = 0; - int strict_width = 0; + if (! result) { + current = result = new element_t; + } else { + current->next = new element_t; + current = current->next; + } + + if (! str.empty()) { + current->type = element_t::STRING; + current->chars = str; + str = ""; + + current->next = new element_t; + current = current->next; + } ++p; if (*p == '-') { - leftalign = true; + current->align_left = true; ++p; } @@ -45,7 +62,7 @@ std::string format_t::report_line(const item_t * item, while (*p && std::isdigit(*p)) num += *p++; if (! num.empty()) - width = std::atol(num.c_str()); + current->min_width = std::atol(num.c_str()); if (*p == '.') { ++p; @@ -53,132 +70,172 @@ std::string format_t::report_line(const item_t * item, while (*p && std::isdigit(*p)) num += *p++; if (! num.empty()) { - strict_width = std::atol(num.c_str()); - if (width == 0) - width = strict_width; + current->max_width = std::atol(num.c_str()); + if (current->min_width == 0) + current->min_width = current->max_width; } } - std::ostringstream out; - - if (leftalign) - out << std::left; - else - out << std::right; - - if (width > 0) - out.width(width); - switch (*p) { case '%': - out << "%"; + current->type = element_t::STRING; + current->chars = "%"; break; - case '(': { + case '(': ++p; num = ""; while (*p && *p != ')') num += *p++; - assert(*p == ')'); + if (*p != ')') + throw format_error("Missing ')'"); - node_t * style = parse_expr(num, NULL); - balance_t value = style->compute(item); - value.write(out, width, strict_width > 0 ? strict_width : width); - delete style; + current->type = element_t::VALUE_EXPR; + current->val_expr = parse_expr(num); break; - } - case '[': { + case '[': ++p; num = ""; while (*p && *p != ']') num += *p++; - assert(*p == ']'); - - if (item->date != -1) { - char buf[256]; - std::strftime(buf, 255, num.c_str(), std::gmtime(&item->date)); - out << (strict_width == 0 ? buf : truncated(buf, strict_width)); - } else { - out << " "; - } + if (*p != ']') + throw format_error("Missing ']'"); + + current->type = element_t::DATE_STRING; + current->chars = num; break; - } - case 'd': { - if (item->date != -1) { - char buf[32]; - std::strftime(buf, 31, "%Y/%m/%d", std::gmtime(&item->date)); - out << (strict_width == 0 ? buf : truncated(buf, strict_width)); - } else { - out << " "; - } + case 'd': + current->type = element_t::DATE_STRING; + current->chars = "%Y/%m/%d"; break; + + case 'p': current->type = element_t::PAYEE; break; + case 'n': current->type = element_t::ACCOUNT_NAME; break; + case 'N': current->type = element_t::ACCOUNT_FULLNAME; break; + case 't': current->type = element_t::VALUE; break; + case 'T': current->type = element_t::TOTAL; break; + case '_': current->type = element_t::SPACER; break; } + } else { + str += *p; + } + } - case 'p': - out << (strict_width == 0 ? - item->payee : truncated(item->payee, strict_width)); - break; + if (! str.empty()) { + if (! result) { + current = result = new element_t; + } else { + current->next = new element_t; + current = current->next; + } + current->type = element_t::STRING; + current->chars = str; + } - case 'n': - if (item->account) { - std::string name = maximal_account_name(item, displayed_parent); - out << (strict_width == 0 ? name : truncated(name, strict_width)); - } else { - out << " "; - } - break; + return result; +} - case 'N': - if (item->account) - out << (strict_width == 0 ? - item->account->fullname() : - truncated(item->account->fullname(), strict_width)); - else - out << " "; - break; +void format_t::format_elements(std::ostream& out, const item_t * item, + const item_t * displayed_parent) const +{ + std::string result; - case 't': - if (value_style) { - balance_t value = compute_value(item); - value.write(out, width, strict_width > 0 ? strict_width : width); - } - break; + for (const element_t * elem = elements; + elem; + elem = elem->next) { + if (elem->align_left) + out << std::left; + else + out << std::right; + + if (elem->min_width > 0) + out.width(elem->min_width); + + switch (elem->type) { + case element_t::STRING: + out << elem->chars;; + break; + + case element_t::VALUE_EXPR: { + balance_t value = elem->val_expr->compute(item); + value.write(out, elem->min_width, + elem->max_width > 0 ? elem->max_width : elem->min_width); + break; + } - case 'T': - if (total_style) { - balance_t value = compute_total(item); - value.write(out, width, strict_width > 0 ? strict_width : width); - } - break; + case element_t::DATE_STRING: + if (item->date != -1) { + char buf[256]; + std::strftime(buf, 255, elem->chars.c_str(), std::gmtime(&item->date)); + out << (elem->max_width == 0 ? buf : truncated(buf, elem->max_width)); + } else { + out << " "; + } + break; + + case element_t::PAYEE: + out << (elem->max_width == 0 ? + item->payee : truncated(item->payee, elem->max_width)); + break; + + case element_t::ACCOUNT_NAME: + if (item->account) { + std::string name = maximal_account_name(item, displayed_parent); + out << (elem->max_width == 0 ? name : truncated(name, elem->max_width)); + } else { + out << " "; + } + break; - case '_': { - int depth = 0; - for (const item_t * i = item; i->parent; i = i->parent) - depth++; + case element_t::ACCOUNT_FULLNAME: + if (item->account) + out << (elem->max_width == 0 ? + item->account->fullname() : + truncated(item->account->fullname(), elem->max_width)); + else + out << " "; + break; + + case element_t::VALUE: { + balance_t value = compute_value(item); + value.write(out, elem->min_width, + elem->max_width > 0 ? elem->max_width : elem->min_width); + break; + } - for (const item_t * i = item->parent; - i && i->account && i != displayed_parent; - i = i->parent) - depth--; + case element_t::TOTAL: { + balance_t value = compute_total(item); + value.write(out, elem->min_width, + elem->max_width > 0 ? elem->max_width : elem->min_width); + break; + } - while (--depth >= 0) { - if (width > 0 || strict_width > 0) - out.width(width > strict_width ? width : strict_width); - out << " "; - } - break; - } + case element_t::SPACER: { + int depth = 0; + for (const item_t * i = item; i->parent; i = i->parent) + depth++; + + for (const item_t * i = item->parent; + i && i->account && i != displayed_parent; + i = i->parent) + depth--; + + while (--depth >= 0) { + if (elem->min_width > 0 || elem->max_width > 0) + out.width(elem->min_width > elem->max_width ? + elem->min_width : elem->max_width); + out << " "; } + break; + } - result += out.str(); - } else { - result += *p; + default: + assert(0); + break; } } - - return result; } } // namespace ledger @@ -11,56 +11,89 @@ namespace ledger { std::string truncated(const std::string& str, unsigned int width); std::string maximal_account_name(const item_t * item, const item_t * parent); -struct format_t +struct element_t { - std::string format_string; - node_t * value_style; - node_t * total_style; + enum kind_t { + STRING, + VALUE_EXPR, + DATE_STRING, + PAYEE, + ACCOUNT_NAME, + ACCOUNT_FULLNAME, + VALUE, + TOTAL, + SPACER + }; + + bool align_left; + unsigned int min_width; + unsigned int max_width; + + kind_t type; + std::string chars; + node_t * val_expr; + + struct element_t * next; - format_t() { - value_style = NULL; - total_style = NULL; + element_t() : align_left(false), min_width(0), max_width(0), + type(STRING), val_expr(NULL), next(NULL) {} + + ~element_t() { + if (val_expr) delete val_expr; + if (next) delete next; // recursive, but not too deep } +}; + +struct format_t +{ + element_t * elements; + static node_t * value_expr; + static node_t * total_expr; + + format_t(const std::string& _format) { + elements = parse_elements(_format); + } ~format_t() { - if (value_style) delete value_style; - if (total_style) delete total_style; + if (elements) delete elements; } + static element_t * parse_elements(const std::string& fmt); + + void format_elements(std::ostream& out, const item_t * item, + const item_t * displayed_parent = NULL) const; + #if 1 - balance_t compute_value(const item_t * item) const { - if (value_style) - return value_style->compute(item); + static balance_t compute_value(const item_t * item) { + if (value_expr) + return value_expr->compute(item); else return balance_t(); } - balance_t compute_total(const item_t * item) const { - if (total_style) - return total_style->compute(item); + static balance_t compute_total(const item_t * item) { + if (total_expr) + return total_expr->compute(item); else return balance_t(); } #else - balance_t compute_value(const item_t * item, - const constraints_t& constraints) const { - if (value_style) - return value_style->compute(item, constraints.begin(), constraints.end()); + static balance_t compute_value(const item_t * item, + const constraints_t& constraints) { + if (value_expr) + return value_expr->compute(item, constraints.begin(), constraints.end()); else return balance_t(); } - balance_t compute_total(const item_t * item, - const constraints_t& constraints) const { - if (total_style) - return total_style->compute(item, constraints.begin(), constraints.end()); + static balance_t compute_total(const item_t * item, + const constraints_t& constraints) { + if (total_expr) + return total_expr->compute(item, constraints.begin(), constraints.end()); else return balance_t(); } #endif - - std::string report_line(const item_t * item, - const item_t * displayed_parent = NULL) const; }; } // namespace ledger @@ -113,7 +113,7 @@ static void endElement(void *userData, const char *name) } else if (std::strcmp(name, "gnc:commodity") == 0) { assert(curr_comm); - curr_ledger->add_commodity(curr_comm); + commodity_t::add_commodity(curr_comm); curr_comm = NULL; } else if (std::strcmp(name, "gnc:transaction") == 0) { @@ -166,9 +166,9 @@ static void dataHandler(void *userData, const char *s, int len) if (curr_comm) curr_comm->symbol = std::string(s, len); else if (curr_account) - curr_account_comm = curr_ledger->find_commodity(std::string(s, len)); + curr_account_comm = commodity_t::find_commodity(std::string(s, len)); else if (curr_entry) - entry_comm = curr_ledger->find_commodity(std::string(s, len)); + entry_comm = commodity_t::find_commodity(std::string(s, len)); break; case COMM_NAME: @@ -271,8 +271,8 @@ int parse_gnucash(std::istream& in, ledger_t * ledger, account_t * master) // GnuCash uses the USD commodity without defining it, which really // means $. commodity_t * usd = new commodity_t("$", 2, COMMODITY_STYLE_THOUSANDS); - ledger->add_commodity(usd); - ledger->add_commodity(usd, "USD"); + commodity_t::add_commodity(usd); + commodity_t::add_commodity(usd, "USD"); XML_Parser parser = XML_ParserCreate(NULL); current_parser = parser; @@ -12,7 +12,7 @@ item_t * walk_accounts(const account_t * account, { item_t * item = new item_t; item->account = account; - item->date = constraints.end(); + item->date = constraints.end() - 1; for (constrained_transactions_list_const_iterator i(account->transactions.begin(), @@ -112,6 +112,7 @@ item_t * walk_entries(entries_list::const_iterator begin, item_t * subitem = new item_t; subitem->parent = item; subitem->date = item->date; + subitem->payee = item->payee; subitem->account = (*j)->account; subitem->value = *(*j); item->subitems.push_back(subitem); @@ -125,6 +126,7 @@ item_t * walk_entries(entries_list::const_iterator begin, item_t * subitem = new item_t; subitem->parent = item; subitem->date = item->date; + subitem->payee = item->payee; subitem->account = (*k)->account; subitem->value = *(*k); if (constraints.show_inverted) @@ -12,11 +12,6 @@ ledger_t::~ledger_t() { delete master; - for (commodities_map::iterator i = commodities.begin(); - i != commodities.end(); - i++) - delete (*i).second; - // Don't bother unhooking each entry's transactions from the // accounts they refer to, because all accounts are about to // be deleted. @@ -26,22 +21,6 @@ ledger_t::~ledger_t() delete *i; } -commodity_t * ledger_t::find_commodity(const std::string& symbol, - bool auto_create) -{ - commodities_map::const_iterator i = commodities.find(symbol); - if (i != commodities.end()) - return (*i).second; - - if (auto_create) { - commodity_t * commodity = new commodity_t(symbol); - add_commodity(commodity); - return commodity; - } - - return NULL; -} - bool ledger_t::add_entry(entry_t * entry) { entries.push_back(entry); @@ -73,7 +52,7 @@ bool ledger_t::remove_entry(entry_t * entry) return true; } -int parse_ledger_file(char * p, ledger_t * book) +int parse_ledger_file(char * p, ledger_t * journal) { char * sep = std::strrchr(p, '='); if (sep) *sep++ = '\0'; @@ -82,11 +61,11 @@ int parse_ledger_file(char * p, ledger_t * book) account_t * master; if (sep) - master = book->find_account(sep); + master = journal->find_account(sep); else - master = book->master; + master = journal->master; - book->sources.push_back(p); + journal->sources.push_back(p); unsigned long magic; std::istream::pos_type start = stream.tellg(); @@ -94,9 +73,9 @@ int parse_ledger_file(char * p, ledger_t * book) stream.seekg(start); if (magic == magic_number) - return read_binary_ledger(stream, "", book, master); + return read_binary_ledger(stream, "", journal, master); else - return parse_textual_ledger(stream, book, master); + return parse_textual_ledger(stream, journal, master); } } // namespace ledger @@ -50,8 +50,6 @@ class amount_t base_type quantity; // amount, to MAX_PRECISION commodity_t * commodity; - static commodity_t * null_commodity; - bool valid() const { if (quantity) return commodity != NULL; @@ -185,10 +183,10 @@ class amount_t operator std::string() const; - void parse(std::istream& in, ledger_t * ledger = NULL); - void parse(const std::string& str, ledger_t * ledger = NULL) { + void parse(std::istream& in); + void parse(const std::string& str) { std::istringstream stream(str); - parse(stream, ledger); + parse(stream); } void write_quantity(std::ostream& out) const; @@ -223,6 +221,9 @@ std::ostream& operator<<(std::ostream& out, const amount_t& amt); typedef std::map<const std::time_t, amount_t> history_map; typedef std::pair<const std::time_t, amount_t> history_pair; +typedef std::map<const std::string, commodity_t *> commodities_map; +typedef std::pair<const std::string, commodity_t *> commodities_pair; + class commodity_t { public: @@ -235,11 +236,34 @@ class commodity_t amount_t conversion; unsigned long ident; + // If set, this global function pointer is called to determine + // whether prices have been updated in the meanwhile. + static void (*updater)(commodity_t * commodity, const std::time_t date, const amount_t& price, const std::time_t moment); + // This map remembers all commodities that have been + // defined thus far. + + static commodities_map commodities; + + static void add_commodity(commodity_t * commodity, + const std::string symbol = "") { + commodities.insert(commodities_pair((symbol.empty() ? + commodity->symbol : symbol), + commodity)); + } + static bool remove_commodity(commodity_t * commodity) { + commodities_map::size_type n = commodities.erase(commodity->symbol); + return n > 0; + } + static commodity_t * find_commodity(const std::string& symbol, + bool auto_create = false); + + // Now the per-object constructor and methods + commodity_t(const std::string& _symbol = "", unsigned int _precision = 2, unsigned int _flags = COMMODITY_STYLE_DEFAULTS) @@ -377,17 +401,14 @@ inline std::ostream& operator<<(std::ostream& out, const account_t& acct) { } -typedef std::map<const std::string, commodity_t *> commodities_map; -typedef std::pair<const std::string, commodity_t *> commodities_pair; - typedef std::list<entry_t *> entries_list; class ledger_t { public: - account_t * master; - commodities_map commodities; - entries_list entries; + account_t * master; + entries_list entries; + std::list<std::string> sources; ledger_t() { @@ -409,23 +430,11 @@ class ledger_t return master->find_account(name, auto_create); } - void add_commodity(commodity_t * commodity, const std::string symbol = "") { - commodities.insert(commodities_pair(symbol.empty() ? - commodity->symbol : symbol, commodity)); - } - bool remove_commodity(commodity_t * commodity) { - commodities_map::size_type n = commodities.erase(commodity->symbol); - return n > 0; - } - - commodity_t * find_commodity(const std::string& symbol, - bool auto_create = false); - bool add_entry(entry_t * entry); bool remove_entry(entry_t * entry); }; -int parse_ledger_file(char * p, ledger_t * book); +int parse_ledger_file(char * p, ledger_t * journal); } // namespace ledger @@ -48,7 +48,7 @@ void show_balances(std::ostream& out, if (match && constraints(*i) && ((*i)->subitems.size() != 1 || (*i)->total != (*i)->subitems[0]->total)) { - out << format.report_line(*i, parent); + format.format_elements(out, *i, parent); parent = *i; } @@ -69,9 +69,10 @@ void balance_report(std::ostream& out, show_balances(out, top->subitems, constraints, format, top); - if (constraints.show_subtotals && top->subitems.size() > 1 && top->total) - std::cout << "--------------------\n" - << format.report_line(top); + if (constraints.show_subtotals && top->subitems.size() > 1 && top->total) { + std::cout << "--------------------\n"; + format.format_elements(std::cout, top); + } } @@ -81,7 +82,7 @@ void balance_report(std::ostream& out, // static const std::string reg_fmt - = "%10d %-.20p %/%-.22N %12.66t %12.80T\n"; + = "%10d %-.20p %-.22N %12.66t %12.80T\n%/%22_ %-.22N %12.66t %12.80T\n"; static bool show_commodities_revalued = false; static bool show_commodities_revalued_only = false; @@ -91,12 +92,10 @@ static void report_value_change(std::ostream& out, const balance_pair_t& balance, const balance_pair_t& prev_balance, const constraints_t& constraints, - const format_t& format, - const std::string& first_line_format, - const std::string& next_lines_format) + const format_t& first_line_format, + const format_t& next_lines_format) { - static std::time_t prev_date = -1; - + static std::time_t prev_date = -1; if (prev_date == -1) { prev_date = date; return; @@ -105,11 +104,11 @@ static void report_value_change(std::ostream& out, item_t temp; temp.date = prev_date; temp.total = prev_balance; - balance_t prev_bal = format.compute_total(&temp); + balance_t prev_bal = format_t::compute_total(&temp); temp.date = date; temp.total = balance; - balance_t cur_bal = format.compute_total(&temp); + balance_t cur_bal = format_t::compute_total(&temp); if (balance_t diff = cur_bal - prev_bal) { temp.value = diff; @@ -117,17 +116,8 @@ static void report_value_change(std::ostream& out, temp.payee = "Commodities revalued"; if (constraints(&temp)) { - format_t copy = format; - - copy.format_string = first_line_format; - out << copy.report_line(&temp); - - copy.format_string = next_lines_format; - out << copy.report_line(&temp); - - // Prevent double-deletion - copy.value_style = NULL; - copy.total_style = NULL; + first_line_format.format_elements(out, &temp); + next_lines_format.format_elements(out, &temp); } } @@ -137,25 +127,12 @@ static void report_value_change(std::ostream& out, void register_report(std::ostream& out, item_t * top, const constraints_t& constraints, - const format_t& format) + const format_t& first_line_format, + const format_t& next_lines_format) { if (constraints.sort_order) top->sort(constraints.sort_order); - format_t copy = format; - - std::string first_line_format; - std::string next_lines_format; - - const char * f = format.format_string.c_str(); - if (const char * p = std::strstr(f, "%/")) { - first_line_format = std::string(f, 0, p - f); - next_lines_format = std::string(p + 2); - } else { - first_line_format = format.format_string; - next_lines_format = format.format_string; - } - balance_pair_t balance; balance_pair_t last_reported; account_t splits(NULL, "<Total>"); @@ -163,13 +140,6 @@ void register_report(std::ostream& out, for (items_deque::const_iterator i = top->subitems.begin(); i != top->subitems.end(); i++) { - copy.format_string = first_line_format; - - std::string header = copy.report_line(*i, top); - unsigned int header_len = header.length(); - - copy.format_string = next_lines_format; - bool first = true; if ((*i)->subitems.size() > 1 && ! constraints.show_expanded) { @@ -187,14 +157,14 @@ void register_report(std::ostream& out, bool show = constraints(&summary); if (show && show_commodities_revalued) report_value_change(out, summary.date, balance, last_reported, - constraints, copy, first_line_format, - next_lines_format); + constraints, first_line_format, next_lines_format); balance += summary.value; if (show) { if (! show_commodities_revalued_only) - out << header << copy.report_line(&summary, *i); + first_line_format.format_elements(out, *i, top); + if (show_commodities_revalued) last_reported = balance; } @@ -207,7 +177,7 @@ void register_report(std::ostream& out, bool show = constraints(*j); if (show && first && show_commodities_revalued) { report_value_change(out, (*i)->date, balance, last_reported, - constraints, copy, first_line_format, + constraints, first_line_format, next_lines_format); if (show_commodities_revalued_only) first = false; @@ -219,13 +189,12 @@ void register_report(std::ostream& out, if (! show_commodities_revalued_only) { if (first) { first = false; - out << header; + first_line_format.format_elements(out, *j, *i); } else { - out.width(header_len); - out << " "; + next_lines_format.format_elements(out, *j, *i); } - out << copy.report_line(*j, *i); } + if (show_commodities_revalued) last_reported = balance; } @@ -235,12 +204,7 @@ void register_report(std::ostream& out, if (show_commodities_revalued) report_value_change(out, constraints.end(), balance, last_reported, - constraints, copy, first_line_format, - next_lines_format); - - // To stop these from getting deleted when copy goes out of scope - copy.value_style = NULL; - copy.total_style = NULL; + constraints, first_line_format, next_lines_format); } @@ -377,7 +341,7 @@ bool add_new_entry(int index, int argc, char **argv, ledger_t * ledger) } -void set_price_conversion(const std::string& setting, ledger_t * ledger) +void set_price_conversion(const std::string& setting) { char buf[128]; std::strcpy(buf, setting.c_str()); @@ -392,9 +356,9 @@ void set_price_conversion(const std::string& setting, ledger_t * ledger) *p++ = '\0'; amount_t price; - price.parse(p, ledger); + price.parse(p); - commodity_t * commodity = ledger->find_commodity(c, true); + commodity_t * commodity = commodity_t::find_commodity(c, true); commodity->set_conversion(price); } } @@ -402,7 +366,6 @@ void set_price_conversion(const std::string& setting, ledger_t * ledger) static long pricing_leeway = 24 * 3600; static std::string price_db; -static ledger_t * current_ledger = NULL; static bool cache_dirty = false; void download_price_quote(commodity_t * commodity, @@ -438,7 +401,7 @@ void download_price_quote(commodity_t * commodity, if (p) *p = '\0'; amount_t current; - current.parse(buf, current_ledger); + current.parse(buf); commodity->add_price(now, current); @@ -506,13 +469,12 @@ static void show_help(std::ostream& out) int main(int argc, char * argv[]) { std::list<std::string> files; - ledger::ledger_t * book = new ledger::ledger_t; + ledger::ledger_t * journal = new ledger::ledger_t; ledger::constraints_t constraints; - ledger::format_t format; - - std::string sort_order; - std::string value_style = "a"; - std::string total_style = "T"; + std::string format_string; + std::string sort_order; + std::string value_expr = "a"; + std::string total_expr = "T"; // Initialize some variables based on environment variable settings @@ -540,19 +502,17 @@ int main(int argc, char * argv[]) if (access(p, R_OK) != -1) { std::ifstream instr(p); if (! ledger::read_binary_ledger(instr, std::getenv("LEDGER"), - book)) { + journal)) { // We need to throw away what we've read, and create a new // ledger - delete book; - book = new ledger::ledger_t; + delete journal; + journal = new ledger::ledger_t; } else { ledger::cache_dirty = false; } } } - ledger::current_ledger = book; - // Parse the command-line options int c, index; @@ -584,7 +544,7 @@ int main(int argc, char * argv[]) break; case 'p': - ledger::set_price_conversion(optarg, book); + ledger::set_price_conversion(optarg); break; // Constraint options @@ -593,7 +553,6 @@ int main(int argc, char * argv[]) break; case 'b': - constraints.have_beginning = true; if (! ledger::parse_date(optarg, &constraints.begin_date)) { std::cerr << "Error: Bad begin date: " << optarg << std::endl; return 1; @@ -601,7 +560,6 @@ int main(int argc, char * argv[]) break; case 'e': - constraints.have_ending = true; if (! ledger::parse_date(optarg, &constraints.end_date)) { std::cerr << "Error: Bad end date: " << optarg << std::endl; return 1; @@ -609,8 +567,7 @@ int main(int argc, char * argv[]) break; case 'c': - constraints.end_date = std::time(NULL); - constraints.have_ending = true; + constraints.end_date = std::time(NULL); break; case 'd': @@ -635,7 +592,7 @@ int main(int argc, char * argv[]) // Customizing output case 'F': - format.format_string = optarg; + format_string = optarg; break; case 'M': @@ -663,7 +620,7 @@ int main(int argc, char * argv[]) break; case 'l': - constraints.predicate = ledger::parse_expr(optarg, book); + constraints.predicate = ledger::parse_expr(optarg); break; // Commodity reporting @@ -680,61 +637,61 @@ int main(int argc, char * argv[]) break; case 't': - value_style = optarg; + value_expr = optarg; break; case 'T': - total_style = optarg; + total_expr = optarg; break; case 'O': - value_style = "a"; - total_style = "T"; + value_expr = "a"; + total_expr = "T"; break; case 'B': - value_style = "c"; - total_style = "C"; + value_expr = "c"; + total_expr = "C"; break; case 'V': ledger::show_commodities_revalued = true; - value_style = "v"; - total_style = "V"; + value_expr = "v"; + total_expr = "V"; break; case 'G': ledger::show_commodities_revalued = ledger::show_commodities_revalued_only = true; - value_style = "c"; - total_style = "G"; + value_expr = "c"; + total_expr = "G"; break; case 'A': - value_style = "a"; - total_style = "MT"; + value_expr = "a"; + total_expr = "MT"; break; case 'D': - value_style = "a"; - total_style = "DMT"; + value_expr = "a"; + total_expr = "DMT"; break; case 'Z': - value_style = "a"; - total_style = "MDMT"; + value_expr = "a"; + total_expr = "MDMT"; break; case 'W': - value_style = "a"; - total_style = "MD(MT*(d-b/e-b))"; + value_expr = "a"; + total_expr = "MD(MT*(d-b/e-b))"; break; case 'X': - value_style = "a"; - total_style = "a+MD(MT*(d-b/e-b))"; + value_expr = "a"; + total_expr = "a+MD(MT*(d-b/e-b))"; break; } } @@ -755,14 +712,14 @@ int main(int argc, char * argv[]) if (files.empty()) { if (char * p = std::getenv("LEDGER")) for (p = std::strtok(p, ":"); p; p = std::strtok(NULL, ":")) - entry_count += parse_ledger_file(p, book); + entry_count += parse_ledger_file(p, journal); } else { for (std::list<std::string>::iterator i = files.begin(); i != files.end(); i++) { char buf[4096]; char * p = buf; std::strcpy(p, (*i).c_str()); - entry_count += parse_ledger_file(p, book); + entry_count += parse_ledger_file(p, journal); } } @@ -772,8 +729,8 @@ int main(int argc, char * argv[]) if (! ledger::price_db.empty()) { const char * path = ledger::price_db.c_str(); std::ifstream db(path); - book->sources.push_back(path); - entry_count += ledger::parse_textual_ledger(db, book, book->master); + journal->sources.push_back(path); + entry_count += ledger::parse_textual_ledger(db, journal, journal->master); } } catch (ledger::error& err) { @@ -794,7 +751,7 @@ int main(int argc, char * argv[]) const std::string command = argv[index++]; if (command == "entry") - return add_new_entry(index, argc, argv, book) ? 0 : 1; + return add_new_entry(index, argc, argv, journal) ? 0 : 1; // Interpret the remaining arguments as regular expressions, used // for refining report results. @@ -814,77 +771,100 @@ int main(int argc, char * argv[]) // and total style strings if (! sort_order.empty()) - constraints.sort_order = ledger::parse_expr(sort_order, book); - format.value_style = ledger::parse_expr(value_style, book); - format.total_style = ledger::parse_expr(total_style, book); + constraints.sort_order = ledger::parse_expr(sort_order); + + // Setup the meaning of %t and %T encountered in format strings + + ledger::format_t::value_expr = ledger::parse_expr(value_expr); + ledger::format_t::total_expr = ledger::parse_expr(total_expr); // Now handle the command that was identified above. if (command == "print") { #if 0 - ledger::item_t * top - = ledger::walk_entries(book->entries.begin(), book->entries.end(), - constraints, format); - ledger::entry_report(std::cout, top, format); + if (ledger::item_t * top + = ledger::walk_entries(journal->entries.begin(), + journal->entries.end(), + constraints)) { + ledger::format_t * format = new ledger::format_t(format_string); + ledger::entry_report(std::cout, top, *format); #ifdef DEBUG - delete top; + delete top; + delete format; #endif + } #endif } else if (command == "equity") { #if 0 - ledger::item_t * top - = ledger::walk_accounts(book->master, constraints); - - ledger::entry_report(std::cout, top, constraints, format); - + if (ledger::item_t * top + = ledger::walk_accounts(journal->master, constraints)) { + ledger::format_t * format = new ledger::format_t(format_string); + ledger::entry_report(std::cout, top, constraints, *format); #ifdef DEBUG - delete top; + delete top; + delete format; #endif + } #endif } else if (constraints.period == ledger::PERIOD_NONE && ! constraints.sort_order && ! constraints.show_related && (command == "balance" || command == "bal")) { - if (format.format_string.empty()) - format.format_string = ledger::bal_fmt; - if (ledger::item_t * top - = ledger::walk_accounts(book->master, constraints)) { - ledger::balance_report(std::cout, top, constraints, format); + = ledger::walk_accounts(journal->master, constraints)) { + ledger::format_t * format + = new ledger::format_t(format_string.empty() ? + ledger::bal_fmt : format_string); + ledger::balance_report(std::cout, top, constraints, *format); #ifdef DEBUG + delete format; delete top; #endif } } else if (command == "balance" || command == "bal") { - if (format.format_string.empty()) - format.format_string = ledger::bal_fmt; - if (ledger::item_t * list - = ledger::walk_entries(book->entries.begin(), - book->entries.end(), constraints)) + = ledger::walk_entries(journal->entries.begin(), + journal->entries.end(), constraints)) if (ledger::item_t * top - = ledger::walk_items(list, book->master, constraints)) { - ledger::balance_report(std::cout, top, constraints, format); + = ledger::walk_items(list, journal->master, constraints)) { + ledger::format_t * format + = new ledger::format_t(format_string.empty() ? + ledger::bal_fmt : format_string); + ledger::balance_report(std::cout, top, constraints, *format); #ifdef DEBUG + delete format; delete top; delete list; #endif } } else if (command == "register" || command == "reg") { - if (format.format_string.empty()) - format.format_string = ledger::reg_fmt; - if (constraints.show_related) constraints.show_inverted = true; if (ledger::item_t * top - = ledger::walk_entries(book->entries.begin(), - book->entries.end(), constraints)) { - ledger::register_report(std::cout, top, constraints, format); + = ledger::walk_entries(journal->entries.begin(), + journal->entries.end(), constraints)) { + std::string first_line_format; + std::string next_lines_format; + + const char * f = (format_string.empty() ? + ledger::reg_fmt.c_str() : format_string.c_str()); + if (const char * p = std::strstr(f, "%/")) { + first_line_format = std::string(f, 0, p - f); + next_lines_format = std::string(p + 2); + } else { + first_line_format = format_string; + next_lines_format = format_string; + } + + ledger::format_t * format = new ledger::format_t(first_line_format); + ledger::format_t * nformat = new ledger::format_t(next_lines_format); + ledger::register_report(std::cout, top, constraints, *format, *nformat); #ifdef DEBUG + delete format; delete top; #endif } @@ -901,11 +881,24 @@ int main(int argc, char * argv[]) if (const char * p = std::getenv("LEDGER_CACHE")) { std::ofstream outstr(p); assert(std::getenv("LEDGER")); - ledger::write_binary_ledger(outstr, book, std::getenv("LEDGER")); + ledger::write_binary_ledger(outstr, journal, std::getenv("LEDGER")); } #ifdef DEBUG - delete book; + delete journal; + + if (ledger::format_t::value_expr) + delete ledger::format_t::value_expr; + if (ledger::format_t::total_expr) + delete ledger::format_t::total_expr; + + // jww (2004-07-30): This should be moved into some kind of + // "ledger::shutdown" function. + for (ledger::commodities_map::iterator i + = ledger::commodity_t::commodities.begin(); + i != ledger::commodity_t::commodities.end(); + i++) + delete (*i).second; #endif return 0; @@ -186,8 +186,8 @@ inline char peek_next_nonws(std::istream& in) return c; } -transaction_t * parse_transaction_text(char * line, ledger_t * ledger, - account_t * account, entry_t * entry) +transaction_t * parse_transaction_text(char * line, account_t * account, + entry_t * entry) { // The account will be determined later... @@ -208,10 +208,10 @@ transaction_t * parse_transaction_text(char * line, ledger_t * ledger, char * price_str = std::strchr(cost_str, '@'); if (price_str) { *price_str++ = '\0'; - xact->cost.parse(price_str, ledger); + xact->cost.parse(price_str); } - xact->amount.parse(cost_str, ledger); + xact->amount.parse(cost_str); if (price_str) xact->cost *= xact->amount; @@ -233,21 +233,21 @@ transaction_t * parse_transaction_text(char * line, ledger_t * ledger, xact->account = account->find_account(p); if (! xact->amount.commodity) - xact->amount.commodity = ledger->find_commodity("", true); + xact->amount.commodity = commodity_t::find_commodity("", true); if (! xact->cost.commodity) - xact->cost.commodity = ledger->find_commodity("", true); + xact->cost.commodity = commodity_t::find_commodity("", true); return xact; } -transaction_t * parse_transaction(std::istream& in, ledger_t * ledger, - account_t * account, entry_t * entry) +transaction_t * parse_transaction(std::istream& in, account_t * account, + entry_t * entry) { static char line[MAX_LINE + 1]; in.getline(line, MAX_LINE); linenum++; - return parse_transaction_text(line, ledger, account, entry); + return parse_transaction_text(line, account, entry); } class automated_transaction_t @@ -338,8 +338,7 @@ public: } }; -void parse_automated_transactions(std::istream& in, ledger_t * ledger, - account_t * account, +void parse_automated_transactions(std::istream& in, account_t * account, automated_transactions_t& auto_xacts) { static char line[MAX_LINE + 1]; @@ -359,7 +358,7 @@ void parse_automated_transactions(std::istream& in, ledger_t * ledger, transactions_list xacts; while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { - if (transaction_t * xact = parse_transaction(in, ledger, account, NULL)) { + if (transaction_t * xact = parse_transaction(in, account, NULL)) { if (! xact->amount) throw parse_error(path, linenum, "All automated transactions must have a value"); @@ -446,8 +445,7 @@ bool finalize_entry(entry_t * entry) return ! balance; } -entry_t * parse_entry(std::istream& in, ledger_t * ledger, - account_t * master) +entry_t * parse_entry(std::istream& in, account_t * master) { entry_t * curr = new entry_t; @@ -486,7 +484,7 @@ entry_t * parse_entry(std::istream& in, ledger_t * ledger, // Parse all of the transactions associated with this entry while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) - if (transaction_t * xact = parse_transaction(in, ledger, master, curr)) + if (transaction_t * xact = parse_transaction(in, master, curr)) curr->add_transaction(xact); // If there were no transactions, throw away the entry @@ -504,7 +502,7 @@ entry_t * parse_entry(std::istream& in, ledger_t * ledger, // Textual ledger parser // -unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger, +unsigned int parse_textual_ledger(std::istream& in, ledger_t * journal, account_t * master) { static char line[MAX_LINE + 1]; @@ -517,11 +515,11 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger, automated_transactions_t auto_xacts; if (! master) - master = ledger->master; + master = journal->master; account_stack.push_front(master); - path = ledger->sources.back(); + path = journal->sources.back(); linenum = 1; while (! in.eof()) { @@ -602,14 +600,14 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger, char buf[32]; std::sprintf(buf, "%fh", diff); amount_t amt; - amt.parse(buf, ledger); + amt.parse(buf); time_commodity = amt.commodity; transaction_t * xact = new transaction_t(curr, last_account, amt, amt, TRANSACTION_VIRTUAL); curr->add_transaction(xact); - if (! finalize_entry(curr) || ! ledger->add_entry(curr)) + if (! finalize_entry(curr) || ! journal->add_entry(curr)) assert(0); count++; @@ -650,9 +648,9 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger, parse_commodity(in, symbol); in >> line; // the price - price.parse(line, ledger); + price.parse(line); - commodity_t * commodity = ledger->find_commodity(symbol, true); + commodity_t * commodity = commodity_t::find_commodity(symbol, true); commodity->add_price(date, price); break; } @@ -663,7 +661,7 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger, in >> c; parse_commodity(in, symbol); - commodity_t * commodity = ledger->find_commodity(line, true); + commodity_t * commodity = commodity_t::find_commodity(line, true); commodity->flags |= (COMMODITY_STYLE_CONSULTED | COMMODITY_STYLE_NOMARKET); break; @@ -677,9 +675,9 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger, parse_commodity(in, symbol); in >> line; // the price - price.parse(line, ledger); + price.parse(line); - commodity_t * commodity = ledger->find_commodity(symbol, true); + commodity_t * commodity = commodity_t::find_commodity(symbol, true); commodity->set_conversion(price); break; } @@ -700,8 +698,7 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger, break; case '=': // automated transactions - parse_automated_transactions(in, ledger, account_stack.front(), - auto_xacts); + parse_automated_transactions(in, account_stack.front(), auto_xacts); break; case '@': { // account specific @@ -729,12 +726,12 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger, char * p = skip_ws(line); std::ifstream stream(p); - ledger->sources.push_back(p); + journal->sources.push_back(p); unsigned int curr_linenum = linenum; std::string curr_path = path; - count += parse_textual_ledger(stream, ledger, account_stack.front()); + count += parse_textual_ledger(stream, journal, account_stack.front()); linenum = curr_linenum; path = curr_path; @@ -743,11 +740,11 @@ unsigned int parse_textual_ledger(std::istream& in, ledger_t * ledger, default: { unsigned int first_line = linenum; - if (entry_t * entry = parse_entry(in, ledger, account_stack.front())) { + if (entry_t * entry = parse_entry(in, account_stack.front())) { if (! auto_xacts.automated_transactions.empty()) auto_xacts.extend_entry(entry); - if (ledger->add_entry(entry)) + if (journal->add_entry(entry)) count++; else throw parse_error(path, first_line, "Entry does not balance"); @@ -876,11 +873,11 @@ void print_textual_entry(std::ostream& out, entry_t * entry, bool shortcut) out << std::endl; } -void print_textual_ledger(std::ostream& out, ledger_t * ledger, +void print_textual_ledger(std::ostream& out, ledger_t * journal, bool shortcut) { - for (entries_list::const_iterator i = ledger->entries.begin(); - i != ledger->entries.end(); + for (entries_list::const_iterator i = journal->entries.begin(); + i != journal->entries.end(); i++) print_textual_entry(out, *i, shortcut); } @@ -891,12 +888,12 @@ void print_textual_ledger(std::ostream& out, ledger_t * ledger, int main(int argc, char *argv[]) { - book.sources.push_back(argv[1]); + journal.sources.push_back(argv[1]); std::ifstream stream(argv[1]); - ledger::ledger_t book; - int count = parse_textual_ledger(stream, &book, book.master); + ledger::ledger_t journal; + int count = parse_textual_ledger(stream, &journal, journal.master); std::cout << "Read " << count << " entries." << std::endl; - print_textual_ledger(std::cout, &book, true); + print_textual_ledger(std::cout, &journal, true); } #endif // PARSE_TEST |