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 \-equity
.It Fl \-exact
.It Fl \-exchange Ar COMM Pq Fl x
.It Fl \-file Ar FILE
.It Fl \-first Ar INT
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,
const optional<commodity_t&>& in_terms_of) const
optional<amount_t>
amount_t::value(const bool primary_only,
const optional<datetime_t>& moment,
const optional<commodity_t&>& in_terms_of) const
{
if (quantity) {
optional<price_point_t> point(commodity().find_price(in_terms_of, moment));
if (point)
return (point->price * number()).rounded();
if (has_commodity() &&
(! primary_only || commodity().has_flags(COMMODITY_PRIMARY)) &&
(! 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 {
throw_(amount_error, "Cannot determine value of an uninitialized amount");
}

View file

@ -357,7 +357,8 @@ public:
$100.00.
*/
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;
/*@}*/

View file

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

View file

@ -353,8 +353,10 @@ public:
return *this = temp;
}
optional<balance_t> value(const optional<datetime_t>& moment = none,
const optional<commodity_t&>& in_terms_of = none) const;
optional<balance_t>
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:

View file

@ -33,10 +33,10 @@
namespace ledger {
void commodity_t::base_t::history_t::add_price(const commodity_t& source,
const datetime_t& date,
const amount_t& price,
const bool reflexive)
void commodity_t::base_t::history_t::add_price(commodity_t& source,
const datetime_t& date,
const amount_t& price,
const bool reflexive)
{
DEBUG("commodity.prices",
"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);
}
if (reflexive && ! price.commodity().has_flags(COMMODITY_NOMARKET)) {
amount_t inverse = price.inverted();
inverse.set_commodity(const_cast<commodity_t&>(source));
price.commodity().add_price(date, inverse, false);
if (reflexive) {
if (! price.commodity().has_flags(COMMODITY_NOMARKET)) {
amount_t inverse = price.inverted();
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::
add_price(const commodity_t& source,
const datetime_t& date,
const amount_t& price,
const bool reflexive)
add_price(commodity_t& source,
const datetime_t& date,
const amount_t& price,
const bool reflexive)
{
optional<history_t&> hist = history(price.commodity());
if (! hist) {

View file

@ -72,13 +72,13 @@ struct price_point_t
* Long.
*/
class commodity_t
: public delegates_flags<>,
: public delegates_flags<uint_least16_t>,
public equality_comparable1<commodity_t, noncopyable>
{
friend class commodity_pool_t;
public:
class base_t : public noncopyable, public supports_flags<>
class base_t : public noncopyable, public supports_flags<uint_least16_t>
{
base_t();
@ -90,7 +90,7 @@ public:
history_map prices;
ptime last_lookup;
void add_price(const commodity_t& source,
void add_price(commodity_t& source,
const datetime_t& date,
const amount_t& price,
const bool reflexive = true);
@ -111,14 +111,14 @@ public:
{
history_by_commodity_map histories;
void add_price(const commodity_t& source,
void add_price(commodity_t& source,
const datetime_t& date,
const amount_t& price,
const bool reflexive = true);
bool remove_price(const datetime_t& date, commodity_t& commodity);
optional<price_point_t>
find_price(const commodity_t& source,
find_price(const commodity_t& source,
const optional<commodity_t&>& commodity = none,
const optional<datetime_t>& moment = none,
const optional<datetime_t>& oldest = none
@ -127,7 +127,7 @@ public:
#endif
) const;
optional<price_point_t>
find_price(const commodity_t& source,
find_price(const commodity_t& source,
const std::vector<commodity_t *>& commodities,
const optional<datetime_t>& moment = none,
const optional<datetime_t>& oldest = none
@ -142,15 +142,16 @@ public:
history(const std::vector<commodity_t *>& commodities);
};
#define COMMODITY_STYLE_DEFAULTS 0x00
#define COMMODITY_STYLE_SUFFIXED 0x01
#define COMMODITY_STYLE_SEPARATED 0x02
#define COMMODITY_STYLE_EUROPEAN 0x04
#define COMMODITY_STYLE_THOUSANDS 0x08
#define COMMODITY_NOMARKET 0x10
#define COMMODITY_BUILTIN 0x20
#define COMMODITY_WALKED 0x40
#define COMMODITY_KNOWN 0x80
#define COMMODITY_STYLE_DEFAULTS 0x000
#define COMMODITY_STYLE_SUFFIXED 0x001
#define COMMODITY_STYLE_SEPARATED 0x002
#define COMMODITY_STYLE_EUROPEAN 0x004
#define COMMODITY_STYLE_THOUSANDS 0x008
#define COMMODITY_NOMARKET 0x010
#define COMMODITY_BUILTIN 0x020
#define COMMODITY_WALKED 0x040
#define COMMODITY_KNOWN 0x080
#define COMMODITY_PRIMARY 0x100
string symbol;
amount_t::precision_t precision;
@ -164,7 +165,7 @@ public:
public:
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) {
TRACE_CTOR(base_t, "const string&");
}
@ -191,7 +192,7 @@ public:
public:
explicit commodity_t(commodity_pool_t * _parent,
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) {
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);
}
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;
if (env.has(2))
commodity = amount_t::current_pool->find_or_create(env.get<string>(2));
if (args.has(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 =
env.value_at(0).value(env.has(1) ?
env.get<datetime_t>(1) : optional<datetime_t>(),
commodity ?
optional<commodity_t&>(*commodity) :
optional<commodity_t&>());
args.value_at(0).value(! args.has(2),
args.has(1) ?
args.get<datetime_t>(1) : optional<datetime_t>(),
commodity ?
optional<commodity_t&>(*commodity) :
optional<commodity_t&>());
DEBUG("report.market", "result is: " << result);
return result;
@ -165,13 +166,13 @@ value_t report_t::fn_quantity(call_scope_t& args)
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
(env.get<string>(0),
env.has(1) && env.get<long>(1) > 0 ? env.get<long>(1) : 0,
env.has(2) ? env.get<long>(2) : -1));
(args.get<string>(0),
args.has(1) && args.get<long>(1) > 0 ? args.get<long>(1) : 0,
args.has(2) ? args.get<long>(2) : -1));
}
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(equity);
else OPT(exact);
else OPT(exchange_);
break;
case 'f':
OPT(flat);
@ -498,7 +500,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT_(wide);
break;
case 'x':
OPT_CH(comm_as_payee);
OPT_CH(exchange_);
break;
case 'y':
OPT_CH(date_format_);

View file

@ -269,7 +269,7 @@ public:
});
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, comm_as_account);
OPTION(report_t, color);
@ -374,6 +374,14 @@ public:
OPTION(report_t, equity);
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, forecast_while_);
OPTION(report_t, format_); // -F
@ -402,8 +410,10 @@ public:
OPTION_(report_t, market, DO() { // -V
parent->HANDLER(revalued).on_only();
parent->HANDLER(display_amount_).set_expr("market(amount_expr)");
parent->HANDLER(display_total_).set_expr("market(total_expr)");
parent->HANDLER(display_amount_)
.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
@ -447,11 +457,11 @@ public:
OPTION(report_t, period_sort_);
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_) {
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

View file

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

View file

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

View file

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

View file

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

View file

@ -159,6 +159,10 @@ namespace {
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) {
if (xact.has_xdata() &&
xact.xdata().has_flags(XACT_EXT_COMPOUND)) {
@ -262,6 +266,8 @@ expr_t::ptr_op_t xact_t::lookup(const string& name)
case 'p':
if (name == "payee")
return WRAP_FUNCTOR(get_wrapper<&get_payee>);
else if (name == "primary")
return WRAP_FUNCTOR(get_wrapper<&get_commodity_is_primary>);
break;
case 't':

View file

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