Merge branch 'next'

This commit is contained in:
John Wiegley 2009-11-12 14:29:08 -05:00
commit bfd9ecf2af
33 changed files with 921 additions and 559 deletions

1
acprep
View file

@ -764,6 +764,7 @@ class PrepareBuild(CommandLineApp):
self.CPPFLAGS.append('-D_GLIBCXX_FULLY_DYNAMIC_STRING=1')
self.configure_args.append('--disable-shared')
self.configure_args.append('--enable-doxygen')
self.options.use_glibcxx_debug = True
self.locate_my_libraries()

View file

@ -1,4 +1,4 @@
.Dd March 15, 2009
.Dd November 12, 2009
.Dt ledger 1
.Sh NAME
.Nm ledger
@ -9,45 +9,52 @@ ledger
.Op Ar options
.Op Ar arguments
.Sh DESCRIPTION
Ledger is a command-line accounting tool providing the user access to the
power of double-entry accounting. It is only a reporting tool, which means it
never modifies your data files, nor can it be used to create or remove data.
Ledger is a command-line accounting tool based on the power and completeness
of double-entry accounting. It is only a reporting tool, which means it never
modifies your data files, but it does offers a large selection of reports, and
different ways to customize them to your liking.
.Pp
.Sh COMMANDS
Ledger accepts several top-level commands, each of which is used to generate a
different report. Most of them accept a
Ledger accepts several top-level commands, each of which generates a different
kind of basic report. Most of them accept a
.Ar report-query
argument, in order to determine what to report. To understand what is
accepted by
argument, in order to determine what should be reported. To understand the
syntax of a
.Ar report-query ,
see the section on
.Sx QUERIES .
In its most basic form, simply specifying one or more strings will produce a
In its most basic form, simply specifying one or more strings produces a
report for all accounts containing those strings.
.Pp
If no command at all is given, Ledger enters a
If no command is given, Ledger enters a
.Tn REPL ,
or command loop, allowing several commands to be executed against the same
dataset without reparsing.
.Pp
The following is a summary of all the reporting commands accepted by Ledger:
The following is a complete list of reporting commands accepted by Ledger:
.Pp
.Bl -tag -width foo
.It Nm balance Oo Ar query Oc
Produce a balance report showing subtotals for matching leaf accounts, and
aggregate totals for all the parents of those accounts. The most common
options with this command are:
.Bl -tag -width balance
.It Nm balance Oo Ar report-query Oc
Produces a balance report showing totals for all matching accounts, and
aggregate totals for parents of those accounts. Options most commonly used
with this command are:
.Pp
.Bl -tag -compact -width "--collapse (-n)"
.It Fl \-basis Pq Fl B
Report in terms of cost basis, not amount or value. This is the only form of
report which is guaranteed to always balance to zero, when no
.Ar report-query
is specified.
.It Fl \-collapse Pq Fl n
Only show totals in the top-most accounts.
Only show totals for the top-most accounts.
.It Fl \-empty Pq Fl E
Show matching accounts whose total happens to be zero.
Also show accounts whose total is zero.
.It Fl \-flat
Rather than displaying a hierarchical tree, flatten it to show only subtotals
for accounts directly matching the query.
Rather than display a hierarchical tree, flatten the report to show subtotals
for only accounts matching
.Ar report-query .
.It Fl \-no\-total
Suppress the final total usually shown at the bottom of the report.
Suppress the summary total shown at the bottom of the report (when not zero).
.El
.Pp
The synonyms
@ -55,31 +62,198 @@ The synonyms
and
.Nm b
are also accepted.
.It Nm csv Oo Ar query Oc
.It Nm budget Oo Ar report-query Oc
A special balance report which includes three extra columns: the amount
budgeted during the reporting period, how spending differed from the budget,
and the percentage of budget spent (exceeds 100% if you go over budget).
Note that budgeting requires one or more
.Do
periodic transactions
.Dc
to be defined in your data file(s). See the manual for more information.
.It Nm cleared Oo Ar report-query Oc
A special balance report which adds two extra columns: the cleared balance for
each account, and the date of the most recent cleared posting in that account.
For this accounting to be meaningful, the cleared flag must be set on at least
one posting. See the manual for more information.
.It Nm csv Oo Ar report-query Oc
Report of postings matching the
.Ar report-query
in CSV format (comma-separated values). Useful for exporting data to a
spreadsheet for further analysis or charting.
.It Nm draft Oo Ar draft-template Oc
Generate and display a new, properly formatted Ledger transaction by comparing
the
.Ar draft-template
to the transactions in your data file(s). For more information on draft
templates and using this command to quickly create new transactions, see the
section
.Sx DRAFTS .
.Pp
The synonyms
.Nm entry
and
.Nm xact
are also accepted.
.It Nm emacs Oo Ar query Oc
Outputs posting and transaction data in a format readily consumed by the Emacs
editor, in a series of Lisp forms. This is used by the
.Li ledger.el
Emacs mode to process reporting data from Ledger.
.Pp
The synonym
.Nm lisp
is also accepted.
.It Nm equity Oo Ar query Oc
.It Nm generate
.It Nm prices Oo Ar query Oc
.It Nm pricesdb Oo Ar query Oc
.It Nm print Oo Ar query Oc
.It Nm register Oo Ar query Oc
.It Nm equity Oo Ar report-query Oc
Prints a series of transactions that balance current totals for
accounts matching the
.Ar report-query
in a special account called
.Li Equity:Opening Balances .
The purpose of this report is to close the books for a prior year, while using
these equity transactions to carry forward those balances.
.It Nm prices Oo Ar report-query Oc
Reports prices for all commodities in postings matching the
.Ar report-query .
The prices are reported with the granularity of a single day.
.It Nm pricesdb Oo Ar report-query Oc
Reports prices for all commodities in postings matching the
.Ar report-query .
Prices are reported down to the second, using the same format as the
.Li ~/.pricedb
file.
.It Nm print Oo Ar report-query Oc
Prints out the full transactions of any matching postings using the same
format as they would appear in a data file. This can be used to extract
subsets from a Ledger file to transfer to other files.
.It Nm push Oo Ar options Oc
In the
.Tn REPL ,
this command pushes a set of command-line options, so that they will apply to
all subsequent reports.
.It Nm pop
In the
.Tn REPL ,
pops any option settings that have been pushed.
.It Nm register Oo Ar report-query Oc
List all postings matching the
.Ar report-query .
This is one of the most common commands, and can be used to provide a variety
of useful reports. Options most commonly used
with this command are:
.Pp
.Bl -tag -compact -width "--collapse (-n)"
.It Fl \-average Pq Fl A
Show the running average, rather than a running total.
.It Fl \-current Pq Fl c
Don't show postings beyond the present day.
.It Fl \-exchange Ar commodity Pq Fl X
Render all values in the given
.Ar commodity ,
if a price conversion rate can be determined. Rates are always displayed
relative to the date of the posting they are calculated for. This means a
.Nm register
report is a historical value report. For current values, it may be preferable
to use the
.Nm balance
report.
.It Fl \-gain Pq Fl G
Show any gains (or losses) in commodity values over time.
.It Fl \-head Ar number
Only show the top
.Ar number
postings.
.It Fl \-invert
Invert the value of amounts shown.
.It Fl \-market Pq Fl V
Show current market values for all amounts. This is determined in a somewhat
magical fashion. It is probably more straightforward to use
.Fl \-exchange Pq Fl X .
.It Fl \-period Ar time-period Pq Fl p
Show postings only for the given
.Ar time-period .
.It Fl \-related Pq Fl r
Show postings that are related to those that would have been shown. It has
the effect of displaying the
.Do
other side
.Dc
of the values.
.It Fl \-sort Ar value-expression Pq Fl S
Sort postings by evaluating the given
.Ar value-expression .
Note that a comma-separated list of expressions is allowed, in which case each
sorting term is used in order to determine the final ordering. For example,
to search by date and then amount, one would use:
.Li -S 'date, amount' .
.It Fl \-tail Ar number
Only show the last
.Ar number
postings.
.It Fl \-uncleared Pq Fl U
Only show uncleared (i.e., recent) postings.
.El
.Pp
There are also several grouping options that can be useful:
.Pp
.Bl -tag -compact -width "--collapse (-n)"
.It Fl \-by-payee Pq Fl P
Group postings by common payee names.
.It Fl \-daily Pq Fl D
Group postings by day.
.It Fl \-weekly Pq Fl W
Group postings by week (starting on Sundays).
.It Fl \-start-of-week Ar day-name
Set the start of each grouped way to the given
.Ar day-name .
.It Fl \-monthly Pq Fl M
Group postings by month.
.It Fl \-quarterly
Group postings by fiscal quarter.
.It Fl \-yearly Pq Fl Y
Group postings by year.
.It Fl \-dow
Group postings by the day of the week on which they took place.
.It Fl \-subtotal Pq Fl s
Group all postings together. This is very similar to the totals shown by the
.Nm balance
report.
.El
.Pp
The synonyms
.Nm reg
and
.Nm r
are also accepted.
.It Nm reload
Used solely by the
.It Nm xact Oo Ar date Oc
The synonym
.Nm entry
is also accepted.
.Tn REPL ,
and causes an immediate reloading of all journal files in the session.
.It Nm stats Oo Ar query Oc
.It Nm server
This command requires that Python support be active. If so, it starts up an
HTTP server listening for requests on port 9000. This provides an alternate
interface to creating and viewing reports. Note that this is very much a
work-in-progress, and will not be fully functional until a later version.
.It Nm stats Oo Ar report-query Oc
Provides summary information about all the postings matching
.Ar report-query .
It provides information such as:
.Bl -bullet -offset indent -compact
.It
Time range of all matching postings
.It
Unique payees
.It
Unique accounts
.It
Postings total
.It
Uncleared postings
.It
Days since last posting
.It
More...
.El
.It Nm xml Oo Ar report-query Oc
Outputs data relating to the current report in XML format. It includes all
accounts and commodities involved in the report, plus the postings and the
transactions they are contained in. See the manual for more information.
.El
.Pp
.Sh OPTIONS
@ -349,6 +523,56 @@ for example:
.It Nm xact
.El
.Pp
.Sh DRAFTS
.Pp
.Sh FORMATS
.Pp
.Sh DEBUG COMMANDS
In addition to the regular reporting commands, Ledger also accepts several
debug commands:
.Bl -tag -width balance
.It Nm args Oo Ar report-query Oc
Accepts a
.Ar report-query
as its argument and displays it back to the user along with a complete
analysis of how Ledger interpreted it. Useful if you want to understand how
report queries are translated into value expressions.
.It Nm eval Oo Ar value-expression Oc
Evaluates the given
.Ar value-expression
and prints the result. For more on value expressions, see the section
.Sx EXPRESSIONS .
.It Nm format Oo Ar format-string Oc
Accepts a
.Ar format-string
and displays an analysis of how it was parsed, and what it would look like
applied to a sample transaction. For more on format strings, see the section
.Sx FORMATS .
.It Nm generate
Generates 50 randomly composed yet valid Ledger transactions.
.It Nm parse Oo Ar value-expression Oc
Parses the given
.Ar value-expression
and display an analysis of the expression tree and its evaluated value. For
more on value expressions, see the section
.Sx EXPRESSIONS .
.It Nm python Oo Ar file Oc
Invokes a Python interpreter to read the given
.Ar file .
What is special about this is that the ledger module is builtin, not read from
disk, so it doesn't require Ledger to be installed anywhere, or the shared
library variants to be built.
.It Nm reload
Used only in the
.Tn REPL ,
it causes an immediate reloading of all data files for the current session.
.It Nm template Oo Ar draft-template Oc
Accepts a
.Ar draft-template
and displays information about how it was parsed. See the section on
.Sx DRAFTS .
.El
.Pp
.Sh SEE ALSO
.Xr beancount 1,
.Xr hledger 1

View file

@ -327,16 +327,25 @@ bool account_t::valid() const
return true;
}
bool account_t::children_with_xdata() const
{
foreach (const accounts_map::value_type& pair, accounts)
if (pair.second->has_xdata() ||
pair.second->children_with_xdata())
return true;
return false;
}
std::size_t account_t::children_with_flags(xdata_t::flags_t flags) const
{
std::size_t count = 0;
bool grandchildren_visited = false;
foreach (const accounts_map::value_type& pair, accounts) {
foreach (const accounts_map::value_type& pair, accounts)
if (pair.second->has_xflags(flags) ||
pair.second->children_with_flags(flags))
count++;
}
// Although no immediately children were visited, if any progeny at all were
// visited, it counts as one.

View file

@ -241,6 +241,7 @@ public:
bool has_xflags(xdata_t::flags_t flags) const {
return xdata_ && xdata_->has_flags(flags);
}
bool children_with_xdata() const;
std::size_t children_with_flags(xdata_t::flags_t flags) const;
#if defined(HAVE_BOOST_SERIALIZATION)

View file

@ -201,23 +201,23 @@ bool archive_t::should_load(const std::list<path>& data_files)
return true;
}
bool archive_t::should_save(shared_ptr<journal_t> journal)
bool archive_t::should_save(journal_t& journal)
{
std::list<path> data_files;
DEBUG("archive.journal", "Should the archive be saved?");
if (journal->was_loaded) {
if (journal.was_loaded) {
DEBUG("archive.journal", "No, it's one we loaded before");
return false;
}
if (journal->sources.empty()) {
if (journal.sources.empty()) {
DEBUG("archive.journal", "No, there were no sources!");
return false;
}
foreach (const journal_t::fileinfo_t& i, journal->sources) {
foreach (const journal_t::fileinfo_t& i, journal.sources) {
if (i.from_stream) {
DEBUG("archive.journal", "No, one source was from a stream");
return false;
@ -241,14 +241,14 @@ bool archive_t::should_save(shared_ptr<journal_t> journal)
return true;
}
void archive_t::save(shared_ptr<journal_t> journal)
void archive_t::save(journal_t& journal)
{
INFO_START(archive, "Saved journal file cache");
ofstream stream(file, std::ios::binary);
write_header_bits(stream);
sources = journal->sources;
sources = journal.sources;
#if defined(DEBUG_ON)
foreach (const journal_t::fileinfo_t& i, sources)
@ -263,12 +263,12 @@ void archive_t::save(shared_ptr<journal_t> journal)
DEBUG("archive.journal",
"Archiving journal with " << sources.size() << " sources");
oa << *journal;
oa << journal;
INFO_FINISH(archive);
}
bool archive_t::load(shared_ptr<journal_t> journal)
bool archive_t::load(journal_t& journal)
{
INFO_START(archive, "Read cached journal file");
@ -282,8 +282,8 @@ bool archive_t::load(shared_ptr<journal_t> journal)
archive_t temp;
iarchive >> temp;
iarchive >> *journal.get();
journal->was_loaded = true;
iarchive >> journal;
journal.was_loaded = true;
INFO_FINISH(archive);

View file

@ -69,10 +69,10 @@ public:
bool read_header();
bool should_load(const std::list<path>& data_files);
bool should_save(shared_ptr<journal_t> journal);
bool should_save(journal_t& journal);
void save(shared_ptr<journal_t> journal);
bool load(shared_ptr<journal_t> journal);
void save(journal_t& journal);
bool load(journal_t& journal);
#if defined(HAVE_BOOST_SERIALIZATION)
private:

View file

@ -159,6 +159,11 @@ post_handler_ptr chain_post_handlers(report_t& report,
handler.reset(new sort_posts(handler, "date"));
}
if (report.HANDLED(date_))
handler.reset(new transfer_details(handler, transfer_details::SET_DATE,
report.session.journal->master,
report.HANDLER(date_).str(),
report));
if (report.HANDLED(account_))
handler.reset(new transfer_details(handler, transfer_details::SET_ACCOUNT,
report.session.journal->master,

View file

@ -30,17 +30,17 @@
*/
/**
* @addtogroup derive
* @addtogroup expr
*/
/**
* @file derive.h
* @file draft.h
* @author John Wiegley
*
* @ingroup report
*/
#ifndef _DERIVE_H
#define _DERIVE_H
#ifndef _DRAFT_H
#define _DRAFT_H
#include "exprbase.h"
#include "value.h"
@ -110,4 +110,4 @@ value_t template_command(call_scope_t& args);
} // namespace ledger
#endif // _DERIVE_H
#endif // _DRAFT_H

View file

@ -706,22 +706,27 @@ void transfer_details::operator()(post_t& post)
temp.set_state(post.state());
bind_scope_t bound_scope(scope, temp);
value_t substitute(expr.calc(bound_scope));
switch (which_element) {
case SET_PAYEE:
xact.payee = expr.calc(bound_scope).to_string();
case SET_DATE:
xact.set_date(substitute.to_date());
break;
case SET_ACCOUNT: {
std::list<string> account_names;
temp.account->remove_post(&temp);
split_string(expr.calc(bound_scope).to_string(), ':', account_names);
split_string(substitute.to_string(), ':', account_names);
temp.account = create_temp_account_from_path(account_names, temps,
xact.journal->master);
temp.account->add_post(&temp);
break;
}
case SET_PAYEE:
xact.payee = substitute.to_string();
break;
default:
assert(false);
break;

View file

@ -61,6 +61,35 @@ public:
virtual void operator()(post_t&) {}
};
class collect_posts : public item_handler<post_t>
{
public:
std::vector<post_t *> posts;
collect_posts() : item_handler<post_t>() {
TRACE_CTOR(collect_posts, "");
}
virtual ~collect_posts() {
TRACE_DTOR(collect_posts);
}
std::size_t length() const {
return posts.size();
}
std::vector<post_t *>::iterator begin() {
return posts.begin();
}
std::vector<post_t *>::iterator end() {
return posts.end();
}
virtual void flush() {}
virtual void operator()(post_t& post) {
posts.push_back(&post);
}
};
class posts_iterator;
class pass_down_posts : public item_handler<post_t>
@ -550,8 +579,9 @@ class transfer_details : public item_handler<post_t>
public:
enum element_t {
SET_PAYEE,
SET_ACCOUNT
SET_DATE,
SET_ACCOUNT,
SET_PAYEE
} which_element;
transfer_details(post_handler_ptr handler,

View file

@ -194,7 +194,7 @@ void global_scope_t::execute_command(strings_list args, bool at_repl)
if (! at_repl)
session().read_journal_files();
normalize_report_options(verb);
report().normalize_options(verb);
if (! bool(command = look_for_command(bound_scope, verb)))
throw_(std::logic_error, _("Unrecognized command '%1'") << verb);
@ -416,178 +416,6 @@ expr_t::func_t global_scope_t::look_for_command(scope_t& scope,
return expr_t::func_t();
}
void global_scope_t::normalize_report_options(const string& verb)
{
// Patch up some of the reporting options based on what kind of
// command it was.
report_t& rep(report());
#ifdef HAVE_ISATTY
if (! rep.HANDLED(force_color)) {
if (! rep.HANDLED(no_color) && isatty(STDOUT_FILENO))
rep.HANDLER(color).on_only(string("?normalize"));
if (rep.HANDLED(color) && ! isatty(STDOUT_FILENO))
rep.HANDLER(color).off();
}
if (! rep.HANDLED(force_pager)) {
if (rep.HANDLED(pager_) && ! isatty(STDOUT_FILENO))
rep.HANDLER(pager_).off();
}
#endif
item_t::use_effective_date = (rep.HANDLED(effective) &&
! rep.HANDLED(actual_dates));
rep.session.journal->commodity_pool->keep_base = rep.HANDLED(base);
rep.session.journal->commodity_pool->get_quotes = rep.session.HANDLED(download);
if (rep.session.HANDLED(price_exp_))
rep.session.journal->commodity_pool->quote_leeway =
rep.session.HANDLER(price_exp_).value.as_long();
if (rep.session.HANDLED(price_db_))
rep.session.journal->commodity_pool->price_db =
rep.session.HANDLER(price_db_).str();
else
rep.session.journal->commodity_pool->price_db = none;
if (rep.HANDLED(date_format_))
set_date_format(rep.HANDLER(date_format_).str().c_str());
if (rep.HANDLED(datetime_format_))
set_datetime_format(rep.HANDLER(datetime_format_).str().c_str());
if (rep.HANDLED(start_of_week_)) {
if (optional<date_time::weekdays> weekday =
string_to_day_of_week(rep.HANDLER(start_of_week_).str()))
start_of_week = *weekday;
}
if (verb == "print" || verb == "xact" || verb == "dump") {
rep.HANDLER(related).on_only(string("?normalize"));
rep.HANDLER(related_all).on_only(string("?normalize"));
}
else if (verb == "equity") {
rep.HANDLER(equity).on_only(string("?normalize"));
}
if (verb == "print")
rep.HANDLER(limit_).on(string("?normalize"), "actual");
if (! rep.HANDLED(empty))
rep.HANDLER(display_).on(string("?normalize"), "amount|(!post&total)");
if (verb[0] != 'b' && verb[0] != 'r')
rep.HANDLER(base).on_only(string("?normalize"));
// If a time period was specified with -p, check whether it also gave a
// begin and/or end to the report period (though these can be overridden
// using -b or -e). Then, if no _duration_ was specified (such as monthly),
// then ignore the period since the begin/end are the only interesting
// details.
if (rep.HANDLED(period_)) {
if (! rep.HANDLED(sort_all_))
rep.HANDLER(sort_xacts_).on_only(string("?normalize"));
date_interval_t interval(rep.HANDLER(period_).str());
if (! rep.HANDLED(begin_) && interval.start) {
string predicate =
"date>=[" + to_iso_extended_string(*interval.start) + "]";
rep.HANDLER(limit_).on(string("?normalize"), predicate);
}
if (! rep.HANDLED(end_) && interval.end) {
string predicate =
"date<[" + to_iso_extended_string(*interval.end) + "]";
rep.HANDLER(limit_).on(string("?normalize"), predicate);
}
if (! interval.duration)
rep.HANDLER(period_).off();
}
// If -j or -J were specified, set the appropriate format string now so as
// to avoid option ordering issues were we to have done it during the
// initial parsing of the options.
if (rep.HANDLED(amount_data)) {
rep.HANDLER(format_)
.on_with(string("?normalize"), rep.HANDLER(plot_amount_format_).value);
}
else if (rep.HANDLED(total_data)) {
rep.HANDLER(format_)
.on_with(string("?normalize"), rep.HANDLER(plot_total_format_).value);
}
// If the --exchange (-X) option was used, parse out any final price
// settings that may be there.
if (rep.HANDLED(exchange_) &&
rep.HANDLER(exchange_).str().find('=') != string::npos) {
value_t(0L).exchange_commodities(rep.HANDLER(exchange_).str(), true,
rep.terminus);
}
long cols = 0;
if (rep.HANDLED(columns_))
cols = rep.HANDLER(columns_).value.to_long();
else if (const char * columns = std::getenv("COLUMNS"))
cols = lexical_cast<long>(columns);
else
cols = 80L;
if (cols > 0) {
DEBUG("auto.columns", "cols = " << cols);
if (! rep.HANDLER(date_width_).specified)
rep.HANDLER(date_width_)
.on_with(none, static_cast<long>(format_date(CURRENT_DATE(),
FMT_PRINTED).length()));
long date_width = rep.HANDLER(date_width_).value.to_long();
long payee_width = (rep.HANDLER(payee_width_).specified ?
rep.HANDLER(payee_width_).value.to_long() :
int(double(cols) * 0.263157));
long account_width = (rep.HANDLER(account_width_).specified ?
rep.HANDLER(account_width_).value.to_long() :
int(double(cols) * 0.302631));
long amount_width = (rep.HANDLER(amount_width_).specified ?
rep.HANDLER(amount_width_).value.to_long() :
int(double(cols) * 0.157894));
long total_width = (rep.HANDLER(total_width_).specified ?
rep.HANDLER(total_width_).value.to_long() :
amount_width);
DEBUG("auto.columns", "date_width = " << date_width);
DEBUG("auto.columns", "payee_width = " << payee_width);
DEBUG("auto.columns", "account_width = " << account_width);
DEBUG("auto.columns", "amount_width = " << amount_width);
DEBUG("auto.columns", "total_width = " << total_width);
if (! rep.HANDLER(date_width_).specified &&
! rep.HANDLER(payee_width_).specified &&
! rep.HANDLER(account_width_).specified &&
! rep.HANDLER(amount_width_).specified &&
! rep.HANDLER(total_width_).specified) {
long total = (4 /* the spaces between */ + date_width + payee_width +
account_width + amount_width + total_width);
if (total > cols) {
DEBUG("auto.columns", "adjusting account down");
account_width -= total - cols;
DEBUG("auto.columns", "account_width now = " << account_width);
}
}
if (! rep.HANDLER(date_width_).specified)
rep.HANDLER(date_width_).on_with(string("?normalize"), date_width);
if (! rep.HANDLER(payee_width_).specified)
rep.HANDLER(payee_width_).on_with(string("?normalize"), payee_width);
if (! rep.HANDLER(account_width_).specified)
rep.HANDLER(account_width_).on_with(string("?normalize"), account_width);
if (! rep.HANDLER(amount_width_).specified)
rep.HANDLER(amount_width_).on_with(string("?normalize"), amount_width);
if (! rep.HANDLER(total_width_).specified)
rep.HANDLER(total_width_).on_with(string("?normalize"), total_width);
}
}
void global_scope_t::visit_man_page() const
{
int pid = fork();

View file

@ -62,7 +62,6 @@ public:
void normalize_session_options();
expr_t::func_t look_for_precommand(scope_t& scope, const string& verb);
expr_t::func_t look_for_command(scope_t& scope, const string& verb);
void normalize_report_options(const string& verb);
char * prompt_string();

View file

@ -1,81 +0,0 @@
/*
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @addtogroup util
*/
/**
* @file hooks.h
* @author John Wiegley
*
* @ingroup util
*/
#ifndef _HOOKS_H
#define _HOOKS_H
template <typename T, typename Data>
class hooks_t : public boost::noncopyable
{
public:
typedef boost::function<bool (Data&, bool)> function_t;
protected:
std::list<T *> list;
public:
hooks_t() {
TRACE_CTOR(hooks_t, "");
}
~hooks_t() throw() {
TRACE_DTOR(hooks_t);
}
void add_hook(T * func, const bool prepend = false) {
if (prepend)
list.push_front(func);
else
list.push_back(func);
}
void remove_hook(T * func) {
list.remove(func);
}
bool run_hooks(Data& item) {
foreach (T * func, list)
if (! (*func)(item))
return false;
return true;
}
};
#endif // _HOOKS_H

View file

@ -172,6 +172,12 @@ public:
virtual optional<date_t> effective_date() const {
return _date_eff;
}
virtual void set_date(const date_t& date) {
if (use_effective_date)
_date_eff = date;
else
_date = date;
}
void set_state(state_t new_state) {
_state = new_state;

View file

@ -126,16 +126,23 @@ bool journal_t::add_xact(xact_t * xact)
{
xact->journal = this;
if (! xact->finalize() || ! xact_finalize_hooks.run_hooks(*xact)) {
if (! xact->finalize()) {
xact->journal = NULL;
return false;
}
extend_xact(xact);
xacts.push_back(xact);
return true;
}
void journal_t::extend_xact(xact_base_t * xact)
{
foreach (auto_xact_t * auto_xact, auto_xacts)
auto_xact->extend_xact(*xact);
}
bool journal_t::remove_xact(xact_t * xact)
{
bool found = false;
@ -204,6 +211,26 @@ std::size_t journal_t::read(const path& pathname,
return count;
}
bool journal_t::has_xdata()
{
foreach (xact_t * xact, xacts)
if (xact->has_xdata())
return true;
foreach (auto_xact_t * xact, auto_xacts)
if (xact->has_xdata())
return true;
foreach (period_xact_t * xact, period_xacts)
if (xact->has_xdata())
return true;
if (master->has_xdata() || master->children_with_xdata())
return true;
return false;
}
void journal_t::clear_xdata()
{
foreach (xact_t * xact, xacts)

View file

@ -43,15 +43,14 @@
#define _JOURNAL_H
#include "utils.h"
#include "hooks.h"
#include "times.h"
namespace ledger {
class commodity_pool_t;
class xact_base_t;
class xact_t;
class auto_xact_t;
class xact_finalizer_t;
class period_xact_t;
class account_t;
class scope_t;
@ -114,7 +113,6 @@ public:
bool was_loaded;
shared_ptr<commodity_pool_t> commodity_pool;
hooks_t<xact_finalizer_t, xact_t> xact_finalize_hooks;
journal_t();
journal_t(const path& pathname);
@ -138,6 +136,7 @@ public:
account_t * find_account_re(const string& regexp);
bool add_xact(xact_t * xact);
void extend_xact(xact_base_t * xact);
bool remove_xact(xact_t * xact);
xacts_list::iterator xacts_begin() {
@ -159,13 +158,6 @@ public:
return period_xacts.end();
}
void add_xact_finalizer(xact_finalizer_t * finalizer) {
xact_finalize_hooks.add_hook(finalizer);
}
void remove_xact_finalizer(xact_finalizer_t * finalizer) {
xact_finalize_hooks.remove_hook(finalizer);
}
std::size_t read(std::istream& in,
const path& pathname,
account_t * master = NULL,
@ -180,6 +172,7 @@ public:
const path * original_file = NULL,
bool strict = false);
bool has_xdata();
void clear_xdata();
bool valid() const;

View file

@ -92,7 +92,7 @@ public:
{
TRACE_CTOR(post_t, "copy");
}
~post_t() {
virtual ~post_t() {
TRACE_DTOR(post_t);
}

View file

@ -33,9 +33,14 @@
#include "pyinterp.h"
#include "pyutils.h"
#include "hooks.h"
#include "journal.h"
#include "xact.h"
#include "post.h"
#include "chain.h"
#include "filters.h"
#include "iterators.h"
#include "scope.h"
#include "report.h"
namespace ledger {
@ -130,51 +135,113 @@ namespace {
return journal.find_account(name, auto_create);
}
struct py_xact_finalizer_t : public xact_finalizer_t {
object pyobj;
py_xact_finalizer_t() {}
py_xact_finalizer_t(object obj) : pyobj(obj) {}
py_xact_finalizer_t(const py_xact_finalizer_t& other)
: pyobj(other.pyobj) {}
virtual bool operator()(xact_t& xact) {
return call<bool>(pyobj.ptr(), xact);
}
};
std::list<py_xact_finalizer_t> py_finalizers;
void py_add_xact_finalizer(journal_t& journal, object x)
{
py_finalizers.push_back(py_xact_finalizer_t(x));
journal.add_xact_finalizer(&py_finalizers.back());
}
void py_remove_xact_finalizer(journal_t& journal, object x)
{
for (std::list<py_xact_finalizer_t>::iterator i = py_finalizers.begin();
i != py_finalizers.end();
i++)
if ((*i).pyobj == x) {
journal.remove_xact_finalizer(&(*i));
py_finalizers.erase(i);
return;
}
}
void py_run_xact_finalizers(journal_t& journal, xact_t& xact)
{
journal.xact_finalize_hooks.run_hooks(xact);
}
std::size_t py_read(journal_t& journal, const string& pathname)
{
return journal.read(pathname);
}
struct collector_wrapper
{
journal_t& journal;
report_t report;
collect_posts * posts_collector;
post_handler_ptr chain;
collector_wrapper(journal_t& _journal, report_t& base)
: journal(_journal), report(base),
posts_collector(new collect_posts) {}
~collector_wrapper() {
journal.clear_xdata();
}
std::size_t length() const {
return posts_collector->length();
}
std::vector<post_t *>::iterator begin() {
return posts_collector->begin();
}
std::vector<post_t *>::iterator end() {
return posts_collector->end();
}
};
shared_ptr<collector_wrapper>
py_collect(journal_t& journal, const string& query)
{
if (journal.has_xdata()) {
PyErr_SetString(PyExc_RuntimeError,
_("Cannot have multiple journal collections open at once"));
throw_error_already_set();
}
report_t& current_report(downcast<report_t>(*scope_t::default_scope));
shared_ptr<collector_wrapper> coll(new collector_wrapper(journal,
current_report));
std::auto_ptr<journal_t> save_journal
(current_report.session.journal.release());
current_report.session.journal.reset(&journal);
try {
strings_list remaining =
process_arguments(split_arguments(query.c_str()), coll->report);
coll->report.normalize_options("register");
value_t args;
foreach (const string& arg, remaining)
args.push_back(string_value(arg));
coll->report.parse_query_args(args, "@Journal.collect");
journal_posts_iterator walker(coll->journal);
coll->chain =
chain_post_handlers(coll->report,
post_handler_ptr(coll->posts_collector));
pass_down_posts(coll->chain, walker);
}
catch (...) {
current_report.session.journal.release();
current_report.session.journal.reset(save_journal.release());
throw;
}
current_report.session.journal.release();
current_report.session.journal.reset(save_journal.release());
return coll;
}
post_t * posts_getitem(collector_wrapper& collector, long i)
{
post_t * post = collector.posts_collector->posts[i];
std::cerr << typeid(post).name() << std::endl;
std::cerr << typeid(*post).name() << std::endl;
std::cerr << typeid(post->account).name() << std::endl;
std::cerr << typeid(*post->account).name() << std::endl;
return post;
}
} // unnamed namespace
void export_journal()
{
class_< item_handler<post_t>, shared_ptr<item_handler<post_t> >,
boost::noncopyable >("PostHandler")
;
class_< collect_posts, bases<item_handler<post_t> >,
shared_ptr<collect_posts>, boost::noncopyable >("PostCollector")
.def("__len__", &collect_posts::length)
.def("__iter__", range<return_internal_reference<> >
(&collect_posts::begin, &collect_posts::end))
;
class_< collector_wrapper, shared_ptr<collector_wrapper>,
boost::noncopyable >("PostCollectorWrapper", no_init)
.def("__len__", &collector_wrapper::length)
.def("__getitem__", posts_getitem, return_internal_reference<>())
.def("__iter__", range<return_internal_reference<> >
(&collector_wrapper::begin, &collector_wrapper::end))
;
class_< journal_t::fileinfo_t > ("FileInfo")
.def(init<path>())
@ -206,11 +273,6 @@ void export_journal()
.add_property("commodity_pool",
make_getter(&journal_t::commodity_pool,
return_internal_reference<>()))
#if 0
.add_property("xact_finalize_hooks",
make_getter(&journal_t::xact_finalize_hooks),
make_setter(&journal_t::xact_finalize_hooks))
#endif
.def("add_account", &journal_t::add_account)
.def("remove_account", &journal_t::remove_account)
@ -223,12 +285,8 @@ void export_journal()
.def("add_xact", &journal_t::add_xact)
.def("remove_xact", &journal_t::remove_xact)
.def("add_xact_finalizer", py_add_xact_finalizer)
.def("remove_xact_finalizer", py_remove_xact_finalizer)
.def("run_xact_finalizers", py_run_xact_finalizers)
.def("__len__", xacts_len)
.def("__getitem__", xacts_getitem, return_internal_reference<1>())
.def("__getitem__", xacts_getitem, return_internal_reference<>())
.def("__iter__", range<return_internal_reference<> >
(&journal_t::xacts_begin, &journal_t::xacts_end))
@ -243,8 +301,11 @@ void export_journal()
.def("read", py_read)
.def("has_xdata", &journal_t::has_xdata)
.def("clear_xdata", &journal_t::clear_xdata)
.def("collect", py_collect)
.def("valid", &journal_t::valid)
;
}

View file

@ -115,8 +115,10 @@ void export_post()
make_getter(&post_t::xdata_t::datetime),
make_setter(&post_t::xdata_t::datetime))
.add_property("account",
make_getter(&post_t::xdata_t::account),
make_setter(&post_t::xdata_t::account))
make_getter(&post_t::xdata_t::account,
return_internal_reference<>()),
make_setter(&post_t::xdata_t::account,
with_custodian_and_ward<1, 2>()))
.add_property("sort_values",
make_getter(&post_t::xdata_t::sort_values),
make_setter(&post_t::xdata_t::sort_values))

View file

@ -122,16 +122,12 @@ void export_xact()
.def("lookup", &xact_t::lookup)
.def("has_xdata", &xact_t::has_xdata)
.def("clear_xdata", &xact_t::clear_xdata)
.def("valid", &xact_t::valid)
;
class_< xact_finalizer_t, boost::noncopyable >
("TransactionFinalizer", no_init)
.def("__call__", &xact_finalizer_t::operator())
;
class_< auto_xact_t, bases<xact_base_t> > ("AutomatedTransaction")
.def(init<predicate_t>())
@ -142,16 +138,6 @@ void export_xact()
.def("extend_xact", &auto_xact_t::extend_xact)
;
class_< auto_xact_finalizer_t, bases<xact_finalizer_t> >
("AutomatedTransactionFinalizer")
.add_property("journal",
make_getter(&auto_xact_finalizer_t::journal,
return_internal_reference<>()),
make_setter(&auto_xact_finalizer_t::journal,
with_custodian_and_ward<1, 2>()))
.def("__call__", &auto_xact_finalizer_t::operator())
;
class_< period_xact_t, bases<xact_base_t> > ("PeriodicTransaction")
.def(init<string>())
@ -162,16 +148,6 @@ void export_xact()
make_getter(&period_xact_t::period_string),
make_setter(&period_xact_t::period_string))
;
class_< func_finalizer_t, bases<xact_finalizer_t> >
("FunctionalFinalizer", init<func_finalizer_t::func_t>())
.add_property("func",
make_getter(&func_finalizer_t::func),
make_setter(&func_finalizer_t::func))
.def("__call__", &func_finalizer_t::operator())
;
scope().attr("extend_xact_base") = &extend_xact_base;
}
} // namespace ledger

View file

@ -60,19 +60,19 @@ void export_xact();
void initialize_for_python()
{
export_account();
export_amount();
export_balance();
export_times();
export_utils();
export_commodity();
export_amount();
export_value();
export_account();
export_balance();
export_expr();
export_format();
export_item();
export_journal();
export_post();
export_times();
export_utils();
export_value();
export_xact();
export_journal();
}
struct python_run

View file

@ -33,6 +33,7 @@
#include "report.h"
#include "session.h"
#include "pool.h"
#include "format.h"
#include "query.h"
#include "output.h"
@ -47,6 +48,201 @@
namespace ledger {
void report_t::normalize_options(const string& verb)
{
// Patch up some of the reporting options based on what kind of
// command it was.
#ifdef HAVE_ISATTY
if (! HANDLED(force_color)) {
if (! HANDLED(no_color) && isatty(STDOUT_FILENO))
HANDLER(color).on_only(string("?normalize"));
if (HANDLED(color) && ! isatty(STDOUT_FILENO))
HANDLER(color).off();
}
if (! HANDLED(force_pager)) {
if (HANDLED(pager_) && ! isatty(STDOUT_FILENO))
HANDLER(pager_).off();
}
#endif
item_t::use_effective_date = (HANDLED(effective) &&
! HANDLED(actual_dates));
session.journal->commodity_pool->keep_base = HANDLED(base);
session.journal->commodity_pool->get_quotes = session.HANDLED(download);
if (session.HANDLED(price_exp_))
session.journal->commodity_pool->quote_leeway =
session.HANDLER(price_exp_).value.as_long();
if (session.HANDLED(price_db_))
session.journal->commodity_pool->price_db =
session.HANDLER(price_db_).str();
else
session.journal->commodity_pool->price_db = none;
if (HANDLED(date_format_))
set_date_format(HANDLER(date_format_).str().c_str());
if (HANDLED(datetime_format_))
set_datetime_format(HANDLER(datetime_format_).str().c_str());
if (HANDLED(start_of_week_)) {
if (optional<date_time::weekdays> weekday =
string_to_day_of_week(HANDLER(start_of_week_).str()))
start_of_week = *weekday;
}
if (verb == "print" || verb == "xact" || verb == "dump") {
HANDLER(related).on_only(string("?normalize"));
HANDLER(related_all).on_only(string("?normalize"));
}
else if (verb == "equity") {
HANDLER(equity).on_only(string("?normalize"));
}
if (verb == "print")
HANDLER(limit_).on(string("?normalize"), "actual");
if (! HANDLED(empty))
HANDLER(display_).on(string("?normalize"), "amount|(!post&total)");
if (verb[0] != 'b' && verb[0] != 'r')
HANDLER(base).on_only(string("?normalize"));
// If a time period was specified with -p, check whether it also gave a
// begin and/or end to the report period (though these can be overridden
// using -b or -e). Then, if no _duration_ was specified (such as monthly),
// then ignore the period since the begin/end are the only interesting
// details.
if (HANDLED(period_)) {
if (! HANDLED(sort_all_))
HANDLER(sort_xacts_).on_only(string("?normalize"));
date_interval_t interval(HANDLER(period_).str());
if (! HANDLED(begin_) && interval.start) {
string predicate =
"date>=[" + to_iso_extended_string(*interval.start) + "]";
HANDLER(limit_).on(string("?normalize"), predicate);
}
if (! HANDLED(end_) && interval.end) {
string predicate =
"date<[" + to_iso_extended_string(*interval.end) + "]";
HANDLER(limit_).on(string("?normalize"), predicate);
}
if (! interval.duration)
HANDLER(period_).off();
}
// If -j or -J were specified, set the appropriate format string now so as
// to avoid option ordering issues were we to have done it during the
// initial parsing of the options.
if (HANDLED(amount_data)) {
HANDLER(format_)
.on_with(string("?normalize"), HANDLER(plot_amount_format_).value);
}
else if (HANDLED(total_data)) {
HANDLER(format_)
.on_with(string("?normalize"), HANDLER(plot_total_format_).value);
}
// If the --exchange (-X) option was used, parse out any final price
// settings that may be there.
if (HANDLED(exchange_) &&
HANDLER(exchange_).str().find('=') != string::npos) {
value_t(0L).exchange_commodities(HANDLER(exchange_).str(), true,
terminus);
}
long cols = 0;
if (HANDLED(columns_))
cols = HANDLER(columns_).value.to_long();
else if (const char * columns = std::getenv("COLUMNS"))
cols = lexical_cast<long>(columns);
else
cols = 80L;
if (cols > 0) {
DEBUG("auto.columns", "cols = " << cols);
if (! HANDLER(date_width_).specified)
HANDLER(date_width_)
.on_with(none, static_cast<long>(format_date(CURRENT_DATE(),
FMT_PRINTED).length()));
long date_width = HANDLER(date_width_).value.to_long();
long payee_width = (HANDLER(payee_width_).specified ?
HANDLER(payee_width_).value.to_long() :
int(double(cols) * 0.263157));
long account_width = (HANDLER(account_width_).specified ?
HANDLER(account_width_).value.to_long() :
int(double(cols) * 0.302631));
long amount_width = (HANDLER(amount_width_).specified ?
HANDLER(amount_width_).value.to_long() :
int(double(cols) * 0.157894));
long total_width = (HANDLER(total_width_).specified ?
HANDLER(total_width_).value.to_long() :
amount_width);
DEBUG("auto.columns", "date_width = " << date_width);
DEBUG("auto.columns", "payee_width = " << payee_width);
DEBUG("auto.columns", "account_width = " << account_width);
DEBUG("auto.columns", "amount_width = " << amount_width);
DEBUG("auto.columns", "total_width = " << total_width);
if (! HANDLER(date_width_).specified &&
! HANDLER(payee_width_).specified &&
! HANDLER(account_width_).specified &&
! HANDLER(amount_width_).specified &&
! HANDLER(total_width_).specified) {
long total = (4 /* the spaces between */ + date_width + payee_width +
account_width + amount_width + total_width);
if (total > cols) {
DEBUG("auto.columns", "adjusting account down");
account_width -= total - cols;
DEBUG("auto.columns", "account_width now = " << account_width);
}
}
if (! HANDLER(date_width_).specified)
HANDLER(date_width_).on_with(string("?normalize"), date_width);
if (! HANDLER(payee_width_).specified)
HANDLER(payee_width_).on_with(string("?normalize"), payee_width);
if (! HANDLER(account_width_).specified)
HANDLER(account_width_).on_with(string("?normalize"), account_width);
if (! HANDLER(amount_width_).specified)
HANDLER(amount_width_).on_with(string("?normalize"), amount_width);
if (! HANDLER(total_width_).specified)
HANDLER(total_width_).on_with(string("?normalize"), total_width);
}
}
void report_t::parse_query_args(const value_t& args, const string& whence)
{
query_t query(args, what_to_keep());
if (! query)
throw_(std::runtime_error,
_("Invalid query predicate: %1") << query.text());
HANDLER(limit_).on(whence, query.text());
DEBUG("report.predicate",
"Predicate = " << HANDLER(limit_).str());
if (query.tokens_remaining()) {
query.parse_again();
if (! query)
throw_(std::runtime_error,
_("Invalid display predicate: %1") << query.text());
HANDLER(display_).on(whence, query.text());
DEBUG("report.predicate",
"Display predicate = " << HANDLER(display_).str());
}
}
void report_t::posts_report(post_handler_ptr handler)
{
journal_posts_iterator walker(*session.journal.get());
@ -353,6 +549,69 @@ value_t report_t::fn_lot_tag(call_scope_t& scope)
return NULL_VALUE;
}
value_t report_t::fn_to_boolean(call_scope_t& scope)
{
interactive_t args(scope, "v");
args.value_at(0).in_place_cast(value_t::BOOLEAN);
return args.value_at(0);
}
value_t report_t::fn_to_int(call_scope_t& scope)
{
interactive_t args(scope, "v");
args.value_at(0).in_place_cast(value_t::INTEGER);
return args.value_at(0);
}
value_t report_t::fn_to_datetime(call_scope_t& scope)
{
interactive_t args(scope, "v");
args.value_at(0).in_place_cast(value_t::DATETIME);
return args.value_at(0);
}
value_t report_t::fn_to_date(call_scope_t& scope)
{
interactive_t args(scope, "v");
args.value_at(0).in_place_cast(value_t::DATE);
return args.value_at(0);
}
value_t report_t::fn_to_amount(call_scope_t& scope)
{
interactive_t args(scope, "v");
args.value_at(0).in_place_cast(value_t::AMOUNT);
return args.value_at(0);
}
value_t report_t::fn_to_balance(call_scope_t& scope)
{
interactive_t args(scope, "v");
args.value_at(0).in_place_cast(value_t::BALANCE);
return args.value_at(0);
}
value_t report_t::fn_to_string(call_scope_t& scope)
{
interactive_t args(scope, "v");
args.value_at(0).in_place_cast(value_t::STRING);
return args.value_at(0);
}
value_t report_t::fn_to_mask(call_scope_t& scope)
{
interactive_t args(scope, "v");
args.value_at(0).in_place_cast(value_t::MASK);
return args.value_at(0);
}
value_t report_t::fn_to_sequence(call_scope_t& scope)
{
interactive_t args(scope, "v");
args.value_at(0).in_place_cast(value_t::SEQUENCE);
return args.value_at(0);
}
namespace {
value_t fn_black(call_scope_t&) {
return string_value("black");
@ -393,54 +652,6 @@ namespace {
value_t fn_null(call_scope_t&) {
return NULL_VALUE;
}
template <class Type = post_t,
class handler_ptr = post_handler_ptr,
void (report_t::*report_method)(handler_ptr) =
&report_t::posts_report>
class reporter
{
shared_ptr<item_handler<Type> > handler;
report_t& report;
string whence;
public:
reporter(item_handler<Type> * _handler, report_t& _report,
const string& _whence)
: handler(_handler), report(_report), whence(_whence) {}
value_t operator()(call_scope_t& args)
{
if (args.size() > 0) {
query_t query(args.value(), report.what_to_keep());
if (! query)
throw_(std::runtime_error,
_("Invalid query predicate: %1") << query.text());
report.HANDLER(limit_).on(whence, query.text());
DEBUG("report.predicate",
"Predicate = " << report.HANDLER(limit_).str());
if (query.tokens_remaining()) {
query.parse_again();
if (! query)
throw_(std::runtime_error,
_("Invalid display predicate: %1") << query.text());
report.HANDLER(display_).on(whence, query.text());
DEBUG("report.predicate",
"Display predicate = " << report.HANDLER(display_).str());
}
}
(report.*report_method)(handler_ptr(handler));
return true;
}
};
}
value_t report_t::reload_command(call_scope_t&)
@ -561,6 +772,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
break;
case 'd':
OPT(daily);
else OPT(date_);
else OPT(date_format_);
else OPT(datetime_format_);
else OPT(depth_);
@ -634,7 +846,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(plot_total_format_);
else OPT(price);
else OPT(prices_format_);
else OPT(pricesdb_format_);
else OPT(pricedb_format_);
else OPT(print_format_);
else OPT(payee_width_);
else OPT(prepend_format_);
@ -822,6 +1034,24 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return MAKE_FUNCTOR(report_t::fn_today);
else if (is_eq(p, "t"))
return MAKE_FUNCTOR(report_t::fn_display_amount);
else if (is_eq(p, "to_boolean"))
return MAKE_FUNCTOR(report_t::fn_to_boolean);
else if (is_eq(p, "to_int"))
return MAKE_FUNCTOR(report_t::fn_to_int);
else if (is_eq(p, "to_datetime"))
return MAKE_FUNCTOR(report_t::fn_to_datetime);
else if (is_eq(p, "to_date"))
return MAKE_FUNCTOR(report_t::fn_to_date);
else if (is_eq(p, "to_amount"))
return MAKE_FUNCTOR(report_t::fn_to_amount);
else if (is_eq(p, "to_balance"))
return MAKE_FUNCTOR(report_t::fn_to_balance);
else if (is_eq(p, "to_string"))
return MAKE_FUNCTOR(report_t::fn_to_string);
else if (is_eq(p, "to_mask"))
return MAKE_FUNCTOR(report_t::fn_to_mask);
else if (is_eq(p, "to_sequence"))
return MAKE_FUNCTOR(report_t::fn_to_sequence);
break;
case 'T':
@ -929,12 +1159,12 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
(new format_posts(*this, report_format(HANDLER(prices_format_)),
maybe_format(HANDLER(prepend_format_))),
*this, "#prices"));
else if (is_eq(p, "pricesdb"))
else if (is_eq(p, "pricedb"))
return expr_t::op_t::wrap_functor
(reporter<post_t, post_handler_ptr, &report_t::commodities_report>
(new format_posts(*this, report_format(HANDLER(pricesdb_format_)),
(new format_posts(*this, report_format(HANDLER(pricedb_format_)),
maybe_format(HANDLER(prepend_format_))),
*this, "#pricesdb"));
*this, "#pricedb"));
break;
case 'r':
@ -951,8 +1181,6 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
case 's':
if (is_eq(p, "stats") || is_eq(p, "stat"))
return WRAP_FUNCTOR(report_statistics);
else if (is_eq(p, "server"))
return session.lookup(symbol_t::COMMAND, "server");
break;
case 'x':

View file

@ -44,6 +44,7 @@
#include "interactive.h"
#include "expr.h"
#include "query.h"
#include "chain.h"
#include "stream.h"
#include "option.h"
@ -124,6 +125,9 @@ public:
output_stream.close();
}
void normalize_options(const string& verb);
void parse_query_args(const value_t& args, const string& whence);
void posts_report(post_handler_ptr handler);
void generate_report(post_handler_ptr handler);
void xact_report(post_handler_ptr handler, xact_t& xact);
@ -155,6 +159,15 @@ public:
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_to_boolean(call_scope_t& scope);
value_t fn_to_int(call_scope_t& scope);
value_t fn_to_datetime(call_scope_t& scope);
value_t fn_to_date(call_scope_t& scope);
value_t fn_to_amount(call_scope_t& scope);
value_t fn_to_balance(call_scope_t& scope);
value_t fn_to_string(call_scope_t& scope);
value_t fn_to_mask(call_scope_t& scope);
value_t fn_to_sequence(call_scope_t& scope);
value_t fn_now(call_scope_t&) {
return terminus;
@ -217,6 +230,7 @@ public:
HANDLER(csv_format_).report(out);
HANDLER(current).report(out);
HANDLER(daily).report(out);
HANDLER(date_).report(out);
HANDLER(date_format_).report(out);
HANDLER(datetime_format_).report(out);
HANDLER(depth_).report(out);
@ -262,7 +276,7 @@ public:
HANDLER(prepend_format_).report(out);
HANDLER(price).report(out);
HANDLER(prices_format_).report(out);
HANDLER(pricesdb_format_).report(out);
HANDLER(pricedb_format_).report(out);
HANDLER(print_format_).report(out);
HANDLER(quantity).report(out);
HANDLER(quarterly).report(out);
@ -450,6 +464,7 @@ public:
parent->HANDLER(period_).on(string("--daily"), "daily");
});
OPTION(report_t, date_);
OPTION(report_t, date_format_);
OPTION(report_t, datetime_format_);
@ -712,11 +727,11 @@ public:
OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
on(none,
"%-.9(date) %-8(account) %(justify(scrub(display_amount), 12, "
"%(date) %-8(account) %(justify(scrub(display_amount), 12, "
" 2 + 9 + 8 + 12, true, color))\n");
});
OPTION__(report_t, pricesdb_format_, CTOR(report_t, pricesdb_format_) {
OPTION__(report_t, pricedb_format_, CTOR(report_t, pricedb_format_) {
on(none,
"P %(datetime) %(account) %(scrub(display_amount))\n");
});
@ -896,6 +911,34 @@ public:
DO_(args) { value = args[1].to_long(); specified = true; });
};
template <class Type = post_t,
class handler_ptr = post_handler_ptr,
void (report_t::*report_method)(handler_ptr) =
&report_t::posts_report>
class reporter
{
shared_ptr<item_handler<Type> > handler;
report_t& report;
string whence;
public:
reporter(item_handler<Type> * _handler,
report_t& _report, const string& _whence)
: handler(_handler), report(_report), whence(_whence) {}
value_t operator()(call_scope_t& args)
{
if (args.size() > 0)
report.parse_query_args(args.value(), whence);
(report.*report_method)(handler_ptr(handler));
return true;
}
};
} // namespace ledger
#endif // _REPORT_H

View file

@ -106,7 +106,7 @@ std::size_t session_t::read_data(const string& master_account)
if (! (cache &&
cache->should_load(HANDLER(file_).data_files) &&
cache->load(journal))) {
cache->load(*journal.get()))) {
#endif // HAVE_BOOST_SERIALIZATION
if (price_db_path) {
if (exists(*price_db_path)) {
@ -142,8 +142,8 @@ std::size_t session_t::read_data(const string& master_account)
assert(xact_count == journal->xacts.size());
#if defined(HAVE_BOOST_SERIALIZATION)
if (cache && cache->should_save(journal))
cache->save(journal);
if (cache && cache->should_save(*journal.get()))
cache->save(*journal.get());
}
#endif // HAVE_BOOST_SERIALIZATION

View file

@ -58,9 +58,9 @@ class session_t : public symbol_scope_t
friend void set_session_context(session_t * session);
public:
bool flush_on_next_data_file;
date_t::year_type current_year;
shared_ptr<journal_t> journal;
bool flush_on_next_data_file;
date_t::year_type current_year;
std::auto_ptr<journal_t> journal;
explicit session_t();
virtual ~session_t() {

View file

@ -79,8 +79,6 @@ namespace {
optional<date_t::year_type> current_year;
scoped_ptr<auto_xact_finalizer_t> auto_xact_finalizer;
instance_t(std::list<account_t *>& _account_stack,
std::list<string>& _tag_stack,
#if defined(TIMELOG_SUPPORT)
@ -227,9 +225,6 @@ instance_t::~instance_t()
TRACE_DTOR(instance_t);
account_stack.pop_front();
if (auto_xact_finalizer.get())
journal.remove_xact_finalizer(auto_xact_finalizer.get());
}
void instance_t::parse()
@ -546,11 +541,6 @@ void instance_t::automated_xact_directive(char * line)
try {
if (! auto_xact_finalizer.get()) {
auto_xact_finalizer.reset(new auto_xact_finalizer_t(&journal));
journal.add_xact_finalizer(auto_xact_finalizer.get());
}
std::auto_ptr<auto_xact_t> ae
(new auto_xact_t(query_t(string(skip_ws(line + 1)),
keep_details_t(true, true, true))));
@ -601,8 +591,7 @@ void instance_t::period_xact_directive(char * line)
pe->journal = &journal;
if (pe->finalize()) {
extend_xact_base(&journal, *pe.get());
journal.extend_xact(pe.get());
journal.period_xacts.push_back(pe.get());
pe->pos = position_t();

View file

@ -417,6 +417,8 @@ void report_memory(std::ostream& out, bool report_all)
namespace ledger {
#if defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
string::string() : std::string() {
TRACE_CTOR(string, "");
}
@ -453,6 +455,8 @@ string::~string() throw() {
TRACE_DTOR(string);
}
#endif // defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
string empty_string("");
strings_list split_arguments(const char * line)

View file

@ -181,6 +181,8 @@ void report_memory(std::ostream& out, bool report_all = false);
namespace ledger {
#if defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
class string : public std::string
{
public:
@ -253,6 +255,12 @@ inline bool operator!=(const char* __lhs, const string& __rhs)
inline bool operator!=(const string& __lhs, const char* __rhs)
{ return __lhs.compare(__rhs) != 0; }
#else // defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
typedef std::string string;
#endif // defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
extern string empty_string;
strings_list split_arguments(const char * line);

View file

@ -1146,6 +1146,12 @@ void value_t::in_place_cast(type_t cast_type)
case AMOUNT:
set_amount(amount_t(as_string()));
return;
case DATE:
set_date(parse_date(as_string()));
return;
case DATETIME:
set_datetime(parse_datetime(as_string()));
return;
case MASK:
set_mask(as_string());
return;

View file

@ -76,6 +76,15 @@ bool xact_base_t::remove_post(post_t * post)
return true;
}
bool xact_base_t::has_xdata()
{
foreach (post_t * post, posts)
if (post->has_xdata())
return true;
return false;
}
void xact_base_t::clear_xdata()
{
foreach (post_t * post, posts)
@ -372,6 +381,55 @@ bool xact_base_t::finalize()
return true;
}
bool xact_base_t::verify()
{
// Scan through and compute the total balance for the xact.
value_t balance;
foreach (post_t * post, posts) {
if (! post->must_balance())
continue;
amount_t& p(post->cost ? *post->cost : post->amount);
assert(! p.is_null());
// If the amount was a cost, it very likely has the "keep_precision" flag
// set, meaning commodity display precision is ignored when displaying the
// amount. We never want this set for the balance, so we must clear the
// flag in a temporary to avoid it propagating into the balance.
add_or_set_value(balance, p.keep_precision() ?
p.rounded().reduced() : p.reduced());
}
VERIFY(balance.valid());
// Now that the post list has its final form, calculate the balance once
// more in terms of total cost, accounting for any possible gain/loss
// amounts.
foreach (post_t * post, posts) {
if (! post->cost)
continue;
if (post->amount.commodity() == post->cost->commodity())
throw_(balance_error,
_("A posting's cost must be of a different commodity than its amount"));
}
if (! balance.is_null() && ! balance.is_zero()) {
add_error_context(item_context(*this, _("While balancing transaction")));
add_error_context(_("Unbalanced remainder is:"));
add_error_context(value_context(balance));
add_error_context(_("Amount to balance against:"));
add_error_context(value_context(magnitude()));
throw_(balance_error, _("Transaction does not balance"));
}
VERIFY(valid());
return true;
}
xact_t::xact_t(const xact_t& e)
: xact_base_t(e), code(e.code), payee(e.payee)
{
@ -486,6 +544,8 @@ void auto_xact_t::extend_xact(xact_base_t& xact)
try {
bool needs_further_verification = false;
foreach (post_t * initial_post, initial_posts) {
if (! initial_post->has_flags(ITEM_GENERATED) &&
predicate(*initial_post)) {
@ -555,10 +615,16 @@ void auto_xact_t::extend_xact(xact_base_t& xact)
xact.add_post(new_post);
new_post->account->add_post(new_post);
if (new_post->must_balance())
needs_further_verification = true;
}
}
}
if (needs_further_verification)
xact.verify();
}
catch (const std::exception& err) {
add_error_context(item_context(*this, _("While applying automated transaction")));

View file

@ -77,8 +77,10 @@ public:
value_t magnitude() const;
virtual bool finalize();
bool finalize();
bool verify();
bool has_xdata();
void clear_xdata();
virtual bool valid() const {
@ -140,11 +142,6 @@ private:
#endif // HAVE_BOOST_SERIALIZATION
};
struct xact_finalizer_t {
virtual ~xact_finalizer_t() {}
virtual bool operator()(xact_t& xact) = 0;
};
class auto_xact_t : public xact_base_t
{
public:
@ -183,39 +180,6 @@ private:
#endif // HAVE_BOOST_SERIALIZATION
};
struct auto_xact_finalizer_t : public xact_finalizer_t
{
journal_t * journal;
auto_xact_finalizer_t() : journal(NULL) {
TRACE_CTOR(auto_xact_finalizer_t, "");
}
auto_xact_finalizer_t(const auto_xact_finalizer_t& other)
: xact_finalizer_t(), journal(other.journal) {
TRACE_CTOR(auto_xact_finalizer_t, "copy");
}
auto_xact_finalizer_t(journal_t * _journal) : journal(_journal) {
TRACE_CTOR(auto_xact_finalizer_t, "journal_t *");
}
~auto_xact_finalizer_t() throw() {
TRACE_DTOR(auto_xact_finalizer_t);
}
virtual bool operator()(xact_t& xact);
#if defined(HAVE_BOOST_SERIALIZATION)
private:
/** Serialization. */
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned int /* version */) {
ar & journal;
}
#endif // HAVE_BOOST_SERIALIZATION
};
class period_xact_t : public xact_base_t
{
public:
@ -253,38 +217,6 @@ private:
#endif // HAVE_BOOST_SERIALIZATION
};
class func_finalizer_t : public xact_finalizer_t
{
func_finalizer_t();
public:
typedef function<bool (xact_t& xact)> func_t;
func_t func;
func_finalizer_t(func_t _func) : func(_func) {
TRACE_CTOR(func_finalizer_t, "func_t");
}
func_finalizer_t(const func_finalizer_t& other) :
xact_finalizer_t(), func(other.func) {
TRACE_CTOR(func_finalizer_t, "copy");
}
~func_finalizer_t() throw() {
TRACE_DTOR(func_finalizer_t);
}
virtual bool operator()(xact_t& xact) {
return func(xact);
}
};
void extend_xact_base(journal_t * journal, xact_base_t& xact);
inline bool auto_xact_finalizer_t::operator()(xact_t& xact) {
extend_xact_base(journal, xact);
return true;
}
typedef std::list<xact_t *> xacts_list;
typedef std::list<auto_xact_t *> auto_xacts_list;
typedef std::list<period_xact_t *> period_xacts_list;

View file

@ -1,4 +1,4 @@
pricesdb --pricesdb-format='P %(date) %(scrub(display_amount))\n'
pricedb --pricedb-format='P %(date) %(scrub(display_amount))\n'
<<<
P 2009/01/01 13:30:00 AAPL $10.00
P 2009/01/01 14:30:00 AAPL $20.00

View file

@ -89,7 +89,6 @@ libledger_report_la_LDFLAGS = -release $(VERSION)
pkginclude_HEADERS = \
src/utils.h \
src/flags.h \
src/hooks.h \
src/error.h \
src/times.h \
src/mask.h \
@ -440,7 +439,8 @@ fullcheck: cppunittests
######################################################################
EXTRA_DIST += doc/README doc/LICENSE doc/NEWS doc/ledger.pdf
EXTRA_DIST += doc/README doc/NEWS doc/ledger.pdf
EXTRA_DIST += doc/LICENSE doc/LICENSE-sha1 doc/LICENSE-utfcpp
if USE_DOXYGEN
EXTRA_DIST += doc/Doxyfile doc/refman.pdf
endif