added support for interval reporting; changed some option flags

This commit is contained in:
John Wiegley 2004-08-12 20:06:06 -04:00
parent 02be02011b
commit 7610aec86d
14 changed files with 403 additions and 195 deletions

101
NEWS
View file

@ -3,30 +3,39 @@
* 2.0
- The code base has been rewritten for clarity and consistency. As a
result, the code is much simpler and more robust (and in most cases
faster).
- The code base has been rewritten for clarity and consistency. The
code is now simpler, more robust, and a fair bit faster.
- Register reports now show the account being credited/debited. Use
new -o option to see "other accounts" -- or the account the
credit/debit came from. (This was the old behavior in 1.x, but can
lead to confusing reports when viewing accounts with subaccounts.)
The -o option also works for balance reports, where it will show all
the account totals related to your query.
- The most significant feature addition in this version is the use of
"value expressions". These are now used in many places to indicate
what to display, the sorting order, and even the output format.
- Regexps specified after the command are applied to account names
only. To search on a payee name, use "--" to separate the two kinds
of regexps. For example, to find payee's named John within all
Expenses accounts:
A value expression is a simple string that uses one letter codes to
indicate transaction, entry and account details. Logic and math
operators are supported, as well as a few useful functions. See the
README.
- If the environment variable LEDGER is used, a binary cache of that
current ledger will be kept, to speed up later queries of the same
data. The default location is in ~/.ledger, but this can be changed
with the environment variable LEDGER_CACHE. This only happens if no
"-f" flag was seen (i.e., if the LEDGER environment variable is
used).
- New "-y DATEFMT" options will change the date format used throughout
ledger. The default is "%Y/%m/%d".
- Regexps specified after the command name now apply to account names
only. To search on a payee, use "--" to separate the two kinds of
regexps. For example, to find a payee named "John" within all
Expenses accounts, use:
ledger register expenses -- john
- The use of "+" and "-" in ledger files (to specify permanent
regexps) has been removed.
- The -G switch no longer generates gnuplot-safe data. It now reports
totals in terms of net gain/loss.
FYI: The above command is identical (and internally converted) to:
ledger -l "/expenses/|//john/" register
- To include entries from a file into a specific account, use:
@ ACCOUNT
@ -36,10 +45,12 @@
All entries specified within the "@ ACCOUNT/@@" range will be added
under that account.
- If the environment variable LEDGER_CACHE is set to a filename, a a
binary dump of the current ledger will be written then, to speed up
later queries of the same data. This only happens if no "-f" flag
was seen (i.e., if the LEDGER environment variable is used).
- Register reports now show the account being changed. Use the -r
option to see "other accounts" -- or the account the credit/debit
came from. (This was the old behavior in 1.x, but can lead to
confusing reports when viewing accounts with subaccounts.) The -r
option also works for balance reports, where it will show all the
account totals related to your query.
- There are several new default reporting styles, which work both in
the balance and register reports:
@ -54,6 +65,32 @@
-W Report the trend, with older values affecting the trend less
-X Report expected amount for the next transaction
- Automated transactions now use a single value expression as a
predicate. This means the new syntax is:
= VALUE-EXPR
TRANSACTIONS...
Only one VALUE-EXPR is supported, compared to the multiple account
regexps supported before. By using a VALUE-EXPR as a predicate,
matching may now be much more comprehensive and selective.
- The use of "+" and "-" in ledger files (to specify permanent
regexps) has been removed.
- The -G switch no longer generates gnuplot-safe data. It now reports
totals in terms of net gain/loss.
- The -l flag now takes an expression string as a "predicate".
Therefore, to equal the old behavior of "-l $100", you would use:
-l AT<{$100}
- The -S flag now takes an expression string, which yields the value
that will be sorted on.
----------------------------------------------------------------------
- Value expressions are now supported, where the totals reported can
be changed using -t and -T and an expression string composed of:
@ -101,14 +138,6 @@
-W == -t a -T MD(MA*(d-b/e-b))
-X == -t a -T a+MD(MA*(d-b/e-b))
- The -l flag now takes an expression string as a "predicate".
Therefore, to equal the old behavior of "-l $100", you would use:
-l AT<{$100}
- The -S flag now takes an expression string, which yields the value
that will be sorted on.
- User-specified format strings are supported with a -F option. The
strings are very much like printf format strings, with the following
syntax for each substitution:
@ -142,17 +171,7 @@
%?10d %?-.20p %-.22a %12.66t %12.80T
%20T %-a
- Automated transactions now use a single value expression as a
predicate. This means the new syntax is:
= VALUE-EXPR
TRANSACTIONS...
Only one VALUE-EXPR is supported, compared to the multiple account
regexps supported before. By using a VALUE-EXPR as a predicate,
matching may now be much more comprehensive and selective.
* 1.7 (never released)
* 1.7
- Pricing histories are now supported, so that ledger remembers
historical pricing of all commodities, and can give register reports

View file

@ -31,8 +31,77 @@ static const char * formats[] = {
std::time_t interval_t::increment(const std::time_t moment)
{
// jww (2004-08-11): NYI
return moment + seconds;
std::time_t then = moment;
if (years || months) {
struct std::tm * desc = std::gmtime(&then);
if (years)
desc->tm_year += years;
if (months) {
desc->tm_mon += months;
if (desc->tm_mon > 11) {
desc->tm_year++;
desc->tm_mon -= 12;
}
}
then = std::mktime(desc);
}
return then + seconds;
}
interval_t * interval_t::parse(std::istream& in)
{
unsigned long years = 0;
unsigned long months = 0;
unsigned long seconds = 0;
std::string word;
in >> word;
if (word == "every") {
in >> word;
if (std::isdigit(word[0])) {
int quantity = std::atol(word.c_str());
in >> word;
if (word == "days")
seconds = 86400 * quantity;
else if (word == "weeks")
seconds = 7 * 86400 * quantity;
else if (word == "months")
months = quantity;
else if (word == "quarters")
months = 3 * quantity;
else if (word == "years")
years = quantity;
}
else if (word == "day")
seconds = 86400;
else if (word == "week")
seconds = 7 * 86400;
else if (word == "monthly")
months = 1;
else if (word == "quarter")
months = 3;
else if (word == "year")
years = 1;
}
else if (word == "daily")
seconds = 86400;
else if (word == "weekly")
seconds = 7 * 86400;
else if (word == "biweekly")
seconds = 14 * 86400;
else if (word == "monthly")
months = 1;
else if (word == "bimonthly")
months = 2;
else if (word == "quarterly")
months = 3;
else if (word == "yearly")
years = 1;
return new interval_t(seconds, months, years);
}
bool parse_date_mask(const char * date_str, struct std::tm * result)

View file

@ -11,22 +11,24 @@ struct interval_t
unsigned long months;
unsigned long seconds;
interval_t(unsigned long _seconds, unsigned long _months,
unsigned long _years)
interval_t(unsigned long _seconds, unsigned long _months = 0,
unsigned long _years = 0)
: years(_years), months(_months), seconds(_seconds) {}
std::time_t increment(const std::time_t);
static interval_t * parse(std::istream& in);
};
extern bool parse_date_mask(const char * date_str, struct std::tm * result);
extern bool parse_date(const char * date_str, std::time_t * result,
const int year = -1);
extern bool quick_parse_date(char * date_str, std::time_t * result);
extern struct std::tm * now_tm;
bool parse_date_mask(const char * date_str, struct std::tm * result);
bool parse_date(const char * date_str, std::time_t * result,
const int year = -1);
bool quick_parse_date(char * date_str, std::time_t * result);
} // namespace ledger
#endif // _DATETIME_H

View file

@ -81,6 +81,14 @@ inline bool _debug_active(const char * const cls) {
}
#define DEBUG_PRINT_(x) DEBUG_PRINT(_debug_cls, x)
#define DEBUG_PRINT_TIME(cls, x) { \
char buf[256]; \
std::strftime(buf, 255, "%Y/%m/%d", std::gmtime(&x)); \
DEBUG_PRINT(cls, #x << " is " << buf); \
}
#define DEBUG_PRINT_TIME_(x) DEBUG_PRINT_TIME(_debug_cls, x)
#define CONFIRM(x) assert(x)
#if RELEASE_LEVEL == DEVELOPER

13
error.h
View file

@ -27,11 +27,18 @@ class compute_error : public error
virtual ~compute_error() throw() {}
};
class expr_error : public error
class value_expr_error : public error
{
public:
expr_error(const std::string& reason) throw() : error(reason) {}
virtual ~expr_error() throw() {}
value_expr_error(const std::string& reason) throw() : error(reason) {}
virtual ~value_expr_error() throw() {}
};
class interval_expr_error : public error
{
public:
interval_expr_error(const std::string& reason) throw() : error(reason) {}
virtual ~interval_expr_error() throw() {}
};
class format_error : public error

View file

@ -33,6 +33,8 @@ std::string partial_account_name(const account_t * account)
return name;
}
std::string format_t::date_format = "%Y/%m/%d";
std::auto_ptr<value_expr_t> format_t::value_expr;
std::auto_ptr<value_expr_t> format_t::total_expr;
@ -114,15 +116,14 @@ element_t * format_t::parse_elements(const std::string& fmt)
current->chars = num;
break;
case 'd':
case 'D':
current->type = element_t::DATE_STRING;
// jww (2004-08-10): allow this to be changed
current->chars = "%Y/%m/%d";
current->chars = format_t::date_format;
break;
case 'X': current->type = element_t::CLEARED; break;
case 'C': current->type = element_t::CODE; break;
case 'p': current->type = element_t::PAYEE; break;
case 'P': current->type = element_t::PAYEE; break;
case 'n': current->type = element_t::ACCOUNT_NAME; break;
case 'N': current->type = element_t::ACCOUNT_FULLNAME; break;
case 'o': current->type = element_t::OPT_AMOUNT; break;

View file

@ -52,6 +52,8 @@ struct format_t
{
element_t * elements;
static std::string date_format;
static std::auto_ptr<value_expr_t> value_expr;
static std::auto_ptr<value_expr_t> total_expr;
@ -107,14 +109,15 @@ class format_transactions : public item_handler<transaction_t>
}
virtual void operator()(transaction_t * xact) {
if (last_entry != xact->entry) {
first_line_format.format_elements(output_stream, details_t(xact));
last_entry = xact->entry;
} else {
next_lines_format.format_elements(output_stream, details_t(xact));
if (! (xact->dflags & TRANSACTION_DISPLAYED)) {
if (last_entry != xact->entry) {
first_line_format.format_elements(output_stream, details_t(xact));
last_entry = xact->entry;
} else {
next_lines_format.format_elements(output_stream, details_t(xact));
}
xact->dflags |= TRANSACTION_DISPLAYED;
}
xact->dflags |= TRANSACTION_DISPLAYED;
}
};

177
main.cc
View file

@ -12,39 +12,13 @@
namespace ledger {
static const std::string bal_fmt = "%20T %2_%-n\n";
static const std::string reg_fmt
= "%10d %-.20p %-.22N %12.66t %12.80T\n\
= "%D %-.20P %-.22N %12.66t %12.80T\n\
%/ %-.22N %12.66t %12.80T\n";
static const std::string print_fmt
= "\n%10d %X%C%p\n %-34N %12o\n%/ %-34N %12o\n";
= "\n%D %X%C%P\n %-34N %12o\n%/ %-34N %12o\n";
static const std::string equity_fmt
= "\n%10d %X%C%p\n%/ %-34N %12t\n";
void set_price_conversion(const std::string& setting)
{
char buf[128];
std::strcpy(buf, setting.c_str());
assert(setting.length() < 128);
char * c = buf;
char * p = std::strchr(buf, '=');
if (! p) {
std::cerr << "Warning: Invalid price setting: " << setting << std::endl;
} else {
*p++ = '\0';
amount_t price;
price.parse(p);
commodity_t * commodity = commodity_t::find_commodity(c, true);
commodity->set_conversion(price);
}
}
= "\n%D %X%C%P\n%/ %-34N %12t\n";
static long pricing_leeway = 24 * 3600;
@ -102,6 +76,20 @@ void download_price_quote(commodity_t * commodity,
} // namespace ledger
static std::string ledger_cache_file()
{
std::string cache_file;
if (const char * p = std::getenv("LEDGER_CACHE")) {
cache_file = p;
}
else if (const char * p = std::getenv("HOME")) {
cache_file = p;
cache_file += "/.ledger";
}
return cache_file;
}
static void show_version(std::ostream& out)
{
out
@ -157,9 +145,13 @@ int main(int argc, char * argv[])
{
using namespace ledger;
std::auto_ptr<journal_t> journal(new journal_t);
std::list<std::string> files;
std::auto_ptr<value_expr_t> sort_order;
std::auto_ptr<journal_t> journal(new journal_t);
std::list<std::string> files;
std::auto_ptr<value_expr_t> sort_order;
std::auto_ptr<std::ostream> output_stream;
std::auto_ptr<interval_t> report_interval;
#define OUT() (output_stream.get() ? *output_stream.get() : std::cout)
std::string predicate;
std::string display_predicate;
@ -167,6 +159,7 @@ int main(int argc, char * argv[])
std::string sort_string;
std::string value_expr = "a";
std::string total_expr = "T";
std::time_t interval_begin = 0;
bool show_subtotals = true;
bool show_expanded = false;
@ -205,18 +198,20 @@ int main(int argc, char * argv[])
cache_dirty = true;
if (use_cache)
if (const char * p = std::getenv("LEDGER_CACHE"))
if (access(p, R_OK) != -1) {
std::ifstream instr(p);
if (! read_binary_journal(instr, std::getenv("LEDGER"),
journal.get())) {
// Throw away what's been read, and create a new journal
journal.reset(new journal_t);
} else {
cache_dirty = false;
}
if (use_cache) {
std::string cache_file = ledger_cache_file();
if (! cache_file.empty() &&
access(cache_file.c_str(), R_OK) != -1) {
std::ifstream stream(cache_file.c_str());
if (! read_binary_journal(stream, std::getenv("LEDGER"),
journal.get())) {
// Throw away what's been read, and create a new journal
journal.reset(new journal_t);
} else {
cache_dirty = false;
}
}
}
}
// Parse the command-line options
@ -224,7 +219,7 @@ int main(int argc, char * argv[])
int c, index;
while (-1 !=
(c = getopt(argc, argv,
"+ABb:Ccd:DEe:F:f:Ghi:L:l:MnoOP:p:QRS:st:T:UVvWXZ"))) {
"+ABb:Ccd:DEe:F:f:Ghi:L:l:MnOo:P:p:QRrS:sT:t:UVvWXYy:Zz:"))) {
switch (char(c)) {
// Basic options
case 'h':
@ -240,8 +235,19 @@ int main(int argc, char * argv[])
use_cache = false;
break;
case 'o':
if (std::string(optarg) != "-")
output_stream.reset(new std::ofstream(optarg));
break;
case 'p':
set_price_conversion(optarg);
if (char * p = std::strchr(optarg, '=')) {
*p = ' ';
std::string conversion = "C ";
conversion += p;
std::istringstream stream(conversion);
parse_textual_journal(stream, journal.get(), journal->master);
}
break;
case 'b':
@ -294,6 +300,10 @@ int main(int argc, char * argv[])
format_string = optarg;
break;
case 'y':
format_t::date_format = optarg;
break;
case 'E':
show_empty = true;
break;
@ -310,10 +320,39 @@ int main(int argc, char * argv[])
sort_string = optarg;
break;
case 'o':
case 'r':
show_related = true;
break;
case 'z': {
std::string str(optarg);
std::istringstream stream(str);
report_interval.reset(interval_t::parse(stream));
if (! stream.eof()) {
std::string word;
stream >> word;
if (word == "from") {
stream >> word;
if (! parse_date(word.c_str(), &interval_begin))
throw interval_expr_error("Could not parse 'from' date");
}
}
break;
}
case 'W':
report_interval.reset(new interval_t(604800, 0, 0));
break;
case 'M':
report_interval.reset(new interval_t(0, 1, 0));
break;
case 'Y':
report_interval.reset(new interval_t(0, 0, 1));
break;
case 'l':
if (! predicate.empty())
predicate += "&";
@ -386,23 +425,16 @@ int main(int argc, char * argv[])
total_expr = "DMT";
break;
case 'Z':
case 'X':
value_expr = "a";
total_expr = "MDMT";
break;
#if 0
case 'W':
case 'Z':
value_expr = "a";
total_expr = "MD(MT*(d-b/e-b))";
total_expr = "MD(MT/(1+(((t-d)/(30*86400))<0?0:((t-d)/(30*86400)))))";
break;
case 'X':
value_expr = "a";
total_expr = "a+MD(MT*(d-b/e-b))";
break;
#endif
default:
assert(0);
break;
@ -444,7 +476,7 @@ int main(int argc, char * argv[])
std::ifstream db(path);
journal->sources.push_back(path);
entry_count += parse_textual_journal(db, journal.get(),
journal->master);
journal->master);
}
}
catch (error& err) {
@ -597,7 +629,7 @@ int main(int argc, char * argv[])
formatter.reset(new filter_transactions(formatter.release(), predicate));
walk_entries(journal->entries, *formatter.get());
format_account acct_formatter(std::cout, format, display_predicate);
format_account acct_formatter(OUT(), format, display_predicate);
if (show_subtotals)
sum_accounts(journal->master);
walk_accounts(journal->master, acct_formatter, sort_order.get());
@ -605,7 +637,7 @@ int main(int argc, char * argv[])
if (format_account::disp_subaccounts_p(journal->master)) {
std::string end_format = "--------------------\n";
format.reset(end_format + f);
format.format_elements(std::cout, details_t(journal->master));
format.format_elements(OUT(), details_t(journal->master));
}
}
else if (command == "E") {
@ -614,12 +646,12 @@ int main(int argc, char * argv[])
formatter.reset(new filter_transactions(formatter.release(), predicate));
walk_entries(journal->entries, *formatter.get());
format_equity acct_formatter(std::cout, format, nformat, display_predicate);
format_equity acct_formatter(OUT(), format, nformat, display_predicate);
sum_accounts(journal->master);
walk_accounts(journal->master, acct_formatter, sort_order.get());
}
else if (command == "e") {
format_transactions formatter(std::cout, format, nformat);
format_transactions formatter(OUT(), format, nformat);
walk_transactions(new_entry->transactions, formatter);
}
else {
@ -631,7 +663,7 @@ int main(int argc, char * argv[])
// format_transactions write each transaction received to the
// output stream.
formatter.reset(new format_transactions(std::cout, format, nformat));
formatter.reset(new format_transactions(OUT(), format, nformat));
// sort_transactions will sort all the transactions it sees, based
// on the `sort_order' value expression.
@ -653,8 +685,8 @@ int main(int argc, char * argv[])
// list to account for changes in market value of commodities,
// which otherwise would affect the running total unpredictably.
if (show_revalued)
formatter.reset(new changed_value_transactions(formatter.release() /*,
show_revalued_only*/));
formatter.reset(new changed_value_transactions(formatter.release(),
show_revalued_only));
// collapse_transactions causes entries with multiple transactions
// to appear as entries with a subtotaled transaction for each
@ -671,9 +703,10 @@ int main(int argc, char * argv[])
// everything.
if (show_expanded)
formatter.reset(new subtotal_transactions(formatter.release()));
else if (0)
formatter.reset(new interval_transactions(formatter.release(), 0,
interval_t(9676800, 0, 0)));
else if (report_interval.get())
formatter.reset(new interval_transactions(formatter.release(),
*report_interval.get(),
interval_begin));
// related_transactions will pass along all transactions related
// to the transaction received. If `show_all_related' is true,
@ -703,12 +736,14 @@ int main(int argc, char * argv[])
// Save the cache, if need be
if (use_cache && cache_dirty)
if (const char * p = std::getenv("LEDGER_CACHE")) {
std::ofstream outstr(p);
if (use_cache && cache_dirty) {
std::string cache_file = ledger_cache_file();
if (! cache_file.empty()) {
assert(std::getenv("LEDGER"));
write_binary_journal(outstr, journal.get(), std::getenv("LEDGER"));
std::ofstream stream(cache_file.c_str());
write_binary_journal(stream, journal.get(), std::getenv("LEDGER"));
}
}
return 0;
}

View file

@ -13,7 +13,7 @@ running_total = 0.0
index = 1
last_line = ""
for line in os.popen("./ledger %s reg %s" % (sys.argv[1], sys.argv[2])):
for line in os.popen("../ledger %s reg %s" % (sys.argv[1], sys.argv[2])):
value = clean(line[55:67])
total = clean(line[68:80])
@ -29,7 +29,7 @@ for line in os.popen("./ledger %s reg %s" % (sys.argv[1], sys.argv[2])):
balance_total = 0.0
for line in os.popen("./ledger %s bal %s" % (sys.argv[1], sys.argv[2])):
for line in os.popen("../ledger %s bal %s" % (sys.argv[1], sys.argv[2])):
balance_total = clean(line[:20])
if abs(balance_total - running_total) > 0.001:

View file

@ -2,9 +2,9 @@
for test in \
"-O nrl:checking" \
"-B 401" \
"-V 401" \
"-G 401" \
"-B 401" \
"-V 401" \
"-G 401" \
"-B retire" \
"-V retire" \
"-G retire"

View file

@ -125,6 +125,10 @@ void value_expr_t::compute(balance_t& result, const details_t& details) const
result = (unsigned int) std::time(NULL);
break;
case TODAY:
result = (unsigned int) std::time(NULL);
break;
case CLEARED:
if (details.entry) {
result = details.entry->state == entry_t::CLEARED;
@ -188,6 +192,15 @@ void value_expr_t::compute(balance_t& result, const details_t& details) const
result = abs(result);
break;
case F_STRIP: {
assert(left);
left->compute(result, details);
amount_t amt = result.amount();
amt.commodity = commodity_t::null_commodity;
result = amt;
break;
}
case F_PAYEE_MASK:
assert(mask);
if (details.entry)
@ -214,6 +227,10 @@ void value_expr_t::compute(balance_t& result, const details_t& details) const
moment = std::time(NULL);
break;
case TODAY:
moment = std::time(NULL);
break;
default:
throw compute_error("Invalid date passed to P(value,date)");
}
@ -341,7 +358,7 @@ value_expr_t * parse_value_term(std::istream& in)
if (c == '}')
in.get(c);
else
throw expr_error("Missing '}'");
throw value_expr_error("Missing '}'");
} else {
while (! in.eof() && std::isdigit(c) || c == '.') {
in.get(c);
@ -363,6 +380,7 @@ value_expr_t * parse_value_term(std::istream& in)
case 'a': node = new value_expr_t(value_expr_t::AMOUNT); break;
case 'c': node = new value_expr_t(value_expr_t::COST); break;
case 'd': node = new value_expr_t(value_expr_t::DATE); break;
case 't': node = new value_expr_t(value_expr_t::TODAY); break;
case 'X': node = new value_expr_t(value_expr_t::CLEARED); break;
case 'R': node = new value_expr_t(value_expr_t::REAL); break;
case 'n': node = new value_expr_t(value_expr_t::INDEX); break;
@ -389,6 +407,11 @@ value_expr_t * parse_value_term(std::istream& in)
node->left = parse_value_term(in);
break;
case 'S':
node = new value_expr_t(value_expr_t::F_STRIP);
node->left = parse_value_term(in);
break;
case 'M':
node = new value_expr_t(value_expr_t::F_ARITH_MEAN);
node->left = parse_value_term(in);
@ -413,7 +436,7 @@ value_expr_t * parse_value_term(std::istream& in)
if (peek_next_nonws(in) == ')')
in.get(c);
else
throw expr_error("Missing ')'");
throw value_expr_error("Missing ')'");
} else {
node->left = parse_value_term(in);
}
@ -445,7 +468,7 @@ value_expr_t * parse_value_term(std::istream& in)
value_expr_t::F_PAYEE_MASK : value_expr_t::F_ACCOUNT_MASK);
node->mask = new mask_t(ident);
} else {
throw expr_error("Missing closing '/'");
throw value_expr_error("Missing closing '/'");
}
break;
}
@ -455,7 +478,7 @@ value_expr_t * parse_value_term(std::istream& in)
if (peek_next_nonws(in) == ')')
in.get(c);
else
throw expr_error("Missing ')'");
throw value_expr_error("Missing ')'");
break;
case '[': {
@ -471,9 +494,9 @@ value_expr_t * parse_value_term(std::istream& in)
in.get(c);
node = new value_expr_t(value_expr_t::CONSTANT_T);
if (! parse_date(ident.c_str(), &node->constant_t))
throw expr_error("Failed to parse date");
throw value_expr_error("Failed to parse date");
} else {
throw expr_error("Missing ']'");
throw value_expr_error("Missing ']'");
}
break;
}
@ -609,7 +632,7 @@ value_expr_t * parse_logic_expr(std::istream& in)
if (! in.eof()) {
std::ostringstream err;
err << "Unexpected character '" << c << "'";
throw expr_error(err.str());
throw value_expr_error(err.str());
}
}
}
@ -656,7 +679,7 @@ value_expr_t * parse_value_expr(std::istream& in)
if (c != ':') {
std::ostringstream err;
err << "Unexpected character '" << c << "'";
throw expr_error(err.str());
throw value_expr_error(err.str());
}
in.get(c);
choices->right = parse_logic_expr(in);
@ -667,7 +690,7 @@ value_expr_t * parse_value_expr(std::istream& in)
if (! in.eof()) {
std::ostringstream err;
err << "Unexpected character '" << c << "'";
throw expr_error(err.str());
throw value_expr_error(err.str());
}
}
c = peek_next_nonws(in);
@ -730,12 +753,13 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node)
out << "DATE/TIME[" << node->constant_t << "]";
break;
case value_expr_t::AMOUNT: out << "AMOUNT"; break;
case value_expr_t::COST: out << "COST"; break;
case value_expr_t::DATE: out << "DATE"; break;
case value_expr_t::CLEARED: out << "CLEARED"; break;
case value_expr_t::REAL: out << "REAL"; break;
case value_expr_t::INDEX: out << "INDEX"; break;
case value_expr_t::AMOUNT: out << "AMOUNT"; break;
case value_expr_t::COST: out << "COST"; break;
case value_expr_t::DATE: out << "DATE"; break;
case value_expr_t::TODAY: out << "TODAY"; break;
case value_expr_t::CLEARED: out << "CLEARED"; break;
case value_expr_t::REAL: out << "REAL"; break;
case value_expr_t::INDEX: out << "INDEX"; break;
case value_expr_t::BALANCE: out << "BALANCE"; break;
case value_expr_t::COST_BALANCE: out << "COST_BALANCE"; break;
case value_expr_t::TOTAL: out << "TOTAL"; break;
@ -759,6 +783,12 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node)
out << ")";
break;
case value_expr_t::F_STRIP:
out << "STRIP(";
dump_value_expr(out, node->left);
out << ")";
break;
case value_expr_t::F_PAYEE_MASK:
assert(node->mask);
out << "P_MASK(" << node->mask->pattern << ")";

View file

@ -48,6 +48,7 @@ struct value_expr_t
AMOUNT,
COST,
DATE,
TODAY,
CLEARED,
REAL,
INDEX, // for accounts, this is the DEPTH
@ -63,6 +64,7 @@ struct value_expr_t
F_VALUE,
F_NEG,
F_ABS,
F_STRIP,
F_PAYEE_MASK,
F_ACCOUNT_MASK,

64
walk.cc
View file

@ -3,6 +3,21 @@
namespace ledger {
void sort_transactions::flush()
{
std::stable_sort(transactions.begin(), transactions.end(),
compare_items<transaction_t>(sort_order));
for (transactions_deque::iterator i = transactions.begin();
i != transactions.end();
i++)
(*handler)(*i);
transactions.clear();
handler->flush();
}
void calc_transactions::operator()(transaction_t * xact)
{
if (last_xact)
@ -92,8 +107,12 @@ void changed_value_transactions::operator()(transaction_t * xact)
}
}
if (xact)
if (xact) {
if (changed_values_only)
xact->dflags |= TRANSACTION_DISPLAYED;
(*handler)(xact);
}
last_xact = xact;
}
@ -103,8 +122,14 @@ void subtotal_transactions::flush()
entry_t * entry = new entry_t;
char buf[256];
// jww (2004-08-10): allow for a format string here
std::strftime(buf, 255, "- %Y/%m/%d", std::gmtime(&finish));
std::string fmt = "- ";
fmt += format_t::date_format;
// Make sure the end date is inclusive
if (start != finish)
finish -= 86400;
std::strftime(buf, 255, fmt.c_str(), std::gmtime(&finish));
entry->payee = buf;
entry_temps.push_back(entry);
@ -153,4 +178,37 @@ void subtotal_transactions::operator()(transaction_t * xact)
(*i).second += *xact;
}
void interval_transactions::operator()(transaction_t * xact)
{
std::time_t quant = interval.increment(begin);
if (std::difftime(xact->entry->date, quant) > 0) {
if (last_xact) {
start = begin;
finish = quant;
flush();
}
if (! interval.seconds) {
struct std::tm * desc = std::gmtime(&xact->entry->date);
if (interval.years)
desc->tm_mon = 0;
desc->tm_mday = 1;
desc->tm_hour = 0;
desc->tm_min = 0;
desc->tm_sec = 0;
quant = std::mktime(desc);
}
std::time_t temp;
while (std::difftime(xact->entry->date,
temp = interval.increment(quant)) > 0)
quant = temp;
begin = quant;
}
subtotal_transactions::operator()(xact);
last_xact = xact;
}
} // namespace ledger

44
walk.h
View file

@ -87,19 +87,7 @@ class sort_transactions : public item_handler<transaction_t>
delete handler;
}
virtual void flush() {
std::stable_sort(transactions.begin(), transactions.end(),
compare_items<transaction_t>(sort_order));
for (transactions_deque::iterator i = transactions.begin();
i != transactions.end();
i++)
(*handler)(*i);
transactions.clear();
handler->flush();
}
virtual void flush();
virtual void operator()(transaction_t * xact) {
transactions.push_back(xact);
@ -207,6 +195,7 @@ class changed_value_transactions : public item_handler<transaction_t>
// This filter requires that calc_transactions be used at some point
// later in the chain.
bool changed_values_only;
transaction_t * last_xact;
item_handler<transaction_t> * handler;
@ -215,8 +204,10 @@ class changed_value_transactions : public item_handler<transaction_t>
transactions_deque xact_temps;
public:
changed_value_transactions(item_handler<transaction_t> * _handler)
: last_xact(NULL), handler(_handler) {}
changed_value_transactions(item_handler<transaction_t> * _handler,
bool _changed_values_only)
: changed_values_only(_changed_values_only), last_xact(NULL),
handler(_handler) {}
virtual ~changed_value_transactions() {
flush();
@ -288,7 +279,8 @@ class interval_transactions : public subtotal_transactions
public:
interval_transactions(item_handler<transaction_t> * _handler,
std::time_t _begin, const interval_t& _interval)
const interval_t& _interval,
const std::time_t _begin = 0)
: subtotal_transactions(_handler),
begin(_begin), interval(_interval), last_xact(NULL) {}
@ -297,25 +289,7 @@ class interval_transactions : public subtotal_transactions
finish = interval.increment(begin);
}
virtual void operator()(transaction_t * xact) {
if (std::difftime(xact->entry->date, interval.increment(begin)) > 0) {
if (last_xact) {
start = begin;
finish = interval.increment(begin);
flush();
}
begin = interval.increment(begin);
std::time_t temp;
while (std::difftime(xact->entry->date,
temp = interval.increment(begin)) > 0)
begin = temp;
}
subtotal_transactions::operator()(xact);
last_xact = xact;
}
virtual void operator()(transaction_t * xact);
};
class related_transactions : public item_handler<transaction_t>