diff options
-rw-r--r-- | amount.cc | 29 | ||||
-rw-r--r-- | amount.h | 21 | ||||
-rw-r--r-- | binary.cc | 40 | ||||
-rw-r--r-- | config.cc | 50 | ||||
-rw-r--r-- | config.h | 2 | ||||
-rw-r--r-- | journal.cc | 2 | ||||
-rw-r--r-- | main.cc | 3 | ||||
-rw-r--r-- | textual.cc | 38 | ||||
-rw-r--r-- | walk.cc | 4 |
9 files changed, 127 insertions, 62 deletions
@@ -596,10 +596,9 @@ bool amount_t::realzero() const amount_t amount_t::value(const std::time_t moment) const { if (quantity) { - commodity_t& comm = commodity(); - if (! (comm.flags() & COMMODITY_STYLE_NOMARKET)) - if (amount_t amt = comm.value(moment)) - return (amt * *this).round(); + amount_t amt(commodity().value(moment)); + if (! amt.realzero()) + return (amt * *this).round(); } return *this; } @@ -973,6 +972,13 @@ void parse_annotations(std::istream& in, amount_t& price, price.parse(buf, AMOUNT_PARSE_NO_MIGRATE); price.reduce(); + + // Since this price will maintain its own precision, make sure + // it is at least as large as the base commodity, since the user + // may have only specified {$1} or something similar. + + if (price.quantity->prec < price.commodity().precision()) + price = price.round(); // no need to retain individual precision } else if (c == '[') { if (date) @@ -1084,7 +1090,7 @@ void amount_t::parse(std::istream& in, unsigned char flags) } assert(commodity_); - if (price || date || ! tag.empty()) + if (! price.realzero() || date || ! tag.empty()) commodity_ = annotated_commodity_t::find_or_create(*commodity_, price, date, tag); } @@ -1435,6 +1441,19 @@ void commodity_base_t::add_price(const std::time_t date, const amount_t& price) } } +bool commodity_base_t::remove_price(const std::time_t date) +{ + if (history) { + history_map::size_type n = history->prices.erase(date); + if (n > 0) { + if (history->prices.empty()) + history = NULL; + return true; + } + } + return false; +} + commodity_base_t * commodity_base_t::create(const std::string& symbol) { commodity_base_t * commodity = new commodity_base_t(symbol); @@ -283,6 +283,9 @@ class amount_t friend void clean_commodity_history(char * item_pool, char * item_pool_end); + + friend void parse_annotations(std::istream& in, amount_t& price, + std::time_t& date, std::string& tag); }; unsigned int sizeof_bigint_t(); @@ -365,8 +368,9 @@ class commodity_base_t amount_t * smaller; amount_t * larger; - commodity_base_t() : precision(0), flags(COMMODITY_STYLE_DEFAULTS), - history(NULL), smaller(NULL), larger(NULL) {} + 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, @@ -388,18 +392,13 @@ class commodity_base_t struct history_t { history_map prices; std::time_t last_lookup; + std::time_t bogus_time; + history_t() : last_lookup(0), bogus_time(0) {} }; 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; - } - + void add_price(const std::time_t date, const amount_t& price); + bool remove_price(const std::time_t date); amount_t value(const std::time_t moment = std::time(NULL)); class updater_t { @@ -12,9 +12,9 @@ namespace ledger { static unsigned long binary_magic_number = 0xFFEED765; #ifdef DEBUG_ENABLED -static unsigned long format_version = 0x00020607; +static unsigned long format_version = 0x00020609; #else -static unsigned long format_version = 0x00020606; +static unsigned long format_version = 0x00020608; #endif static account_t ** accounts; @@ -415,6 +415,7 @@ inline void read_binary_commodity_base_extra(char *& data, { commodity_base_t * commodity = base_commodities[ident]; + bool read_history = false; for (unsigned long i = 0, count = read_binary_long<unsigned long>(data); i < count; i++) { @@ -429,8 +430,9 @@ inline void read_binary_commodity_base_extra(char *& data, if (! commodity->history) commodity->history = new commodity_base_t::history_t; commodity->history->prices.insert(history_pair(when, amt)); + read_history = true; } - if (commodity->history) + if (read_history) read_binary_long(data, commodity->history->last_lookup); unsigned char flag; @@ -629,8 +631,7 @@ unsigned int read_binary_journal(std::istream& in, // expression passed to an option, we'll just override the // flags, but keep the commodity pointer intact. if (c == commodity_base_t::commodities.end() || - (*c).second->history || (*c).second->smaller || - (*c).second->larger) + (*c).second->smaller || (*c).second->larger) throw new error(std::string("Failed to read base commodity from cache: ") + commodity->symbol); @@ -638,7 +639,6 @@ unsigned int read_binary_journal(std::istream& in, (*c).second->note = commodity->note; (*c).second->precision = commodity->precision; (*c).second->flags = commodity->flags; - (*c).second->history = commodity->history; (*c).second->smaller = commodity->smaller; (*c).second->larger = commodity->larger; @@ -646,9 +646,6 @@ unsigned int read_binary_journal(std::istream& in, } } - 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]; @@ -680,6 +677,9 @@ unsigned int read_binary_journal(std::istream& in, commodity->base->symbol); } + for (commodity_base_t::ident_t i = 0; i < bc_count; i++) + read_binary_commodity_base_extra(data, i); + commodity_t::ident_t ident; read_binary_long(data, ident); if (ident == 0xffffffff || ident == 0) @@ -971,8 +971,12 @@ void write_binary_commodity_base(std::ostream& out, commodity_base_t * commodity write_binary_number(out, commodity->flags); } -void write_binary_commodity_base_extra(std::ostream& out, commodity_base_t * commodity) +void write_binary_commodity_base_extra(std::ostream& out, + commodity_base_t * commodity) { + if (commodity->history && commodity->history->bogus_time) + commodity->remove_price(commodity->history->bogus_time); + if (! commodity->history) { write_binary_long<unsigned long>(out, 0); } else { @@ -1127,12 +1131,6 @@ void write_binary_journal(std::ostream& out, journal_t * journal) 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_base_extra(out, (*i).second); - write_binary_long<commodity_t::ident_t> (out, commodity_t::commodities.size()); @@ -1155,6 +1153,16 @@ void write_binary_journal(std::ostream& out, journal_t * journal) } } + // Write out the history and smaller/larger convertible links after + // both the base and the main commodities have been written, since + // the amounts in both will refer to the mains. + + for (base_commodities_map::const_iterator i = + commodity_base_t::commodities.begin(); + i != commodity_base_t::commodities.end(); + i++) + write_binary_commodity_base_extra(out, (*i).second); + if (commodity_t::default_commodity) write_binary_long(out, commodity_t::default_commodity->ident); else @@ -106,21 +106,10 @@ std::string expand_path(const std::string& path) return result; } -std::string resolve_path(const std::string& path) -{ - std::string resolved;; +inline std::string resolve_path(const std::string& path) { if (path[0] == '~') - resolved = expand_path(path); - else - resolved = path; - -#ifdef HAVE_REALPATH - char buf[PATH_MAX]; - ::realpath(resolved.c_str(), buf); - return std::string(buf); -#else - return resolved; -#endif + return expand_path(path); + return path; } void config_t::reset() @@ -1256,6 +1245,38 @@ OPT_BEGIN(market, "V") { ledger::total_expr.reset(new value_expr("V")); } OPT_END(market); +namespace { + void parse_price_setting(const char * optarg) + { + char * equals = std::strchr(optarg, '='); + if (! equals) + return; + + while (std::isspace(*optarg)) + optarg++; + while (equals > optarg && std::isspace(*(equals - 1))) + equals--; + + std::string symbol(optarg, 0, equals - optarg); + amount_t price(equals + 1); + + if (commodity_t * commodity = commodity_t::find_or_create(symbol)) { + commodity->add_price(now, price); + commodity->history()->bogus_time = now; + } + } +} + +OPT_BEGIN(set_price, ":") { + std::string arg(optarg); + std::string::size_type beg = 0; + for (std::string::size_type pos = arg.find(';'); + pos != std::string::npos; + beg = pos + 1, pos = arg.find(';', beg)) + parse_price_setting(std::string(arg, beg, pos).c_str()); + parse_price_setting(std::string(arg, beg).c_str()); +} OPT_END(set_price); + OPT_BEGIN(performance, "g") { ledger::amount_expr.reset(new value_expr("P(a,m)-b")); ledger::total_expr.reset(new value_expr("P(O,m)-B")); @@ -1362,6 +1383,7 @@ option_t config_options[CONFIG_OPTIONS_SIZE] = { { "reconcile-date", '\0', true, opt_reconcile_date, false }, { "register-format", '\0', true, opt_register_format, false }, { "related", 'r', false, opt_related, false }, + { "set-price", '\0', true, opt_set_price, false }, { "sort", 'S', true, opt_sort, false }, { "subtotal", 's', false, opt_subtotal, false }, { "tail", '\0', true, opt_tail, false }, @@ -107,7 +107,7 @@ class config_t std::list<item_handler<transaction_t> *>& ptrs); }; -#define CONFIG_OPTIONS_SIZE 89 +#define CONFIG_OPTIONS_SIZE 90 extern option_t config_options[CONFIG_OPTIONS_SIZE]; void option_help(std::ostream& out); @@ -197,7 +197,7 @@ bool entry_base_t::finalize() continue; if (! empty_allowed) - break; + throw new error("Only one transaction with null amount allowed per entry"); empty_allowed = false; // If one transaction gives no value at all, its value will become @@ -389,7 +389,8 @@ int parse_and_report(config_t& config, int argc, char * argv[], char * envp[]) // Write out the binary cache, if need be - if (config.use_cache && config.cache_dirty && ! config.cache_file.empty()) { + if (config.use_cache && config.cache_dirty && + ! config.cache_file.empty()) { TRACE_PUSH(binary_cache, "Writing journal file"); std::ofstream stream(config.cache_file.c_str()); @@ -630,20 +630,36 @@ unsigned int textual_parser_t::parse(std::istream& in, char * date_field = skip_ws(line + 1); char * time_field = next_element(date_field); if (! time_field) break; - char * symbol_and_price = next_element(time_field); - if (! symbol_and_price) break; - char date_buffer[64]; - std::strcpy(date_buffer, date_field); - date_buffer[std::strlen(date_field)] = ' '; - std::strcpy(&date_buffer[std::strlen(date_field) + 1], time_field); - - std::time_t date; + char * symbol_and_price; + std::time_t date; struct std::tm when; - if (strptime(date_buffer, "%Y/%m/%d %H:%M:%S", &when)) { - date = std::mktime(&when); + + if (std::isdigit(time_field[0])) { + symbol_and_price = next_element(time_field); + if (! symbol_and_price) break; + + char date_buffer[64]; + std::strcpy(date_buffer, date_field); + date_buffer[std::strlen(date_field)] = ' '; + std::strcpy(&date_buffer[std::strlen(date_field) + 1], time_field); + + if (strptime(date_buffer, "%Y/%m/%d %H:%M:%S", &when)) { + date = std::mktime(&when); + } else { + throw new parse_error("Failed to parse date/time"); + } } else { - throw new parse_error("Failed to parse date"); + symbol_and_price = time_field; + + if (strptime(date_field, "%Y/%m/%d", &when)) { + when.tm_hour = 0; + when.tm_min = 0; + when.tm_sec = 0; + date = std::mktime(&when); + } else { + throw new parse_error("Failed to parse date"); + } } std::string symbol; @@ -42,7 +42,7 @@ 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 || value) { + else if (xact.cost || ! value.realzero()) { value.add(xact.amount, xact.cost); } else { @@ -784,7 +784,7 @@ void sum_accounts(account_t& account) value_t result; compute_amount(result, details_t(account)); - if (result) + if (! result.realzero()) xdata.total += result; xdata.total_count += xdata.count; } |