Merge branch 'next'
This commit is contained in:
commit
bfd9ecf2af
33 changed files with 921 additions and 559 deletions
1
acprep
1
acprep
|
|
@ -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()
|
||||
|
|
|
|||
298
doc/ledger.1
298
doc/ledger.1
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
10
src/draft.h
10
src/draft.h
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
174
src/global.cc
174
src/global.cc
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
81
src/hooks.h
81
src/hooks.h
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ public:
|
|||
{
|
||||
TRACE_CTOR(post_t, "copy");
|
||||
}
|
||||
~post_t() {
|
||||
virtual ~post_t() {
|
||||
TRACE_DTOR(post_t);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
336
src/report.cc
336
src/report.cc
|
|
@ -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':
|
||||
|
|
|
|||
49
src/report.h
49
src/report.h
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
66
src/xact.cc
66
src/xact.cc
|
|
@ -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")));
|
||||
|
|
|
|||
74
src/xact.h
74
src/xact.h
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue