Added --exchange (-x) option

This is like -V, except it lets you specify the goal commodity to report
in terms of, for example:

    reg -x CAD
This commit is contained in:
John Wiegley 2009-02-22 04:43:34 -04:00
parent 04fd1ae24c
commit e124811d8a
15 changed files with 121 additions and 76 deletions

View file

@ -128,6 +128,7 @@ See \fB\-\-basis\fR.
.It Fl \-end Pq Fl e .It Fl \-end Pq Fl e
.It Fl \-equity .It Fl \-equity
.It Fl \-exact .It Fl \-exact
.It Fl \-exchange Ar COMM Pq Fl x
.It Fl \-file Ar FILE .It Fl \-file Ar FILE
.It Fl \-first Ar INT .It Fl \-first Ar INT
See \fB\-\-head\fR. See \fB\-\-head\fR.

View file

@ -509,13 +509,19 @@ void amount_t::in_place_unreduce()
} }
} }
optional<amount_t> amount_t::value(const optional<datetime_t>& moment, optional<amount_t>
const optional<commodity_t&>& in_terms_of) const amount_t::value(const bool primary_only,
const optional<datetime_t>& moment,
const optional<commodity_t&>& in_terms_of) const
{ {
if (quantity) { if (quantity) {
optional<price_point_t> point(commodity().find_price(in_terms_of, moment)); if (has_commodity() &&
if (point) (! primary_only || commodity().has_flags(COMMODITY_PRIMARY)) &&
return (point->price * number()).rounded(); (! in_terms_of || commodity() != *in_terms_of)) {
optional<price_point_t> point(commodity().find_price(in_terms_of, moment));
if (point)
return (point->price * number()).rounded();
}
} else { } else {
throw_(amount_error, "Cannot determine value of an uninitialized amount"); throw_(amount_error, "Cannot determine value of an uninitialized amount");
} }

View file

@ -357,7 +357,8 @@ public:
$100.00. $100.00.
*/ */
optional<amount_t> optional<amount_t>
value(const optional<datetime_t>& moment = none, value(const bool primary_only = true,
const optional<datetime_t>& moment = none,
const optional<commodity_t&>& in_terms_of = none) const; const optional<commodity_t&>& in_terms_of = none) const;
/*@}*/ /*@}*/

View file

@ -159,7 +159,8 @@ balance_t& balance_t::operator/=(const amount_t& amt)
} }
optional<balance_t> optional<balance_t>
balance_t::value(const optional<datetime_t>& moment, balance_t::value(const bool primary_only,
const optional<datetime_t>& moment,
const optional<commodity_t&>& in_terms_of) const const optional<commodity_t&>& in_terms_of) const
{ {
optional<balance_t> temp; optional<balance_t> temp;
@ -167,7 +168,8 @@ balance_t::value(const optional<datetime_t>& moment,
foreach (const amounts_map::value_type& pair, amounts) { foreach (const amounts_map::value_type& pair, amounts) {
if (! temp) if (! temp)
temp = balance_t(); temp = balance_t();
if (optional<amount_t> val = pair.second.value(moment, in_terms_of)) if (optional<amount_t> val = pair.second.value(primary_only, moment,
in_terms_of))
*temp += *val; *temp += *val;
else else
*temp += pair.second; *temp += pair.second;

View file

@ -353,8 +353,10 @@ public:
return *this = temp; return *this = temp;
} }
optional<balance_t> value(const optional<datetime_t>& moment = none, optional<balance_t>
const optional<commodity_t&>& in_terms_of = none) const; value(const bool primary_only = false,
const optional<datetime_t>& moment = none,
const optional<commodity_t&>& in_terms_of = none) const;
/** /**
* Truth tests. An balance may be truth test in two ways: * Truth tests. An balance may be truth test in two ways:

View file

@ -33,10 +33,10 @@
namespace ledger { namespace ledger {
void commodity_t::base_t::history_t::add_price(const commodity_t& source, void commodity_t::base_t::history_t::add_price(commodity_t& source,
const datetime_t& date, const datetime_t& date,
const amount_t& price, const amount_t& price,
const bool reflexive) const bool reflexive)
{ {
DEBUG("commodity.prices", DEBUG("commodity.prices",
"add_price to " << source << " : " << date << ", " << price); "add_price to " << source << " : " << date << ", " << price);
@ -50,10 +50,13 @@ void commodity_t::base_t::history_t::add_price(const commodity_t& source,
assert(result.second); assert(result.second);
} }
if (reflexive && ! price.commodity().has_flags(COMMODITY_NOMARKET)) { if (reflexive) {
amount_t inverse = price.inverted(); if (! price.commodity().has_flags(COMMODITY_NOMARKET)) {
inverse.set_commodity(const_cast<commodity_t&>(source)); amount_t inverse = price.inverted();
price.commodity().add_price(date, inverse, false); inverse.set_commodity(const_cast<commodity_t&>(source));
price.commodity().add_price(date, inverse, false);
}
source.add_flags(COMMODITY_PRIMARY);
} }
} }
@ -68,10 +71,10 @@ bool commodity_t::base_t::history_t::remove_price(const datetime_t& date)
} }
void commodity_t::base_t::varied_history_t:: void commodity_t::base_t::varied_history_t::
add_price(const commodity_t& source, add_price(commodity_t& source,
const datetime_t& date, const datetime_t& date,
const amount_t& price, const amount_t& price,
const bool reflexive) const bool reflexive)
{ {
optional<history_t&> hist = history(price.commodity()); optional<history_t&> hist = history(price.commodity());
if (! hist) { if (! hist) {

View file

@ -72,13 +72,13 @@ struct price_point_t
* Long. * Long.
*/ */
class commodity_t class commodity_t
: public delegates_flags<>, : public delegates_flags<uint_least16_t>,
public equality_comparable1<commodity_t, noncopyable> public equality_comparable1<commodity_t, noncopyable>
{ {
friend class commodity_pool_t; friend class commodity_pool_t;
public: public:
class base_t : public noncopyable, public supports_flags<> class base_t : public noncopyable, public supports_flags<uint_least16_t>
{ {
base_t(); base_t();
@ -90,7 +90,7 @@ public:
history_map prices; history_map prices;
ptime last_lookup; ptime last_lookup;
void add_price(const commodity_t& source, void add_price(commodity_t& source,
const datetime_t& date, const datetime_t& date,
const amount_t& price, const amount_t& price,
const bool reflexive = true); const bool reflexive = true);
@ -111,14 +111,14 @@ public:
{ {
history_by_commodity_map histories; history_by_commodity_map histories;
void add_price(const commodity_t& source, void add_price(commodity_t& source,
const datetime_t& date, const datetime_t& date,
const amount_t& price, const amount_t& price,
const bool reflexive = true); const bool reflexive = true);
bool remove_price(const datetime_t& date, commodity_t& commodity); bool remove_price(const datetime_t& date, commodity_t& commodity);
optional<price_point_t> optional<price_point_t>
find_price(const commodity_t& source, find_price(const commodity_t& source,
const optional<commodity_t&>& commodity = none, const optional<commodity_t&>& commodity = none,
const optional<datetime_t>& moment = none, const optional<datetime_t>& moment = none,
const optional<datetime_t>& oldest = none const optional<datetime_t>& oldest = none
@ -127,7 +127,7 @@ public:
#endif #endif
) const; ) const;
optional<price_point_t> optional<price_point_t>
find_price(const commodity_t& source, find_price(const commodity_t& source,
const std::vector<commodity_t *>& commodities, const std::vector<commodity_t *>& commodities,
const optional<datetime_t>& moment = none, const optional<datetime_t>& moment = none,
const optional<datetime_t>& oldest = none const optional<datetime_t>& oldest = none
@ -142,15 +142,16 @@ public:
history(const std::vector<commodity_t *>& commodities); history(const std::vector<commodity_t *>& commodities);
}; };
#define COMMODITY_STYLE_DEFAULTS 0x00 #define COMMODITY_STYLE_DEFAULTS 0x000
#define COMMODITY_STYLE_SUFFIXED 0x01 #define COMMODITY_STYLE_SUFFIXED 0x001
#define COMMODITY_STYLE_SEPARATED 0x02 #define COMMODITY_STYLE_SEPARATED 0x002
#define COMMODITY_STYLE_EUROPEAN 0x04 #define COMMODITY_STYLE_EUROPEAN 0x004
#define COMMODITY_STYLE_THOUSANDS 0x08 #define COMMODITY_STYLE_THOUSANDS 0x008
#define COMMODITY_NOMARKET 0x10 #define COMMODITY_NOMARKET 0x010
#define COMMODITY_BUILTIN 0x20 #define COMMODITY_BUILTIN 0x020
#define COMMODITY_WALKED 0x40 #define COMMODITY_WALKED 0x040
#define COMMODITY_KNOWN 0x80 #define COMMODITY_KNOWN 0x080
#define COMMODITY_PRIMARY 0x100
string symbol; string symbol;
amount_t::precision_t precision; amount_t::precision_t precision;
@ -164,7 +165,7 @@ public:
public: public:
explicit base_t(const string& _symbol) explicit base_t(const string& _symbol)
: supports_flags<>(COMMODITY_STYLE_DEFAULTS), : supports_flags<uint_least16_t>(COMMODITY_STYLE_DEFAULTS),
symbol(_symbol), precision(0), searched(false) { symbol(_symbol), precision(0), searched(false) {
TRACE_CTOR(base_t, "const string&"); TRACE_CTOR(base_t, "const string&");
} }
@ -191,7 +192,7 @@ public:
public: public:
explicit commodity_t(commodity_pool_t * _parent, explicit commodity_t(commodity_pool_t * _parent,
const shared_ptr<base_t>& _base) const shared_ptr<base_t>& _base)
: delegates_flags<>(*_base.get()), base(_base), : delegates_flags<uint_least16_t>(*_base.get()), base(_base),
parent_(_parent), annotated(false) { parent_(_parent), annotated(false) {
TRACE_CTOR(commodity_t, ""); TRACE_CTOR(commodity_t, "");
} }

View file

@ -120,22 +120,23 @@ value_t report_t::fn_display_total(call_scope_t& scope)
return HANDLER(display_total_).expr.calc(scope); return HANDLER(display_total_).expr.calc(scope);
} }
value_t report_t::fn_market_value(call_scope_t& args) value_t report_t::fn_market_value(call_scope_t& scope)
{ {
interactive_t env(args, "a&ts"); interactive_t args(scope, "a&ts");
commodity_t * commodity = NULL; commodity_t * commodity = NULL;
if (env.has(2)) if (args.has(2))
commodity = amount_t::current_pool->find_or_create(env.get<string>(2)); commodity = amount_t::current_pool->find_or_create(args.get<string>(2));
DEBUG("report.market", "getting market value of: " << env.value_at(0)); DEBUG("report.market", "getting market value of: " << args.value_at(0));
value_t result = value_t result =
env.value_at(0).value(env.has(1) ? args.value_at(0).value(! args.has(2),
env.get<datetime_t>(1) : optional<datetime_t>(), args.has(1) ?
commodity ? args.get<datetime_t>(1) : optional<datetime_t>(),
optional<commodity_t&>(*commodity) : commodity ?
optional<commodity_t&>()); optional<commodity_t&>(*commodity) :
optional<commodity_t&>());
DEBUG("report.market", "result is: " << result); DEBUG("report.market", "result is: " << result);
return result; return result;
@ -165,13 +166,13 @@ value_t report_t::fn_quantity(call_scope_t& args)
return args[0].to_amount().number(); return args[0].to_amount().number();
} }
value_t report_t::fn_truncate(call_scope_t& args) value_t report_t::fn_truncate(call_scope_t& scope)
{ {
interactive_t env(args, "v&ll"); interactive_t args(scope, "v&ll");
return string_value(format_t::truncate return string_value(format_t::truncate
(env.get<string>(0), (args.get<string>(0),
env.has(1) && env.get<long>(1) > 0 ? env.get<long>(1) : 0, args.has(1) && args.get<long>(1) > 0 ? args.get<long>(1) : 0,
env.has(2) ? env.get<long>(2) : -1)); args.has(2) ? args.get<long>(2) : -1));
} }
value_t report_t::fn_justify(call_scope_t& scope) value_t report_t::fn_justify(call_scope_t& scope)
@ -400,6 +401,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT_(end_); else OPT_(end_);
else OPT(equity); else OPT(equity);
else OPT(exact); else OPT(exact);
else OPT(exchange_);
break; break;
case 'f': case 'f':
OPT(flat); OPT(flat);
@ -498,7 +500,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT_(wide); else OPT_(wide);
break; break;
case 'x': case 'x':
OPT_CH(comm_as_payee); OPT_CH(exchange_);
break; break;
case 'y': case 'y':
OPT_CH(date_format_); OPT_CH(date_format_);

View file

@ -269,7 +269,7 @@ public:
}); });
OPTION(report_t, code_as_payee); OPTION(report_t, code_as_payee);
OPTION(report_t, comm_as_payee); // -x OPTION(report_t, comm_as_payee);
OPTION(report_t, code_as_account); OPTION(report_t, code_as_account);
OPTION(report_t, comm_as_account); OPTION(report_t, comm_as_account);
OPTION(report_t, color); OPTION(report_t, color);
@ -374,6 +374,14 @@ public:
OPTION(report_t, equity); OPTION(report_t, equity);
OPTION(report_t, exact); OPTION(report_t, exact);
OPTION_(report_t, exchange_, DO_(args) { // -x
on_with(args[0]);
call_scope_t no_args(*parent);
parent->HANDLER(market).parent = parent;
parent->HANDLER(market).handler(no_args);
});
OPTION(report_t, flat); OPTION(report_t, flat);
OPTION(report_t, forecast_while_); OPTION(report_t, forecast_while_);
OPTION(report_t, format_); // -F OPTION(report_t, format_); // -F
@ -402,8 +410,10 @@ public:
OPTION_(report_t, market, DO() { // -V OPTION_(report_t, market, DO() { // -V
parent->HANDLER(revalued).on_only(); parent->HANDLER(revalued).on_only();
parent->HANDLER(display_amount_).set_expr("market(amount_expr)"); parent->HANDLER(display_amount_)
parent->HANDLER(display_total_).set_expr("market(total_expr)"); .set_expr("exchange ? market(amount_expr, now, exchange) : market(amount_expr)");
parent->HANDLER(display_total_)
.set_expr("exchange ? market(total_expr, now, exchange) : market(total_expr)");
}); });
OPTION_(report_t, monthly, DO() { // -M OPTION_(report_t, monthly, DO() { // -M
@ -447,11 +457,11 @@ public:
OPTION(report_t, period_sort_); OPTION(report_t, period_sort_);
OPTION__(report_t, plot_amount_format_, CTOR(report_t, plot_amount_format_) { OPTION__(report_t, plot_amount_format_, CTOR(report_t, plot_amount_format_) {
on("%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(amount)))\n"); on("%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n");
}); });
OPTION__(report_t, plot_total_format_, CTOR(report_t, plot_total_format_) { OPTION__(report_t, plot_total_format_, CTOR(report_t, plot_total_format_) {
on("%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(total)))\n"); on("%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n");
}); });
OPTION_(report_t, price, DO() { // -I OPTION_(report_t, price, DO() { // -I

View file

@ -235,6 +235,11 @@ expr_t::ptr_op_t session_t::lookup(const string& name)
{ {
const char * p = name.c_str(); const char * p = name.c_str();
switch (*p) { switch (*p) {
case 'n':
if (is_eq(p, "now"))
return MAKE_FUNCTOR(session_t::fn_now);
break;
case 'o': case 'o':
if (WANT_OPT()) { p += OPT_PREFIX_LEN; if (WANT_OPT()) { p += OPT_PREFIX_LEN;
if (option_t<session_t> * handler = lookup_option(p)) if (option_t<session_t> * handler = lookup_option(p))

View file

@ -98,6 +98,9 @@ public:
clean_accounts(); clean_accounts();
} }
value_t fn_now(call_scope_t&) {
return CURRENT_TIME();
}
value_t fn_today(call_scope_t&) { value_t fn_today(call_scope_t&) {
return CURRENT_DATE(); return CURRENT_DATE();
} }

View file

@ -1137,23 +1137,25 @@ bool value_t::is_zero() const
return false; return false;
} }
value_t value_t::value(const optional<datetime_t>& moment, value_t value_t::value(const bool primary_only,
const optional<datetime_t>& moment,
const optional<commodity_t&>& in_terms_of) const const optional<commodity_t&>& in_terms_of) const
{ {
switch (type()) { switch (type()) {
case INTEGER: case INTEGER:
return *this; return *this;
case AMOUNT: { case AMOUNT:
if (optional<amount_t> val = as_amount().value(moment, in_terms_of)) if (optional<amount_t> val =
as_amount().value(primary_only, moment, in_terms_of))
return *val; return *val;
return false; return *this;
}
case BALANCE: { case BALANCE:
if (optional<balance_t> bal = as_balance().value(moment, in_terms_of)) if (optional<balance_t> bal =
as_balance().value(primary_only, moment, in_terms_of))
return *bal; return *bal;
return false; return *this;
}
default: default:
break; break;

View file

@ -427,8 +427,9 @@ public:
void in_place_unreduce(); // exists for efficiency's sake void in_place_unreduce(); // exists for efficiency's sake
// Return the "market value" of a given value at a specific time. // Return the "market value" of a given value at a specific time.
value_t value(const optional<datetime_t>& moment = none, value_t value(const bool primary_only = false,
const optional<commodity_t&>& in_terms_of = none) const; const optional<datetime_t>& moment = none,
const optional<commodity_t&>& in_terms_of = none) const;
/** /**
* Truth tests. * Truth tests.

View file

@ -159,6 +159,10 @@ namespace {
return string_value(xact.amount.commodity().symbol()); return string_value(xact.amount.commodity().symbol());
} }
value_t get_commodity_is_primary(xact_t& xact) {
return xact.amount.commodity().has_flags(COMMODITY_PRIMARY);
}
value_t get_cost(xact_t& xact) { value_t get_cost(xact_t& xact) {
if (xact.has_xdata() && if (xact.has_xdata() &&
xact.xdata().has_flags(XACT_EXT_COMPOUND)) { xact.xdata().has_flags(XACT_EXT_COMPOUND)) {
@ -262,6 +266,8 @@ expr_t::ptr_op_t xact_t::lookup(const string& name)
case 'p': case 'p':
if (name == "payee") if (name == "payee")
return WRAP_FUNCTOR(get_wrapper<&get_payee>); return WRAP_FUNCTOR(get_wrapper<&get_payee>);
else if (name == "primary")
return WRAP_FUNCTOR(get_wrapper<&get_commodity_is_primary>);
break; break;
case 't': case 't':

View file

@ -68,11 +68,11 @@ void CommodityTestCase::testPriceHistory()
cad.add_price(jan17_06, amount_t("$1.11")); cad.add_price(jan17_06, amount_t("$1.11"));
#ifndef NOT_FOR_PYTHON #ifndef NOT_FOR_PYTHON
optional<amount_t> amt = x1.value(feb28_07sbm); optional<amount_t> amt = x1.value(false, feb28_07sbm);
assertTrue(amt); assertTrue(amt);
assertEqual(amount_t("$1831.83"), *amt); assertEqual(amount_t("$1831.83"), *amt);
amt = x1.value(CURRENT_TIME()); amt = x1.value(false, CURRENT_TIME());
assertTrue(amt); assertTrue(amt);
assertEqual(string("$2124.12"), amt->to_string()); assertEqual(string("$2124.12"), amt->to_string());
#ifdef INTEGER_MATH #ifdef INTEGER_MATH
@ -81,18 +81,18 @@ void CommodityTestCase::testPriceHistory()
assertEqual(string("$2124.1220"), amt->to_fullstring()); assertEqual(string("$2124.1220"), amt->to_fullstring());
#endif #endif
amt = x1.value(CURRENT_TIME(), euro); amt = x1.value(false, CURRENT_TIME(), euro);
assertTrue(amt); assertTrue(amt);
assertEqual(string("EUR 1366.87"), amt->rounded().to_string()); assertEqual(string("EUR 1366.87"), amt->rounded().to_string());
// Add a newer Euro pricing // Add a newer Euro pricing
aapl.add_price(jan17_07, amount_t("EUR 23.00")); aapl.add_price(jan17_07, amount_t("EUR 23.00"));
amt = x1.value(CURRENT_TIME(), euro); amt = x1.value(false, CURRENT_TIME(), euro);
assertTrue(amt); assertTrue(amt);
assertEqual(string("EUR 2302.30"), amt->to_string()); assertEqual(string("EUR 2302.30"), amt->to_string());
amt = x1.value(CURRENT_TIME(), cad); amt = x1.value(false, CURRENT_TIME(), cad);
assertTrue(amt); assertTrue(amt);
assertEqual(string("CAD 3223.22"), amt->to_string()); assertEqual(string("CAD 3223.22"), amt->to_string());
#endif // NOT_FOR_PYTHON #endif // NOT_FOR_PYTHON