summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-10-31 04:17:40 -0400
committerJohn Wiegley <johnw@newartisans.com>2009-10-31 04:17:40 -0400
commit97a9b42b2c5ef908e0c47ecfd39771a79f8fa8a2 (patch)
tree8eadb82cc54e66353e05309fe42c2900b93628bf /src
parenta2cb549b1dff9024e3f700203e424e496b25fd91 (diff)
parenta0a980b9f4ebf1493682ecf1eb745bf52649aac5 (diff)
downloadfork-ledger-97a9b42b2c5ef908e0c47ecfd39771a79f8fa8a2.tar.gz
fork-ledger-97a9b42b2c5ef908e0c47ecfd39771a79f8fa8a2.tar.bz2
fork-ledger-97a9b42b2c5ef908e0c47ecfd39771a79f8fa8a2.zip
Merge branch 'next'
Diffstat (limited to 'src')
-rw-r--r--src/chain.cc6
-rw-r--r--src/derive.cc46
-rw-r--r--src/emacs.cc4
-rw-r--r--src/filters.cc6
-rw-r--r--src/filters.h13
-rw-r--r--src/item.cc27
-rw-r--r--src/iterators.cc71
-rw-r--r--src/main.cc14
-rw-r--r--src/option.cc9
-rw-r--r--src/option.h15
-rw-r--r--src/report.cc42
-rw-r--r--src/report.h5
-rw-r--r--src/session.cc8
-rw-r--r--src/utils.h18
14 files changed, 223 insertions, 61 deletions
diff --git a/src/chain.cc b/src/chain.cc
index d7d1460b..db52269c 100644
--- a/src/chain.cc
+++ b/src/chain.cc
@@ -209,7 +209,11 @@ post_handler_ptr chain_post_handlers(report_t& report,
= new forecast_posts(handler,
item_predicate(report.HANDLER(forecast_while_).str(),
report.what_to_keep()),
- report);
+ report,
+ report.HANDLED(forecast_years_) ?
+ static_cast<std::size_t>
+ (report.HANDLER(forecast_years_).value.to_long()) :
+ 5UL);
forecast_handler->add_period_xacts(report.session.journal->period_xacts);
handler.reset(forecast_handler);
diff --git a/src/derive.cc b/src/derive.cc
index ef2d1e51..df27d4b6 100644
--- a/src/derive.cc
+++ b/src/derive.cc
@@ -54,6 +54,8 @@ namespace {
bool from;
optional<mask_t> account_mask;
optional<amount_t> amount;
+ optional<string> cost_operator;
+ optional<amount_t> cost;
post_template_t() : from(false) {}
};
@@ -111,6 +113,10 @@ namespace {
if (post.amount)
out << _(" Amount: ") << *post.amount << std::endl;
+
+ if (post.cost)
+ out << _(" Cost: ") << *post.cost_operator
+ << " " << *post.cost << std::endl;
}
}
}
@@ -148,6 +154,8 @@ namespace {
string arg = (*begin).to_string();
if (arg == "at") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
tmpl.payee_mask = (*++begin).to_string();
}
else if (arg == "to" || arg == "from") {
@@ -155,22 +163,41 @@ namespace {
tmpl.posts.push_back(xact_template_t::post_template_t());
post = &tmpl.posts.back();
}
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
post->account_mask = mask_t((*++begin).to_string());
post->from = arg == "from";
}
else if (arg == "on") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
tmpl.date = parse_date((*++begin).to_string());
check_for_date = false;
}
else if (arg == "code") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
tmpl.code = (*++begin).to_string();
}
else if (arg == "note") {
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
tmpl.note = (*++begin).to_string();
}
else if (arg == "rest") {
; // just ignore this argument
}
+ else if (arg == "@" || arg == "@@") {
+ amount_t cost;
+ post->cost_operator = arg;
+ if (begin == end)
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ arg = (*++begin).to_string();
+ if (! cost.parse(arg, amount_t::PARSE_SOFT_FAIL |
+ amount_t::PARSE_NO_MIGRATE))
+ throw std::runtime_error(_("Invalid xact command arguments"));
+ post->cost = cost;
+ }
else {
// Without a preposition, it is either:
//
@@ -443,6 +470,25 @@ namespace {
}
}
+ if (post.cost) {
+ if (post.cost->sign() < 0)
+ throw parse_error(_("A posting's cost may not be negative"));
+
+ post.cost->in_place_unround();
+
+ if (*post.cost_operator == "@") {
+ // For the sole case where the cost might be uncommoditized,
+ // guarantee that the commodity of the cost after multiplication
+ // is the same as it was before.
+ commodity_t& cost_commodity(post.cost->commodity());
+ *post.cost *= new_post->amount;
+ post.cost->set_commodity(cost_commodity);
+ }
+
+ new_post->cost = *post.cost;
+ DEBUG("derive.xact", "Copied over posting cost");
+ }
+
if (found_commodity &&
! new_post->amount.is_null() &&
! new_post->amount.has_commodity()) {
diff --git a/src/emacs.cc b/src/emacs.cc
index 24d3f1c1..1d3f28a3 100644
--- a/src/emacs.cc
+++ b/src/emacs.cc
@@ -41,7 +41,7 @@ namespace ledger {
void format_emacs_posts::write_xact(xact_t& xact)
{
out << "\"" << xact.pos->pathname << "\" "
- << (xact.pos->beg_line + 1) << " ";
+ << xact.pos->beg_line << " ";
tm when = gregorian::to_tm(xact.date());
std::time_t date = std::mktime(&when); // jww (2008-04-20): Is this GMT or local?
@@ -77,7 +77,7 @@ void format_emacs_posts::operator()(post_t& post)
out << "\n";
}
- out << " (" << (post.pos->beg_line + 1) << " ";
+ out << " (" << post.pos->beg_line << " ";
out << "\"" << post.reported_account()->fullname() << "\" \""
<< post.amount << "\"";
diff --git a/src/filters.cc b/src/filters.cc
index 814cf276..fe8761a5 100644
--- a/src/filters.cc
+++ b/src/filters.cc
@@ -871,9 +871,11 @@ void forecast_posts::flush()
date_t next = *(*least).first.next;
assert(next > begin);
- if ((next - last).days() > 365 * 5) {
+ if (static_cast<std::size_t>((next - last).days()) >
+ static_cast<std::size_t>(365U) * forecast_years) {
DEBUG("filters.forecast",
- "Forecast transaction exceeds 5 years beyond today");
+ "Forecast transaction exceeds " << forecast_years
+ << " years beyond today");
pending_posts.erase(least);
continue;
}
diff --git a/src/filters.h b/src/filters.h
index 3eb5da8d..7c1f169c 100644
--- a/src/filters.h
+++ b/src/filters.h
@@ -764,16 +764,19 @@ public:
*/
class forecast_posts : public generate_posts
{
- item_predicate pred;
- scope_t& context;
+ item_predicate pred;
+ scope_t& context;
+ const std::size_t forecast_years;
public:
forecast_posts(post_handler_ptr handler,
const item_predicate& predicate,
- scope_t& _context)
- : generate_posts(handler), pred(predicate), context(_context) {
+ scope_t& _context,
+ const std::size_t _forecast_years)
+ : generate_posts(handler), pred(predicate), context(_context),
+ forecast_years(_forecast_years) {
TRACE_CTOR(forecast_posts,
- "post_handler_ptr, const item_predicate&, scope_t&");
+ "post_handler_ptr, item_predicate, scope_t&, std::size_t");
}
virtual ~forecast_posts() throw() {
TRACE_DTOR(forecast_posts);
diff --git a/src/item.cc b/src/item.cc
index 631423a9..debda7a3 100644
--- a/src/item.cc
+++ b/src/item.cc
@@ -32,6 +32,7 @@
#include <system.hh>
#include "item.h"
+#include "interactive.h"
namespace ledger {
@@ -210,15 +211,31 @@ namespace {
return item.has_tag(args[0].as_string());
else if (args[0].is_mask())
return item.has_tag(args[0].as_mask());
- } else {
- return item.has_tag(args[0].to_mask(), args[1].to_mask());
+ else
+ throw_(std::logic_error,
+ _("Expected string for argument 1, but received %1")
+ << args[0].label());
+ }
+ else if (args.size() == 2) {
+ if (args[0].is_mask() && args[1].is_mask())
+ return item.has_tag(args[0].to_mask(), args[1].to_mask());
+ else
+ throw_(std::logic_error,
+ _("Expected masks for arguments 1 and 2, but received %1 and %2")
+ << args[0].label() << args[1].label());
+ }
+ else if (args.size() == 0) {
+ throw_(std::logic_error, _("Too few arguments to function"));
+ }
+ else {
+ throw_(std::logic_error, _("Too many arguments to function"));
}
return false;
}
- value_t get_tag(call_scope_t& args) {
- item_t& item(find_scope<item_t>(args));
- if (optional<string> value = item.get_tag(args[0].as_string()))
+ value_t get_tag(call_scope_t& scope) {
+ in_context_t<item_t> env(scope, "s");
+ if (optional<string> value = env->get_tag(env.get<string>(0)))
return string_value(*value);
return false;
}
diff --git a/src/iterators.cc b/src/iterators.cc
index e9560cc0..50c67ade 100644
--- a/src/iterators.cc
+++ b/src/iterators.cc
@@ -90,37 +90,48 @@ void posts_commodities_iterator::reset(journal_t& journal)
std::map<string, xact_t *> xacts_by_commodity;
foreach (commodity_t * comm, commodities) {
- optional<commodity_t::varied_history_t&> history = comm->varied_history();
- if (! history)
- continue;
-
- account_t * account = journal.master->find_account(comm->symbol());
-
- foreach (commodity_t::base_t::history_by_commodity_map::value_type pair,
- history->histories) {
- foreach (commodity_t::base_t::history_map::value_type hpair,
- pair.second.prices) {
- xact_t * xact;
- string symbol = hpair.second.commodity().symbol();
-
- std::map<string, xact_t *>::iterator i =
- xacts_by_commodity.find(symbol);
- if (i != xacts_by_commodity.end()) {
- xact = (*i).second;
- } else {
- xact = &temps.create_xact();
- xact_temps.push_back(xact);
- xact->payee = symbol;
- xact->_date = hpair.first.date();
- xacts_by_commodity.insert
- (std::pair<string, xact_t *>(symbol, xact));
+ if (optional<commodity_t::varied_history_t&> history =
+ comm->varied_history()) {
+ account_t * account = journal.master->find_account(comm->symbol());
+
+ foreach (commodity_t::base_t::history_by_commodity_map::value_type pair,
+ history->histories) {
+ foreach (commodity_t::base_t::history_map::value_type hpair,
+ pair.second.prices) {
+ xact_t * xact;
+ string symbol = hpair.second.commodity().symbol();
+
+ std::map<string, xact_t *>::iterator i =
+ xacts_by_commodity.find(symbol);
+ if (i != xacts_by_commodity.end()) {
+ xact = (*i).second;
+ } else {
+ xact = &temps.create_xact();
+ xact_temps.push_back(xact);
+ xact->payee = symbol;
+ xact->_date = hpair.first.date();
+ xacts_by_commodity.insert
+ (std::pair<string, xact_t *>(symbol, xact));
+ }
+
+ bool post_already_exists = false;
+
+ foreach (post_t * post, xact->posts) {
+ if (post->_date == hpair.first.date() &&
+ post->amount == hpair.second) {
+ post_already_exists = true;
+ break;
+ }
+ }
+
+ if (! post_already_exists) {
+ post_t& temp = temps.create_post(*xact, account);
+ temp._date = hpair.first.date();
+ temp.amount = hpair.second;
+
+ temp.xdata().datetime = hpair.first;
+ }
}
-
- post_t& temp = temps.create_post(*xact, account);
- temp._date = hpair.first.date();
- temp.amount = hpair.second;
-
- temp.xdata().datetime = hpair.first;
}
}
}
diff --git a/src/main.cc b/src/main.cc
index 9f0e8690..0aec8886 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -76,11 +76,13 @@ int main(int argc, char * argv[], char * envp[])
::textdomain("ledger");
#endif
- // Create the session object, which maintains nearly all state relating to
- // this invocation of Ledger; and register all known journal parsers.
- std::auto_ptr<global_scope_t> global_scope(new global_scope_t(envp));
+ std::auto_ptr<global_scope_t> global_scope;
try {
+ // Create the session object, which maintains nearly all state relating to
+ // this invocation of Ledger; and register all known journal parsers.
+ global_scope.reset(new global_scope_t(envp));
+
global_scope->session().set_flush_on_next_data_file(true);
// Construct an STL-style argument list from the process command arguments
@@ -181,7 +183,11 @@ int main(int argc, char * argv[], char * envp[])
}
}
catch (const std::exception& err) {
- global_scope->report_error(err);
+ if (global_scope.get())
+ global_scope->report_error(err);
+ else
+ std::cerr << "Exception during initialization: " << err.what()
+ << std::endl;
}
catch (int _status) {
status = _status; // used for a "quick" exit, and is used only
diff --git a/src/option.cc b/src/option.cc
index 883080e2..8da66b36 100644
--- a/src/option.cc
+++ b/src/option.cc
@@ -116,13 +116,16 @@ void process_environment(const char ** envp, const string& tag,
const char * tag_p = tag.c_str();
string::size_type tag_len = tag.length();
+ assert(tag_p);
+ assert(tag_len > 0);
+
for (const char ** p = envp; *p; p++) {
- if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) {
- char buf[128];
+ if (std::strlen(*p) >= tag_len && std::strncmp(*p, tag_p, tag_len) == 0) {
+ char buf[8192];
char * r = buf;
const char * q;
for (q = *p + tag_len;
- *q && *q != '=' && r - buf < 128;
+ *q && *q != '=' && r - buf < 8191;
q++)
if (*q == '_')
*r++ = '-';
diff --git a/src/option.h b/src/option.h
index 83710a1c..0600779c 100644
--- a/src/option.h
+++ b/src/option.h
@@ -162,10 +162,21 @@ public:
virtual void handler(call_scope_t& args) {
if (wants_arg) {
- if (args.empty() || args.size() == 1)
+ if (args.size() < 2)
throw_(std::runtime_error, _("No argument provided for %1") << desc());
+ else if (args.size() > 2)
+ throw_(std::runtime_error, _("To many arguments provided for %1") << desc());
+ else if (! args[0].is_string())
+ throw_(std::runtime_error, _("Context argument for %1 not a string") << desc());
on_with(args[0].as_string(), args[1]);
- } else {
+ }
+ else if (args.size() < 1) {
+ throw_(std::runtime_error, _("No argument provided for %1") << desc());
+ }
+ else if (! args[0].is_string()) {
+ throw_(std::runtime_error, _("Context argument for %1 not a string") << desc());
+ }
+ else {
on_only(args[0].as_string());
}
diff --git a/src/report.cc b/src/report.cc
index bc0680d1..096536c9 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -235,12 +235,13 @@ value_t report_t::fn_justify(call_scope_t& scope)
return string_value(out.str());
}
-value_t report_t::fn_quoted(call_scope_t& args)
+value_t report_t::fn_quoted(call_scope_t& scope)
{
+ interactive_t args(scope, "s");
std::ostringstream out;
out << '"';
- foreach (const char ch, args[0].to_string()) {
+ foreach (const char ch, args.get<string>(0)) {
if (ch == '"')
out << "\\\"";
else
@@ -253,8 +254,7 @@ value_t report_t::fn_quoted(call_scope_t& args)
value_t report_t::fn_join(call_scope_t& scope)
{
- interactive_t args(scope, "s");
-
+ interactive_t args(scope, "s");
std::ostringstream out;
foreach (const char ch, args.get<string>(0)) {
@@ -315,6 +315,39 @@ value_t report_t::fn_price(call_scope_t& scope)
return args.value_at(0).price();
}
+value_t report_t::fn_lot_date(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ if (args.value_at(0).is_annotated()) {
+ const annotation_t& details(args.value_at(0).annotation());
+ if (details.date)
+ return *details.date;
+ }
+ return NULL_VALUE;
+}
+
+value_t report_t::fn_lot_price(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ if (args.value_at(0).is_annotated()) {
+ const annotation_t& details(args.value_at(0).annotation());
+ if (details.price)
+ return *details.price;
+ }
+ return NULL_VALUE;
+}
+
+value_t report_t::fn_lot_tag(call_scope_t& scope)
+{
+ interactive_t args(scope, "v");
+ if (args.value_at(0).is_annotated()) {
+ const annotation_t& details(args.value_at(0).annotation());
+ if (details.tag)
+ return string_value(*details.tag);
+ }
+ return NULL_VALUE;
+}
+
namespace {
value_t fn_black(call_scope_t&) {
return string_value("black");
@@ -559,6 +592,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
case 'f':
OPT(flat);
else OPT_ALT(forecast_while_, forecast_);
+ else OPT(forecast_years_);
else OPT(format_);
else OPT(force_color);
else OPT(force_pager);
diff --git a/src/report.h b/src/report.h
index 4d028e33..0fe7d05b 100644
--- a/src/report.h
+++ b/src/report.h
@@ -160,6 +160,9 @@ public:
value_t fn_ansify_if(call_scope_t& scope);
value_t fn_percent(call_scope_t& scope);
value_t fn_price(call_scope_t& scope);
+ value_t fn_lot_date(call_scope_t& scope);
+ value_t fn_lot_price(call_scope_t& scope);
+ value_t fn_lot_tag(call_scope_t& scope);
value_t fn_now(call_scope_t&) {
return terminus;
@@ -240,6 +243,7 @@ public:
HANDLER(force_color).report(out);
HANDLER(force_pager).report(out);
HANDLER(forecast_while_).report(out);
+ HANDLER(forecast_years_).report(out);
HANDLER(format_).report(out);
HANDLER(gain).report(out);
HANDLER(head_).report(out);
@@ -544,6 +548,7 @@ public:
OPTION(report_t, force_color);
OPTION(report_t, force_pager);
OPTION(report_t, forecast_while_);
+ OPTION(report_t, forecast_years_);
OPTION(report_t, format_); // -F
OPTION_(report_t, gain, DO() { // -G
diff --git a/src/session.cc b/src/session.cc
index b7fdf275..6efc03f2 100644
--- a/src/session.cc
+++ b/src/session.cc
@@ -146,9 +146,11 @@ std::size_t session_t::read_data(const string& master_account)
cache->should_load(HANDLER(file_).data_files) &&
cache->load(journal))) {
if (price_db_path) {
- if (exists(*price_db_path) && read_journal(*price_db_path) > 0)
- throw_(parse_error, _("Transactions not allowed in price history file"));
- journal->sources.push_back(journal_t::fileinfo_t(*price_db_path));
+ if (exists(*price_db_path)) {
+ if (read_journal(*price_db_path) > 0)
+ throw_(parse_error, _("Transactions not allowed in price history file"));
+ journal->sources.push_back(journal_t::fileinfo_t(*price_db_path));
+ }
HANDLER(file_).data_files.remove(*price_db_path);
}
diff --git a/src/utils.h b/src/utils.h
index c662acbe..cdb43037 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -580,6 +580,15 @@ inline char peek_next_nonws(std::istream& in) {
str.get(var); \
if (in.eof()) \
break; \
+ switch (var) { \
+ case 'b': var = '\b'; break; \
+ case 'f': var = '\f'; break; \
+ case 'n': var = '\n'; break; \
+ case 'r': var = '\r'; break; \
+ case 't': var = '\t'; break; \
+ case 'v': var = '\v'; break; \
+ default: break; \
+ } \
} \
*_p++ = var; \
var = static_cast<char>(str.peek()); \
@@ -600,6 +609,15 @@ inline char peek_next_nonws(std::istream& in) {
str.get(var); \
if (in.eof()) \
break; \
+ switch (var) { \
+ case 'b': var = '\b'; break; \
+ case 'f': var = '\f'; break; \
+ case 'n': var = '\n'; break; \
+ case 'r': var = '\r'; break; \
+ case 't': var = '\t'; break; \
+ case 'v': var = '\v'; break; \
+ default: break; \
+ } \
idx++; \
} \
*_p++ = var; \