diff options
-rw-r--r-- | Makefile.am | 13 | ||||
-rwxr-xr-x | acprep | 2 | ||||
-rw-r--r-- | amount.cc | 590 | ||||
-rw-r--r-- | amount.h | 325 | ||||
-rw-r--r-- | balance.cc | 14 | ||||
-rw-r--r-- | balance.h | 4 | ||||
-rw-r--r-- | binary.cc | 282 | ||||
-rw-r--r-- | config.cc | 11 | ||||
-rw-r--r-- | datetime.cc | 62 | ||||
-rw-r--r-- | datetime.h | 22 | ||||
-rw-r--r-- | format.cc | 6 | ||||
-rw-r--r-- | gnucash.cc | 33 | ||||
-rw-r--r-- | journal.cc | 21 | ||||
-rw-r--r-- | journal.h | 4 | ||||
-rw-r--r-- | main.cc | 2 | ||||
-rw-r--r-- | ofx.cc | 5 | ||||
-rw-r--r-- | option.cc | 14 | ||||
-rw-r--r-- | qif.cc | 10 | ||||
-rw-r--r-- | quotes.cc | 12 | ||||
-rw-r--r-- | quotes.h | 4 | ||||
-rw-r--r-- | reconcile.cc | 2 | ||||
-rw-r--r-- | textual.cc | 92 | ||||
-rw-r--r-- | valexpr.cc | 26 | ||||
-rw-r--r-- | valexpr.h | 6 | ||||
-rw-r--r-- | walk.cc | 14 | ||||
-rw-r--r-- | xml.cc | 24 |
26 files changed, 1008 insertions, 592 deletions
diff --git a/Makefile.am b/Makefile.am index f141beb2..2af4a5eb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,17 +1,23 @@ lib_LTLIBRARIES = libamounts.la libledger.la + libamounts_la_CXXFLAGS = libamounts_la_SOURCES = \ amount.cc \ balance.cc \ + datetime.cc \ value.cc if HAVE_BOOST_PYTHON libamounts_la_CXXFLAGS += -DUSE_BOOST_PYTHON=1 endif +if DEBUG +libamounts_la_CXXFLAGS += -DDEBUG_LEVEL=4 +libamounts_la_SOURCES += debug.cc +endif + libledger_la_CXXFLAGS = libledger_la_SOURCES = \ binary.cc \ config.cc \ - datetime.cc \ derive.cc \ emacs.cc \ format.cc \ @@ -40,7 +46,6 @@ libledger_la_SOURCES += ofx.cc endif if DEBUG libledger_la_CXXFLAGS += -DDEBUG_LEVEL=4 -libledger_la_SOURCES += debug.cc endif libledger_la_LDFLAGS = -version-info 2:6 @@ -49,13 +54,13 @@ pkginclude_HEADERS = \ \ amount.h \ balance.h \ + datetime.h \ value.h \ + debug.h \ util.h \ \ binary.h \ config.h \ - datetime.h \ - debug.h \ derive.h \ emacs.h \ error.h \ @@ -17,7 +17,7 @@ fi autoconf INCDIRS="-I/sw/include -I/usr/local/include/boost-1_33 -I/usr/include/httpd/xml" -#INCDIRS="$INCDIRS -I/sw/include/libofx" +INCDIRS="$INCDIRS -I/sw/include/libofx" INCDIRS="$INCDIRS -I/sw/include/python2.4" INCDIRS="$INCDIRS -Wno-long-double" LIBDIRS="-L/sw/lib -L/usr/local/lib -L/sw/lib/python2.4/config" @@ -1,4 +1,5 @@ #include "amount.h" +#include "datetime.h" #include "util.h" #include <list> @@ -45,10 +46,13 @@ static mpz_t temp; static mpz_t divisor; static amount_t::bigint_t true_value; -commodity_t::updater_t * commodity_t::updater = NULL; -commodities_map commodity_t::commodities; -commodity_t * commodity_t::null_commodity; -commodity_t * commodity_t::default_commodity = NULL; +base_commodities_map commodity_base_t::commodities; + +commodity_base_t::updater_t * commodity_base_t::updater = NULL; + +commodities_map commodity_t::commodities; +commodity_t * commodity_t::null_commodity; +commodity_t * commodity_t::default_commodity = NULL; static struct _init_amounts { _init_amounts() { @@ -57,23 +61,26 @@ static struct _init_amounts { mpz_set_ui(true_value.val, 1); - commodity_t::updater = NULL; - commodity_t::null_commodity = commodity_t::find_commodity("", true); + commodity_base_t::updater = NULL; + commodity_t::null_commodity = commodity_t::create(""); commodity_t::default_commodity = NULL; + commodity_t::null_commodity->add_flags(COMMODITY_STYLE_NOMARKET | + COMMODITY_STYLE_BUILTIN); + // Add time commodity conversions, so that timelog's may be parsed // in terms of seconds, but reported as minutes or hours. commodity_t * commodity; - commodity = commodity_t::find_commodity("s", true); - commodity->flags() |= COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN; + commodity = commodity_t::create("s"); + commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); parse_conversion("1.0m", "60s"); parse_conversion("1.0h", "60m"); #if 0 - commodity = commodity_t::find_commodity("b", true); - commodity->flags() |= COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN; + commodity = commodity_t::create("b"); + commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); parse_conversion("1.00 Kb", "1024 b"); parse_conversion("1.00 Mb", "1024 Kb"); @@ -86,9 +93,9 @@ static struct _init_amounts { mpz_clear(temp); mpz_clear(divisor); - if (commodity_t::updater) { - delete commodity_t::updater; - commodity_t::updater = NULL; + if (commodity_base_t::updater) { + delete commodity_base_t::updater; + commodity_base_t::updater = NULL; } for (commodities_map::iterator i = commodity_t::commodities.begin(); @@ -603,8 +610,7 @@ std::string amount_t::quantity_string() const commodity_t& comm(commodity()); unsigned short precision; - if (comm == *commodity_t::null_commodity || - comm.flags() & COMMODITY_STYLE_VARIABLE) { + if (! comm || comm.flags() & COMMODITY_STYLE_VARIABLE) { mpz_ui_pow_ui(divisor, 10, quantity->prec); mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); precision = quantity->prec; @@ -707,11 +713,10 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) // Ensure the value is rounded to the commodity's precision before // outputting it. NOTE: `rquotient' is used here as a temp variable! - commodity_t& comm(base.commodity()); + commodity_t& comm(base.commodity()); unsigned short precision; - if (comm == *commodity_t::null_commodity || - comm.flags() & COMMODITY_STYLE_VARIABLE) { + if (! comm || comm.flags() & COMMODITY_STYLE_VARIABLE) { mpz_ui_pow_ui(divisor, 10, base.quantity->prec); mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor); precision = base.quantity->prec; @@ -755,16 +760,8 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) } if (! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) { - if (comm.quote) { - std::string::size_type idx = comm.symbol.find(" {", 0); - if (idx != std::string::npos) - out << "\"" << comm.symbol.substr(0, idx) << "\"" - << comm.symbol.substr(idx); - else - out << "\"" << comm.symbol << "\""; - } else { - out << comm.symbol; - } + comm.write(out); + if (comm.flags() & COMMODITY_STYLE_SEPARATED) out << " "; } @@ -828,22 +825,22 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) if (comm.flags() & COMMODITY_STYLE_SUFFIXED) { if (comm.flags() & COMMODITY_STYLE_SEPARATED) out << " "; - if (comm.quote) { - std::string::size_type idx = comm.symbol.find(" {", 0); - if (idx != std::string::npos) - out << "\"" << comm.symbol.substr(0, idx) << "\"" - << comm.symbol.substr(idx); - else - out << "\"" << comm.symbol << "\""; - } else { - out << comm.symbol; - } + + comm.write(out); } mpz_clear(quotient); mpz_clear(rquotient); mpz_clear(remainder); + // If there are any annotations associated with this commodity, + // output them now. + + if (comm.annotated) { + annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm)); + ann.write_annotations(out); + } + // Things are output to a string first, so that if anyone has // specified a width or fill for _out, it will be applied to the // entire amount string, and not just the first part. @@ -862,7 +859,9 @@ void parse_quantity(std::istream& in, std::string& value) value = buf; } -void parse_commodity(std::istream& in, std::string& symbol) +void parse_commodity(std::istream& in, std::string& name, + std::string& symbol, std::string& price, + std::string& date, std::string& tag) { char buf[256]; char c = peek_next_nonws(in); @@ -877,7 +876,84 @@ void parse_commodity(std::istream& in, std::string& symbol) READ_INTO(in, buf, 255, c, ! std::isspace(c) && ! std::isdigit(c) && c != '-' && c != '.'); } - symbol = buf; + name = symbol = buf; + + bool added_name = false; + + c = peek_next_nonws(in); + + if (c == '{') { + in.get(c); + READ_INTO(in, buf, 255, c, c != '}'); + if (c == '}') + in.get(c); + else + throw amount_error("Commodity price lacks closing brace"); + price = buf; + if (! added_name) { + added_name = true; + if (commodity_t::needs_quotes(symbol)) { + name = "\""; + name += symbol; + name += "\""; + } + } + name += " {"; + name += price; + name += "}"; + c = peek_next_nonws(in); + } + + if (c == '[') { + in.get(c); + READ_INTO(in, buf, 255, c, c != ']'); + if (c == ']') + in.get(c); + else + throw amount_error("Commodity date lacks closing bracket"); + date = buf; + if (! added_name) { + added_name = true; + if (commodity_t::needs_quotes(symbol)) { + name = "\""; + name += symbol; + name += "\""; + } + } + name += " ["; + name += date; + name += "]"; + c = peek_next_nonws(in); + } + + if (c == '(') { + in.get(c); + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') + in.get(c); + else + throw amount_error("Commodity tag lacks closing parenthesis"); + tag = buf; + if (! added_name) { + added_name = true; + if (commodity_t::needs_quotes(symbol)) { + name = "\""; + name += symbol; + name += "\""; + } + } + name += " ("; + name += tag; + name += ")"; + c = peek_next_nonws(in); + } + + DEBUG_PRINT("amounts.commodities", "Parsed commodity: " + << "name " << name << " " + << "symbol " << symbol << std::endl + << " price " << price << " " + << " date " << date << " " + << " tag " << tag); } void amount_t::parse(std::istream& in, unsigned short flags) @@ -887,9 +963,12 @@ void amount_t::parse(std::istream& in, unsigned short flags) // [-]NUM[ ]SYM [@ AMOUNT] // SYM[ ][-]NUM [@ AMOUNT] + std::string name; std::string symbol; std::string quant; std::string price; + std::string date; + std::string tag; unsigned int comm_flags = COMMODITY_STYLE_DEFAULTS; bool negative = false; @@ -908,13 +987,13 @@ void amount_t::parse(std::istream& in, unsigned short flags) if (std::isspace(n)) comm_flags |= COMMODITY_STYLE_SEPARATED; - parse_commodity(in, symbol); + parse_commodity(in, name, symbol, price, date, tag); if (! symbol.empty()) comm_flags |= COMMODITY_STYLE_SUFFIXED; } } else { - parse_commodity(in, symbol); + parse_commodity(in, name, symbol, price, date, tag); if (std::isspace(in.peek())) comm_flags |= COMMODITY_STYLE_SEPARATED; @@ -930,37 +1009,19 @@ void amount_t::parse(std::istream& in, unsigned short flags) // Create the commodity if has not already been seen, and update the // precision if something greater was used for the quantity. - bool newly_created = (commodity_t::commodities.find(symbol) == - commodity_t::commodities.end()); - - commodity_ = commodity_t::find_commodity(symbol, true); - - // If a per-unit price is specified for this amount, record it by - // creating a specialized commodity at that price. This is a - // different from the whole transaction cost, which is associated - // with the transaction and not with the amount. For example, a - // sale of 10 AAPL shares for $100 (the cost) is a different thing - // from selling 10 AAPL {$10} (where $10 is the commodity price) for - // $50, which implies a capital loss of $50. - - if (peek_next_nonws(in) == '{') { - char c; - char buf[256]; - in.get(c); - READ_INTO(in, buf, 255, c, c != '}'); - if (c == '}') - in.get(c); - else - throw amount_error("Commodity price lacks closing brace"); - - symbol = symbol + " {" + buf + "}"; - commodity_t * priced_commodity = - commodity_t::find_commodity(symbol, true); + bool newly_created = false; - priced_commodity->price = new amount_t(buf); - priced_commodity->base = commodity_; + commodity_ = commodity_t::find(name); + if (! commodity_) { + newly_created = true; - commodity_ = priced_commodity; + if (! price.empty() || ! date.empty() || ! tag.empty()) { + commodity_ = annotated_commodity_t::create(symbol, price, date, tag); + } else { + assert(name == symbol); + commodity_ = commodity_t::create(symbol); + } + assert(commodity_); } // Determine the precision of the amount, based on the usage of @@ -995,9 +1056,9 @@ void amount_t::parse(std::istream& in, unsigned short flags) // Set the commodity's flags and precision accordingly if (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE)) { - commodity().flags() |= comm_flags; + commodity().add_flags(comm_flags); if (quantity->prec > commodity().precision()) - commodity().precision() = quantity->prec; + commodity().set_precision(quantity->prec); } // Now we have the final number. Remove commas and periods, if @@ -1054,12 +1115,12 @@ void parse_conversion(const std::string& larger_str, larger *= smaller; if (larger.commodity()) { - larger.commodity().smaller() = new amount_t(smaller); - larger.commodity().flags() = (smaller.commodity().flags() | - COMMODITY_STYLE_NOMARKET); + larger.commodity().set_smaller(smaller); + larger.commodity().add_flags(smaller.commodity().flags() | + COMMODITY_STYLE_NOMARKET); } if (smaller.commodity()) - smaller.commodity().larger() = new amount_t(larger); + smaller.commodity().set_larger(larger); } @@ -1177,98 +1238,187 @@ void amount_t::write_quantity(std::ostream& out) const bool amount_t::valid() const { if (quantity) { -#if 0 - // jww (2006-02-24): It's OK for commodity_ to be null here, it - // just means to use the null_commodity - if (! commodity_) - return false; -#endif - if (quantity->ref == 0) return false; } else if (commodity_) { return false; } - return true; } +void amount_t::annotate_commodity(const amount_t& price, + const std::time_t date, + const std::string& tag) +{ + const commodity_t * this_base; + annotated_commodity_t * this_ann = NULL; + + if (commodity().annotated) { + this_ann = &static_cast<annotated_commodity_t&>(commodity()); + this_base = this_ann->base; + } else { + this_base = &commodity(); + } + + DEBUG_PRINT("amounts.commodities", "Annotating commodity for amount " + << *this << std::endl + << " price " << price << " " + << " date " << date << " " + << " tag " << tag); + + annotated_commodity_t * ann_comm = + annotated_commodity_t::find_or_create(*this_base, price, + date == 0 && this_ann ? + this_ann->date : date, + tag.empty() && this_ann ? + this_ann->tag : tag); + if (ann_comm) + set_commodity(*ann_comm); -void commodity_t::set_symbol(const std::string& sym) + DEBUG_PRINT("amounts.commodities", " Annotated amount is " << *this); +} + +void amount_t::reduce_commodity(const bool keep_price, + const bool keep_date, + const bool keep_tag) { - *(const_cast<std::string *>(&symbol)) = sym; - quote = false; - for (const char * p = symbol.c_str(); *p; p++) - if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.') { - if (std::isspace(*p) && *(p + 1) == '{') - return; - quote = true; - return; - } + if (! commodity().annotated) + return; + if (keep_price && keep_date && keep_tag) + return; + + DEBUG_PRINT("amounts.commodities", "Reducing commodity for amount " + << *this << std::endl + << " keep price " << keep_price << " " + << " keep date " << keep_date << " " + << " keep tag " << keep_tag); + + annotated_commodity_t& + ann_comm(static_cast<annotated_commodity_t&>(commodity())); + + annotated_commodity_t * new_ann_comm = + annotated_commodity_t::find_or_create(*ann_comm.base, + keep_price ? + ann_comm.price : amount_t(), + keep_date ? ann_comm.date : 0, + keep_tag ? ann_comm.tag : ""); + assert(new_ann_comm); + set_commodity(*new_ann_comm); + + DEBUG_PRINT("amounts.commodities", " Reduced amount is " << *this); } -void commodity_t::add_price(const std::time_t date, const amount_t& price) + +void commodity_base_t::add_price(const std::time_t date, const amount_t& price) { - if (! history()) - history() = new history_t; + if (! history) + history = new history_t; - history_map::iterator i = history()->prices.find(date); - if (i != history()->prices.end()) { + history_map::iterator i = history->prices.find(date); + if (i != history->prices.end()) { (*i).second = price; } else { std::pair<history_map::iterator, bool> result - = history()->prices.insert(history_pair(date, price)); + = history->prices.insert(history_pair(date, price)); assert(result.second); } } -commodity_t * commodity_t::find_commodity(const std::string& symbol, - bool auto_create) +commodity_base_t * commodity_base_t::create(const std::string& symbol) { - commodities_map::const_iterator i = commodities.find(symbol); - if (i != commodities.end()) - return (*i).second; + commodity_base_t * commodity = new commodity_base_t(symbol); + + DEBUG_PRINT("amounts.commodities", "Creating base commodity " << symbol); + + std::pair<base_commodities_map::iterator, bool> result + = commodities.insert(base_commodities_pair(symbol, commodity)); + assert(result.second); + + return commodity; +} + +bool commodity_t::needs_quotes(const std::string& symbol) +{ + for (const char * p = symbol.c_str(); *p; p++) + if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.') + return true; - if (auto_create) { - commodity_t * commodity = new commodity_t(symbol); - add_commodity(commodity); + return false; +} + +commodity_t * commodity_t::create(const std::string& symbol) +{ + std::auto_ptr<commodity_t> commodity(new commodity_t); - // Start out the new commodity with the default commodity's flags - // and precision, if one has been defined. - if (default_commodity) - commodity->flags() = - (default_commodity->flags() & ~(COMMODITY_STYLE_THOUSANDS | - COMMODITY_STYLE_NOMARKET)); + commodity->ptr = commodity_base_t::create(symbol); - return commodity; + if (needs_quotes(symbol)) { + commodity->qualified_symbol = "\""; + commodity->qualified_symbol += symbol; + commodity->qualified_symbol += "\""; + } else { + commodity->qualified_symbol = symbol; } + DEBUG_PRINT("amounts.commodities", + "Creating commodity " << commodity->qualified_symbol); + + std::pair<commodities_map::iterator, bool> result + = commodities.insert(commodities_pair(symbol, commodity.get())); + if (! result.second) + return NULL; + + // Start out the new commodity with the default commodity's flags + // and precision, if one has been defined. + if (default_commodity) + commodity->drop_flags(COMMODITY_STYLE_THOUSANDS | + COMMODITY_STYLE_NOMARKET); + + return commodity.release(); +} + +commodity_t * commodity_t::find_or_create(const std::string& symbol) +{ + DEBUG_PRINT("amounts.commodities", "Find-or-create commodity " << symbol); + + commodity_t * commodity = find(symbol); + if (! commodity) + return create(symbol); +} + +commodity_t * commodity_t::find(const std::string& symbol) +{ + DEBUG_PRINT("amounts.commodities", "Find commodity " << symbol); + + commodities_map::const_iterator i = commodities.find(symbol); + if (i != commodities.end()) + return (*i).second; return NULL; } -amount_t commodity_t::value(const std::time_t moment) +amount_t commodity_base_t::value(const std::time_t moment) { std::time_t age = 0; amount_t price; - if (history()) { - assert(history()->prices.size() > 0); + if (history) { + assert(history->prices.size() > 0); if (moment == 0) { - history_map::reverse_iterator r = history()->prices.rbegin(); + history_map::reverse_iterator r = history->prices.rbegin(); age = (*r).first; price = (*r).second; } else { - history_map::iterator i = history()->prices.lower_bound(moment); - if (i == history()->prices.end()) { - history_map::reverse_iterator r = history()->prices.rbegin(); + history_map::iterator i = history->prices.lower_bound(moment); + if (i == history->prices.end()) { + history_map::reverse_iterator r = history->prices.rbegin(); age = (*r).first; price = (*r).second; } else { age = (*i).first; if (std::difftime(moment, age) != 0) { - if (i != history()->prices.begin()) { + if (i != history->prices.begin()) { --i; age = (*i).first; price = (*i).second; @@ -1284,12 +1434,146 @@ amount_t commodity_t::value(const std::time_t moment) if (updater) (*updater)(*this, moment, age, - (history() && history()->prices.size() > 0 ? - (*history()->prices.rbegin()).first : 0), price); + (history && history->prices.size() > 0 ? + (*history->prices.rbegin()).first : 0), price); return price; } +std::string annotated_commodity_t::date_format = "%Y/%m/%d"; + +void +annotated_commodity_t::write_annotations(std::ostream& out, + const amount_t& price, + const std::time_t date, + const std::string& tag) +{ + if (price) + out << " {" << price << '}'; + + if (date) { + char buf[128]; + std::strftime(buf, 127, annotated_commodity_t::date_format.c_str(), + std::localtime(&date)); + out << " [" << buf << ']'; + } + + if (! tag.empty()) + out << " (" << tag << ')'; +} + +std::string +annotated_commodity_t::make_qualified_name(const commodity_t& comm, + const amount_t& price, + const std::time_t date, + const std::string& tag) +{ + std::ostringstream name; + + comm.write(name); + write_annotations(name, price, date, tag); + + DEBUG_PRINT("amounts.commodities", "make_qualified_name for " + << comm.qualified_symbol << std::endl + << " price " << price << " " + << " date " << date << " " + << " tag " << tag); + + DEBUG_PRINT("amounts.commodities", "qualified_name is " << name.str()); + + return name.str(); +} + +annotated_commodity_t * +annotated_commodity_t::create(const commodity_t& comm, + const amount_t& price, + const std::time_t date, + const std::string& tag, + const std::string& entry_name) +{ + std::auto_ptr<annotated_commodity_t> commodity(new annotated_commodity_t); + + // Set the annotated bits + commodity->price = price; + commodity->date = date; + commodity->tag = tag; + commodity->base = &comm; + commodity->ptr = comm.ptr; + + commodity->qualified_symbol = comm.symbol(); + + std::string mapping_key; + if (entry_name.empty()) + mapping_key = make_qualified_name(comm, price, date, tag); + else + mapping_key = entry_name; + + DEBUG_PRINT("amounts.commodities", "Creating annotated commodity " + << "symbol " << commodity->symbol() + << " key " << mapping_key << std::endl + << " price " << price << " " + << " date " << date << " " + << " tag " << tag); + + // Add the fully annotated name to the map, so that this symbol may + // quickly be found again. + std::pair<commodities_map::iterator, bool> result + = commodities.insert(commodities_pair(mapping_key, commodity.get())); + if (! result.second) + return NULL; + + return commodity.release(); +} + +annotated_commodity_t * +annotated_commodity_t::create(const std::string& symbol, + const amount_t& price, + const std::time_t date, + const std::string& tag) +{ + commodity_t * comm = commodity_t::find_or_create(symbol); + assert(comm); + return create(*comm, price, date, tag); +} + +annotated_commodity_t * +annotated_commodity_t::create(const std::string& symbol, + const std::string& price, + const std::string& date, + const std::string& tag) +{ + commodity_t * comm = commodity_t::find_or_create(symbol); + assert(comm); + + amount_t real_price; + if (! price.empty()) + real_price.parse(price); + + std::time_t real_date = 0; + if (! date.empty()) + parse_date(date.c_str(), &real_date); + + return create(*comm, real_price, real_date, tag); +} + +annotated_commodity_t * +annotated_commodity_t::find_or_create(const commodity_t& comm, + const amount_t& price, + const std::time_t date, + const std::string& tag) +{ + std::string name = make_qualified_name(comm, price, date, tag); + + commodity_t * base = commodity_t::find(name); + if (base) { + assert(base->annotated); + return static_cast<annotated_commodity_t *>(base); + } + base = commodity_t::find_or_create(comm.base_symbol()); + + return create(*base, price, date, tag, name); +} + } // namespace ledger #ifdef USE_BOOST_PYTHON @@ -1314,12 +1598,12 @@ void py_parse_2(amount_t& amount, const std::string& str) { amount.parse(str); } -struct commodity_updater_wrap : public commodity_t::updater_t +struct commodity_updater_wrap : public commodity_base_t::updater_t { PyObject * self; commodity_updater_wrap(PyObject * self_) : self(self_) {} - virtual void operator()(commodity_t& commodity, + virtual void operator()(commodity_base_t& commodity, const std::time_t moment, const std::time_t date, const std::time_t last, @@ -1328,14 +1612,9 @@ struct commodity_updater_wrap : public commodity_t::updater_t } }; -commodity_t * py_find_commodity_1(const std::string& symbol) -{ - return commodity_t::find_commodity(symbol); -} - -commodity_t * py_find_commodity_2(const std::string& symbol, bool auto_create) +commodity_t * py_find_commodity(const std::string& symbol) { - return commodity_t::find_commodity(symbol, auto_create); + return commodity_t::find(symbol); } #define EXC_TRANSLATOR(type) \ @@ -1410,7 +1689,8 @@ void export_amount() .def("valid", &amount_t::valid) ; - class_< commodity_t::updater_t, commodity_updater_wrap, boost::noncopyable > + class_< commodity_base_t::updater_t, commodity_updater_wrap, + boost::noncopyable > ("Updater") ; @@ -1422,44 +1702,38 @@ void export_amount() scope().attr("COMMODITY_STYLE_NOMARKET") = COMMODITY_STYLE_NOMARKET; scope().attr("COMMODITY_STYLE_VARIABLE") = COMMODITY_STYLE_VARIABLE; +#if 0 class_< commodity_t > ("Commodity") - .def(init<std::string, optional<unsigned int, unsigned int> >()) - - .add_property("symbol", &commodity_t::symbol, - &commodity_t::set_symbol) + .add_property("symbol", &commodity_t::symbol) - // jww (2006-02-28): Use getters and setters! - .def_readwrite("name", &commodity_t::name_) - .def_readwrite("note", &commodity_t::note_) - .def_readwrite("precision", &commodity_t::precision_) - .def_readwrite("flags", &commodity_t::flags_) - .def_readwrite("ident", &commodity_t::ident) - .def_readwrite("updater", &commodity_t::updater) + .add_property("name", &commodity_t::name) + .add_property("note", &commodity_t::note) + .add_property("precision", &commodity_t::precision) + .add_property("flags", &commodity_t::flags) +#if 0 + .add_property("updater", &commodity_t::updater) +#endif .add_property("smaller", - make_getter(&commodity_t::smaller_, + make_getter(&commodity_t::smaller, return_value_policy<reference_existing_object>())) .add_property("larger", - make_getter(&commodity_t::larger_, + make_getter(&commodity_t::larger, return_value_policy<reference_existing_object>())) .def(self_ns::str(self)) + .def("find", py_find_commodity, + return_value_policy<reference_existing_object>()) + .staticmethod("find") + .def("add_price", &commodity_t::add_price) .def("remove_price", &commodity_t::remove_price) .def("value", &commodity_t::value) .def("valid", &commodity_t::valid) ; - - scope().attr("NullCommodity") = commodity_t::null_commodity; - - def("add_commodity", &commodity_t::add_commodity); - def("remove_commodity", &commodity_t::remove_commodity); - def("find_commodity", py_find_commodity_1, - return_value_policy<reference_existing_object>()); - def("find_commodity", py_find_commodity_2, - return_value_policy<reference_existing_object>()); +#endif #define EXC_TRANSLATE(type) \ register_exception_translator<type>(&exc_translate_ ## type); @@ -10,6 +10,8 @@ #include <cassert> #include <exception> +#include "debug.h" + namespace ledger { class commodity_t; @@ -70,10 +72,15 @@ class amount_t void set_commodity(commodity_t& comm) { commodity_ = &comm; } + void annotate_commodity(const amount_t& price, + const std::time_t date = 0, + const std::string& tag = ""); + void reduce_commodity(const bool keep_price = false, + const bool keep_date = false, + const bool keep_tag = false); void clear_commodity() { commodity_ = NULL; } - amount_t base() const; amount_t price() const; bool null() const { @@ -298,147 +305,264 @@ inline std::istream& operator>>(std::istream& in, 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_base_t; -class commodity_t +typedef std::map<const std::string, commodity_base_t *> base_commodities_map; +typedef std::pair<const std::string, commodity_base_t *> base_commodities_pair; + +class commodity_base_t { public: + friend class commodity_t; + friend class annotated_commodity_t; + + typedef unsigned long ident_t; + + ident_t ident; + std::string name; + std::string note; + unsigned short precision; + unsigned short flags; + amount_t * smaller; + amount_t * larger; + + commodity_base_t() : precision(0), flags(COMMODITY_STYLE_DEFAULTS), + history(NULL), smaller(NULL), larger(NULL) {} + + commodity_base_t(const std::string& _symbol, + unsigned int _precision = 0, + unsigned int _flags = COMMODITY_STYLE_DEFAULTS) + : precision(_precision), flags(_flags), history(NULL), + smaller(NULL), larger(NULL), symbol(_symbol) {} + + ~commodity_base_t() { + if (history) delete history; + if (smaller) delete smaller; + if (larger) delete larger; + } + + static base_commodities_map commodities; + static commodity_base_t * create(const std::string& symbol); + + std::string symbol; + + struct history_t { + history_map prices; + std::time_t last_lookup; + }; + history_t * history; + + void add_price(const std::time_t date, const amount_t& price); + bool remove_price(const std::time_t date) { + if (history) { + history_map::size_type n = history->prices.erase(date); + return n > 0; + } + return false; + } + + amount_t value(const std::time_t moment = std::time(NULL)); + class updater_t { public: virtual ~updater_t() {} - virtual void operator()(commodity_t& commodity, + virtual void operator()(commodity_base_t& commodity, const std::time_t moment, const std::time_t date, const std::time_t last, amount_t& price) = 0; }; + friend class updater_t; - typedef unsigned long ident_t; + static updater_t * updater; +}; - struct history_t { - history_map prices; - std::time_t last_lookup; - }; +typedef std::map<const std::string, commodity_t *> commodities_map; +typedef std::pair<const std::string, commodity_t *> commodities_pair; + +class commodity_t +{ + friend class annotated_commodity_t; - history_t * history_; + public: + // This map remembers all commodities that have been defined. - history_t *& history() { - return base ? base->history() : history_; - } + static commodities_map commodities; + static commodity_t * null_commodity; + static commodity_t * default_commodity; - const std::string symbol; - bool quote; - std::string name_; - std::string note_; - unsigned short precision_; - unsigned short flags_; - ident_t ident; - amount_t * smaller_; - amount_t * larger_; - commodity_t * base; // base commodity for AAPL {$10} is AAPL - amount_t * price; // its price is therefore $10.00 + static commodity_t * create(const std::string& symbol); + static commodity_t * find(const std::string& name); + static commodity_t * find_or_create(const std::string& symbol); - std::string& name() { - return base ? base->name() : name_; - } - std::string& note() { - return base ? base->note() : note_; + static bool needs_quotes(const std::string& symbol); + + static void make_alias(const std::string& symbol, + commodity_t * commodity); + + // These are specific to each commodity reference + + typedef unsigned long ident_t; + + ident_t ident; + commodity_base_t * ptr; + std::string qualified_symbol; + bool annotated; + + public: + explicit commodity_t() : ptr(NULL), annotated(false) {} + + operator bool() const { + return this != null_commodity; } - unsigned short& precision() { - return base ? base->precision() : precision_; + bool operator==(const commodity_t& comm) const { + return ptr == comm.ptr; } - unsigned short& flags() { - return base ? base->flags() : flags_; + bool operator!=(const commodity_t& comm) const { + return ptr != comm.ptr; } - amount_t *& smaller() { - return base ? base->smaller() : smaller_; + + std::string base_symbol() const { + return ptr->symbol; } - amount_t *& larger() { - return base ? base->larger() : larger_; + std::string symbol() const { + return qualified_symbol; } - // If set, this global function pointer is called to determine - // whether prices have been updated in the meanwhile. - - static updater_t * updater; - - // This map remembers all commodities that have been defined. + void write(std::ostream& out) const { + out << symbol(); + } - static commodities_map commodities; - static commodity_t * null_commodity; - static commodity_t * default_commodity; + std::string name() const { + return ptr->name; + } + void set_name(const std::string& arg) { + ptr->name = arg; + } - static bool add_commodity(commodity_t * commodity, - const std::string symbol = "") { - // The argument "symbol" is useful for creating a symbol alias to - // an underlying commodity type; it is used by the Gnucash parser - // to link "USD" to "$". - std::pair<commodities_map::iterator, bool> result - = commodities.insert(commodities_pair((symbol.empty() ? - commodity->symbol : symbol), - commodity)); - return result.second; + std::string note() const { + return ptr->note; } - static bool remove_commodity(commodity_t * commodity) { - commodities_map::size_type n = commodities.erase(commodity->symbol); - return n > 0; + void set_note(const std::string& arg) { + ptr->note = arg; } - static commodity_t * find_commodity(const std::string& symbol, - bool auto_create = false); - // Now the per-object constructor and methods + unsigned short precision() const { + return ptr->precision; + } + void set_precision(unsigned short arg) { + ptr->precision = arg; + } - commodity_t(const std::string& _symbol = "", - unsigned int _precision = 0, - unsigned int _flags = COMMODITY_STYLE_DEFAULTS) - : precision_(_precision), flags_(_flags), history_(NULL), - smaller_(NULL), larger_(NULL), base(NULL), price(NULL) { - set_symbol(_symbol); + unsigned short flags() const { + return ptr->flags; } - ~commodity_t() { - if (history_) delete history_; - if (smaller_) delete smaller_; - if (larger_) delete larger_; - if (price) delete price; + void set_flags(unsigned short arg) { + ptr->flags = arg; + } + void add_flags(unsigned short arg) { + ptr->flags |= arg; + } + void drop_flags(unsigned short arg) { + ptr->flags &= ~arg; } - operator bool() const { - return this != null_commodity; + amount_t * smaller() const { + return ptr->smaller; } - bool operator==(const commodity_t& comm) const { - return this == &comm; + void set_smaller(const amount_t& arg) { + if (ptr->smaller) + delete ptr->smaller; + ptr->smaller = new amount_t(arg); } - bool operator!=(const commodity_t& comm) const { - return this != &comm; + + amount_t * larger() const { + return ptr->larger; + } + void set_larger(const amount_t& arg) { + if (ptr->larger) + delete ptr->larger; + ptr->larger = new amount_t(arg); } - void set_symbol(const std::string& sym); + commodity_base_t::history_t * history() const { + return ptr->history; + } - void add_price(const std::time_t date, const amount_t& price); + void add_price(const std::time_t date, const amount_t& price) { + return ptr->add_price(date, price); + } bool remove_price(const std::time_t date) { - if (history_) { - history_map::size_type n = history_->prices.erase(date); - return n > 0; - } - return false; + return ptr->remove_price(date); + } + amount_t value(const std::time_t moment = std::time(NULL)) const { + return ptr->value(moment); } - - amount_t value(const std::time_t moment = std::time(NULL)); bool valid() const { - if (symbol.empty() && this != null_commodity) + if (symbol().empty() && this != null_commodity) return false; - if (precision_ > 16) + if (precision() > 16) return false; return true; } }; +class annotated_commodity_t : public commodity_t +{ + public: + const commodity_t * base; + + amount_t price; + std::time_t date; + std::string tag; + + static std::string date_format; + + static void write_annotations(std::ostream& out, + const amount_t& price, + const std::time_t date, + const std::string& tag); + static + std::string make_qualified_name(const commodity_t& comm, + const amount_t& price, + const std::time_t date, + const std::string& tag); + static + annotated_commodity_t * create(const commodity_t& comm, + const amount_t& price, + const std::time_t date, + const std::string& tag, + const std::string& entry_name = ""); + static + annotated_commodity_t * create(const std::string& symbol, + const amount_t& price, + const std::time_t date, + const std::string& tag); + static + annotated_commodity_t * create(const std::string& symbol, + const std::string& price, + const std::string& date, + const std::string& tag); + static + annotated_commodity_t * find_or_create(const commodity_t& comm, + const amount_t& price, + const std::time_t date, + const std::string& tag); + + explicit annotated_commodity_t() { + annotated = true; + } + + void write_annotations(std::ostream& out) const { + annotated_commodity_t::write_annotations(out, price, date, tag); + } +}; + inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { - out << comm.symbol; + out << comm.symbol(); return out; } @@ -449,21 +573,12 @@ inline commodity_t& amount_t::commodity() const { return *commodity_; } -inline amount_t amount_t::base() const { - if (commodity_ && commodity_->price) { - amount_t temp(*this); - assert(commodity_->base); - temp.set_commodity(*(commodity_->base)); - return temp; - } else { - return *this; - } -} - inline amount_t amount_t::price() const { - if (commodity_ && commodity_->price) { - amount_t temp(*commodity_->price); + if (commodity_ && commodity_->annotated) { + amount_t temp(((annotated_commodity_t *)commodity_)->price); temp *= *this; + DEBUG_PRINT("amounts.commodities", + "Returning price of " << *this << " = " << temp); return temp; } else { return 0L; @@ -48,7 +48,7 @@ balance_t balance_t::price() const struct compare_amount_commodities { bool operator()(const amount_t * left, const amount_t * right) const { - return left->commodity().symbol < right->commodity().symbol; + return left->commodity().symbol() < right->commodity().symbol(); } }; @@ -106,8 +106,7 @@ balance_t& balance_t::operator*=(const balance_t& bal) return *this *= (*bal.amounts.begin()).second; } else { - std::string msg; - std::ostringstream errmsg(msg); + std::ostringstream errmsg; errmsg << "It makes no sense to multiply two balances: " << *this << " * " << bal; throw amount_error(errmsg.str()); @@ -120,8 +119,7 @@ balance_t& balance_t::operator/=(const balance_t& bal) return (*this = 0L); } else if (! bal) { - std::string msg; - std::ostringstream errmsg(msg); + std::ostringstream errmsg; errmsg << "Attempt to divide by zero: " << *this << " / " << bal; throw amount_error(errmsg.str()); } @@ -132,8 +130,7 @@ balance_t& balance_t::operator/=(const balance_t& bal) return (*this = 1L); } else { - std::string msg; - std::ostringstream errmsg(msg); + std::ostringstream errmsg; errmsg << "It makes no sense to divide two balances: " << *this << " / " << bal; throw amount_error(errmsg.str()); @@ -149,8 +146,7 @@ balance_t::operator amount_t() const return amount_t(); } else { - std::string msg; - std::ostringstream errmsg(msg); + std::ostringstream errmsg; errmsg << "Cannot convert a balance with " << "multiple commodities to an amount: " << *this; throw amount_error(errmsg.str()); @@ -147,7 +147,7 @@ class balance_t balance_t& operator*=(const amount_t& amt) { // Multiplying by the null commodity causes all amounts to be // increased by the same factor. - if (amt.commodity().symbol.empty()) { + if (! amt.commodity()) { for (amounts_map::iterator i = amounts.begin(); i != amounts.end(); i++) @@ -172,7 +172,7 @@ class balance_t balance_t& operator/=(const amount_t& amt) { // Dividing by the null commodity causes all amounts to be // increased by the same factor. - if (amt.commodity().symbol.empty()) { + if (! amt.commodity()) { for (amounts_map::iterator i = amounts.begin(); i != amounts.end(); i++) @@ -10,25 +10,29 @@ namespace ledger { -static unsigned long binary_magic_number = 0xFFEED765; +static unsigned long binary_magic_number = 0xFFEED765; #ifdef DEBUG_ENABLED -static unsigned long format_version = 0x00020601; +static unsigned long format_version = 0x00020603; #else -static unsigned long format_version = 0x00020600; +static unsigned long format_version = 0x00020602; #endif -static account_t ** accounts; -static account_t ** accounts_next; -static unsigned int account_index; +static account_t ** accounts; +static account_t ** accounts_next; +static unsigned int account_index; -static commodity_t ** commodities; -static commodity_t ** commodities_next; -static unsigned int commodity_index; +static commodity_base_t ** base_commodities; +static commodity_base_t ** base_commodities_next; +static unsigned int base_commodity_index; -extern char * bigints; -extern char * bigints_next; -extern unsigned int bigints_index; -extern unsigned int bigints_count; +static commodity_t ** commodities; +static commodity_t ** commodities_next; +static unsigned int commodity_index; + +extern char * bigints; +extern char * bigints_next; +extern unsigned int bigints_index; +extern unsigned int bigints_count; #if DEBUG_LEVEL >= ALPHA #define read_binary_guard(in, id) { \ @@ -400,26 +404,24 @@ inline void read_binary_period_entry(char *& data, period_entry_t * entry, entry->period.parse(stream); } -inline commodity_t * read_binary_commodity(char *& data) +inline commodity_base_t * read_binary_commodity_base(char *& data) { - commodity_t * commodity = new commodity_t; - *commodities_next++ = commodity; + commodity_base_t * commodity = new commodity_base_t; + *base_commodities_next++ = commodity; - read_binary_string(data, *(const_cast<std::string *>(&commodity->symbol))); - read_binary_number(data, commodity->quote); - read_binary_string(data, commodity->name_); - read_binary_string(data, commodity->note_); - read_binary_number(data, commodity->precision_); - read_binary_number(data, commodity->flags_); - read_binary_long(data, commodity->ident); + read_binary_string(data, commodity->symbol); + read_binary_string(data, commodity->name); + read_binary_string(data, commodity->note); + read_binary_number(data, commodity->precision); + read_binary_number(data, commodity->flags); return commodity; } -inline void read_binary_commodity_extra(char *& data, - commodity_t::ident_t ident) +inline void read_binary_commodity_base_extra(char *& data, + commodity_t::ident_t ident) { - commodity_t * commodity = commodities[ident]; + commodity_base_t * commodity = base_commodities[ident]; for (unsigned long i = 0, count = read_binary_long<unsigned long>(data); i < count; @@ -432,12 +434,12 @@ inline void read_binary_commodity_extra(char *& data, // Upon insertion, amt will be copied, which will cause the amount // to be duplicated (and thus not lost when the journal's // item_pool is deleted. - if (! commodity->history_) - commodity->history_ = new commodity_t::history_t; - commodity->history_->prices.insert(history_pair(when, amt)); + if (! commodity->history) + commodity->history = new commodity_base_t::history_t; + commodity->history->prices.insert(history_pair(when, amt)); } - if (commodity->history_) - read_binary_long(data, commodity->history_->last_lookup); + if (commodity->history) + read_binary_long(data, commodity->history->last_lookup); unsigned char flag; @@ -445,24 +447,47 @@ inline void read_binary_commodity_extra(char *& data, if (flag) { amount_t amt; read_binary_amount(data, amt); - commodity->smaller_ = new amount_t(amt); + commodity->smaller = new amount_t(amt); } flag = read_binary_number<unsigned char>(data); if (flag) { amount_t amt; read_binary_amount(data, amt); - commodity->larger_ = new amount_t(amt); + commodity->larger = new amount_t(amt); } +} - flag = read_binary_number<unsigned char>(data); - if (flag) { - amount_t amt; - read_binary_amount(data, amt); - commodity->price = new amount_t(amt); - commodity->base = - commodities[read_binary_long<commodity_t::ident_t>(data) - 1]; - } +inline commodity_t * read_binary_commodity(char *& data) +{ + commodity_t * commodity = new commodity_t; + *commodities_next++ = commodity; + + commodity->ptr = + base_commodities[read_binary_long<commodity_base_t::ident_t>(data) - 1]; + + read_binary_string(data, commodity->qualified_symbol); + commodity->annotated = false; + + return commodity; +} + +inline commodity_t * read_binary_commodity_annotated(char *& data) +{ + annotated_commodity_t * commodity = new annotated_commodity_t; + *commodities_next++ = commodity; + + commodity->ptr = + base_commodities[read_binary_long<commodity_base_t::ident_t>(data) - 1]; + + read_binary_string(data, commodity->qualified_symbol); + commodity->annotated = true; + + read_binary_amount(data, commodity->price); + read_binary_long(data, commodity->date); + read_binary_string(data, commodity->tag); + + return commodity; } inline @@ -472,7 +497,6 @@ account_t * read_binary_account(char *& data, journal_t * journal, account_t * acct = new account_t(NULL); *accounts_next++ = acct; - acct->ident = read_binary_long<account_t::ident_t>(data); acct->journal = journal; account_t::ident_t id; @@ -512,8 +536,9 @@ unsigned int read_binary_journal(std::istream& in, journal_t * journal, account_t * master) { - account_index = - commodity_index = 0; + account_index = + base_commodity_index = + commodity_index = 0; // Read in the files that participated in this journal, so that they // can be checked for changes on reading. @@ -587,30 +612,59 @@ unsigned int read_binary_journal(std::istream& in, bigints = bigints_next = (item_pool + sizeof(entry_t) * count + sizeof(transaction_t) * xact_count); - // Read in the commodities + // Read in the base commodities and then derived commodities + + commodity_base_t::ident_t bc_count = + read_binary_long<commodity_base_t::ident_t>(data); + base_commodities = base_commodities_next = new commodity_base_t *[bc_count]; - commodity_t::ident_t c_count = read_binary_long<commodity_t::ident_t>(data); + for (commodity_base_t::ident_t i = 0; i < bc_count; i++) { + commodity_base_t * commodity = read_binary_commodity_base(data); + + if (commodity->flags & COMMODITY_STYLE_BUILTIN) + commodity_base_t::commodities.erase(commodity->symbol); + + std::pair<base_commodities_map::iterator, bool> result = + commodity_base_t::commodities.insert + (base_commodities_pair(commodity->symbol, commodity)); + if (! result.second) + throw error(std::string("Failed to read base commodity from cache: ") + + commodity->symbol); + } + + for (commodity_base_t::ident_t i = 0; i < bc_count; i++) + read_binary_commodity_base_extra(data, i); + + commodity_t::ident_t c_count = read_binary_long<commodity_t::ident_t>(data); commodities = commodities_next = new commodity_t *[c_count]; + for (commodity_t::ident_t i = 0; i < c_count; i++) { - commodity_t * commodity = read_binary_commodity(data); - if (! (commodity->flags_ & COMMODITY_STYLE_BUILTIN)) { - if (commodity->symbol == "") { - commodity_t::commodities.erase(commodity->symbol); + commodity_t * commodity; + std::string mapping_key; + + if (read_binary_number<char>(data) == 0) { + commodity = read_binary_commodity(data); + mapping_key = commodity->ptr->symbol; + } else { + read_binary_string(data, mapping_key); + commodity = read_binary_commodity_annotated(data); + } + + if (commodity->flags() & COMMODITY_STYLE_BUILTIN) { + commodity_t::commodities.erase(mapping_key); + if (commodity->symbol() == "") { delete commodity_t::null_commodity; commodity_t::null_commodity = commodity; } - - std::pair<commodities_map::iterator, bool> result = - commodity_t::commodities.insert(commodities_pair(commodity->symbol, - commodity)); - if (! result.second) - throw error(std::string("Failed to read commodity from cache: ") + - commodity->symbol); } - } - for (commodity_t::ident_t i = 0; i < c_count; i++) - read_binary_commodity_extra(data, i); + std::pair<commodities_map::iterator, bool> result = + commodity_t::commodities.insert(commodities_pair(mapping_key, + commodity)); + if (! result.second) + throw error(std::string("Failed to read commodity from cache: ") + + commodity->ptr->symbol); + } commodity_t::ident_t ident; read_binary_long(data, ident); @@ -747,7 +801,7 @@ inline void write_binary_string(std::ostream& out, const std::string& str) void write_binary_amount(std::ostream& out, const amount_t& amt) { if (amt.commodity_) - write_binary_long(out, amt.commodity().ident); + write_binary_long(out, amt.commodity_->ident); else write_binary_long<commodity_t::ident_t>(out, 0xffffffff); @@ -762,7 +816,7 @@ void write_binary_mask(std::ostream& out, mask_t * mask) void write_binary_value_expr(std::ostream& out, const value_expr_t * expr) { - if (expr == NULL) { + if (! expr) { write_binary_number<unsigned char>(out, 0); return; } @@ -816,7 +870,7 @@ void write_binary_transaction(std::ostream& out, transaction_t * xact, write_binary_long(out, xact->_date_eff); write_binary_long(out, xact->account->ident); - if (xact->amount_expr != NULL) { + if (xact->amount_expr) { write_binary_number<char>(out, 1); write_binary_value_expr(out, xact->amount_expr); } else { @@ -830,7 +884,7 @@ void write_binary_transaction(std::ostream& out, transaction_t * xact, if (xact->cost && (! (ignore_calculated && xact->flags & TRANSACTION_CALCULATED))) { write_binary_number<char>(out, 1); - if (xact->cost_expr != NULL) { + if (xact->cost_expr) { write_binary_number<char>(out, 1); write_binary_value_expr(out, xact->cost_expr); } else { @@ -898,54 +952,69 @@ void write_binary_period_entry(std::ostream& out, period_entry_t * entry) write_binary_string(out, entry->period_string); } -void write_binary_commodity(std::ostream& out, commodity_t * commodity) +void write_binary_commodity_base(std::ostream& out, commodity_base_t * commodity) { + commodity->ident = ++base_commodity_index; + write_binary_string(out, commodity->symbol); - write_binary_number(out, commodity->quote); - write_binary_string(out, commodity->name_); - write_binary_string(out, commodity->note_); - write_binary_number(out, commodity->precision_); - write_binary_number(out, commodity->flags_); - commodity->ident = ++commodity_index; - write_binary_long(out, commodity->ident); + write_binary_string(out, commodity->name); + write_binary_string(out, commodity->note); + write_binary_number(out, commodity->precision); + write_binary_number(out, commodity->flags); } -void write_binary_commodity_extra(std::ostream& out, commodity_t * commodity) +void write_binary_commodity_base_extra(std::ostream& out, commodity_base_t * commodity) { - if (! commodity->history_) { + if (! commodity->history) { write_binary_long<unsigned long>(out, 0); } else { - write_binary_long<unsigned long>(out, commodity->history_->prices.size()); - for (history_map::const_iterator i = commodity->history_->prices.begin(); - i != commodity->history_->prices.end(); + write_binary_long<unsigned long>(out, commodity->history->prices.size()); + for (history_map::const_iterator i = commodity->history->prices.begin(); + i != commodity->history->prices.end(); i++) { write_binary_long(out, (*i).first); write_binary_amount(out, (*i).second); } - write_binary_long(out, commodity->history_->last_lookup); + write_binary_long(out, commodity->history->last_lookup); } - if (commodity->smaller_) { + if (commodity->smaller) { write_binary_number<unsigned char>(out, 1); - write_binary_amount(out, *commodity->smaller_); + write_binary_amount(out, *commodity->smaller); } else { write_binary_number<unsigned char>(out, 0); } - if (commodity->larger_) { + if (commodity->larger) { write_binary_number<unsigned char>(out, 1); - write_binary_amount(out, *commodity->larger_); + write_binary_amount(out, *commodity->larger); } else { write_binary_number<unsigned char>(out, 0); } +} - if (commodity->price) { - write_binary_number<unsigned char>(out, 1); - write_binary_amount(out, *commodity->price); - write_binary_long(out, commodity->base->ident); - } else { - write_binary_number<unsigned char>(out, 0); - } +void write_binary_commodity(std::ostream& out, commodity_t * commodity) +{ + commodity->ident = ++commodity_index; + + write_binary_long(out, commodity->ptr->ident); + write_binary_string(out, commodity->qualified_symbol); +} + +void write_binary_commodity_annotated(std::ostream& out, + commodity_t * commodity) +{ + commodity->ident = ++commodity_index; + + write_binary_long(out, commodity->ptr->ident); + write_binary_string(out, commodity->qualified_symbol); + + annotated_commodity_t * ann_comm = + static_cast<annotated_commodity_t *>(commodity); + + write_binary_amount(out, ann_comm->price); + write_binary_long(out, ann_comm->date); + write_binary_string(out, ann_comm->tag); } static inline account_t::ident_t count_accounts(account_t * account) @@ -964,7 +1033,6 @@ void write_binary_account(std::ostream& out, account_t * account) { account->ident = ++account_index; - write_binary_long(out, account->ident); if (account->parent) write_binary_long(out, account->parent->ident); else @@ -983,8 +1051,9 @@ void write_binary_account(std::ostream& out, account_t * account) void write_binary_journal(std::ostream& out, journal_t * journal) { - account_index = - commodity_index = 0; + account_index = + base_commodity_index = + commodity_index = 0; write_binary_number(out, binary_magic_number); write_binary_number(out, format_version); @@ -1041,17 +1110,34 @@ void write_binary_journal(std::ostream& out, journal_t * journal) // Write out the commodities write_binary_long<commodity_t::ident_t> - (out, commodity_t::commodities.size()); + (out, commodity_base_t::commodities.size()); - for (commodities_map::const_iterator i = commodity_t::commodities.begin(); - i != commodity_t::commodities.end(); + for (base_commodities_map::const_iterator i = + commodity_base_t::commodities.begin(); + i != commodity_base_t::commodities.end(); + i++) + write_binary_commodity_base(out, (*i).second); + + for (base_commodities_map::const_iterator i = + commodity_base_t::commodities.begin(); + i != commodity_base_t::commodities.end(); i++) - write_binary_commodity(out, (*i).second); + write_binary_commodity_base_extra(out, (*i).second); + + write_binary_long<commodity_t::ident_t> + (out, commodity_t::commodities.size()); for (commodities_map::const_iterator i = commodity_t::commodities.begin(); i != commodity_t::commodities.end(); - i++) - write_binary_commodity_extra(out, (*i).second); + i++) { + write_binary_number<char>(out, (*i).second->annotated ? 1 : 0); + if (! (*i).second->annotated) { + write_binary_commodity(out, (*i).second); + } else { + write_binary_string(out, (*i).first); // the mapping key + write_binary_commodity_annotated(out, (*i).second); + } + } if (commodity_t::default_commodity) write_binary_long(out, commodity_t::default_commodity->ident); @@ -292,9 +292,9 @@ void config_t::process_options(const std::string& command, // If downloading is to be supported, configure the updater - if (! commodity_t::updater && download_quotes) - commodity_t::updater = new quotes_by_script(price_db, pricing_leeway, - cache_dirty); + if (! commodity_base_t::updater && download_quotes) + commodity_base_t::updater = + new quotes_by_script(price_db, pricing_leeway, cache_dirty); if (! date_format.empty()) format_t::date_format = date_format; @@ -702,8 +702,9 @@ OPT_BEGIN(account, "a:") { config->account = optarg; } OPT_END(account); -OPT_BEGIN(debug, "") { +OPT_BEGIN(debug, ":") { config->debug_mode = true; + ::setenv("DEBUG_CLASS", optarg, 1); } OPT_END(debug); ////////////////////////////////////////////////////////////////////// @@ -1098,7 +1099,7 @@ option_t config_options[CONFIG_OPTIONS_SIZE] = { { "csv-register-format", '\0', true, opt_csv_register_format, false }, { "current", 'c', false, opt_current, false }, { "date-format", 'y', true, opt_date_format, false }, - { "debug", '\0', false, opt_debug, false }, + { "debug", '\0', true, opt_debug, false }, { "deviation", 'D', false, opt_deviation, false }, { "display", 'd', true, opt_display, false }, { "dow", '\0', false, opt_dow, false }, diff --git a/datetime.cc b/datetime.cc index 16c1e04e..cf26d0c9 100644 --- a/datetime.cc +++ b/datetime.cc @@ -3,13 +3,10 @@ #endif #include "datetime.h" -#include "error.h" #include <ctime> #include <cctype> -namespace ledger { - std::time_t now = std::time(NULL); int now_year = std::localtime(&now)->tm_year; @@ -108,8 +105,7 @@ static void parse_inclusion_specifier(const std::string& word, struct std::tm when; if (! parse_date_mask(word.c_str(), &when)) - throw interval_expr_error(std::string("Could not parse date mask: ") + - word); + throw datetime_error(std::string("Could not parse date mask: ") + word); when.tm_hour = 0; when.tm_min = 0; @@ -316,61 +312,5 @@ bool parse_date(const char * date_str, std::time_t * result, const int year) bool quick_parse_date(const char * date_str, std::time_t * result) { -#if 1 return parse_date(date_str, result, now_year + 1900); -#else - int year = -1, month = -1, day, num = 0; - - for (const char * p = date_str; *p; p++) { - if (*p == '/' || *p == '-' || *p == '.') { - if (year == -1) - year = num; - else - month = num; - num = 0; - } - else if (*p < '0' || *p > '9') { - return false; - } - else { - num *= 10; - num += *p - '0'; - } - } - - day = num; - - if (month == -1) { - month = year; - year = now_year + 1900; - } - - if (base == -1 || year != base_year) { - struct std::tm when; - std::memset(&when, 0, sizeof(when)); - - base_year = year == -1 ? now_year + 1900 : year; - when.tm_year = year == -1 ? now_year : year - 1900; - when.tm_mday = 1; - - base = std::mktime(&when); - year = base_year; - } - - *result = base; - - --month; - while (--month >= 0) { - *result += month_days[month] * 24 * 60 * 60; - if (month == 1 && year % 4 == 0 && year != 2000) // february in leap years - *result += 24 * 60 * 60; - } - - if (--day) - *result += day * 24 * 60 * 60; - - return true; -#endif } - -} // namespace ledger @@ -1,13 +1,9 @@ #ifndef _DATETIME_H #define _DATETIME_H -#include "debug.h" - #include <ctime> #include <sstream> -namespace ledger { - struct interval_t { unsigned int years; @@ -20,19 +16,12 @@ struct interval_t std::time_t _begin = 0, std::time_t _end = 0) : years(_years), months(_months), seconds(_seconds), begin(_begin), end(_end) { - DEBUG_PRINT("ledger.memory.ctors", "ctor interval_t"); } interval_t(const std::string& desc) : years(0), months(0), seconds(0), begin(0), end(0) { - DEBUG_PRINT("ledger.memory.ctors", "ctor interval_t"); std::istringstream stream(desc); parse(stream); } -#ifdef DEBUG_ENABLED - ~interval_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor interval_t"); - } -#endif operator bool() const { return seconds > 0 || months > 0 || years > 0; @@ -57,6 +46,15 @@ bool parse_date(const char * date_str, std::time_t * result, const int year = -1); bool quick_parse_date(const char * date_str, std::time_t * result); -} // namespace ledger +class datetime_error : public std::exception { + std::string reason; + public: + datetime_error(const std::string& _reason) throw() : reason(_reason) {} + virtual ~datetime_error() throw() {} + + virtual const char* what() const throw() { + return reason.c_str(); + } +}; #endif // _DATETIME_H @@ -163,7 +163,7 @@ element_t * format_t::parse_elements(const std::string& fmt) current->type = element_t::VALUE_EXPR; - assert(current->val_expr == NULL); + assert(! current->val_expr); current->val_expr = new value_expr(std::string(b, p)); break; } @@ -346,7 +346,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const commodity_t& comm(unit_cost.commodity()); bool has_flag = comm.flags() & COMMODITY_STYLE_VARIABLE; if (! has_flag) - unit_cost.commodity().flags() |= COMMODITY_STYLE_VARIABLE; + comm.add_flags(COMMODITY_STYLE_VARIABLE); std::ostringstream stream; stream << details.xact->amount << " @ " << unit_cost; @@ -354,7 +354,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const use_disp = true; if (! has_flag) - unit_cost.commodity().flags() &= ~COMMODITY_STYLE_VARIABLE; + comm.drop_flags(COMMODITY_STYLE_VARIABLE); } else if (details.entry) { unsigned int xacts_count = 0; @@ -84,7 +84,10 @@ static void startElement(void *userData, const char *name, const char **atts) action = ACCOUNT_PARENT; else if (std::strcmp(name, "gnc:commodity") == 0) { assert(! curr_comm); +#if 0 + // jww (2006-03-02): !!! curr_comm = new commodity_t(""); +#endif } else if (std::strcmp(name, "cmdty:id") == 0) action = COMM_SYM; @@ -133,7 +136,10 @@ static void endElement(void *userData, const char *name) } else if (std::strcmp(name, "gnc:commodity") == 0) { assert(curr_comm); +#if 0 + // jww (2006-03-02): !!! commodity_t::add_commodity(curr_comm); +#endif curr_comm = NULL; } else if (std::strcmp(name, "gnc:transaction") == 0) { @@ -252,20 +258,25 @@ static void dataHandler(void *userData, const char *s, int len) case COMM_SYM: if (curr_comm) { +#if 0 + // jww (2006-03-02): !!! curr_comm->set_symbol(std::string(s, len)); +#endif } else if (curr_account) { std::string symbol(s, len); - commodity_t * comm = commodity_t::find_commodity(symbol, true); + commodity_t * comm = commodity_t::find_or_create(symbol); + assert(comm); if (symbol != "$" && symbol != "USD") - comm->flags() |= COMMODITY_STYLE_SEPARATED; + comm->add_flags(COMMODITY_STYLE_SEPARATED); account_comms.insert(account_comm_pair(curr_account, comm)); } else if (curr_entry) { std::string symbol(s, len); - entry_comm = commodity_t::find_commodity(symbol, true); + entry_comm = commodity_t::find_or_create(symbol); + assert(entry_comm); if (symbol != "$" && symbol != "USD") - entry_comm->flags() |= COMMODITY_STYLE_SEPARATED; + entry_comm->add_flags(COMMODITY_STYLE_SEPARATED); } break; @@ -274,7 +285,7 @@ static void dataHandler(void *userData, const char *s, int len) break; case COMM_PREC: - curr_comm->precision() = len - 1; + curr_comm->set_precision(len - 1); break; case ENTRY_NUM: @@ -308,7 +319,7 @@ static void dataHandler(void *userData, const char *s, int len) curr_value.set_commodity(*entry_comm); if (precision > entry_comm->precision()) - entry_comm->precision() = precision; + entry_comm->set_precision(precision); break; } @@ -380,11 +391,13 @@ unsigned int gnucash_parser_t::parse(std::istream& in, // GnuCash uses the USD commodity without defining it, which really // means $. - commodity_t * usd; - usd = new commodity_t("$", 2, COMMODITY_STYLE_THOUSANDS); - commodity_t::add_commodity(usd, "$"); - usd = new commodity_t("$", 2, COMMODITY_STYLE_THOUSANDS); + commodity_t * usd = commodity_t::find_or_create("$"); + usd->set_precision(2); + usd->add_flags(COMMODITY_STYLE_THOUSANDS); +#if 0 + //jww (2006-03-02): !!! make an alias here commodity_t::add_commodity(usd, "USD"); +#endif offset = 2; parser = current_parser = XML_ParserCreate(NULL); @@ -105,9 +105,12 @@ bool entry_base_t::finalize() balance += *p; } - if ((*x)->cost && (*x)->amount.commodity().price) - balance += (*((*x)->amount.commodity().price) * (*x)->amount - - *((*x)->cost)); + if ((*x)->cost && (*x)->amount.commodity().annotated) { + annotated_commodity_t& + ann_comm(static_cast<annotated_commodity_t&>((*x)->amount.commodity())); + if (ann_comm.price) + balance += ann_comm.price * (*x)->amount - *((*x)->cost); + } } } @@ -115,6 +118,9 @@ bool entry_base_t::finalize() if (no_amounts) return true; + // If there is only one transaction, balance against the basket + // account if one has been set. + if (journal && journal->basket && transactions.size() == 1) { assert(balance.type < value_t::BALANCE); transaction_t * nxact = new transaction_t(journal->basket); @@ -149,8 +155,13 @@ bool entry_base_t::finalize() continue; assert((*x)->amount); - balance -= (*x)->amount; + + entry_t * entry = dynamic_cast<entry_t *>(this); + (*x)->amount.annotate_commodity(abs(per_unit_cost), + entry ? entry->actual_date() : 0, + entry ? entry->code : ""); + (*x)->cost = new amount_t(- (per_unit_cost * (*x)->amount)); balance += *(*x)->cost; } @@ -289,7 +300,7 @@ void auto_entry_t::extend_entry(entry_base_t& entry) t != transactions.end(); t++) { amount_t amt; - if ((*t)->amount.commodity().symbol.empty()) + if (! (*t)->amount.commodity()) amt = (*i)->amount * (*t)->amount; else amt = (*t)->amount; @@ -167,11 +167,9 @@ class entry_t : public entry_base_t } entry_t(const entry_t& e); -#ifdef DEBUG_ENABLED virtual ~entry_t() { DEBUG_PRINT("ledger.memory.dtors", "dtor entry_t"); } -#endif std::time_t actual_date() const { return _date; @@ -247,11 +245,9 @@ class period_entry_t : public entry_base_t DEBUG_PRINT("ledger.memory.ctors", "ctor period_entry_t"); } -#ifdef DEBUG_ENABLED virtual ~period_entry_t() { DEBUG_PRINT("ledger.memory.dtors", "dtor period_entry_t"); } -#endif virtual bool valid() const { return period; @@ -209,7 +209,7 @@ int parse_and_report(int argc, char * argv[], char * envp[]) // rightmost '/' character in the pager pathname. See manpage // for strrchr. arg0 = std::strrchr(config.pager.c_str(), '/'); - if (arg0 != NULL) + if (arg0) arg0++; else arg0 = config.pager.c_str(); // No slashes in pager. @@ -69,8 +69,7 @@ int ofx_proc_transaction_cb(struct OfxTransactionData data, assert(ac != ofx_account_currencies.end()); commodity_t * default_commodity = (*ac).second; - std::string buf; - std::ostringstream stream(buf); + std::ostringstream stream; stream << - data.units; // jww (2005-02-09): what if the amount contains fees? @@ -84,7 +83,7 @@ int ofx_proc_transaction_cb(struct OfxTransactionData data, } if (data.unitprice_valid && data.unitprice != 1.0) { - std::ostringstream cstream(buf); + std::ostringstream cstream; stream << - data.unitprice; xact->cost = new amount_t(stream.str() + " " + default_commodity->symbol); } @@ -49,7 +49,7 @@ bool process_option(option_t * options, const std::string& name, const char * arg) { option_t * opt = search_options(options, name.c_str()); - if (opt != NULL && ! opt->handled) { + if (opt && ! opt->handled) { opt->handler(arg); opt->handled = true; return true; @@ -89,12 +89,12 @@ void process_arguments(option_t * options, int argc, char ** argv, } opt = search_options(options, name); - if (opt == NULL) + if (! opt) throw option_error(std::string("illegal option --") + name); - if (opt->wants_arg && value == NULL) { + if (opt->wants_arg && ! value) { value = *++i; - if (value == NULL) + if (! value) throw option_error(std::string("missing option argument for --") + name); } @@ -102,18 +102,18 @@ void process_arguments(option_t * options, int argc, char ** argv, } else { char c = (*i)[1]; opt = search_options(options, c); - if (opt == NULL) + if (! opt) throw option_error(std::string("illegal option -") + c); if (opt->wants_arg) { value = *++i; - if (value == NULL) + if (! value) throw option_error(std::string("missing option argument for -") + c); } } assert(opt); - assert(value == NULL || opt->wants_arg); + assert(! value || opt->wants_arg); process_option(opt, value); next: @@ -118,13 +118,15 @@ unsigned int qif_parser_t::parse(std::istream& in, unsigned long flags = xact->amount.commodity().flags(); unsigned short prec = xact->amount.commodity().precision(); - if (! def_commodity) - def_commodity = commodity_t::find_commodity("$", true); + if (! def_commodity) { + def_commodity = commodity_t::find_or_create("$"); + assert(def_commodity); + } xact->amount.set_commodity(*def_commodity); - def_commodity->flags() |= flags; + def_commodity->add_flags(flags); if (prec > def_commodity->precision()) - def_commodity->precision() = prec; + def_commodity->set_precision(prec); if (c == '$') { saw_splits = true; @@ -9,7 +9,7 @@ namespace ledger { -void quotes_by_script::operator()(commodity_t& commodity, +void quotes_by_script::operator()(commodity_base_t& commodity, const std::time_t moment, const std::time_t date, const std::time_t last, @@ -22,12 +22,12 @@ void quotes_by_script::operator()(commodity_t& commodity, DEBUG_PRINT_TIME_(moment); DEBUG_PRINT_TIME_(date); DEBUG_PRINT_TIME_(last); - if (commodity.history()) - DEBUG_PRINT_TIME_(commodity.history()->last_lookup); + if (commodity.history) + DEBUG_PRINT_TIME_(commodity.history->last_lookup); DEBUG_PRINT_("pricing_leeway is " << pricing_leeway); - if ((commodity.history() && - std::difftime(now, commodity.history()->last_lookup) < pricing_leeway) || + if ((commodity.history && + std::difftime(now, commodity.history->last_lookup) < pricing_leeway) || std::difftime(now, last) < pricing_leeway || (price && std::difftime(moment, date) > 0 && std::difftime(moment, date) <= pricing_leeway)) @@ -61,7 +61,7 @@ void quotes_by_script::operator()(commodity_t& commodity, price.parse(buf); commodity.add_price(now, price); - commodity.history()->last_lookup = now; + commodity.history->last_lookup = now; cache_dirty = true; if (price && ! price_db.empty()) { @@ -5,7 +5,7 @@ namespace ledger { -class quotes_by_script : public commodity_t::updater_t +class quotes_by_script : public commodity_base_t::updater_t { std::string price_db; unsigned long pricing_leeway; @@ -18,7 +18,7 @@ class quotes_by_script : public commodity_t::updater_t : price_db(_price_db), pricing_leeway(_pricing_leeway), cache_dirty(_cache_dirty) {} - virtual void operator()(commodity_t& commodity, + virtual void operator()(commodity_base_t& commodity, const std::time_t moment, const std::time_t date, const std::time_t last, diff --git a/reconcile.cc b/reconcile.cc index ec893c0b..e2f11198 100644 --- a/reconcile.cc +++ b/reconcile.cc @@ -71,7 +71,7 @@ void reconcile_transactions::flush() balance -= cleared_balance; if (balance.type >= value_t::BALANCE) throw error(std::string("Reconcile balance is not of the same commodity ('") + - b_comm.symbol + "' != '" + cb_comm.symbol + "')"); + b_comm.symbol() + "' != '" + cb_comm.symbol() + "')"); // If the amount to reconcile is the same as the pending balance, // then assume an exact match and return the results right away. @@ -131,7 +131,7 @@ value_expr_t * parse_amount(const char * text, amount_t& amt, { char * altbuf = NULL; - if (*text && *text != '(') { + if (*text && *text != '(' && *text != '-') { bool in_quote = false; bool seen_digit = false; for (const char * p = text + 1; *p; p++) @@ -295,47 +295,29 @@ transaction_t * parse_transaction(char * line, account_t * account, AMOUNT_PARSE_NO_REDUCE, *xact); if (xact->amount_expr) xact->amount_expr->acquire(); + if (price) { xact->cost = new amount_t; xact->cost_expr = parse_amount(price, *xact->cost, AMOUNT_PARSE_NO_MIGRATE, *xact); if (xact->cost_expr) xact->cost_expr->acquire(); - if (per_unit) { - if (xact->amount.commodity().base == NULL) { - std::string symbol = xact->amount.commodity().symbol; - symbol += " {"; - symbol += price; - symbol += "}"; - - commodity_t * priced_commodity = - commodity_t::find_commodity(symbol, true); - - priced_commodity->price = new amount_t(*xact->cost); - priced_commodity->base = &xact->amount.commodity(); - - xact->amount.set_commodity(*priced_commodity); - } + if (per_unit) { + if (! xact->amount.commodity().annotated) + xact->amount.annotate_commodity(*xact->cost, + xact->entry->actual_date(), + xact->entry->code); *xact->cost *= xact->amount; *xact->cost = xact->cost->round(xact->cost->commodity().precision()); } - else if (xact->amount.commodity().base == NULL) { + else if (! xact->amount.commodity().annotated) { amount_t cost(*xact->cost); cost /= xact->amount; cost = cost.round(cost.commodity().precision()); - std::string symbol; - std::ostringstream symstr(symbol); - symstr << xact->amount.commodity().symbol << " {" << cost << "}"; - - commodity_t * priced_commodity = - commodity_t::find_commodity(symstr.str(), true); - - priced_commodity->price = new amount_t(cost); - priced_commodity->base = &xact->amount.commodity(); - - xact->amount.set_commodity(*priced_commodity); + xact->amount.annotate_commodity(cost, xact->entry->actual_date(), + xact->entry->code); } } xact->amount.reduce(); @@ -348,7 +330,7 @@ bool parse_transactions(std::istream& in, account_t * account, entry_base_t& entry, const std::string& kind, - istream_pos_type& beg_pos) + unsigned long beg_pos) { static char line[MAX_LINE + 1]; bool added = false; @@ -357,7 +339,7 @@ bool parse_transactions(std::istream& in, in.getline(line, MAX_LINE); if (in.eof()) break; - beg_pos += istream_pos_type(std::strlen(line) + 1); + beg_pos += std::strlen(line) + 1; linenum++; if (line[0] == ' ' || line[0] == '\t' || line[0] == '\r') { @@ -382,7 +364,7 @@ namespace { } entry_t * parse_entry(std::istream& in, char * line, account_t * master, - textual_parser_t& parser, istream_pos_type& beg_pos) + textual_parser_t& parser, unsigned long beg_pos) { std::auto_ptr<entry_t> curr(new entry_t); @@ -441,15 +423,15 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master, TIMER_START(entry_xacts); - istream_pos_type end_pos; - unsigned long beg_line = linenum; + unsigned long end_pos; + unsigned long beg_line = linenum; while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { line[0] = '\0'; in.getline(line, MAX_LINE); if (in.eof() && line[0] == '\0') break; - end_pos = beg_pos + istream_pos_type(std::strlen(line) + 1); + end_pos = beg_pos + std::strlen(line) + 1; linenum++; if (line[0] == ' ' || line[0] == '\t' || line[0] == '\r') { @@ -581,16 +563,16 @@ unsigned int textual_parser_t::parse(std::istream& in, src_idx = journal->sources.size() - 1; linenum = 1; - istream_pos_type beg_pos = in.tellg(); - istream_pos_type end_pos; - unsigned long beg_line = linenum; + unsigned long beg_pos = in.tellg(); + unsigned long end_pos; + unsigned long beg_line = linenum; while (in.good() && ! in.eof()) { try { in.getline(line, MAX_LINE); if (in.eof()) break; - end_pos = beg_pos + istream_pos_type(std::strlen(line) + 1); + end_pos = beg_pos + std::strlen(line) + 1; linenum++; switch (line[0]) { @@ -690,8 +672,8 @@ unsigned int textual_parser_t::parse(std::istream& in, parse_symbol(symbol_and_price, symbol); amount_t price(symbol_and_price); - commodity_t * commodity = commodity_t::find_commodity(symbol, true); - commodity->add_price(date, price); + if (commodity_t * commodity = commodity_t::find_or_create(symbol)) + commodity->add_price(date, price); break; } @@ -700,8 +682,8 @@ unsigned int textual_parser_t::parse(std::istream& in, std::string symbol; parse_symbol(p, symbol); - commodity_t * commodity = commodity_t::find_commodity(symbol, true); - commodity->flags() |= COMMODITY_STYLE_NOMARKET; + if (commodity_t * commodity = commodity_t::find_or_create(symbol)) + commodity->add_flags(COMMODITY_STYLE_NOMARKET); break; } @@ -774,11 +756,11 @@ unsigned int textual_parser_t::parse(std::istream& in, char * p = next_element(line); std::string word(line + 1); if (word == "include") { - push_var<std::string> save_path(path); - push_var<unsigned int> save_src_idx(src_idx); - push_var<istream_pos_type> save_beg_pos(beg_pos); - push_var<istream_pos_type> save_end_pos(end_pos); - push_var<unsigned int> save_linenum(linenum); + push_var<std::string> save_path(path); + push_var<unsigned int> save_src_idx(src_idx); + push_var<unsigned long> save_beg_pos(beg_pos); + push_var<unsigned long> save_end_pos(end_pos); + push_var<unsigned int> save_linenum(linenum); path = p; if (path[0] != '/' && path[0] != '\\') { @@ -831,7 +813,7 @@ unsigned int textual_parser_t::parse(std::istream& in, default: { unsigned int first_line = linenum; - istream_pos_type pos = end_pos; + unsigned long pos = end_pos; if (entry_t * entry = parse_entry(in, line, account_stack.front(), *this, pos)) { if (journal->add_entry(entry)) { @@ -845,11 +827,10 @@ unsigned int textual_parser_t::parse(std::istream& in, print_entry(std::cerr, *entry); delete entry; - std::string msgbuf; - std::ostringstream msg(msgbuf); - msg << "Entry above does not balance; remainder is: " - << entry_balance; - throw parse_error(path, first_line, msg.str()); + std::ostringstream errmsg; + errmsg << "Entry above does not balance; remainder is: " + << entry_balance; + throw parse_error(path, first_line, errmsg.str()); } } else { throw parse_error(path, first_line, "Failed to parse entry"); @@ -935,10 +916,9 @@ void write_textual_journal(journal_t& journal, std::string path, auto_entries_list::iterator al = journal.auto_entries.begin(); period_entries_list::iterator pl = journal.period_entries.begin(); - istream_pos_type pos = 0; - istream_pos_type jump_to; + unsigned long pos = 0; - format_t hdr_fmt(write_hdr_format); + format_t hdr_fmt(write_hdr_format); std::ifstream in(found.c_str()); while (! in.eof()) { @@ -96,7 +96,7 @@ namespace { value_expr_t * reduce_leaves(value_expr_t * expr, const details_t& details, value_expr_t * context) { - if (expr == NULL) + if (! expr) return NULL; value_auto_ptr temp; @@ -119,7 +119,7 @@ namespace { value_expr_t * find_leaf(value_expr_t * context, int goal, int& found) { - if (context == NULL) + if (! context) return NULL; if (context->kind != value_expr_t::O_COM) { @@ -164,7 +164,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, transaction_xdata_(*details.xact).dflags & TRANSACTION_COMPOSITE) result = transaction_xdata_(*details.xact).composite_amount; else - result = base_amount(details.xact->amount); + result = details.xact->amount; } else if (details.account && account_has_xdata(*details.account)) { result = account_xdata(*details.account).value; @@ -210,7 +210,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, if (details.xact->cost) result = *details.xact->cost; else - result = base_amount(details.xact->amount); + result = details.xact->amount; } } else if (details.account && account_has_xdata(*details.account)) { @@ -493,7 +493,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, case F_COMMODITY_MASK: assert(mask); if (details.xact) - result = mask->match(details.xact->amount.commodity().symbol); + result = mask->match(details.xact->amount.commodity().symbol()); else result = false; break; @@ -757,7 +757,7 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope) // Define the value associated with the defined identifier value_auto_ptr def(parse_boolean_expr(in, params.get())); - if (def.get() == NULL) + if (! def.get()) throw value_expr_error(std::string("Definition failed for '") + buf + "'"); node.reset(new value_expr_t(value_expr_t::O_DEF)); @@ -773,7 +773,7 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope) } else { assert(scope); value_expr_t * def = scope->lookup(buf); - if (def == NULL) { + if (! def) { if (buf[1] == '\0' && (buf[0] == 'c' || buf[0] == 'C' || buf[0] == 'p' || buf[0] == 'w' || buf[0] == 'W' || buf[0] == 'e')) @@ -796,15 +796,14 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope) } in.get(c); - if (args.get() != NULL) { + if (args.get()) { count = count_leaves(args.get()); node->set_right(args.release()); } } if (count != def->left->constant_i) { - std::string msg; - std::ostringstream errmsg(msg); + std::ostringstream errmsg; errmsg << "Wrong number of arguments to '" << buf << "': saw " << count << ", wanted " << def->left->constant_i; @@ -905,7 +904,8 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope) unexpected(c, '}'); node.reset(new value_expr_t(value_expr_t::CONSTANT_A)); - node->constant_a = new amount_t(buf); + node->constant_a = new amount_t(); + node->constant_a->parse(buf, AMOUNT_PARSE_NO_MIGRATE); break; } @@ -1447,10 +1447,10 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node, if (node->right) dump_value_expr(out, node->right, depth + 1); } else { - assert(node->right == NULL); + assert(! node->right); } } else { - assert(node->left == NULL); + assert(! node->left); } } @@ -349,7 +349,7 @@ public: } virtual ~value_expr() { DEBUG_PRINT("ledger.memory.dtors", "dtor value_expr"); - if (parsed != NULL) + if (parsed) parsed->release(); } @@ -363,12 +363,12 @@ extern std::auto_ptr<value_calc> amount_expr; extern std::auto_ptr<value_calc> total_expr; inline void compute_amount(value_t& result, const details_t& details) { - if (amount_expr.get() != NULL) + if (amount_expr.get()) amount_expr->compute(result, details); } inline void compute_total(value_t& result, const details_t& details) { - if (total_expr.get() != NULL) + if (total_expr.get()) total_expr->compute(result, details); } @@ -44,14 +44,8 @@ void add_transaction_to(const transaction_t& xact, value_t& value) transaction_xdata_(xact).dflags & TRANSACTION_COMPOSITE) { value += transaction_xdata_(xact).composite_amount; } - else if (xact.cost || xact.amount.commodity().price || value) { - if (xact.amount.commodity().price) { - amount_t price(*xact.amount.commodity().price); - price *= xact.amount; - value.add(base_amount(xact.amount), &price, xact.cost); - } else { - value.add(base_amount(xact.amount), NULL, xact.cost); - } + else if (xact.cost || xact.amount.commodity().annotated || value) { + value.add(xact.amount, xact.cost); } else { value = xact.amount; @@ -500,7 +494,7 @@ void set_comm_as_payee::operator()(transaction_t& xact) entry_t& entry = entry_temps.back(); entry._date = xact.date(); entry.code = xact.entry->code; - entry.payee = xact.amount.commodity().symbol; + entry.payee = xact.amount.commodity().symbol(); xact_temps.push_back(xact); transaction_t& temp = xact_temps.back(); @@ -829,7 +823,7 @@ void walk_commodities(commodities_map& commodities, continue; entry_temps.push_back(entry_t()); - acct_temps.push_back(account_t(NULL, (*i).second->symbol)); + acct_temps.push_back(account_t(NULL, (*i).second->symbol())); if ((*i).second->history()) for (history_map::iterator j = (*i).second->history()->prices.begin(); @@ -117,24 +117,26 @@ static void endElement(void *userData, const char *name) } else if (std::strcmp(name, "symbol") == 0) { assert(! curr_comm); - curr_comm = commodity_t::find_commodity(data, true); - curr_comm->flags() |= COMMODITY_STYLE_SUFFIXED; + curr_comm = commodity_t::find_or_create(data); + assert(curr_comm); + curr_comm->add_flags(COMMODITY_STYLE_SUFFIXED); if (! comm_flags.empty()) { for (std::string::size_type i = 0, l = comm_flags.length(); i < l; i++) { switch (comm_flags[i]) { - case 'P': curr_comm->flags() &= ~COMMODITY_STYLE_SUFFIXED; break; - case 'S': curr_comm->flags() |= COMMODITY_STYLE_SEPARATED; break; - case 'T': curr_comm->flags() |= COMMODITY_STYLE_THOUSANDS; break; - case 'E': curr_comm->flags() |= COMMODITY_STYLE_EUROPEAN; break; + case 'P': curr_comm->drop_flags(COMMODITY_STYLE_SUFFIXED); break; + case 'S': curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); break; + case 'T': curr_comm->add_flags(COMMODITY_STYLE_THOUSANDS); break; + case 'E': curr_comm->add_flags(COMMODITY_STYLE_EUROPEAN); break; } } } } +#if 0 + // jww (2006-03-02): !!! else if (std::strcmp(name, "price") == 0) { assert(curr_comm); amount_t * price = new amount_t(data); - std::string symbol; - std::ostringstream symstr(symbol); + std::ostringstream symstr; symstr << curr_comm->symbol << " {" << *price << "}"; commodity_t * priced_comm = commodity_t::find_commodity(symstr.str(), true); @@ -142,6 +144,7 @@ static void endElement(void *userData, const char *name) priced_comm->base = curr_comm; curr_comm = priced_comm; } +#endif else if (std::strcmp(name, "quantity") == 0) { curr_entry->transactions.back()->amount.parse(data); if (curr_comm) { @@ -149,7 +152,7 @@ static void endElement(void *userData, const char *name) if (i != std::string::npos) { int precision = data.length() - i - 1; if (precision > curr_comm->precision()) - curr_comm->precision() = precision; + curr_comm->set_precision(precision); } curr_entry->transactions.back()->amount.set_commodity(*curr_comm); curr_comm = NULL; @@ -260,6 +263,8 @@ void xml_write_amount(std::ostream& out, const amount_t& amount, if (c.flags() & COMMODITY_STYLE_EUROPEAN) out << 'E'; out << "\">\n"; for (int i = 0; i < depth + 4; i++) out << ' '; +#if 0 + // jww (2006-03-02): !!! if (c.price) { out << "<symbol>" << c.base->symbol << "</symbol>\n"; for (int i = 0; i < depth + 4; i++) out << ' '; @@ -270,6 +275,7 @@ void xml_write_amount(std::ostream& out, const amount_t& amount, } else { out << "<symbol>" << c.symbol << "</symbol>\n"; } +#endif for (int i = 0; i < depth + 2; i++) out << ' '; out << "</commodity>\n"; |