diff options
-rw-r--r-- | amount.cc | 464 | ||||
-rw-r--r-- | balance.cc | 128 | ||||
-rw-r--r-- | gnucash.cc | 16 | ||||
-rw-r--r-- | ledger.cc | 23 | ||||
-rw-r--r-- | ledger.h | 22 | ||||
-rw-r--r-- | main.cc | 9 | ||||
-rw-r--r-- | parse.cc | 46 |
7 files changed, 424 insertions, 284 deletions
@@ -47,46 +47,27 @@ class gmp_amount : public amount return quantity_comm->symbol; } - virtual amount * copy() const { - gmp_amount * new_amt = new gmp_amount(); - new_amt->priced = priced; - mpz_set(new_amt->price, price); - new_amt->price_comm = price_comm; - mpz_set(new_amt->quantity, quantity); - new_amt->quantity_comm = quantity_comm; - return new_amt; - } - - virtual amount * value() const { - if (! priced) { - return copy(); - } else { - gmp_amount * new_amt = new gmp_amount(); - new_amt->priced = false; - multiply(new_amt->quantity, quantity, price); - new_amt->quantity_comm = price_comm; - return new_amt; - } - } + virtual amount * copy() const; + virtual amount * value() const; virtual operator bool() const; virtual void credit(const amount * other) { *this += *other; } + virtual void negate() { + mpz_ui_sub(quantity, 0, quantity); + } virtual void operator+=(const amount& other); virtual void parse(const char * num) { *this = num; } virtual amount& operator=(const char * num); - virtual operator std::string() const; - - static const std::string to_str(const commodity * comm, const mpz_t val); - - static void parse(mpz_t out, char * num); - static void round(mpz_t out, const mpz_t val, int prec); - static void multiply(mpz_t out, const mpz_t l, const mpz_t r); + virtual std::string as_str(bool full_prec) const; + virtual operator std::string() const { + return as_str(false); + } friend amount * create_amount(const char * value, const amount * price); }; @@ -141,46 +122,129 @@ amount * create_amount(const char * value, const amount * price) return a; } +static void round(mpz_t out, const mpz_t val, int prec) +{ + mpz_t divisor; + mpz_t quotient; + mpz_t remainder; + + mpz_init(divisor); + mpz_init(quotient); + mpz_init(remainder); + + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); + mpz_tdiv_qr(quotient, remainder, val, divisor); + + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec - 1); + mpz_mul_ui(divisor, divisor, 5); + if (mpz_cmp(remainder, divisor) >= 0) { + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); + mpz_sub(remainder, divisor, remainder); + mpz_add(out, val, remainder); + } else { + mpz_sub(out, val, remainder); + } + + mpz_clear(divisor); + mpz_clear(quotient); + mpz_clear(remainder); +} + +static void multiply(mpz_t out, const mpz_t l, const mpz_t r) +{ + mpz_t divisor; + + mpz_init(divisor); + + mpz_mul(out, l, r); + + // The number is at double-precision right now, so rounding at + // precision 0 effectively means rounding to the ordinary + // precision. + round(out, out, 0); + + // after multiplying, truncate to the correct precision + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); + mpz_tdiv_q(out, out, divisor); + + mpz_clear(divisor); +} + +amount * gmp_amount::copy() const +{ + gmp_amount * new_amt = new gmp_amount(); +#if 0 + // Don't copy the price + new_amt->priced = priced; + mpz_set(new_amt->price, price); + new_amt->price_comm = price_comm; +#endif + mpz_set(new_amt->quantity, quantity); + new_amt->quantity_comm = quantity_comm; + return new_amt; +} + +amount * gmp_amount::value() const +{ + if (! priced) { + return copy(); + } else { + gmp_amount * new_amt = new gmp_amount(); + new_amt->priced = false; + multiply(new_amt->quantity, quantity, price); + new_amt->quantity_comm = price_comm; + return new_amt; + } +} + gmp_amount::operator bool() const { mpz_t copy; mpz_init_set(copy, quantity); assert(quantity_comm); - gmp_amount::round(copy, copy, quantity_comm->precision); + if (quantity_comm->precision < MAX_PRECISION) + round(copy, copy, quantity_comm->precision); bool zero = mpz_sgn(copy) == 0; mpz_clear(copy); return ! zero; } -const std::string gmp_amount::to_str(const commodity * comm, const mpz_t val) +static std::string amount_to_str(const commodity * comm, const mpz_t val, + bool full_precision) { - mpz_t copy; + mpz_t temp; mpz_t quotient; mpz_t rquotient; mpz_t remainder; mpz_t divisor; + bool negative = false; - mpz_init_set(copy, val); + mpz_init_set(temp, val); mpz_init(quotient); mpz_init(rquotient); mpz_init(remainder); mpz_init(divisor); - gmp_amount::round(copy, copy, comm->precision); + if (! full_precision && comm->precision < MAX_PRECISION) + round(temp, temp, comm->precision); mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); - mpz_tdiv_qr(quotient, remainder, copy, divisor); + mpz_tdiv_qr(quotient, remainder, temp, divisor); if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) negative = true; mpz_abs(quotient, quotient); mpz_abs(remainder, remainder); - assert(MAX_PRECISION - comm->precision > 0); - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - comm->precision); - mpz_tdiv_qr(rquotient, remainder, remainder, divisor); + if (full_precision || comm->precision == MAX_PRECISION) { + mpz_set(rquotient, remainder); + } else { + assert(MAX_PRECISION - comm->precision > 0); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - comm->precision); + mpz_tdiv_qr(rquotient, remainder, remainder, divisor); + } std::ostringstream s; @@ -192,10 +256,51 @@ const std::string gmp_amount::to_str(const commodity * comm, const mpz_t val) if (negative) s << "-"; - s << quotient; - s << '.'; - s.width(comm->precision); + if (comm->thousands) { + // jww (2003-09-29): use a smarter starting value + + bool printed = false; + + for (int powers = 27; powers >= 0; powers -= 3) { + mpz_ui_pow_ui(divisor, 10, powers); + mpz_tdiv_q(temp, quotient, divisor); + + if (mpz_sgn(temp) == 0) + continue; + + mpz_ui_pow_ui(divisor, 10, 3); + mpz_tdiv_r(temp, temp, divisor); + + if (printed) { + s.width(3); + s.fill('0'); + } + s << temp; + + if (powers > 0) { + if (comm->european) + s << "."; + else + s << ","; + + printed = true; + } + } + } else { + s << quotient; + } + + if (comm->european) + s << ','; + else + s << '.'; + + if (full_precision) + s.width(MAX_PRECISION); + else + s.width(comm->precision); + s.fill('0'); s << rquotient; @@ -205,7 +310,7 @@ const std::string gmp_amount::to_str(const commodity * comm, const mpz_t val) s << comm->symbol; } - mpz_clear(copy); + mpz_clear(temp); mpz_clear(quotient); mpz_clear(rquotient); mpz_clear(remainder); @@ -214,26 +319,30 @@ const std::string gmp_amount::to_str(const commodity * comm, const mpz_t val) return s.str(); } -gmp_amount::operator std::string() const +std::string gmp_amount::as_str(bool full_prec) const { std::ostringstream s; assert(quantity_comm); - s << to_str(quantity_comm, quantity); + s << amount_to_str(quantity_comm, quantity, full_prec); if (priced) { assert(price_comm); - s << " @ " << to_str(price_comm, price); + s << " @ " << amount_to_str(price_comm, price, full_prec); } return s.str(); } -void gmp_amount::parse(mpz_t out, char * num) +static void parse_number(mpz_t out, const char * num, commodity * comm) { if (char * p = std::strchr(num, '/')) { mpz_t numer; mpz_t val; + // The number was specified as a numerator over denominator, such + // as 5250/100. This gives us the precision, and avoids any + // nastiness having to do with international numbering formats. + std::string numer_str(num, p - num); mpz_init_set_str(numer, numer_str.c_str(), 10); mpz_init(val); @@ -250,12 +359,20 @@ void gmp_amount::parse(mpz_t out, char * num) else { static char buf[256]; - // jww (2003-09-28): What if there is no decimal? + // The number is specified as the user desires, with the + // commodity telling us how to parse it. std::memset(buf, '0', 255); std::strncpy(buf, num, std::strlen(num)); - char * t = std::strchr(buf, '.'); + if (comm->thousands) + while (char * t = std::strchr(buf, comm->european ? '.' : ',')) + do { *t = *(t + 1); } while (*(t++ + 1)); + + char * t = std::strchr(buf, comm->european ? ',' : '.'); + if (! t) + t = buf + std::strlen(num); + for (int prec = 0; prec < MAX_PRECISION; prec++) { *t = *(t + 1); t++; @@ -266,6 +383,93 @@ void gmp_amount::parse(mpz_t out, char * num) } } +static commodity * parse_amount(mpz_t out, const char * num, + int matched, int * ovector, int base) +{ + static char buf[256]; + + bool saw_commodity = false; + + std::string symbol; + bool prefix, separate, thousands, european; + int precision, result; + + if (ovector[base * 2] >= 0) { + // A prefix symbol was found + saw_commodity = true; + prefix = true; + separate = ovector[(base + 2) * 2] != ovector[(base + 2) * 2 + 1]; + result = pcre_copy_substring(num, ovector, matched, base + 1, buf, 255); + assert(result >= 0); + symbol = buf; + } + + // This is the value, and must be present + assert(ovector[(base + 3) * 2] >= 0); + result = pcre_copy_substring(num, ovector, matched, base + 3, buf, 255); + assert(result >= 0); + + // Determine the precision used + if (char * p = std::strchr(buf, '.')) + precision = std::strlen(++p); + else if (char * p = std::strchr(buf, '/')) + precision = std::strlen(++p) - 1; + else + precision = 0; + + // Where "thousands" markers used? Is it a european number? + if (char * p = std::strrchr(buf, ',')) { + if (std::strchr(p, '.')) + thousands = true; + else + european = true; + } + + // Parse the actual quantity + std::string value_str = buf; + + if (ovector[(base + 4) * 2] >= 0) { + // A suffix symbol was found + saw_commodity = true; + prefix = false; + separate = ovector[(base + 5) * 2] != ovector[(base + 5) * 2 + 1]; + result = pcre_copy_substring(num, ovector, matched, base + 6, buf, 255); + assert(result >= 0); + symbol = buf; + } + + commodity * comm = NULL; + + if (! saw_commodity) { + std::cerr << "Error: No commodity specified: " << value_str + << std::endl; + std::exit(1); + } else { + commodities_iterator item = commodities.find(symbol.c_str()); + if (item == commodities.end()) { + comm = new commodity(symbol, prefix, separate, + thousands, european, precision); + } else { + comm = (*item).second; +#if 0 + // If a finer precision was used than the commodity allows, + // increase the precision. + if (precision > comm->precision) + comm->precision = precision; +#else + if (use_warnings && precision > comm->precision) + std::cerr << "Warning: Use of higher precision than expected: " + << value_str << std::endl; +#endif + } + } + assert(comm); + + parse_number(out, value_str.c_str(), comm); + + return comm; +} + amount& gmp_amount::operator=(const char * num) { // Compile the regular expression used for parsing amounts @@ -274,133 +478,23 @@ amount& gmp_amount::operator=(const char * num) const char *error; int erroffset; static const std::string amount_re = - "(([^-0-9/.]+)(\\s*))?([-0-9/.]+)((\\s*)([^-0-9/.@]+))?"; + "(([^-0-9/.,]+)(\\s*))?([-0-9/.,]+)((\\s*)([^-0-9/.,@]+))?"; const std::string regexp = "^" + amount_re + "(\\s*@\\s*" + amount_re + ")?$"; re = pcre_compile(regexp.c_str(), 0, &error, &erroffset, NULL); } - bool saw_commodity; - std::string symbol; - bool prefix; - bool separate; - int precision; - - static char buf[256]; int ovector[60]; - int matched, result; + int matched; matched = pcre_exec(re, NULL, num, std::strlen(num), 0, 0, ovector, 60); if (matched > 0) { - saw_commodity = false; - - if (ovector[1 * 2] >= 0) { - // A prefix symbol was found - saw_commodity = true; - prefix = true; - separate = ovector[3 * 2] != ovector[3 * 2 + 1]; - result = pcre_copy_substring(num, ovector, matched, 2, buf, 255); - assert(result >= 0); - symbol = buf; - } - - // This is the value, and must be present - assert(ovector[4 * 2] >= 0); - result = pcre_copy_substring(num, ovector, matched, 4, buf, 255); - assert(result >= 0); - - // Determine the precision used - if (char * p = std::strchr(buf, '.')) - precision = std::strlen(++p); - else if (char * p = std::strchr(buf, '/')) - precision = std::strlen(++p) - 1; - else - precision = 0; - - // Parse the actual quantity - parse(quantity, buf); - - if (ovector[5 * 2] >= 0) { - // A suffix symbol was found - saw_commodity = true; - prefix = false; - separate = ovector[6 * 2] != ovector[6 * 2 + 1]; - result = pcre_copy_substring(num, ovector, matched, 7, buf, 255); - assert(result >= 0); - symbol = buf; - } - - if (! saw_commodity) { - quantity_comm = commodity_usd; - } else { - commodities_iterator item = commodities.find(symbol.c_str()); - if (item == commodities.end()) { - quantity_comm = new commodity(symbol, prefix, separate, precision); - } else { - quantity_comm = (*item).second; - - // If a finer precision was used than the commodity allows, - // increase the precision. - if (precision > quantity_comm->precision) - quantity_comm->precision = precision; - } - } + quantity_comm = parse_amount(quantity, num, matched, ovector, 1); // If the following succeeded, then we have a price if (ovector[8 * 2] >= 0) { - saw_commodity = false; - - if (ovector[9 * 2] >= 0) { - // A prefix symbol was found - saw_commodity = true; - prefix = true; - separate = ovector[11 * 2] != ovector[11 * 2 + 1]; - result = pcre_copy_substring(num, ovector, matched, 10, buf, 255); - assert(result >= 0); - symbol = buf; - } - - assert(ovector[12 * 2] >= 0); - result = pcre_copy_substring(num, ovector, matched, 4, buf, 255); - assert(result >= 0); - - // Determine the precision used - if (char * p = std::strchr(buf, '.')) - precision = std::strlen(++p); - else if (char * p = std::strchr(buf, '/')) - precision = std::strlen(++p) - 1; - else - precision = 0; - - // Parse the actual price - parse(price, buf); priced = true; - - if (ovector[13 * 2] >= 0) { - // A suffix symbol was found - saw_commodity = true; - prefix = false; - separate = ovector[14 * 2] != ovector[14 * 2 + 1]; - result = pcre_copy_substring(num, ovector, matched, 15, buf, 255); - assert(result >= 0); - symbol = buf; - } - - if (! saw_commodity) { - price_comm = commodity_usd; - } else { - commodities_iterator item = commodities.find(symbol.c_str()); - if (item == commodities.end()) { - price_comm = new commodity(symbol, prefix, separate, precision); - } else { - price_comm = (*item).second; - - // If a finer precision was used than the commodity allows, - // increase the precision. - if (precision > price_comm->precision) - price_comm->precision = precision; - } - } + price_comm = parse_amount(price, num, matched, ovector, 9); } } else { std::cerr << "Failed to parse amount: " << num << std::endl; @@ -415,52 +509,4 @@ void gmp_amount::operator+=(const amount& _other) mpz_add(quantity, quantity, other.quantity); } -void gmp_amount::round(mpz_t out, const mpz_t val, int prec) -{ - mpz_t divisor; - mpz_t quotient; - mpz_t remainder; - - mpz_init(divisor); - mpz_init(quotient); - mpz_init(remainder); - - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); - mpz_tdiv_qr(quotient, remainder, val, divisor); - - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec - 1); - mpz_mul_ui(divisor, divisor, 5); - if (mpz_cmp(remainder, divisor) >= 0) { - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); - mpz_sub(remainder, divisor, remainder); - mpz_add(out, val, remainder); - } else { - mpz_sub(out, val, remainder); - } - - mpz_clear(divisor); - mpz_clear(quotient); - mpz_clear(remainder); -} - -void gmp_amount::multiply(mpz_t out, const mpz_t l, const mpz_t r) -{ - mpz_t divisor; - - mpz_init(divisor); - - mpz_mul(out, l, r); - - // The number is at double-precision right now, so rounding at - // precision 0 effectively means rounding to the ordinary - // precision. - gmp_amount::round(out, out, 0); - - // after multiplying, truncate to the correct precision - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); - mpz_tdiv_q(out, out, divisor); - - mpz_clear(divisor); -} - } // namespace ledger @@ -9,6 +9,12 @@ namespace ledger { // Balance report. // +static bool show_current = false; +static bool show_cleared = false; +static bool show_children = false; +static bool show_empty = false; +static bool no_subtotals = false; + static inline bool matches(const std::list<pcre *>& regexps, const std::string& str) { for (std::list<pcre *>::const_iterator r = regexps.begin(); @@ -22,15 +28,37 @@ static inline bool matches(const std::list<pcre *>& regexps, return false; } -void report_balances(int argc, char *argv[], std::ostream& out) +void display_total(std::ostream& out, account * acct, + const std::map<account *, totals *>& balances) { - bool show_current = false; - bool show_cleared = false; - bool show_children = false; - bool show_empty = false; - bool no_subtotals = false; + std::map<account *, totals *>::const_iterator b = balances.find(acct); + if (b != balances.end()) { + totals * balance = (*b).second; + + if (balance && (show_empty || *balance)) { + out << *balance; + + if (acct->parent) { + for (account * a = acct; a; a = a->parent) + out << " "; + out << acct->name << std::endl; + } else { + out << " " << *acct << std::endl; + } + } + } + + for (account::iterator i = acct->children.begin(); + i != acct->children.end(); + i++) { + display_total(out, (*i).second, balances); + } +} +void report_balances(int argc, char **argv, std::ostream& out) +{ int c; + optind = 1; while (-1 != (c = getopt(argc, argv, "cCsSn"))) { switch (char(c)) { case 'c': show_current = true; break; @@ -62,16 +90,34 @@ void report_balances(int argc, char *argv[], std::ostream& out) // totals std::map<account *, totals *> balances; - std::time_t now = std::time(NULL); + totals total_balance; for (ledger_iterator i = ledger.begin(); i != ledger.end(); i++) { for (std::list<transaction *>::iterator x = (*i)->xacts.begin(); x != (*i)->xacts.end(); x++) { account * acct = (*x)->acct; - if (! regexps.empty() && ! matches(regexps, acct->name)) + + if (! regexps.empty()) { + if (show_children) { + bool match = false; + for (account * a = acct; a; a = a->parent) { + if (matches(regexps, a->name)) { + match = true; + break; + } + } + if (! match) + continue; + } + else if (! matches(regexps, acct->name)) { + continue; + } + } + else if (! show_children && acct->parent) { continue; + } while (acct) { totals * balance = NULL; @@ -84,16 +130,27 @@ void report_balances(int argc, char *argv[], std::ostream& out) balance = (*t).second; } + bool do_credit = false; + if (show_current) { if (difftime((*i)->date, now) < 0) - balance->credit((*x)->cost); + do_credit = true; } else if (show_cleared) { if ((*i)->cleared) - balance->credit((*x)->cost); + do_credit = true; } else { + do_credit = true; + } + + if (do_credit) { balance->credit((*x)->cost); + + // If this is a top-level account, then update the total + // running balance as well. + if (! acct->parent) + total_balance.credit((*x)->cost); } if (no_subtotals) @@ -104,6 +161,7 @@ void report_balances(int argc, char *argv[], std::ostream& out) } } +#if 0 // Print out the balance report header std::string which = "Future"; @@ -112,49 +170,23 @@ void report_balances(int argc, char *argv[], std::ostream& out) else if (show_cleared) which = "Cleared"; - std::cout.width(10); - std::cout << std::right << which << std::endl; - - // Walk through the accounts, given the balance report for each - - totals total_balance; - - for (accounts_iterator i = accounts.begin(); i != accounts.end(); i++) { - account * acct = (*i).second; + out.width(20); + out << std::right << which << std::endl + << "--------------------" << std::endl; +#endif - if (! regexps.empty() && ! matches(regexps, acct->name)) - continue; + // Walk through all the top-level accounts, given the balance + // report for each, and then for each of their children. - int depth = 0; - for (account * a = acct; a; a = a->parent) - depth++; - - if (! show_children && depth) - continue; - - totals * balance = balances[acct]; - - if (! show_empty && ! *balance) - continue; - - std::cout.width(10); - std::cout << *balance << " "; - - total_balance.credit(*balance); - - if (depth) { - while (--depth >= 0) - std::cout << " "; - std::cout << acct->name << std::endl; - } else { - std::cout << *acct << std::endl; - } - } + for (accounts_iterator i = accounts.begin(); i != accounts.end(); i++) + if (! (*i).second->parent) + display_total(out, (*i).second, balances); // Print the total of all the balances shown - std::cout.width(10); - std::cout << std::right << total_balance << std::endl; + if (! no_subtotals) + out << "--------------------" << std::endl + << total_balance << std::endl; // Free up temporary variables created on the heap @@ -18,6 +18,8 @@ static amount * curr_value; static std::string curr_quant; static XML_Parser current_parser; +accounts_t accounts_by_id; + enum { NO_ACTION, ACCOUNT_NAME, @@ -96,7 +98,7 @@ static void endElement(void *userData, const char *name) if (std::strcmp(name, "gnc:account") == 0) { assert(curr_account); accounts.insert(accounts_entry(curr_account->name, curr_account)); - accounts.insert(accounts_entry(curr_account_id, curr_account)); + accounts_by_id.insert(accounts_entry(curr_account_id, curr_account)); curr_account = NULL; } else if (std::strcmp(name, "gnc:commodity") == 0) { @@ -131,8 +133,8 @@ static void dataHandler(void *userData, const char *s, int len) break; case ACCOUNT_PARENT: { - accounts_iterator i = accounts.find(std::string(s, len)); - assert(i != accounts.end()); + accounts_iterator i = accounts_by_id.find(std::string(s, len)); + assert(i != accounts_by_id.end()); curr_account->parent = (*i).second; (*i).second->children.insert(account::pair(curr_account->name, curr_account)); @@ -187,8 +189,8 @@ static void dataHandler(void *userData, const char *s, int len) break; case XACT_ACCOUNT: { - accounts_iterator i = accounts.find(std::string(s, len)); - if (i == accounts.end()) { + accounts_iterator i = accounts_by_id.find(std::string(s, len)); + if (i == accounts_by_id.end()) { std::cerr << "Could not find account " << std::string(s, len) << " at line " << XML_GetCurrentLineNumber(current_parser) << std::endl; @@ -246,6 +248,10 @@ bool parse_gnucash(std::istream& in) } XML_ParserFree(parser); + accounts_by_id.clear(); + curr_account_id.clear(); + curr_quant.clear(); + return true; } @@ -3,10 +3,11 @@ namespace ledger { commodities_t commodities; -commodity * commodity_usd; accounts_t accounts; ledger_t ledger; +bool use_warnings = false; + void entry::print(std::ostream& out) const { char buf[32]; @@ -41,7 +42,7 @@ void entry::print(std::ostream& out) const out << std::left << acct_name << " "; out.width(10); - out << std::right << *((*i)->cost); + out << std::right << (*i)->cost->as_str(true); if (! (*i)->note.empty()) out << " ; " << (*i)->note; @@ -61,8 +62,9 @@ bool entry::validate() const } if (balance) { - std::cout << "Totals are:" << std::endl; - balance.print(std::cout); + std::cerr << "Totals are:" << std::endl; + balance.print(std::cerr); + std::cerr << std::endl; } return ! balance; // must balance to 0.0 } @@ -86,8 +88,17 @@ totals::operator bool() const void totals::print(std::ostream& out) const { - for (const_iterator_t i = amounts.begin(); i != amounts.end(); i++) - std::cout << (*i).first << " = " << *((*i).second); + bool first = true; + for (const_iterator_t i = amounts.begin(); i != amounts.end(); i++) + if (*((*i).second)) { + if (first) + first = false; + else + out << std::endl; + + out.width(20); + out << std::right << *((*i).second); + } } amount * totals::value(const std::string& commodity) @@ -1,5 +1,5 @@ #ifndef _LEDGER_H -#define _LEDGER_H "$Revision: 1.3 $" +#define _LEDGER_H "$Revision: 1.4 $" ////////////////////////////////////////////////////////////////////// // @@ -90,11 +90,15 @@ struct commodity bool prefix; bool separate; + bool thousands; + bool european; int precision; - commodity() : prefix(false), separate(true) {} - commodity(const std::string& sym, bool pre, bool sep, int prec); + commodity() : prefix(false), separate(true), + thousands(false), european(false) {} + commodity(const std::string& sym, bool pre, bool sep, + bool thou, bool euro, int prec); }; typedef std::map<const std::string, commodity *> commodities_t; @@ -102,11 +106,11 @@ typedef commodities_t::iterator commodities_iterator; typedef std::pair<const std::string, commodity *> commodities_entry; extern commodities_t commodities; -extern commodity * commodity_usd; -inline commodity::commodity(const std::string& sym, - bool pre, bool sep, int prec) - : symbol(sym), prefix(pre), separate(sep), precision(prec) { +inline commodity::commodity(const std::string& sym, bool pre, bool sep, + bool thou, bool euro, int prec) + : symbol(sym), prefix(pre), separate(sep), + thousands(thou), european(euro), precision(prec) { std::pair<commodities_iterator, bool> result = commodities.insert(commodities_entry(sym, this)); assert(result.second); @@ -129,12 +133,14 @@ class amount // Assignment virtual void credit(const amount * other) = 0; + virtual void negate() = 0; virtual void operator+=(const amount& other) = 0; // String conversion routines virtual void parse(const char * num) = 0; virtual amount& operator=(const char * num) = 0; + virtual std::string as_str(bool full_prec = false) const = 0; virtual operator std::string() const = 0; }; @@ -272,6 +278,8 @@ typedef std::pair<const std::string, account *> accounts_entry; extern accounts_t accounts; +extern bool use_warnings; + } // namespace ledger #endif // _LEDGER_H @@ -13,7 +13,7 @@ namespace ledger { extern bool parse_ledger(std::istream& in); extern bool parse_gnucash(std::istream& in); - extern void report_balances(int argc, char *argv[], std::ostream& out); + extern void report_balances(int argc, char **argv, std::ostream& out); extern void print_ledger(int argc, char *argv[], std::ostream& out); } @@ -35,15 +35,16 @@ int main(int argc, char *argv[]) { // Global defaults - commodity_usd = new commodity("$", true, false, 2); - commodities.insert(commodities_entry("USD", commodity_usd)); + commodity * usd = new commodity("$", true, false, true, false, 2); + commodities.insert(commodities_entry("USD", usd)); // Parse the command-line options int c; - while (-1 != (c = getopt(argc, argv, "+h"))) { + while (-1 != (c = getopt(argc, argv, "+hw"))) { switch (char(c)) { case 'h': show_help(std::cout); break; + case 'w': use_warnings = true; break; } } @@ -135,13 +135,49 @@ bool parse_ledger(std::istream& in) else if (std::isspace(line[0])) { transaction * xact = new transaction(); - xact->cost = create_amount(next_element(line, true)); + char * p = line; + while (std::isspace(*p)) + p++; + + // The call to `next_element' will skip past the account name, + // and return a pointer to the beginning of the amount. Once + // we know where the amount is, we can strip off any + // transaction note, and parse it. + + char * cost_str = next_element(p, true); + char * note_str; + + // If there is no amount given, it is intended as an implicit + // amount; we must use the opposite of the value of the + // preceding transaction. + if (! cost_str || *cost_str == ';') { + if (cost_str) { + while (*cost_str == ';' || std::isspace(*cost_str)) + cost_str++; + xact->note = cost_str; + } + xact->cost = curr->xacts.back()->cost->copy(); + xact->cost->negate(); + } + else { + note_str = std::strchr(cost_str, ';'); + if (note_str) { + *note_str++ = '\0'; + while (std::isspace(*note_str)) + note_str++; + xact->note = note_str; + } + + for (char * t = cost_str + (std::strlen(cost_str) - 1); + std::isspace(*t); + t--) + *t = '\0'; + + xact->cost = create_amount(cost_str); + } - // jww (2003-09-28): Reverse parse the account name to find the - // correct account. This means that each account needs to know - // its children. account * current = NULL; - for (char * tok = std::strtok(line, ":"); + for (char * tok = std::strtok(p, ":"); tok; tok = std::strtok(NULL, ":")) { if (! current) { |