added support for interval reporting; changed some option flags
This commit is contained in:
parent
02be02011b
commit
7610aec86d
14 changed files with 403 additions and 195 deletions
101
NEWS
101
NEWS
|
|
@ -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
|
||||
|
|
|
|||
73
datetime.cc
73
datetime.cc
|
|
@ -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)
|
||||
|
|
|
|||
20
datetime.h
20
datetime.h
|
|
@ -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
|
||||
|
|
|
|||
8
debug.h
8
debug.h
|
|
@ -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
13
error.h
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
17
format.h
17
format.h
|
|
@ -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
177
main.cc
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
60
valexpr.cc
60
valexpr.cc
|
|
@ -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 << ")";
|
||||
|
|
|
|||
|
|
@ -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
64
walk.cc
|
|
@ -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
44
walk.h
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue