Added support for a "fixed" directive

It lets you specify a fixed cost for a duration of a ledger file, for
example:

  fixed ecu $2

  2008/01/01 income
      assets🏦checking  1 ecu
      income:salary

  end fixed

This is equivalent to:

  2008/01/01 income
      assets🏦checking  1 ecu {=$2}
      income:salary
This commit is contained in:
John Wiegley 2009-11-25 04:32:30 -05:00
parent a7424c1df9
commit 2c90c10db1
4 changed files with 75 additions and 21 deletions

View file

@ -286,7 +286,8 @@ commodity_pool_t::exchange(const amount_t& amount,
return breakdown; return breakdown;
} }
optional<price_point_t> commodity_pool_t::parse_price_directive(char * line) optional<std::pair<commodity_t *, price_point_t> >
commodity_pool_t::parse_price_directive(char * line, bool do_not_add_price)
{ {
char * date_field_ptr = line; char * date_field_ptr = line;
char * time_field_ptr = next_element(date_field_ptr); char * time_field_ptr = next_element(date_field_ptr);
@ -295,6 +296,7 @@ optional<price_point_t> commodity_pool_t::parse_price_directive(char * line)
char * symbol_and_price; char * symbol_and_price;
datetime_t datetime; datetime_t datetime;
string symbol;
if (std::isdigit(time_field_ptr[0])) { if (std::isdigit(time_field_ptr[0])) {
symbol_and_price = next_element(time_field_ptr); symbol_and_price = next_element(time_field_ptr);
@ -307,11 +309,12 @@ optional<price_point_t> commodity_pool_t::parse_price_directive(char * line)
datetime = datetime_t(parse_date(date_field)); datetime = datetime_t(parse_date(date_field));
} }
else { else {
symbol_and_price = date_field_ptr; symbol = date_field_ptr;
symbol_and_price = time_field_ptr;
datetime = CURRENT_TIME(); datetime = CURRENT_TIME();
} }
string symbol; if (symbol.empty())
commodity_t::parse_symbol(symbol_and_price, symbol); commodity_t::parse_symbol(symbol_and_price, symbol);
price_point_t point; price_point_t point;
@ -323,9 +326,10 @@ optional<price_point_t> commodity_pool_t::parse_price_directive(char * line)
if (commodity_t * commodity = find_or_create(symbol)) { if (commodity_t * commodity = find_or_create(symbol)) {
DEBUG("commodity.download", "Adding price for " << symbol << ": " DEBUG("commodity.download", "Adding price for " << symbol << ": "
<< point.when << " " << point.price); << point.when << " " << point.price);
if (! do_not_add_price)
commodity->add_price(point.when, point.price, true); commodity->add_price(point.when, point.price, true);
commodity->add_flags(COMMODITY_KNOWN); commodity->add_flags(COMMODITY_KNOWN);
return point; return std::pair<commodity_t *, price_point_t>(commodity, point);
} }
return none; return none;

View file

@ -123,7 +123,8 @@ public:
// Parse commodity prices from a textual representation // Parse commodity prices from a textual representation
optional<price_point_t> parse_price_directive(char * line); optional<std::pair<commodity_t *, price_point_t> >
parse_price_directive(char * line, bool do_not_add_price = false);
commodity_t * commodity_t *
parse_price_expression(const std::string& str, parse_price_expression(const std::string& str,

View file

@ -75,7 +75,7 @@ commodity_quote_from_script(commodity_t& commodity,
if (char * p = std::strchr(buf, '\n')) *p = '\0'; if (char * p = std::strchr(buf, '\n')) *p = '\0';
DEBUG("commodity.download", "downloaded quote: " << buf); DEBUG("commodity.download", "downloaded quote: " << buf);
if (optional<price_point_t> point = if (optional<std::pair<commodity_t *, price_point_t> > point =
commodity_pool_t::current_pool->parse_price_directive(buf)) { commodity_pool_t::current_pool->parse_price_directive(buf)) {
if (commodity_pool_t::current_pool->price_db) { if (commodity_pool_t::current_pool->price_db) {
#if defined(__GNUG__) && __GNUG__ < 3 #if defined(__GNUG__) && __GNUG__ < 3
@ -86,12 +86,12 @@ commodity_quote_from_script(commodity_t& commodity,
std::ios_base::out | std::ios_base::app); std::ios_base::out | std::ios_base::app);
#endif #endif
database << "P " database << "P "
<< format_datetime(point->when, FMT_WRITTEN) << format_datetime(point->second.when, FMT_WRITTEN)
<< " " << commodity.symbol() << " " << commodity.symbol()
<< " " << point->price << " " << point->second.price
<< std::endl; << std::endl;
} }
return point; return point->second;
} }
} else { } else {
DEBUG("commodity.download", DEBUG("commodity.download",

View file

@ -54,7 +54,8 @@ namespace {
static const std::size_t MAX_LINE = 1024; static const std::size_t MAX_LINE = 1024;
public: public:
typedef variant<account_t *, string> state_t; typedef std::pair<commodity_t *, amount_t> fixed_rate_t;
typedef variant<account_t *, string, fixed_rate_t> state_t;
std::list<state_t>& state_stack; std::list<state_t>& state_stack;
@ -99,6 +100,9 @@ namespace {
bool front_is_string() { bool front_is_string() {
return state_stack.front().type() == typeid(string); return state_stack.front().type() == typeid(string);
} }
bool front_is_fixed_rate() {
return state_stack.front().type() == typeid(fixed_rate_t);
}
account_t * top_account() { account_t * top_account() {
foreach (state_t& state, state_stack) foreach (state_t& state, state_stack)
@ -135,6 +139,7 @@ namespace {
void master_account_directive(char * line); void master_account_directive(char * line);
void end_directive(char * line); void end_directive(char * line);
void alias_directive(char * line); void alias_directive(char * line);
void fixed_directive(char * line);
void tag_directive(char * line); void tag_directive(char * line);
void define_directive(char * line); void define_directive(char * line);
bool general_directive(char * line); bool general_directive(char * line);
@ -512,7 +517,7 @@ void instance_t::price_conversion_directive(char * line)
void instance_t::price_xact_directive(char * line) void instance_t::price_xact_directive(char * line)
{ {
optional<price_point_t> point = optional<std::pair<commodity_t *, price_point_t> > point =
commodity_pool_t::current_pool->parse_price_directive(skip_ws(line + 1)); commodity_pool_t::current_pool->parse_price_directive(skip_ws(line + 1));
if (! point) if (! point)
throw parse_error(_("Pricing entry failed to parse")); throw parse_error(_("Pricing entry failed to parse"));
@ -705,10 +710,13 @@ void instance_t::end_directive(char * kind)
if ((name.empty() || name == "account") && ! front_is_account()) if ((name.empty() || name == "account") && ! front_is_account())
throw_(std::runtime_error, throw_(std::runtime_error,
_("'end account' directive does not match open tag directive")); _("'end account' directive does not match open directive"));
else if (name == "tag" && ! front_is_string()) else if (name == "tag" && ! front_is_string())
throw_(std::runtime_error, throw_(std::runtime_error,
_("'end tag' directive does not match open account directive")); _("'end tag' directive does not match open directive"));
else if (name == "fixed" && ! front_is_fixed_rate())
throw_(std::runtime_error,
_("'end fixed' directive does not match open directive"));
if (state_stack.size() <= 1) if (state_stack.size() <= 1)
throw_(std::runtime_error, throw_(std::runtime_error,
@ -738,6 +746,18 @@ void instance_t::alias_directive(char * line)
} }
} }
void instance_t::fixed_directive(char * line)
{
if (optional<std::pair<commodity_t *, price_point_t> > price_point =
commodity_pool_t::current_pool->parse_price_directive(trim_ws(line),
true)) {
state_stack.push_front(fixed_rate_t(price_point->first,
price_point->second.price));
} else {
throw_(std::runtime_error, _("Error in fixed directive"));
}
}
void instance_t::tag_directive(char * line) void instance_t::tag_directive(char * line)
{ {
string tag(trim_ws(line)); string tag(trim_ws(line));
@ -799,6 +819,13 @@ bool instance_t::general_directive(char * line)
} }
break; break;
case 'f':
if (std::strcmp(p, "fixed") == 0) {
fixed_directive(arg);
return true;
}
break;
case 'i': case 'i':
if (std::strcmp(p, "include") == 0) { if (std::strcmp(p, "include") == 0) {
include_directive(arg); include_directive(arg);
@ -812,6 +839,13 @@ bool instance_t::general_directive(char * line)
return true; return true;
} }
break; break;
case 'y':
if (std::strcmp(p, "year") == 0) {
year_directive(arg);
return true;
}
break;
} }
if (expr_t::ptr_op_t op = lookup(symbol_t::DIRECTIVE, p)) { if (expr_t::ptr_op_t op = lookup(symbol_t::DIRECTIVE, p)) {
@ -935,8 +969,8 @@ post_t * instance_t::parse_post(char * line,
post.get(), PARSE_NO_REDUCE | PARSE_SINGLE | post.get(), PARSE_NO_REDUCE | PARSE_SINGLE |
PARSE_NO_ASSIGN, defer_expr); PARSE_NO_ASSIGN, defer_expr);
if (! post->amount.is_null() && honor_strict && strict && if (! post->amount.is_null() && post->amount.has_commodity()) {
post->amount.has_commodity() && if (honor_strict && strict &&
! post->amount.commodity().has_flags(COMMODITY_KNOWN)) { ! post->amount.commodity().has_flags(COMMODITY_KNOWN)) {
if (post->_state == item_t::UNCLEARED) if (post->_state == item_t::UNCLEARED)
warning_(_("\"%1\", line %2: Unknown commodity '%3'") warning_(_("\"%1\", line %2: Unknown commodity '%3'")
@ -944,6 +978,21 @@ post_t * instance_t::parse_post(char * line,
post->amount.commodity().add_flags(COMMODITY_KNOWN); post->amount.commodity().add_flags(COMMODITY_KNOWN);
} }
if (! post->amount.has_annotation()) {
foreach (state_t& state, state_stack) {
if (state.type() == typeid(fixed_rate_t)) {
fixed_rate_t& rate(boost::get<fixed_rate_t>(state));
if (*rate.first == post->amount.commodity()) {
annotation_t details(rate.second);
details.add_flags(ANNOTATION_PRICE_FIXATED);
post->amount.annotate(details);
break;
}
}
}
}
}
DEBUG("textual.parse", "line " << linenum << ": " DEBUG("textual.parse", "line " << linenum << ": "
<< "post amount = " << post->amount); << "post amount = " << post->amount);