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.CPPFLAGS.append('-D_GLIBCXX_FULLY_DYNAMIC_STRING=1')
|
||||||
|
|
||||||
self.configure_args.append('--disable-shared')
|
self.configure_args.append('--disable-shared')
|
||||||
|
self.configure_args.append('--enable-doxygen')
|
||||||
|
|
||||||
self.options.use_glibcxx_debug = True
|
self.options.use_glibcxx_debug = True
|
||||||
self.locate_my_libraries()
|
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
|
.Dt ledger 1
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
.Nm ledger
|
.Nm ledger
|
||||||
|
|
@ -9,45 +9,52 @@ ledger
|
||||||
.Op Ar options
|
.Op Ar options
|
||||||
.Op Ar arguments
|
.Op Ar arguments
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
Ledger is a command-line accounting tool providing the user access to the
|
Ledger is a command-line accounting tool based on the power and completeness
|
||||||
power of double-entry accounting. It is only a reporting tool, which means it
|
of double-entry accounting. It is only a reporting tool, which means it never
|
||||||
never modifies your data files, nor can it be used to create or remove data.
|
modifies your data files, but it does offers a large selection of reports, and
|
||||||
|
different ways to customize them to your liking.
|
||||||
.Pp
|
.Pp
|
||||||
.Sh COMMANDS
|
.Sh COMMANDS
|
||||||
Ledger accepts several top-level commands, each of which is used to generate a
|
Ledger accepts several top-level commands, each of which generates a different
|
||||||
different report. Most of them accept a
|
kind of basic report. Most of them accept a
|
||||||
.Ar report-query
|
.Ar report-query
|
||||||
argument, in order to determine what to report. To understand what is
|
argument, in order to determine what should be reported. To understand the
|
||||||
accepted by
|
syntax of a
|
||||||
.Ar report-query ,
|
.Ar report-query ,
|
||||||
see the section on
|
see the section on
|
||||||
.Sx QUERIES .
|
.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.
|
report for all accounts containing those strings.
|
||||||
.Pp
|
.Pp
|
||||||
If no command at all is given, Ledger enters a
|
If no command is given, Ledger enters a
|
||||||
.Tn REPL ,
|
.Tn REPL ,
|
||||||
or command loop, allowing several commands to be executed against the same
|
or command loop, allowing several commands to be executed against the same
|
||||||
dataset without reparsing.
|
dataset without reparsing.
|
||||||
.Pp
|
.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
|
.Pp
|
||||||
.Bl -tag -width foo
|
.Bl -tag -width balance
|
||||||
.It Nm balance Oo Ar query Oc
|
.It Nm balance Oo Ar report-query Oc
|
||||||
Produce a balance report showing subtotals for matching leaf accounts, and
|
Produces a balance report showing totals for all matching accounts, and
|
||||||
aggregate totals for all the parents of those accounts. The most common
|
aggregate totals for parents of those accounts. Options most commonly used
|
||||||
options with this command are:
|
with this command are:
|
||||||
.Pp
|
.Pp
|
||||||
.Bl -tag -compact -width "--collapse (-n)"
|
.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
|
.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
|
.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
|
.It Fl \-flat
|
||||||
Rather than displaying a hierarchical tree, flatten it to show only subtotals
|
Rather than display a hierarchical tree, flatten the report to show subtotals
|
||||||
for accounts directly matching the query.
|
for only accounts matching
|
||||||
|
.Ar report-query .
|
||||||
.It Fl \-no\-total
|
.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
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The synonyms
|
The synonyms
|
||||||
|
|
@ -55,31 +62,198 @@ The synonyms
|
||||||
and
|
and
|
||||||
.Nm b
|
.Nm b
|
||||||
are also accepted.
|
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
|
.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
|
The synonym
|
||||||
.Nm lisp
|
.Nm lisp
|
||||||
is also accepted.
|
is also accepted.
|
||||||
.It Nm equity Oo Ar query Oc
|
.It Nm equity Oo Ar report-query Oc
|
||||||
.It Nm generate
|
Prints a series of transactions that balance current totals for
|
||||||
.It Nm prices Oo Ar query Oc
|
accounts matching the
|
||||||
.It Nm pricesdb Oo Ar query Oc
|
.Ar report-query
|
||||||
.It Nm print Oo Ar query Oc
|
in a special account called
|
||||||
.It Nm register Oo Ar query Oc
|
.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
|
The synonyms
|
||||||
.Nm reg
|
.Nm reg
|
||||||
and
|
and
|
||||||
.Nm r
|
.Nm r
|
||||||
are also accepted.
|
are also accepted.
|
||||||
.It Nm reload
|
.It Nm server
|
||||||
Used solely by the
|
This command requires that Python support be active. If so, it starts up an
|
||||||
.It Nm xact Oo Ar date Oc
|
HTTP server listening for requests on port 9000. This provides an alternate
|
||||||
The synonym
|
interface to creating and viewing reports. Note that this is very much a
|
||||||
.Nm entry
|
work-in-progress, and will not be fully functional until a later version.
|
||||||
is also accepted.
|
.It Nm stats Oo Ar report-query Oc
|
||||||
.Tn REPL ,
|
Provides summary information about all the postings matching
|
||||||
and causes an immediate reloading of all journal files in the session.
|
.Ar report-query .
|
||||||
.It Nm stats Oo Ar query Oc
|
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
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
.Sh OPTIONS
|
.Sh OPTIONS
|
||||||
|
|
@ -349,6 +523,56 @@ for example:
|
||||||
.It Nm xact
|
.It Nm xact
|
||||||
.El
|
.El
|
||||||
.Pp
|
.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
|
.Sh SEE ALSO
|
||||||
.Xr beancount 1,
|
.Xr beancount 1,
|
||||||
.Xr hledger 1
|
.Xr hledger 1
|
||||||
|
|
|
||||||
|
|
@ -327,16 +327,25 @@ bool account_t::valid() const
|
||||||
return true;
|
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 account_t::children_with_flags(xdata_t::flags_t flags) const
|
||||||
{
|
{
|
||||||
std::size_t count = 0;
|
std::size_t count = 0;
|
||||||
bool grandchildren_visited = false;
|
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) ||
|
if (pair.second->has_xflags(flags) ||
|
||||||
pair.second->children_with_flags(flags))
|
pair.second->children_with_flags(flags))
|
||||||
count++;
|
count++;
|
||||||
}
|
|
||||||
|
|
||||||
// Although no immediately children were visited, if any progeny at all were
|
// Although no immediately children were visited, if any progeny at all were
|
||||||
// visited, it counts as one.
|
// visited, it counts as one.
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,7 @@ public:
|
||||||
bool has_xflags(xdata_t::flags_t flags) const {
|
bool has_xflags(xdata_t::flags_t flags) const {
|
||||||
return xdata_ && xdata_->has_flags(flags);
|
return xdata_ && xdata_->has_flags(flags);
|
||||||
}
|
}
|
||||||
|
bool children_with_xdata() const;
|
||||||
std::size_t children_with_flags(xdata_t::flags_t flags) const;
|
std::size_t children_with_flags(xdata_t::flags_t flags) const;
|
||||||
|
|
||||||
#if defined(HAVE_BOOST_SERIALIZATION)
|
#if defined(HAVE_BOOST_SERIALIZATION)
|
||||||
|
|
|
||||||
|
|
@ -201,23 +201,23 @@ bool archive_t::should_load(const std::list<path>& data_files)
|
||||||
return true;
|
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;
|
std::list<path> data_files;
|
||||||
|
|
||||||
DEBUG("archive.journal", "Should the archive be saved?");
|
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");
|
DEBUG("archive.journal", "No, it's one we loaded before");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (journal->sources.empty()) {
|
if (journal.sources.empty()) {
|
||||||
DEBUG("archive.journal", "No, there were no sources!");
|
DEBUG("archive.journal", "No, there were no sources!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (const journal_t::fileinfo_t& i, journal->sources) {
|
foreach (const journal_t::fileinfo_t& i, journal.sources) {
|
||||||
if (i.from_stream) {
|
if (i.from_stream) {
|
||||||
DEBUG("archive.journal", "No, one source was from a stream");
|
DEBUG("archive.journal", "No, one source was from a stream");
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -241,14 +241,14 @@ bool archive_t::should_save(shared_ptr<journal_t> journal)
|
||||||
return true;
|
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");
|
INFO_START(archive, "Saved journal file cache");
|
||||||
|
|
||||||
ofstream stream(file, std::ios::binary);
|
ofstream stream(file, std::ios::binary);
|
||||||
|
|
||||||
write_header_bits(stream);
|
write_header_bits(stream);
|
||||||
sources = journal->sources;
|
sources = journal.sources;
|
||||||
|
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
foreach (const journal_t::fileinfo_t& i, sources)
|
foreach (const journal_t::fileinfo_t& i, sources)
|
||||||
|
|
@ -263,12 +263,12 @@ void archive_t::save(shared_ptr<journal_t> journal)
|
||||||
|
|
||||||
DEBUG("archive.journal",
|
DEBUG("archive.journal",
|
||||||
"Archiving journal with " << sources.size() << " sources");
|
"Archiving journal with " << sources.size() << " sources");
|
||||||
oa << *journal;
|
oa << journal;
|
||||||
|
|
||||||
INFO_FINISH(archive);
|
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");
|
INFO_START(archive, "Read cached journal file");
|
||||||
|
|
||||||
|
|
@ -282,8 +282,8 @@ bool archive_t::load(shared_ptr<journal_t> journal)
|
||||||
archive_t temp;
|
archive_t temp;
|
||||||
iarchive >> temp;
|
iarchive >> temp;
|
||||||
|
|
||||||
iarchive >> *journal.get();
|
iarchive >> journal;
|
||||||
journal->was_loaded = true;
|
journal.was_loaded = true;
|
||||||
|
|
||||||
INFO_FINISH(archive);
|
INFO_FINISH(archive);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,10 +69,10 @@ public:
|
||||||
bool read_header();
|
bool read_header();
|
||||||
|
|
||||||
bool should_load(const std::list<path>& data_files);
|
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);
|
void save(journal_t& journal);
|
||||||
bool load(shared_ptr<journal_t> journal);
|
bool load(journal_t& journal);
|
||||||
|
|
||||||
#if defined(HAVE_BOOST_SERIALIZATION)
|
#if defined(HAVE_BOOST_SERIALIZATION)
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,11 @@ post_handler_ptr chain_post_handlers(report_t& report,
|
||||||
handler.reset(new sort_posts(handler, "date"));
|
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_))
|
if (report.HANDLED(account_))
|
||||||
handler.reset(new transfer_details(handler, transfer_details::SET_ACCOUNT,
|
handler.reset(new transfer_details(handler, transfer_details::SET_ACCOUNT,
|
||||||
report.session.journal->master,
|
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
|
* @author John Wiegley
|
||||||
*
|
*
|
||||||
* @ingroup report
|
* @ingroup report
|
||||||
*/
|
*/
|
||||||
#ifndef _DERIVE_H
|
#ifndef _DRAFT_H
|
||||||
#define _DERIVE_H
|
#define _DRAFT_H
|
||||||
|
|
||||||
#include "exprbase.h"
|
#include "exprbase.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
|
|
@ -110,4 +110,4 @@ value_t template_command(call_scope_t& args);
|
||||||
|
|
||||||
} // namespace ledger
|
} // namespace ledger
|
||||||
|
|
||||||
#endif // _DERIVE_H
|
#endif // _DRAFT_H
|
||||||
|
|
|
||||||
|
|
@ -706,22 +706,27 @@ void transfer_details::operator()(post_t& post)
|
||||||
temp.set_state(post.state());
|
temp.set_state(post.state());
|
||||||
|
|
||||||
bind_scope_t bound_scope(scope, temp);
|
bind_scope_t bound_scope(scope, temp);
|
||||||
|
value_t substitute(expr.calc(bound_scope));
|
||||||
|
|
||||||
switch (which_element) {
|
switch (which_element) {
|
||||||
case SET_PAYEE:
|
case SET_DATE:
|
||||||
xact.payee = expr.calc(bound_scope).to_string();
|
xact.set_date(substitute.to_date());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SET_ACCOUNT: {
|
case SET_ACCOUNT: {
|
||||||
std::list<string> account_names;
|
std::list<string> account_names;
|
||||||
temp.account->remove_post(&temp);
|
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,
|
temp.account = create_temp_account_from_path(account_names, temps,
|
||||||
xact.journal->master);
|
xact.journal->master);
|
||||||
temp.account->add_post(&temp);
|
temp.account->add_post(&temp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SET_PAYEE:
|
||||||
|
xact.payee = substitute.to_string();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,35 @@ public:
|
||||||
virtual void operator()(post_t&) {}
|
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 posts_iterator;
|
||||||
|
|
||||||
class pass_down_posts : public item_handler<post_t>
|
class pass_down_posts : public item_handler<post_t>
|
||||||
|
|
@ -550,8 +579,9 @@ class transfer_details : public item_handler<post_t>
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum element_t {
|
enum element_t {
|
||||||
SET_PAYEE,
|
SET_DATE,
|
||||||
SET_ACCOUNT
|
SET_ACCOUNT,
|
||||||
|
SET_PAYEE
|
||||||
} which_element;
|
} which_element;
|
||||||
|
|
||||||
transfer_details(post_handler_ptr handler,
|
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)
|
if (! at_repl)
|
||||||
session().read_journal_files();
|
session().read_journal_files();
|
||||||
|
|
||||||
normalize_report_options(verb);
|
report().normalize_options(verb);
|
||||||
|
|
||||||
if (! bool(command = look_for_command(bound_scope, verb)))
|
if (! bool(command = look_for_command(bound_scope, verb)))
|
||||||
throw_(std::logic_error, _("Unrecognized command '%1'") << 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();
|
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
|
void global_scope_t::visit_man_page() const
|
||||||
{
|
{
|
||||||
int pid = fork();
|
int pid = fork();
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,6 @@ public:
|
||||||
void normalize_session_options();
|
void normalize_session_options();
|
||||||
expr_t::func_t look_for_precommand(scope_t& scope, const string& verb);
|
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);
|
expr_t::func_t look_for_command(scope_t& scope, const string& verb);
|
||||||
void normalize_report_options(const string& verb);
|
|
||||||
|
|
||||||
char * prompt_string();
|
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 {
|
virtual optional<date_t> effective_date() const {
|
||||||
return _date_eff;
|
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) {
|
void set_state(state_t new_state) {
|
||||||
_state = new_state;
|
_state = new_state;
|
||||||
|
|
|
||||||
|
|
@ -126,16 +126,23 @@ bool journal_t::add_xact(xact_t * xact)
|
||||||
{
|
{
|
||||||
xact->journal = this;
|
xact->journal = this;
|
||||||
|
|
||||||
if (! xact->finalize() || ! xact_finalize_hooks.run_hooks(*xact)) {
|
if (! xact->finalize()) {
|
||||||
xact->journal = NULL;
|
xact->journal = NULL;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extend_xact(xact);
|
||||||
xacts.push_back(xact);
|
xacts.push_back(xact);
|
||||||
|
|
||||||
return true;
|
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 journal_t::remove_xact(xact_t * xact)
|
||||||
{
|
{
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
@ -204,6 +211,26 @@ std::size_t journal_t::read(const path& pathname,
|
||||||
return count;
|
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()
|
void journal_t::clear_xdata()
|
||||||
{
|
{
|
||||||
foreach (xact_t * xact, xacts)
|
foreach (xact_t * xact, xacts)
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,14 @@
|
||||||
#define _JOURNAL_H
|
#define _JOURNAL_H
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "hooks.h"
|
|
||||||
#include "times.h"
|
#include "times.h"
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
class commodity_pool_t;
|
class commodity_pool_t;
|
||||||
|
class xact_base_t;
|
||||||
class xact_t;
|
class xact_t;
|
||||||
class auto_xact_t;
|
class auto_xact_t;
|
||||||
class xact_finalizer_t;
|
|
||||||
class period_xact_t;
|
class period_xact_t;
|
||||||
class account_t;
|
class account_t;
|
||||||
class scope_t;
|
class scope_t;
|
||||||
|
|
@ -114,7 +113,6 @@ public:
|
||||||
bool was_loaded;
|
bool was_loaded;
|
||||||
|
|
||||||
shared_ptr<commodity_pool_t> commodity_pool;
|
shared_ptr<commodity_pool_t> commodity_pool;
|
||||||
hooks_t<xact_finalizer_t, xact_t> xact_finalize_hooks;
|
|
||||||
|
|
||||||
journal_t();
|
journal_t();
|
||||||
journal_t(const path& pathname);
|
journal_t(const path& pathname);
|
||||||
|
|
@ -138,6 +136,7 @@ public:
|
||||||
account_t * find_account_re(const string& regexp);
|
account_t * find_account_re(const string& regexp);
|
||||||
|
|
||||||
bool add_xact(xact_t * xact);
|
bool add_xact(xact_t * xact);
|
||||||
|
void extend_xact(xact_base_t * xact);
|
||||||
bool remove_xact(xact_t * xact);
|
bool remove_xact(xact_t * xact);
|
||||||
|
|
||||||
xacts_list::iterator xacts_begin() {
|
xacts_list::iterator xacts_begin() {
|
||||||
|
|
@ -159,13 +158,6 @@ public:
|
||||||
return period_xacts.end();
|
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,
|
std::size_t read(std::istream& in,
|
||||||
const path& pathname,
|
const path& pathname,
|
||||||
account_t * master = NULL,
|
account_t * master = NULL,
|
||||||
|
|
@ -180,6 +172,7 @@ public:
|
||||||
const path * original_file = NULL,
|
const path * original_file = NULL,
|
||||||
bool strict = false);
|
bool strict = false);
|
||||||
|
|
||||||
|
bool has_xdata();
|
||||||
void clear_xdata();
|
void clear_xdata();
|
||||||
|
|
||||||
bool valid() const;
|
bool valid() const;
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ public:
|
||||||
{
|
{
|
||||||
TRACE_CTOR(post_t, "copy");
|
TRACE_CTOR(post_t, "copy");
|
||||||
}
|
}
|
||||||
~post_t() {
|
virtual ~post_t() {
|
||||||
TRACE_DTOR(post_t);
|
TRACE_DTOR(post_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,14 @@
|
||||||
|
|
||||||
#include "pyinterp.h"
|
#include "pyinterp.h"
|
||||||
#include "pyutils.h"
|
#include "pyutils.h"
|
||||||
#include "hooks.h"
|
|
||||||
#include "journal.h"
|
#include "journal.h"
|
||||||
#include "xact.h"
|
#include "xact.h"
|
||||||
|
#include "post.h"
|
||||||
|
#include "chain.h"
|
||||||
|
#include "filters.h"
|
||||||
|
#include "iterators.h"
|
||||||
|
#include "scope.h"
|
||||||
|
#include "report.h"
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
|
|
@ -130,51 +135,113 @@ namespace {
|
||||||
return journal.find_account(name, auto_create);
|
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)
|
std::size_t py_read(journal_t& journal, const string& pathname)
|
||||||
{
|
{
|
||||||
return journal.read(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
|
} // unnamed namespace
|
||||||
|
|
||||||
void export_journal()
|
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")
|
class_< journal_t::fileinfo_t > ("FileInfo")
|
||||||
.def(init<path>())
|
.def(init<path>())
|
||||||
|
|
||||||
|
|
@ -206,11 +273,6 @@ void export_journal()
|
||||||
.add_property("commodity_pool",
|
.add_property("commodity_pool",
|
||||||
make_getter(&journal_t::commodity_pool,
|
make_getter(&journal_t::commodity_pool,
|
||||||
return_internal_reference<>()))
|
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("add_account", &journal_t::add_account)
|
||||||
.def("remove_account", &journal_t::remove_account)
|
.def("remove_account", &journal_t::remove_account)
|
||||||
|
|
@ -223,12 +285,8 @@ void export_journal()
|
||||||
.def("add_xact", &journal_t::add_xact)
|
.def("add_xact", &journal_t::add_xact)
|
||||||
.def("remove_xact", &journal_t::remove_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("__len__", xacts_len)
|
||||||
.def("__getitem__", xacts_getitem, return_internal_reference<1>())
|
.def("__getitem__", xacts_getitem, return_internal_reference<>())
|
||||||
|
|
||||||
.def("__iter__", range<return_internal_reference<> >
|
.def("__iter__", range<return_internal_reference<> >
|
||||||
(&journal_t::xacts_begin, &journal_t::xacts_end))
|
(&journal_t::xacts_begin, &journal_t::xacts_end))
|
||||||
|
|
@ -243,8 +301,11 @@ void export_journal()
|
||||||
|
|
||||||
.def("read", py_read)
|
.def("read", py_read)
|
||||||
|
|
||||||
|
.def("has_xdata", &journal_t::has_xdata)
|
||||||
.def("clear_xdata", &journal_t::clear_xdata)
|
.def("clear_xdata", &journal_t::clear_xdata)
|
||||||
|
|
||||||
|
.def("collect", py_collect)
|
||||||
|
|
||||||
.def("valid", &journal_t::valid)
|
.def("valid", &journal_t::valid)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,10 @@ void export_post()
|
||||||
make_getter(&post_t::xdata_t::datetime),
|
make_getter(&post_t::xdata_t::datetime),
|
||||||
make_setter(&post_t::xdata_t::datetime))
|
make_setter(&post_t::xdata_t::datetime))
|
||||||
.add_property("account",
|
.add_property("account",
|
||||||
make_getter(&post_t::xdata_t::account),
|
make_getter(&post_t::xdata_t::account,
|
||||||
make_setter(&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",
|
.add_property("sort_values",
|
||||||
make_getter(&post_t::xdata_t::sort_values),
|
make_getter(&post_t::xdata_t::sort_values),
|
||||||
make_setter(&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("lookup", &xact_t::lookup)
|
||||||
|
|
||||||
|
.def("has_xdata", &xact_t::has_xdata)
|
||||||
.def("clear_xdata", &xact_t::clear_xdata)
|
.def("clear_xdata", &xact_t::clear_xdata)
|
||||||
|
|
||||||
.def("valid", &xact_t::valid)
|
.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")
|
class_< auto_xact_t, bases<xact_base_t> > ("AutomatedTransaction")
|
||||||
.def(init<predicate_t>())
|
.def(init<predicate_t>())
|
||||||
|
|
||||||
|
|
@ -142,16 +138,6 @@ void export_xact()
|
||||||
.def("extend_xact", &auto_xact_t::extend_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")
|
class_< period_xact_t, bases<xact_base_t> > ("PeriodicTransaction")
|
||||||
.def(init<string>())
|
.def(init<string>())
|
||||||
|
|
||||||
|
|
@ -162,16 +148,6 @@ void export_xact()
|
||||||
make_getter(&period_xact_t::period_string),
|
make_getter(&period_xact_t::period_string),
|
||||||
make_setter(&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
|
} // namespace ledger
|
||||||
|
|
|
||||||
|
|
@ -60,19 +60,19 @@ void export_xact();
|
||||||
|
|
||||||
void initialize_for_python()
|
void initialize_for_python()
|
||||||
{
|
{
|
||||||
export_account();
|
export_times();
|
||||||
export_amount();
|
export_utils();
|
||||||
export_balance();
|
|
||||||
export_commodity();
|
export_commodity();
|
||||||
|
export_amount();
|
||||||
|
export_value();
|
||||||
|
export_account();
|
||||||
|
export_balance();
|
||||||
export_expr();
|
export_expr();
|
||||||
export_format();
|
export_format();
|
||||||
export_item();
|
export_item();
|
||||||
export_journal();
|
|
||||||
export_post();
|
export_post();
|
||||||
export_times();
|
|
||||||
export_utils();
|
|
||||||
export_value();
|
|
||||||
export_xact();
|
export_xact();
|
||||||
|
export_journal();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct python_run
|
struct python_run
|
||||||
|
|
|
||||||
336
src/report.cc
336
src/report.cc
|
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
#include "report.h"
|
#include "report.h"
|
||||||
#include "session.h"
|
#include "session.h"
|
||||||
|
#include "pool.h"
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
#include "query.h"
|
#include "query.h"
|
||||||
#include "output.h"
|
#include "output.h"
|
||||||
|
|
@ -47,6 +48,201 @@
|
||||||
|
|
||||||
namespace ledger {
|
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)
|
void report_t::posts_report(post_handler_ptr handler)
|
||||||
{
|
{
|
||||||
journal_posts_iterator walker(*session.journal.get());
|
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;
|
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 {
|
namespace {
|
||||||
value_t fn_black(call_scope_t&) {
|
value_t fn_black(call_scope_t&) {
|
||||||
return string_value("black");
|
return string_value("black");
|
||||||
|
|
@ -393,54 +652,6 @@ namespace {
|
||||||
value_t fn_null(call_scope_t&) {
|
value_t fn_null(call_scope_t&) {
|
||||||
return NULL_VALUE;
|
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&)
|
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;
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
OPT(daily);
|
OPT(daily);
|
||||||
|
else OPT(date_);
|
||||||
else OPT(date_format_);
|
else OPT(date_format_);
|
||||||
else OPT(datetime_format_);
|
else OPT(datetime_format_);
|
||||||
else OPT(depth_);
|
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(plot_total_format_);
|
||||||
else OPT(price);
|
else OPT(price);
|
||||||
else OPT(prices_format_);
|
else OPT(prices_format_);
|
||||||
else OPT(pricesdb_format_);
|
else OPT(pricedb_format_);
|
||||||
else OPT(print_format_);
|
else OPT(print_format_);
|
||||||
else OPT(payee_width_);
|
else OPT(payee_width_);
|
||||||
else OPT(prepend_format_);
|
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);
|
return MAKE_FUNCTOR(report_t::fn_today);
|
||||||
else if (is_eq(p, "t"))
|
else if (is_eq(p, "t"))
|
||||||
return MAKE_FUNCTOR(report_t::fn_display_amount);
|
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;
|
break;
|
||||||
|
|
||||||
case 'T':
|
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_)),
|
(new format_posts(*this, report_format(HANDLER(prices_format_)),
|
||||||
maybe_format(HANDLER(prepend_format_))),
|
maybe_format(HANDLER(prepend_format_))),
|
||||||
*this, "#prices"));
|
*this, "#prices"));
|
||||||
else if (is_eq(p, "pricesdb"))
|
else if (is_eq(p, "pricedb"))
|
||||||
return expr_t::op_t::wrap_functor
|
return expr_t::op_t::wrap_functor
|
||||||
(reporter<post_t, post_handler_ptr, &report_t::commodities_report>
|
(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_))),
|
maybe_format(HANDLER(prepend_format_))),
|
||||||
*this, "#pricesdb"));
|
*this, "#pricedb"));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'r':
|
case 'r':
|
||||||
|
|
@ -951,8 +1181,6 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
|
||||||
case 's':
|
case 's':
|
||||||
if (is_eq(p, "stats") || is_eq(p, "stat"))
|
if (is_eq(p, "stats") || is_eq(p, "stat"))
|
||||||
return WRAP_FUNCTOR(report_statistics);
|
return WRAP_FUNCTOR(report_statistics);
|
||||||
else if (is_eq(p, "server"))
|
|
||||||
return session.lookup(symbol_t::COMMAND, "server");
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'x':
|
case 'x':
|
||||||
|
|
|
||||||
49
src/report.h
49
src/report.h
|
|
@ -44,6 +44,7 @@
|
||||||
|
|
||||||
#include "interactive.h"
|
#include "interactive.h"
|
||||||
#include "expr.h"
|
#include "expr.h"
|
||||||
|
#include "query.h"
|
||||||
#include "chain.h"
|
#include "chain.h"
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
#include "option.h"
|
#include "option.h"
|
||||||
|
|
@ -124,6 +125,9 @@ public:
|
||||||
output_stream.close();
|
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 posts_report(post_handler_ptr handler);
|
||||||
void generate_report(post_handler_ptr handler);
|
void generate_report(post_handler_ptr handler);
|
||||||
void xact_report(post_handler_ptr handler, xact_t& xact);
|
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_date(call_scope_t& scope);
|
||||||
value_t fn_lot_price(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_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&) {
|
value_t fn_now(call_scope_t&) {
|
||||||
return terminus;
|
return terminus;
|
||||||
|
|
@ -217,6 +230,7 @@ public:
|
||||||
HANDLER(csv_format_).report(out);
|
HANDLER(csv_format_).report(out);
|
||||||
HANDLER(current).report(out);
|
HANDLER(current).report(out);
|
||||||
HANDLER(daily).report(out);
|
HANDLER(daily).report(out);
|
||||||
|
HANDLER(date_).report(out);
|
||||||
HANDLER(date_format_).report(out);
|
HANDLER(date_format_).report(out);
|
||||||
HANDLER(datetime_format_).report(out);
|
HANDLER(datetime_format_).report(out);
|
||||||
HANDLER(depth_).report(out);
|
HANDLER(depth_).report(out);
|
||||||
|
|
@ -262,7 +276,7 @@ public:
|
||||||
HANDLER(prepend_format_).report(out);
|
HANDLER(prepend_format_).report(out);
|
||||||
HANDLER(price).report(out);
|
HANDLER(price).report(out);
|
||||||
HANDLER(prices_format_).report(out);
|
HANDLER(prices_format_).report(out);
|
||||||
HANDLER(pricesdb_format_).report(out);
|
HANDLER(pricedb_format_).report(out);
|
||||||
HANDLER(print_format_).report(out);
|
HANDLER(print_format_).report(out);
|
||||||
HANDLER(quantity).report(out);
|
HANDLER(quantity).report(out);
|
||||||
HANDLER(quarterly).report(out);
|
HANDLER(quarterly).report(out);
|
||||||
|
|
@ -450,6 +464,7 @@ public:
|
||||||
parent->HANDLER(period_).on(string("--daily"), "daily");
|
parent->HANDLER(period_).on(string("--daily"), "daily");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
OPTION(report_t, date_);
|
||||||
OPTION(report_t, date_format_);
|
OPTION(report_t, date_format_);
|
||||||
OPTION(report_t, datetime_format_);
|
OPTION(report_t, datetime_format_);
|
||||||
|
|
||||||
|
|
@ -712,11 +727,11 @@ public:
|
||||||
|
|
||||||
OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
|
OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
|
||||||
on(none,
|
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");
|
" 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,
|
on(none,
|
||||||
"P %(datetime) %(account) %(scrub(display_amount))\n");
|
"P %(datetime) %(account) %(scrub(display_amount))\n");
|
||||||
});
|
});
|
||||||
|
|
@ -896,6 +911,34 @@ public:
|
||||||
DO_(args) { value = args[1].to_long(); specified = true; });
|
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
|
} // namespace ledger
|
||||||
|
|
||||||
#endif // _REPORT_H
|
#endif // _REPORT_H
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ std::size_t session_t::read_data(const string& master_account)
|
||||||
|
|
||||||
if (! (cache &&
|
if (! (cache &&
|
||||||
cache->should_load(HANDLER(file_).data_files) &&
|
cache->should_load(HANDLER(file_).data_files) &&
|
||||||
cache->load(journal))) {
|
cache->load(*journal.get()))) {
|
||||||
#endif // HAVE_BOOST_SERIALIZATION
|
#endif // HAVE_BOOST_SERIALIZATION
|
||||||
if (price_db_path) {
|
if (price_db_path) {
|
||||||
if (exists(*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());
|
assert(xact_count == journal->xacts.size());
|
||||||
|
|
||||||
#if defined(HAVE_BOOST_SERIALIZATION)
|
#if defined(HAVE_BOOST_SERIALIZATION)
|
||||||
if (cache && cache->should_save(journal))
|
if (cache && cache->should_save(*journal.get()))
|
||||||
cache->save(journal);
|
cache->save(*journal.get());
|
||||||
}
|
}
|
||||||
#endif // HAVE_BOOST_SERIALIZATION
|
#endif // HAVE_BOOST_SERIALIZATION
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,9 @@ class session_t : public symbol_scope_t
|
||||||
friend void set_session_context(session_t * session);
|
friend void set_session_context(session_t * session);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool flush_on_next_data_file;
|
bool flush_on_next_data_file;
|
||||||
date_t::year_type current_year;
|
date_t::year_type current_year;
|
||||||
shared_ptr<journal_t> journal;
|
std::auto_ptr<journal_t> journal;
|
||||||
|
|
||||||
explicit session_t();
|
explicit session_t();
|
||||||
virtual ~session_t() {
|
virtual ~session_t() {
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,6 @@ namespace {
|
||||||
|
|
||||||
optional<date_t::year_type> current_year;
|
optional<date_t::year_type> current_year;
|
||||||
|
|
||||||
scoped_ptr<auto_xact_finalizer_t> auto_xact_finalizer;
|
|
||||||
|
|
||||||
instance_t(std::list<account_t *>& _account_stack,
|
instance_t(std::list<account_t *>& _account_stack,
|
||||||
std::list<string>& _tag_stack,
|
std::list<string>& _tag_stack,
|
||||||
#if defined(TIMELOG_SUPPORT)
|
#if defined(TIMELOG_SUPPORT)
|
||||||
|
|
@ -227,9 +225,6 @@ instance_t::~instance_t()
|
||||||
TRACE_DTOR(instance_t);
|
TRACE_DTOR(instance_t);
|
||||||
|
|
||||||
account_stack.pop_front();
|
account_stack.pop_front();
|
||||||
|
|
||||||
if (auto_xact_finalizer.get())
|
|
||||||
journal.remove_xact_finalizer(auto_xact_finalizer.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void instance_t::parse()
|
void instance_t::parse()
|
||||||
|
|
@ -546,11 +541,6 @@ void instance_t::automated_xact_directive(char * line)
|
||||||
|
|
||||||
try {
|
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
|
std::auto_ptr<auto_xact_t> ae
|
||||||
(new auto_xact_t(query_t(string(skip_ws(line + 1)),
|
(new auto_xact_t(query_t(string(skip_ws(line + 1)),
|
||||||
keep_details_t(true, true, true))));
|
keep_details_t(true, true, true))));
|
||||||
|
|
@ -601,8 +591,7 @@ void instance_t::period_xact_directive(char * line)
|
||||||
pe->journal = &journal;
|
pe->journal = &journal;
|
||||||
|
|
||||||
if (pe->finalize()) {
|
if (pe->finalize()) {
|
||||||
extend_xact_base(&journal, *pe.get());
|
journal.extend_xact(pe.get());
|
||||||
|
|
||||||
journal.period_xacts.push_back(pe.get());
|
journal.period_xacts.push_back(pe.get());
|
||||||
|
|
||||||
pe->pos = position_t();
|
pe->pos = position_t();
|
||||||
|
|
|
||||||
|
|
@ -417,6 +417,8 @@ void report_memory(std::ostream& out, bool report_all)
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
|
#if defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
|
||||||
|
|
||||||
string::string() : std::string() {
|
string::string() : std::string() {
|
||||||
TRACE_CTOR(string, "");
|
TRACE_CTOR(string, "");
|
||||||
}
|
}
|
||||||
|
|
@ -453,6 +455,8 @@ string::~string() throw() {
|
||||||
TRACE_DTOR(string);
|
TRACE_DTOR(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
|
||||||
|
|
||||||
string empty_string("");
|
string empty_string("");
|
||||||
|
|
||||||
strings_list split_arguments(const char * line)
|
strings_list split_arguments(const char * line)
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,8 @@ void report_memory(std::ostream& out, bool report_all = false);
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
|
#if defined(VERIFY_ON) || defined(HAVE_BOOST_PYTHON)
|
||||||
|
|
||||||
class string : public std::string
|
class string : public std::string
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -253,6 +255,12 @@ inline bool operator!=(const char* __lhs, const string& __rhs)
|
||||||
inline bool operator!=(const string& __lhs, const char* __rhs)
|
inline bool operator!=(const string& __lhs, const char* __rhs)
|
||||||
{ return __lhs.compare(__rhs) != 0; }
|
{ 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;
|
extern string empty_string;
|
||||||
|
|
||||||
strings_list split_arguments(const char * line);
|
strings_list split_arguments(const char * line);
|
||||||
|
|
|
||||||
|
|
@ -1146,6 +1146,12 @@ void value_t::in_place_cast(type_t cast_type)
|
||||||
case AMOUNT:
|
case AMOUNT:
|
||||||
set_amount(amount_t(as_string()));
|
set_amount(amount_t(as_string()));
|
||||||
return;
|
return;
|
||||||
|
case DATE:
|
||||||
|
set_date(parse_date(as_string()));
|
||||||
|
return;
|
||||||
|
case DATETIME:
|
||||||
|
set_datetime(parse_datetime(as_string()));
|
||||||
|
return;
|
||||||
case MASK:
|
case MASK:
|
||||||
set_mask(as_string());
|
set_mask(as_string());
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
66
src/xact.cc
66
src/xact.cc
|
|
@ -76,6 +76,15 @@ bool xact_base_t::remove_post(post_t * post)
|
||||||
return true;
|
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()
|
void xact_base_t::clear_xdata()
|
||||||
{
|
{
|
||||||
foreach (post_t * post, posts)
|
foreach (post_t * post, posts)
|
||||||
|
|
@ -372,6 +381,55 @@ bool xact_base_t::finalize()
|
||||||
return true;
|
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_t::xact_t(const xact_t& e)
|
||||||
: xact_base_t(e), code(e.code), payee(e.payee)
|
: 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 {
|
try {
|
||||||
|
|
||||||
|
bool needs_further_verification = false;
|
||||||
|
|
||||||
foreach (post_t * initial_post, initial_posts) {
|
foreach (post_t * initial_post, initial_posts) {
|
||||||
if (! initial_post->has_flags(ITEM_GENERATED) &&
|
if (! initial_post->has_flags(ITEM_GENERATED) &&
|
||||||
predicate(*initial_post)) {
|
predicate(*initial_post)) {
|
||||||
|
|
@ -555,10 +615,16 @@ void auto_xact_t::extend_xact(xact_base_t& xact)
|
||||||
|
|
||||||
xact.add_post(new_post);
|
xact.add_post(new_post);
|
||||||
new_post->account->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) {
|
catch (const std::exception& err) {
|
||||||
add_error_context(item_context(*this, _("While applying automated transaction")));
|
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;
|
value_t magnitude() const;
|
||||||
|
|
||||||
virtual bool finalize();
|
bool finalize();
|
||||||
|
bool verify();
|
||||||
|
|
||||||
|
bool has_xdata();
|
||||||
void clear_xdata();
|
void clear_xdata();
|
||||||
|
|
||||||
virtual bool valid() const {
|
virtual bool valid() const {
|
||||||
|
|
@ -140,11 +142,6 @@ private:
|
||||||
#endif // HAVE_BOOST_SERIALIZATION
|
#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
|
class auto_xact_t : public xact_base_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -183,39 +180,6 @@ private:
|
||||||
#endif // HAVE_BOOST_SERIALIZATION
|
#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
|
class period_xact_t : public xact_base_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -253,38 +217,6 @@ private:
|
||||||
#endif // HAVE_BOOST_SERIALIZATION
|
#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<xact_t *> xacts_list;
|
||||||
typedef std::list<auto_xact_t *> auto_xacts_list;
|
typedef std::list<auto_xact_t *> auto_xacts_list;
|
||||||
typedef std::list<period_xact_t *> period_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 13:30:00 AAPL $10.00
|
||||||
P 2009/01/01 14:30:00 AAPL $20.00
|
P 2009/01/01 14:30:00 AAPL $20.00
|
||||||
|
|
@ -89,7 +89,6 @@ libledger_report_la_LDFLAGS = -release $(VERSION)
|
||||||
pkginclude_HEADERS = \
|
pkginclude_HEADERS = \
|
||||||
src/utils.h \
|
src/utils.h \
|
||||||
src/flags.h \
|
src/flags.h \
|
||||||
src/hooks.h \
|
|
||||||
src/error.h \
|
src/error.h \
|
||||||
src/times.h \
|
src/times.h \
|
||||||
src/mask.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
|
if USE_DOXYGEN
|
||||||
EXTRA_DIST += doc/Doxyfile doc/refman.pdf
|
EXTRA_DIST += doc/Doxyfile doc/refman.pdf
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue