Merge branch 'next'
This commit is contained in:
commit
cda19829bd
57 changed files with 1750 additions and 694 deletions
87
acprep
87
acprep
|
|
@ -451,6 +451,9 @@ class PrepareBuild(CommandLineApp):
|
|||
op.add_option('', '--no-git', action='store_true', dest='no_git',
|
||||
default=False,
|
||||
help='Do not call out to Git; useful for offline builds')
|
||||
op.add_option('', '--no-python', action='store_true', dest='no_python',
|
||||
default=False,
|
||||
help='Do not enable Python support by default')
|
||||
op.add_option('', '--output', metavar='DIR', action="callback",
|
||||
callback=self.option_output,
|
||||
help='Build in the specified directory')
|
||||
|
|
@ -474,6 +477,9 @@ class PrepareBuild(CommandLineApp):
|
|||
help='Enable full warning flags')
|
||||
|
||||
def main(self, *args):
|
||||
if self.options.no_python:
|
||||
self.configure_args.remove('--enable-python')
|
||||
|
||||
if args and args[0] in ['default', 'debug', 'opt', 'gcov', 'gprof']:
|
||||
self.current_flavor = args[0]
|
||||
args = args[1:]
|
||||
|
|
@ -740,7 +746,7 @@ class PrepareBuild(CommandLineApp):
|
|||
if system == 'Darwin':
|
||||
if exists('/opt/local/bin/port'):
|
||||
self.log.info('Looks like you are using MacPorts on OS X')
|
||||
packages = self.boost_info.dependencies('darwin') + [
|
||||
packages = [
|
||||
'sudo', 'port', 'install', '-f',
|
||||
'automake',
|
||||
'autoconf',
|
||||
|
|
@ -761,7 +767,7 @@ class PrepareBuild(CommandLineApp):
|
|||
'texinfo',
|
||||
'lcov',
|
||||
'sloccount'
|
||||
]
|
||||
] + self.boost_info.dependencies('darwin')
|
||||
self.log.info('Executing: ' + string.join(packages, ' '))
|
||||
self.execute(*packages)
|
||||
elif exists('/sw/bin/fink'):
|
||||
|
|
@ -778,7 +784,7 @@ class PrepareBuild(CommandLineApp):
|
|||
release.close()
|
||||
if re.search('karmic', info):
|
||||
self.log.info('Looks like you are using APT on Ubuntu Karmic')
|
||||
packages = self.boost_info.dependencies('ubuntu-karmic') + [
|
||||
packages = [
|
||||
'sudo', 'apt-get', 'install',
|
||||
'build-essential',
|
||||
'libtool',
|
||||
|
|
@ -799,10 +805,10 @@ class PrepareBuild(CommandLineApp):
|
|||
'texinfo',
|
||||
'lcov',
|
||||
'sloccount'
|
||||
]
|
||||
] + self.boost_info.dependencies('ubuntu-karmic')
|
||||
else:
|
||||
self.log.info('Looks like you are using APT on Ubuntu Hardy')
|
||||
packages = self.boost_info.dependencies('ubuntu-hardy') + [
|
||||
packages = [
|
||||
'sudo', 'apt-get', 'install',
|
||||
'build-essential',
|
||||
'libtool',
|
||||
|
|
@ -823,7 +829,7 @@ class PrepareBuild(CommandLineApp):
|
|||
'texinfo',
|
||||
'lcov',
|
||||
'sloccount'
|
||||
]
|
||||
] + self.boost_info.dependencies('ubuntu-hardy')
|
||||
self.log.info('Executing: ' + string.join(packages, ' '))
|
||||
self.execute(*packages)
|
||||
|
||||
|
|
@ -908,9 +914,6 @@ class PrepareBuild(CommandLineApp):
|
|||
self.CXXFLAGS.append('-march=nocona')
|
||||
self.CXXFLAGS.append('-msse3')
|
||||
|
||||
self.configure_args.append('--enable-doxygen')
|
||||
self.configure_args.append('--enable-python')
|
||||
|
||||
self.locate_darwin_libraries()
|
||||
|
||||
def setup_for_system(self):
|
||||
|
|
@ -1119,13 +1122,13 @@ class PrepareBuild(CommandLineApp):
|
|||
self.log.debug('We are using GLIBCXX_DEBUG, so setting up flags')
|
||||
self.CPPFLAGS.append('-D_GLIBCXX_DEBUG=1')
|
||||
|
||||
if self.boost_info.configure(home_path = '/usr/local/stow/boost_1_42_0',
|
||||
suffix = '-xgcc44-sd-1_42',
|
||||
include_path = 'include/boost-1_42'):
|
||||
if self.boost_info.configure(home_path = '/usr/local/stow/boost_1_43_0',
|
||||
suffix = '-xgcc44-sd-1_43',
|
||||
include_path = 'include/boost-1_43'):
|
||||
pass
|
||||
elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_42_0',
|
||||
suffix = '-xgcc44-d-1_42',
|
||||
include_path = 'include/boost-1_42'):
|
||||
elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_43_0',
|
||||
suffix = '-xgcc44-d-1_43',
|
||||
include_path = 'include/boost-1_43'):
|
||||
pass
|
||||
elif self.boost_info.configure(suffix = '-d'):
|
||||
pass
|
||||
|
|
@ -1133,17 +1136,18 @@ class PrepareBuild(CommandLineApp):
|
|||
else:
|
||||
if self.boost_info.configure():
|
||||
pass
|
||||
elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_42_0',
|
||||
suffix = '-xgcc44-s-1_42',
|
||||
include_path = 'include/boost-1_42'):
|
||||
elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_43_0',
|
||||
suffix = '-xgcc44-s-1_43',
|
||||
include_path = 'include/boost-1_43'):
|
||||
pass
|
||||
elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_42_0',
|
||||
suffix = '-xgcc44-1_42',
|
||||
include_path = 'include/boost-1_42'):
|
||||
elif self.boost_info.configure(home_path = '/usr/local/stow/boost_1_43_0',
|
||||
suffix = '-xgcc44-1_43',
|
||||
include_path = 'include/boost-1_43'):
|
||||
pass
|
||||
|
||||
def setup_flavor_default(self):
|
||||
pass
|
||||
if self.darwin_gcc:
|
||||
self.option_no_pch()
|
||||
|
||||
def setup_flavor_debug(self):
|
||||
self.configure_args.append('--enable-debug')
|
||||
|
|
@ -1183,38 +1187,6 @@ class PrepareBuild(CommandLineApp):
|
|||
self.LDFLAGS.append('-g')
|
||||
self.LDFLAGS.append('-pg')
|
||||
|
||||
#########################################################################
|
||||
# Prettify the output from automake, by rewriting the Makefile #
|
||||
#########################################################################
|
||||
|
||||
def phase_patch(self, *args):
|
||||
"""Alter the Makefile so that it's not nearly so verbose.
|
||||
|
||||
This makes errors and warnings much easier to spot."""
|
||||
self.log.info('Executing phase: patch')
|
||||
|
||||
if exists('Makefile'):
|
||||
self.log.debug('Patching generated Makefile')
|
||||
Makefile = open('Makefile')
|
||||
Makefile_new = open('Makefile.new', 'w')
|
||||
for line in Makefile.readlines():
|
||||
line = re.sub('^\t(\$\((LIBTOOL|CXX)\).*?\.(cc|cpp))$',
|
||||
'\t@echo " " CXX \$@;\\1 > /dev/null', line)
|
||||
line = re.sub('^\tmv -f', '\t@mv -f', line)
|
||||
line = re.sub('^\t\$\(am__mv\)', '\t@$(am__mv)', line)
|
||||
line = re.sub('^\t(\$\((.*?)LINK\).*)',
|
||||
'\t@echo " LD " \$@;\\1 > /dev/null', line)
|
||||
Makefile_new.write(line)
|
||||
Makefile_new.close()
|
||||
Makefile.close()
|
||||
|
||||
os.remove('Makefile')
|
||||
os.rename('Makefile.new', 'Makefile')
|
||||
|
||||
stamp = open('.timestamp', 'w')
|
||||
stamp.write('timestamp')
|
||||
stamp.close()
|
||||
|
||||
#########################################################################
|
||||
# Configure build tree using autoconf #
|
||||
#########################################################################
|
||||
|
|
@ -1290,18 +1262,11 @@ class PrepareBuild(CommandLineApp):
|
|||
self.log.error("Execution failed: " + string.join(conf_args, ' '))
|
||||
sys.exit(1)
|
||||
|
||||
if not self.options.no_patch:
|
||||
self.phase_patch()
|
||||
|
||||
# Wipe the pre-compiled header, if there is one
|
||||
pch = join(self.build_directory(), 'system.hh.gch')
|
||||
if exists(pch):
|
||||
os.remove(pch)
|
||||
else:
|
||||
if not self.options.no_patch and \
|
||||
self.isnewer('Makefile', '.timestamp'):
|
||||
self.phase_patch()
|
||||
|
||||
self.log.debug('configure does not need to be run')
|
||||
|
||||
finally:
|
||||
|
|
|
|||
37
doc/NEWS
37
doc/NEWS
|
|
@ -2,37 +2,16 @@
|
|||
|
||||
* 3.0
|
||||
|
||||
** INCOMPATIBLE CHANGES
|
||||
Due to the magnitude of changes in 3.0, only changes that affect compatibility
|
||||
with 2.x files and usage is mentioned here. For a description of new
|
||||
features, please see the manual.
|
||||
|
||||
*** Commands
|
||||
- The option -g (--performance) was removed.
|
||||
|
||||
**** Removed 'output'
|
||||
**** Removed 'xml'
|
||||
|
||||
*** Options
|
||||
|
||||
**** --descend
|
||||
**** --descend_if EXPR
|
||||
**** --reconcile
|
||||
**** --reconcile-date DATE
|
||||
|
||||
**** -s, -n
|
||||
|
||||
The balance report now defaults to showing all relevant accounts. This is the
|
||||
opposite of how it worked before. That is, "bal" in 3.0 does what "-s bal"
|
||||
did in 2.x. To get the 2.6 behavior, use "bal -n" in 3.0. The -s option no
|
||||
longer has any effect on balance reports.
|
||||
|
||||
** NEW FEATURES
|
||||
|
||||
*** Commands
|
||||
|
||||
*** Options
|
||||
|
||||
**** --anon
|
||||
|
||||
For anonymizing the private details of reports. This is almost solely for the
|
||||
purpose of easing the creation of accurate bug reports.
|
||||
- The balance report now defaults to showing all relevant accounts. This is
|
||||
the opposite of 2.x. That is, "bal" in 3.0 does what "-s bal" did in 2.x.
|
||||
To see 2.6 behavior, use "bal -n" in 3.0. The -s option no longer has any
|
||||
effect on balance reports.
|
||||
|
||||
* 2.6.1
|
||||
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ Here is the Pacific Bell example from above, given as a Ledger
|
|||
posting:
|
||||
|
||||
@smallexample
|
||||
9/29 (100) Pacific Bell
|
||||
9/29 Pacific Bell
|
||||
Expenses:Utilities:Phone $23.00
|
||||
Assets:Checking $-23.00
|
||||
@end smallexample
|
||||
|
|
@ -240,7 +240,7 @@ smart about many things, you don't need to specify the balanced
|
|||
amount, if it is the same as the first line:
|
||||
|
||||
@smallexample
|
||||
9/29 (100) Pacific Bell
|
||||
9/29 Pacific Bell
|
||||
Expenses:Utilities:Phone $23.00
|
||||
Assets:Checking
|
||||
@end smallexample
|
||||
|
|
@ -367,7 +367,7 @@ file whose formatting has gotten out of hand.
|
|||
|
||||
The @command{output} command is very similar to the @command{print}
|
||||
command, except that it attempts to replicate the specified ledger
|
||||
file epostly. The format of the command is:
|
||||
file exactly. The format of the command is:
|
||||
|
||||
@example
|
||||
ledger -f FILENAME output FILENAME
|
||||
|
|
@ -430,7 +430,7 @@ Say you currently have this posting in your ledger file:
|
|||
@end smallexample
|
||||
|
||||
Now it's @samp{2004/4/9}, and you've just eating at @samp{Viva
|
||||
Italiano} again. The epost amounts are different, but the overall
|
||||
Italiano} again. The exact amounts are different, but the overall
|
||||
form is the same. With the @command{xact} command you can type:
|
||||
|
||||
@example
|
||||
|
|
@ -709,13 +709,13 @@ deviation from the average. It is only meaningful in the
|
|||
@command{xml} report.
|
||||
|
||||
@option{--amount-data} (@option{-j}) changes the @command{register}
|
||||
report so that it output nothing but the date and the value column,
|
||||
report so that it outputs nothing but the date and the value column,
|
||||
and the latter without commodities. This is only meaningful if the
|
||||
report uses a single commodity. This data can then be fed to other
|
||||
programs, which could plot the date, analyze it, etc.
|
||||
|
||||
@option{--total-data} (@option{-J}) changes the @command{register}
|
||||
report so that it output nothing but the date and totals column,
|
||||
report so that it outputs nothing but the date and totals column,
|
||||
without commodities.
|
||||
|
||||
@option{--display EXPR} (@option{-d EXPR}) limits which postings
|
||||
|
|
@ -986,7 +986,7 @@ stripped from the total.
|
|||
|
||||
@item [DATEFMT]
|
||||
Inserts the result of formatting a posting's date with a date
|
||||
format string, epostly like those supported by @code{strftime}. For
|
||||
format string, exactly like those supported by @code{strftime}. For
|
||||
example: @samp{%[%Y/%m/%d %H:%M:%S]}.
|
||||
|
||||
@item S
|
||||
|
|
@ -1013,7 +1013,7 @@ output.
|
|||
@item d
|
||||
This is the same as the @samp{%D} option, unless the transaction has an
|
||||
effective date, in which case it prints
|
||||
@samp{[ACTUAL_DATE=EFFECtIVE_DATE]}.
|
||||
@samp{[ACTUAL_DATE=EFFECTIVE_DATE]}.
|
||||
|
||||
@item X
|
||||
If a posting has been cleared, this inserts @samp{*} followed by a
|
||||
|
|
@ -1311,7 +1311,7 @@ DATE[=EDATE] [*|!] [(CODE)] DESC
|
|||
|
||||
If @samp{*} appears after the date (with optional effective date), it
|
||||
indicates the transaction is ``cleared'', which can mean whatever the user
|
||||
wants it t omean. If @samp{!} appears after the date, it indicates d
|
||||
wants it to mean. If @samp{!} appears after the date, it indicates d
|
||||
the transaction is ``pending''; i.e., tentatively cleared from the user's
|
||||
point of view, but not yet actually cleared. If a @samp{CODE} appears
|
||||
in parentheses, it may be used to indicate a check number, or the type
|
||||
|
|
@ -1521,7 +1521,7 @@ Now the report is:
|
|||
@end smallexample
|
||||
|
||||
Since the liability was a virtual posting, it has dropped from the
|
||||
report and we see that final total is balanced.
|
||||
report and we see that the final total is balanced.
|
||||
|
||||
But we only know that it balances because @file{sample.dat} is quite
|
||||
simple, and we happen to know that the 50 shares of Apple stock cost
|
||||
|
|
@ -1571,7 +1571,7 @@ This reports:
|
|||
@end smallexample
|
||||
|
||||
This shows that the @samp{Assets} total is made up from two child
|
||||
account, but that the total for each of the other accounts comes from
|
||||
accounts, but that the total for each of the other accounts comes from
|
||||
one child account.
|
||||
|
||||
Sometimes you may have a lot of children, nested very deeply, but only
|
||||
|
|
@ -1762,7 +1762,7 @@ Although the easiest way to use the register is to report all the
|
|||
postings affecting a set of accounts, it can often result in more
|
||||
information than you want. To cope with an ever-growing amount of
|
||||
data, there are several options which can help you pinpoint your
|
||||
report to epostly the postings that interest you most. This is
|
||||
report to include just the postings that interest you most. This is
|
||||
called the ``calculation'' phase of Ledger. All of its related
|
||||
options are documented under @option{--help-calc}.
|
||||
|
||||
|
|
@ -1969,7 +1969,7 @@ Reports:
|
|||
2004/05/14 Pay day Income:Salary $-500.00 0
|
||||
@end smallexample
|
||||
|
||||
The final total is zero, indicating that the budget matched epostly
|
||||
The final total is zero, indicating that the budget matched exactly
|
||||
for the reported period. Budgeting is most often helpful with period
|
||||
reporting; for example, to show monthly budget results use
|
||||
@option{--budget -p monthly}.
|
||||
|
|
@ -2010,7 +2010,7 @@ Reports:
|
|||
@end smallexample
|
||||
|
||||
The date this report was made was November 5, 2004; the reason the
|
||||
first forecast transaction is in december is that forecast transactions are only
|
||||
first forecast transaction is in December is that forecast transactions are only
|
||||
added for the future, and they only stop after the value expression
|
||||
has matched at least once, which is why the January transaction appears. A
|
||||
forecast report can be very useful for determining when money will run
|
||||
|
|
@ -2206,10 +2206,11 @@ Show yearly sub-totals. Same as @samp{-p yearly}.
|
|||
|
||||
@c --dow show a days-of-the-week report
|
||||
|
||||
There is one kind of period report cannot be done with @option{-p}.
|
||||
This is the @option{--dow}, or ``days of the week'' report, which
|
||||
shows summarized totals for each day of the week. The following
|
||||
examples shows a ``day of the week'' report of income and expenses:
|
||||
There is one kind of period report that cannot be done with
|
||||
@option{-p}. This is the @option{--dow}, or ``days of the week''
|
||||
report, which shows summarized totals for each day of the week. The
|
||||
following examples shows a ``day of the week'' report of income and
|
||||
expenses:
|
||||
|
||||
@example
|
||||
ledger --dow reg ^inc ^exp
|
||||
|
|
@ -2581,7 +2582,7 @@ Based on that explanation, here's another way to look at your balance
|
|||
report: every negative figure means that that account or person or
|
||||
place has less money now than when you started your ledger; and every
|
||||
positive figure means that that account or person or place has more
|
||||
money now that when you started your ledger. Make sense?
|
||||
money now than when you started your ledger. Make sense?
|
||||
|
||||
@node Assets and Liabilities, Typical queries, Stating where money goes, Ledger in Practice
|
||||
@section Assets and Liabilities
|
||||
|
|
@ -3507,7 +3508,7 @@ To view balances without any virtual balances factored in, using the
|
|||
|
||||
As a Bahá'í, I need to compute Huqúqu'lláh whenever I acquire assets.
|
||||
It is similar to tithing for Jews and Christians, or to Zakát for
|
||||
Muslims. The epost details of computing Huqúqu'lláh are somewhat
|
||||
Muslims. The exact details of computing Huqúqu'lláh are somewhat
|
||||
complex, but if you have further interest, please consult the Web.
|
||||
|
||||
Ledger makes this otherwise difficult law very easy. Just set up an
|
||||
|
|
@ -3762,7 +3763,7 @@ And now the time spent has been turned into hard cash in the checking
|
|||
account.
|
||||
|
||||
The advantage to using timeclock and invoicing to bill time is that
|
||||
you will always know, by looking at the balance report, epostly how
|
||||
you will always know, by looking at the balance report, exactly how
|
||||
much unbilled and unpaid time you've spent working for any particular
|
||||
client.
|
||||
|
||||
|
|
@ -3963,13 +3964,13 @@ price-setting directive):
|
|||
P 2009/01/15 00:00:00 S 2 P
|
||||
|
||||
2009/02/01 Sample 2a
|
||||
Assets:Brokerage:Stocks 100 S @ 1 P
|
||||
Assets:Brokerage:Stocks 100 S @@ 1 P
|
||||
Assets:Brokerage:Cash
|
||||
|
||||
P 2009/02/01 00:00:00 S 4 P
|
||||
|
||||
2009/03/01 Sample 3a
|
||||
Assets:Brokerage:Stocks 100 S @@ 100 P
|
||||
Assets:Brokerage:Stocks 100 S @@@@ 100 P
|
||||
Assets:Brokerage:Cash
|
||||
|
||||
P 2009/03/01 00:00:00 S 8 P
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ CPPFLAGS = -D_GLIBCXX_DEBUG=1 -D_GLIBCXX_FULLY_DYNAMIC_STRING=1
|
|||
CFLAGS = $(CPPFLAGS) -g
|
||||
LDFLAGS = -g
|
||||
|
||||
BOOST_VERSION = 1_42_0
|
||||
BOOST_VERSION = 1_43_0
|
||||
BOOST_SOURCE = boost_$(BOOST_VERSION)
|
||||
BOOST_TOOLSET = darwin
|
||||
BOOST_DEFINES = define=_GLIBCXX_DEBUG=1 define=_GLIBCXX_FULLY_DYNAMIC_STRING=1
|
||||
|
|
|
|||
|
|
@ -42,9 +42,12 @@ account_t::~account_t()
|
|||
{
|
||||
TRACE_DTOR(account_t);
|
||||
|
||||
foreach (accounts_map::value_type& pair, accounts)
|
||||
if (! pair.second->has_flags(ACCOUNT_TEMP))
|
||||
foreach (accounts_map::value_type& pair, accounts) {
|
||||
if (! pair.second->has_flags(ACCOUNT_TEMP) ||
|
||||
has_flags(ACCOUNT_TEMP)) {
|
||||
checked_delete(pair.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
account_t * account_t::find_account(const string& name,
|
||||
|
|
@ -79,6 +82,14 @@ account_t * account_t::find_account(const string& name,
|
|||
return NULL;
|
||||
|
||||
account = new account_t(this, first);
|
||||
|
||||
// An account created within a temporary or generated account is itself
|
||||
// temporary or generated, so that the whole tree has the same status.
|
||||
if (has_flags(ACCOUNT_TEMP))
|
||||
account->add_flags(ACCOUNT_TEMP);
|
||||
if (has_flags(ACCOUNT_GENERATED))
|
||||
account->add_flags(ACCOUNT_GENERATED);
|
||||
|
||||
std::pair<accounts_map::iterator, bool> result
|
||||
= accounts.insert(accounts_map::value_type(first, account));
|
||||
assert(result.second);
|
||||
|
|
|
|||
|
|
@ -165,8 +165,8 @@ namespace {
|
|||
|
||||
for (const char * p = buf; *p; p++) {
|
||||
if (*p == '.') {
|
||||
if (commodity_t::european_by_default ||
|
||||
(comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN)))
|
||||
if (commodity_t::decimal_comma_by_default ||
|
||||
(comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA)))
|
||||
out << ',';
|
||||
else
|
||||
out << *p;
|
||||
|
|
@ -179,8 +179,8 @@ namespace {
|
|||
out << *p;
|
||||
|
||||
if (integer_digits > 3 && --integer_digits % 3 == 0) {
|
||||
if (commodity_t::european_by_default ||
|
||||
(comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN)))
|
||||
if (commodity_t::decimal_comma_by_default ||
|
||||
(comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA)))
|
||||
out << '.';
|
||||
else
|
||||
out << ',';
|
||||
|
|
@ -594,6 +594,44 @@ void amount_t::in_place_round()
|
|||
set_keep_precision(false);
|
||||
}
|
||||
|
||||
void amount_t::in_place_truncate()
|
||||
{
|
||||
#if 1
|
||||
if (! quantity)
|
||||
throw_(amount_error, _("Cannot truncate an uninitialized amount"));
|
||||
|
||||
_dup();
|
||||
|
||||
DEBUG("amount.truncate",
|
||||
"Truncating " << *this << " to precision " << display_precision());
|
||||
|
||||
std::ostringstream out;
|
||||
stream_out_mpq(out, MP(quantity), display_precision());
|
||||
|
||||
scoped_array<char> buf(new char [out.str().length() + 1]);
|
||||
std::strcpy(buf.get(), out.str().c_str());
|
||||
|
||||
char * q = buf.get();
|
||||
for (char * p = q; *p != '\0'; p++, q++) {
|
||||
if (*p == '.') p++;
|
||||
if (p != q) *q = *p;
|
||||
}
|
||||
*q = '\0';
|
||||
|
||||
mpq_set_str(MP(quantity), buf.get(), 10);
|
||||
|
||||
mpz_ui_pow_ui(temp, 10, display_precision());
|
||||
mpq_set_z(tempq, temp);
|
||||
mpq_div(MP(quantity), MP(quantity), tempq);
|
||||
|
||||
DEBUG("amount.truncate", "Truncated = " << *this);
|
||||
#else
|
||||
// This naive implementation is straightforward, but extremely inefficient
|
||||
// as it requires parsing the commodity too, which might be fully annotated.
|
||||
*this = amount_t(to_string());
|
||||
#endif
|
||||
}
|
||||
|
||||
void amount_t::in_place_floor()
|
||||
{
|
||||
if (! quantity)
|
||||
|
|
@ -993,8 +1031,9 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
|
|||
|
||||
bool no_more_commas = false;
|
||||
bool no_more_periods = false;
|
||||
bool european_style = (commodity_t::european_by_default ||
|
||||
commodity().has_flags(COMMODITY_STYLE_EUROPEAN));
|
||||
bool decimal_comma_style
|
||||
= (commodity_t::decimal_comma_by_default ||
|
||||
commodity().has_flags(COMMODITY_STYLE_DECIMAL_COMMA));
|
||||
|
||||
new_quantity->prec = 0;
|
||||
|
||||
|
|
@ -1005,16 +1044,16 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
|
|||
if (no_more_periods)
|
||||
throw_(amount_error, _("Too many periods in amount"));
|
||||
|
||||
if (european_style) {
|
||||
if (decimal_comma_style) {
|
||||
if (decimal_offset % 3 != 0)
|
||||
throw_(amount_error, _("Incorrect use of european-style period"));
|
||||
throw_(amount_error, _("Incorrect use of thousand-mark period"));
|
||||
comm_flags |= COMMODITY_STYLE_THOUSANDS;
|
||||
no_more_commas = true;
|
||||
} else {
|
||||
if (last_comma != string::npos) {
|
||||
european_style = true;
|
||||
decimal_comma_style = true;
|
||||
if (decimal_offset % 3 != 0)
|
||||
throw_(amount_error, _("Incorrect use of european-style period"));
|
||||
throw_(amount_error, _("Incorrect use of thousand-mark period"));
|
||||
} else {
|
||||
no_more_periods = true;
|
||||
new_quantity->prec = decimal_offset;
|
||||
|
|
@ -1029,9 +1068,9 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
|
|||
if (no_more_commas)
|
||||
throw_(amount_error, _("Too many commas in amount"));
|
||||
|
||||
if (european_style) {
|
||||
if (decimal_comma_style) {
|
||||
if (last_period != string::npos) {
|
||||
throw_(amount_error, _("Incorrect use of european-style comma"));
|
||||
throw_(amount_error, _("Incorrect use of decimal comma"));
|
||||
} else {
|
||||
no_more_commas = true;
|
||||
new_quantity->prec = decimal_offset;
|
||||
|
|
@ -1041,12 +1080,12 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
|
|||
if (decimal_offset % 3 != 0) {
|
||||
if (last_comma != string::npos ||
|
||||
last_period != string::npos) {
|
||||
throw_(amount_error, _("Incorrect use of American-style comma"));
|
||||
throw_(amount_error, _("Incorrect use of thousand-mark comma"));
|
||||
} else {
|
||||
european_style = true;
|
||||
no_more_commas = true;
|
||||
new_quantity->prec = decimal_offset;
|
||||
decimal_offset = 0;
|
||||
decimal_comma_style = true;
|
||||
no_more_commas = true;
|
||||
new_quantity->prec = decimal_offset;
|
||||
decimal_offset = 0;
|
||||
}
|
||||
} else {
|
||||
comm_flags |= COMMODITY_STYLE_THOUSANDS;
|
||||
|
|
@ -1062,8 +1101,8 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
|
|||
}
|
||||
}
|
||||
|
||||
if (european_style)
|
||||
comm_flags |= COMMODITY_STYLE_EUROPEAN;
|
||||
if (decimal_comma_style)
|
||||
comm_flags |= COMMODITY_STYLE_DECIMAL_COMMA;
|
||||
|
||||
if (flags.has_flags(PARSE_NO_MIGRATE)) {
|
||||
// Can't call set_keep_precision here, because it assumes that `quantity'
|
||||
|
|
|
|||
|
|
@ -346,9 +346,7 @@ public:
|
|||
temp.in_place_truncate();
|
||||
return temp;
|
||||
}
|
||||
void in_place_truncate() {
|
||||
*this = amount_t(to_string());
|
||||
}
|
||||
void in_place_truncate();
|
||||
|
||||
/** Yields an amount which has lost all of its extra precision, beyond what
|
||||
the display precision of the commodity would have printed. */
|
||||
|
|
|
|||
|
|
@ -321,10 +321,8 @@ public:
|
|||
return temp;
|
||||
}
|
||||
void in_place_round() {
|
||||
balance_t temp;
|
||||
foreach (const amounts_map::value_type& pair, amounts)
|
||||
temp += pair.second.rounded();
|
||||
*this = temp;
|
||||
foreach (amounts_map::value_type& pair, amounts)
|
||||
pair.second.in_place_round();
|
||||
}
|
||||
|
||||
balance_t truncated() const {
|
||||
|
|
@ -333,10 +331,8 @@ public:
|
|||
return temp;
|
||||
}
|
||||
void in_place_truncate() {
|
||||
balance_t temp;
|
||||
foreach (const amounts_map::value_type& pair, amounts)
|
||||
temp += pair.second.truncated();
|
||||
*this = temp;
|
||||
foreach (amounts_map::value_type& pair, amounts)
|
||||
pair.second.in_place_truncate();
|
||||
}
|
||||
|
||||
balance_t floored() const {
|
||||
|
|
@ -345,10 +341,8 @@ public:
|
|||
return temp;
|
||||
}
|
||||
void in_place_floor() {
|
||||
balance_t temp;
|
||||
foreach (const amounts_map::value_type& pair, amounts)
|
||||
temp += pair.second.floored();
|
||||
*this = temp;
|
||||
foreach (amounts_map::value_type& pair, amounts)
|
||||
pair.second.in_place_floor();
|
||||
}
|
||||
|
||||
balance_t unrounded() const {
|
||||
|
|
@ -357,10 +351,8 @@ public:
|
|||
return temp;
|
||||
}
|
||||
void in_place_unround() {
|
||||
balance_t temp;
|
||||
foreach (const amounts_map::value_type& pair, amounts)
|
||||
temp += pair.second.unrounded();
|
||||
*this = temp;
|
||||
foreach (amounts_map::value_type& pair, amounts)
|
||||
pair.second.in_place_unround();
|
||||
}
|
||||
|
||||
balance_t reduced() const {
|
||||
|
|
|
|||
129
src/chain.cc
129
src/chain.cc
|
|
@ -39,6 +39,73 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
post_handler_ptr chain_pre_post_handlers(report_t& report,
|
||||
post_handler_ptr base_handler)
|
||||
{
|
||||
post_handler_ptr handler(base_handler);
|
||||
|
||||
// anonymize_posts removes all meaningful information from xact payee's and
|
||||
// account names, for the sake of creating useful bug reports.
|
||||
if (report.HANDLED(anon))
|
||||
handler.reset(new anonymize_posts(handler));
|
||||
|
||||
// This filter_posts will only pass through posts matching the `predicate'.
|
||||
if (report.HANDLED(limit_)) {
|
||||
DEBUG("report.predicate",
|
||||
"Report predicate expression = " << report.HANDLER(limit_).str());
|
||||
handler.reset(new filter_posts
|
||||
(handler, predicate_t(report.HANDLER(limit_).str(),
|
||||
report.what_to_keep()),
|
||||
report));
|
||||
}
|
||||
|
||||
// budget_posts takes a set of posts from a data file and uses them to
|
||||
// generate "budget posts" which balance against the reported posts.
|
||||
//
|
||||
// forecast_posts is a lot like budget_posts, except that it adds xacts
|
||||
// only for the future, and does not balance them against anything but the
|
||||
// future balance.
|
||||
|
||||
if (report.budget_flags != BUDGET_NO_BUDGET) {
|
||||
budget_posts * budget_handler = new budget_posts(handler,
|
||||
report.budget_flags);
|
||||
budget_handler->add_period_xacts(report.session.journal->period_xacts);
|
||||
handler.reset(budget_handler);
|
||||
|
||||
// Apply this before the budget handler, so that only matching posts are
|
||||
// calculated toward the budget. The use of filter_posts above will
|
||||
// further clean the results so that no automated posts that don't match
|
||||
// the filter get reported.
|
||||
if (report.HANDLED(limit_))
|
||||
handler.reset(new filter_posts
|
||||
(handler, predicate_t(report.HANDLER(limit_).str(),
|
||||
report.what_to_keep()),
|
||||
report));
|
||||
}
|
||||
else if (report.HANDLED(forecast_while_)) {
|
||||
forecast_posts * forecast_handler
|
||||
= new forecast_posts(handler,
|
||||
predicate_t(report.HANDLER(forecast_while_).str(),
|
||||
report.what_to_keep()),
|
||||
report,
|
||||
report.HANDLED(forecast_years_) ?
|
||||
static_cast<std::size_t>
|
||||
(report.HANDLER(forecast_years_).value.to_long()) :
|
||||
5UL);
|
||||
forecast_handler->add_period_xacts(report.session.journal->period_xacts);
|
||||
handler.reset(forecast_handler);
|
||||
|
||||
// See above, under budget_posts.
|
||||
if (report.HANDLED(limit_))
|
||||
handler.reset(new filter_posts
|
||||
(handler, predicate_t(report.HANDLER(limit_).str(),
|
||||
report.what_to_keep()),
|
||||
report));
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
post_handler_ptr chain_post_handlers(report_t& report,
|
||||
post_handler_ptr base_handler,
|
||||
bool for_accounts_report)
|
||||
|
|
@ -86,7 +153,8 @@ post_handler_ptr chain_post_handlers(report_t& report,
|
|||
report.HANDLED(unrealized)))
|
||||
handler.reset(new changed_value_posts(handler, report,
|
||||
for_accounts_report,
|
||||
report.HANDLED(unrealized)));
|
||||
report.HANDLED(unrealized),
|
||||
! report.HANDLED(no_rounding)));
|
||||
|
||||
// calc_posts computes the running total. When this appears will determine,
|
||||
// for example, whether filtered posts are included or excluded from the
|
||||
|
|
@ -188,65 +256,6 @@ post_handler_ptr chain_post_handlers(report_t& report,
|
|||
if (report.HANDLED(related))
|
||||
handler.reset(new related_posts(handler, report.HANDLED(related_all)));
|
||||
|
||||
// anonymize_posts removes all meaningful information from xact payee's and
|
||||
// account names, for the sake of creating useful bug reports.
|
||||
if (report.HANDLED(anon))
|
||||
handler.reset(new anonymize_posts(handler));
|
||||
|
||||
// This filter_posts will only pass through posts matching the `predicate'.
|
||||
if (report.HANDLED(limit_)) {
|
||||
DEBUG("report.predicate",
|
||||
"Report predicate expression = " << report.HANDLER(limit_).str());
|
||||
handler.reset(new filter_posts
|
||||
(handler, predicate_t(report.HANDLER(limit_).str(),
|
||||
report.what_to_keep()),
|
||||
report));
|
||||
}
|
||||
|
||||
// budget_posts takes a set of posts from a data file and uses them to
|
||||
// generate "budget posts" which balance against the reported posts.
|
||||
//
|
||||
// forecast_posts is a lot like budget_posts, except that it adds xacts
|
||||
// only for the future, and does not balance them against anything but the
|
||||
// future balance.
|
||||
|
||||
if (report.budget_flags != BUDGET_NO_BUDGET) {
|
||||
budget_posts * budget_handler = new budget_posts(handler,
|
||||
report.budget_flags);
|
||||
budget_handler->add_period_xacts(report.session.journal->period_xacts);
|
||||
handler.reset(budget_handler);
|
||||
|
||||
// Apply this before the budget handler, so that only matching posts are
|
||||
// calculated toward the budget. The use of filter_posts above will
|
||||
// further clean the results so that no automated posts that don't match
|
||||
// the filter get reported.
|
||||
if (report.HANDLED(limit_))
|
||||
handler.reset(new filter_posts
|
||||
(handler, predicate_t(report.HANDLER(limit_).str(),
|
||||
report.what_to_keep()),
|
||||
report));
|
||||
}
|
||||
else if (report.HANDLED(forecast_while_)) {
|
||||
forecast_posts * forecast_handler
|
||||
= new forecast_posts(handler,
|
||||
predicate_t(report.HANDLER(forecast_while_).str(),
|
||||
report.what_to_keep()),
|
||||
report,
|
||||
report.HANDLED(forecast_years_) ?
|
||||
static_cast<std::size_t>
|
||||
(report.HANDLER(forecast_years_).value.to_long()) :
|
||||
5UL);
|
||||
forecast_handler->add_period_xacts(report.session.journal->period_xacts);
|
||||
handler.reset(forecast_handler);
|
||||
|
||||
// See above, under budget_posts.
|
||||
if (report.HANDLED(limit_))
|
||||
handler.reset(new filter_posts
|
||||
(handler, predicate_t(report.HANDLER(limit_).str(),
|
||||
report.what_to_keep()),
|
||||
report));
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
|
|
|||
30
src/chain.h
30
src/chain.h
|
|
@ -65,27 +65,51 @@ public:
|
|||
TRACE_DTOR(item_handler);
|
||||
}
|
||||
|
||||
virtual void title(const string& str) {
|
||||
if (handler)
|
||||
handler->title(str);
|
||||
}
|
||||
|
||||
virtual void flush() {
|
||||
if (handler.get())
|
||||
if (handler)
|
||||
handler->flush();
|
||||
}
|
||||
virtual void operator()(T& item) {
|
||||
if (handler.get()) {
|
||||
if (handler) {
|
||||
check_for_signal();
|
||||
(*handler.get())(item);
|
||||
(*handler)(item);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void clear() {
|
||||
if (handler)
|
||||
handler->clear();
|
||||
}
|
||||
};
|
||||
|
||||
typedef shared_ptr<item_handler<post_t> > post_handler_ptr;
|
||||
typedef shared_ptr<item_handler<account_t> > acct_handler_ptr;
|
||||
|
||||
class report_t;
|
||||
|
||||
post_handler_ptr
|
||||
chain_pre_post_handlers(report_t& report,
|
||||
post_handler_ptr base_handler);
|
||||
|
||||
post_handler_ptr
|
||||
chain_post_handlers(report_t& report,
|
||||
post_handler_ptr base_handler,
|
||||
bool for_accounts_report = false);
|
||||
|
||||
inline post_handler_ptr
|
||||
chain_handlers(report_t& report,
|
||||
post_handler_ptr handler,
|
||||
bool for_accounts_report = false) {
|
||||
handler = chain_post_handlers(report, handler, for_accounts_report);
|
||||
handler = chain_pre_post_handlers(report, handler);
|
||||
return handler;
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
|
||||
#endif // _CHAIN_H
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
bool commodity_t::european_by_default = false;
|
||||
bool commodity_t::decimal_comma_by_default = false;
|
||||
|
||||
void commodity_t::history_t::add_price(commodity_t& source,
|
||||
const datetime_t& date,
|
||||
|
|
@ -454,7 +454,7 @@ void commodity_t::parse_symbol(std::istream& in, string& symbol)
|
|||
{
|
||||
// Invalid commodity characters:
|
||||
// SPACE, TAB, NEWLINE, RETURN
|
||||
// 0-9 . , ; - + * / ^ ? : & | ! =
|
||||
// 0-9 . , ; : ? ! - + * / ^ & | =
|
||||
// < > { } [ ] ( ) @
|
||||
|
||||
static int invalid_chars[256] = {
|
||||
|
|
@ -663,10 +663,10 @@ void to_xml(std::ostream& out, const commodity_t& comm,
|
|||
push_xml x(out, "commodity", true);
|
||||
|
||||
out << " flags=\"";
|
||||
if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) out << 'P';
|
||||
if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << 'S';
|
||||
if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) out << 'T';
|
||||
if (comm.has_flags(COMMODITY_STYLE_EUROPEAN)) out << 'E';
|
||||
if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) out << 'P';
|
||||
if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << 'S';
|
||||
if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) out << 'T';
|
||||
if (comm.has_flags(COMMODITY_STYLE_DECIMAL_COMMA)) out << 'D';
|
||||
out << '"';
|
||||
|
||||
x.close_attrs();
|
||||
|
|
|
|||
|
|
@ -155,16 +155,16 @@ protected:
|
|||
class base_t : public noncopyable, public supports_flags<uint_least16_t>
|
||||
{
|
||||
public:
|
||||
#define COMMODITY_STYLE_DEFAULTS 0x000
|
||||
#define COMMODITY_STYLE_SUFFIXED 0x001
|
||||
#define COMMODITY_STYLE_SEPARATED 0x002
|
||||
#define COMMODITY_STYLE_EUROPEAN 0x004
|
||||
#define COMMODITY_STYLE_THOUSANDS 0x008
|
||||
#define COMMODITY_NOMARKET 0x010
|
||||
#define COMMODITY_BUILTIN 0x020
|
||||
#define COMMODITY_WALKED 0x040
|
||||
#define COMMODITY_KNOWN 0x080
|
||||
#define COMMODITY_PRIMARY 0x100
|
||||
#define COMMODITY_STYLE_DEFAULTS 0x000
|
||||
#define COMMODITY_STYLE_SUFFIXED 0x001
|
||||
#define COMMODITY_STYLE_SEPARATED 0x002
|
||||
#define COMMODITY_STYLE_DECIMAL_COMMA 0x004
|
||||
#define COMMODITY_STYLE_THOUSANDS 0x008
|
||||
#define COMMODITY_NOMARKET 0x010
|
||||
#define COMMODITY_BUILTIN 0x020
|
||||
#define COMMODITY_WALKED 0x040
|
||||
#define COMMODITY_KNOWN 0x080
|
||||
#define COMMODITY_PRIMARY 0x100
|
||||
|
||||
string symbol;
|
||||
amount_t::precision_t precision;
|
||||
|
|
@ -179,8 +179,8 @@ protected:
|
|||
public:
|
||||
explicit base_t(const string& _symbol)
|
||||
: supports_flags<uint_least16_t>
|
||||
(commodity_t::european_by_default ?
|
||||
static_cast<uint_least16_t>(COMMODITY_STYLE_EUROPEAN) :
|
||||
(commodity_t::decimal_comma_by_default ?
|
||||
static_cast<uint_least16_t>(COMMODITY_STYLE_DECIMAL_COMMA) :
|
||||
static_cast<uint_least16_t>(COMMODITY_STYLE_DEFAULTS)),
|
||||
symbol(_symbol), precision(0), searched(false) {
|
||||
TRACE_CTOR(base_t, "const string&");
|
||||
|
|
@ -228,7 +228,7 @@ protected:
|
|||
}
|
||||
|
||||
public:
|
||||
static bool european_by_default;
|
||||
static bool decimal_comma_by_default;
|
||||
|
||||
virtual ~commodity_t() {
|
||||
TRACE_DTOR(commodity_t);
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ value_t convert_command(call_scope_t& scope)
|
|||
|
||||
if (matched) {
|
||||
DEBUG("convert.csv", "Ignored xact with code: " << *xact->code);
|
||||
delete xact; // ignore it
|
||||
checked_delete(xact); // ignore it
|
||||
}
|
||||
else {
|
||||
if (xact->posts.front()->account == NULL) {
|
||||
|
|
@ -135,7 +135,7 @@ value_t convert_command(call_scope_t& scope)
|
|||
}
|
||||
|
||||
if (! journal.add_xact(xact)) {
|
||||
delete xact;
|
||||
checked_delete(xact);
|
||||
throw_(std::runtime_error,
|
||||
_("Failed to finalize derived transaction (check commodities)"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,6 +240,9 @@ void draft_t::parse_args(const value_t& args)
|
|||
|
||||
xact_t * draft_t::insert(journal_t& journal)
|
||||
{
|
||||
if (! tmpl)
|
||||
return NULL;
|
||||
|
||||
if (tmpl->payee_mask.empty())
|
||||
throw std::runtime_error(_("'xact' command requires at least a payee"));
|
||||
|
||||
|
|
@ -528,7 +531,8 @@ value_t xact_command(call_scope_t& args)
|
|||
// Only consider actual postings for the "xact" command
|
||||
report.HANDLER(limit_).on(string("#xact"), "actual");
|
||||
|
||||
report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact);
|
||||
if (new_xact)
|
||||
report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
19
src/emacs.cc
19
src/emacs.cc
|
|
@ -40,18 +40,21 @@ namespace ledger {
|
|||
|
||||
void format_emacs_posts::write_xact(xact_t& xact)
|
||||
{
|
||||
out << "\"" << xact.pos->pathname << "\" "
|
||||
<< xact.pos->beg_line << " ";
|
||||
if (xact.pos)
|
||||
out << "\"" << xact.pos->pathname << "\" "
|
||||
<< xact.pos->beg_line << " ";
|
||||
else
|
||||
out << "\"\" " << -1 << " ";
|
||||
|
||||
tm when = gregorian::to_tm(xact.date());
|
||||
std::time_t date = std::mktime(&when);
|
||||
|
||||
out << "(" << (date / 65536) << " " << (date % 65536) << " 0) ";
|
||||
|
||||
if (! xact.code)
|
||||
out << "nil ";
|
||||
else
|
||||
if (xact.code)
|
||||
out << "\"" << *xact.code << "\" ";
|
||||
else
|
||||
out << "nil ";
|
||||
|
||||
if (xact.payee.empty())
|
||||
out << "nil";
|
||||
|
|
@ -77,7 +80,11 @@ void format_emacs_posts::operator()(post_t& post)
|
|||
out << "\n";
|
||||
}
|
||||
|
||||
out << " (" << post.pos->beg_line << " ";
|
||||
if (post.pos)
|
||||
out << " (" << post.pos->beg_line << " ";
|
||||
else
|
||||
out << " (" << -1 << " ";
|
||||
|
||||
out << "\"" << post.reported_account()->fullname() << "\" \""
|
||||
<< post.amount << "\"";
|
||||
|
||||
|
|
|
|||
266
src/filters.cc
266
src/filters.cc
|
|
@ -39,8 +39,51 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
void post_splitter::print_title(const value_t& val)
|
||||
{
|
||||
if (! report.HANDLED(no_titles)) {
|
||||
std::ostringstream buf;
|
||||
val.print(buf);
|
||||
post_chain->title(buf.str());
|
||||
}
|
||||
}
|
||||
|
||||
void post_splitter::flush()
|
||||
{
|
||||
foreach (value_to_posts_map::value_type pair, posts_map) {
|
||||
preflush_func(pair.first);
|
||||
|
||||
foreach (post_t * post, pair.second)
|
||||
(*post_chain)(*post);
|
||||
|
||||
post_chain->flush();
|
||||
post_chain->clear();
|
||||
|
||||
if (postflush_func)
|
||||
(*postflush_func)(pair.first);
|
||||
}
|
||||
}
|
||||
|
||||
void post_splitter::operator()(post_t& post)
|
||||
{
|
||||
bind_scope_t bound_scope(report, post);
|
||||
value_t result(group_by_expr.calc(bound_scope));
|
||||
|
||||
if (! result.is_null()) {
|
||||
value_to_posts_map::iterator i = posts_map.find(result);
|
||||
if (i != posts_map.end()) {
|
||||
(*i).second.push_back(&post);
|
||||
} else {
|
||||
std::pair<value_to_posts_map::iterator, bool> inserted
|
||||
= posts_map.insert(value_to_posts_map::value_type(result, posts_list()));
|
||||
assert(inserted.second);
|
||||
(*inserted.first).second.push_back(&post);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pass_down_posts::pass_down_posts(post_handler_ptr handler,
|
||||
posts_iterator& iter)
|
||||
posts_iterator& iter)
|
||||
: item_handler<post_t>(handler)
|
||||
{
|
||||
TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator");
|
||||
|
|
@ -312,7 +355,7 @@ namespace {
|
|||
if (functor)
|
||||
(*functor)(post);
|
||||
|
||||
DEBUG("filter.changed_value.rounding", "post.amount = " << post.amount);
|
||||
DEBUG("filters.changed_value.rounding", "post.amount = " << post.amount);
|
||||
|
||||
(*handler)(post);
|
||||
|
||||
|
|
@ -355,7 +398,7 @@ void collapse_posts::report_subtotal()
|
|||
xact.payee = last_xact->payee;
|
||||
xact._date = (is_valid(earliest_date) ?
|
||||
earliest_date : last_xact->_date);
|
||||
DEBUG("filter.collapse", "Pseudo-xact date = " << *xact._date);
|
||||
DEBUG("filters.collapse", "Pseudo-xact date = " << *xact._date);
|
||||
|
||||
handle_value(subtotal, &totals_account, &xact, temps, handler);
|
||||
}
|
||||
|
|
@ -420,10 +463,12 @@ void related_posts::flush()
|
|||
changed_value_posts::changed_value_posts(post_handler_ptr handler,
|
||||
report_t& _report,
|
||||
bool _for_accounts_report,
|
||||
bool _show_unrealized)
|
||||
bool _show_unrealized,
|
||||
bool _show_rounding)
|
||||
: item_handler<post_t>(handler), report(_report),
|
||||
for_accounts_report(_for_accounts_report),
|
||||
show_unrealized(_show_unrealized), last_post(NULL),
|
||||
show_unrealized(_show_unrealized),
|
||||
show_rounding(_show_rounding), last_post(NULL),
|
||||
revalued_account(temps.create_account(_("<Revalued>"))),
|
||||
rounding_account(temps.create_account(_("<Rounding>")))
|
||||
{
|
||||
|
|
@ -458,6 +503,8 @@ changed_value_posts::changed_value_posts(post_handler_ptr handler,
|
|||
void changed_value_posts::flush()
|
||||
{
|
||||
if (last_post && last_post->date() <= report.terminus.date()) {
|
||||
if (! for_accounts_report)
|
||||
output_intermediate_prices(*last_post, report.terminus.date());
|
||||
output_revaluation(*last_post, report.terminus.date());
|
||||
last_post = NULL;
|
||||
}
|
||||
|
|
@ -469,7 +516,6 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
|
|||
if (is_valid(date))
|
||||
post.xdata().date = date;
|
||||
|
||||
value_t repriced_total;
|
||||
try {
|
||||
bind_scope_t bound_scope(report, post);
|
||||
repriced_total = total_expr.calc(bound_scope);
|
||||
|
|
@ -480,14 +526,14 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
|
|||
}
|
||||
post.xdata().date = date_t();
|
||||
|
||||
DEBUG("filter.changed_value",
|
||||
"output_revaluation(last_balance) = " << last_total);
|
||||
DEBUG("filter.changed_value",
|
||||
DEBUG("filters.changed_value",
|
||||
"output_revaluation(last_total) = " << last_total);
|
||||
DEBUG("filters.changed_value",
|
||||
"output_revaluation(repriced_total) = " << repriced_total);
|
||||
|
||||
if (! last_total.is_null()) {
|
||||
if (value_t diff = repriced_total - last_total) {
|
||||
DEBUG("filter.changed_value", "output_revaluation(strip(diff)) = "
|
||||
DEBUG("filters.changed_value", "output_revaluation(strip(diff)) = "
|
||||
<< diff.strip_annotations(report.what_to_keep()));
|
||||
|
||||
xact_t& xact = temps.create_xact();
|
||||
|
|
@ -505,9 +551,10 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
|
|||
/* total= */ repriced_total,
|
||||
/* direct_amount= */ false,
|
||||
/* mark_visited= */ false,
|
||||
/* functor= */ (optional<post_functor_t>
|
||||
/* functor= */ (show_rounding ?
|
||||
optional<post_functor_t>
|
||||
(bind(&changed_value_posts::output_rounding,
|
||||
this, _1))));
|
||||
this, _1)) : none));
|
||||
}
|
||||
else if (show_unrealized) {
|
||||
handle_value
|
||||
|
|
@ -527,29 +574,146 @@ void changed_value_posts::output_revaluation(post_t& post, const date_t& date)
|
|||
}
|
||||
}
|
||||
|
||||
void changed_value_posts::output_intermediate_prices(post_t& post,
|
||||
const date_t& current)
|
||||
{
|
||||
// To fix BZ#199, examine the balance of last_post and determine whether the
|
||||
// price of that amount changed after its date and before the new post's
|
||||
// date. If so, generate an output_revaluation for that price change.
|
||||
// Mostly this is only going to occur if the user has a series of pricing
|
||||
// entries, since a posting-based revaluation would be seen here as a post.
|
||||
|
||||
value_t display_total(last_total);
|
||||
|
||||
if (display_total.type() == value_t::SEQUENCE) {
|
||||
xact_t& xact(temps.create_xact());
|
||||
|
||||
xact.payee = _("Commodities revalued");
|
||||
xact._date = is_valid(current) ? current : post.date();
|
||||
|
||||
post_t& temp(temps.copy_post(post, xact));
|
||||
temp.add_flags(ITEM_GENERATED);
|
||||
|
||||
post_t::xdata_t& xdata(temp.xdata());
|
||||
if (is_valid(current))
|
||||
xdata.date = current;
|
||||
|
||||
DEBUG("filters.revalued", "intermediate last_total = " << last_total);
|
||||
|
||||
switch (last_total.type()) {
|
||||
case value_t::BOOLEAN:
|
||||
case value_t::INTEGER:
|
||||
last_total.in_place_cast(value_t::AMOUNT);
|
||||
// fall through...
|
||||
|
||||
case value_t::AMOUNT:
|
||||
temp.amount = last_total.as_amount();
|
||||
break;
|
||||
|
||||
case value_t::BALANCE:
|
||||
case value_t::SEQUENCE:
|
||||
xdata.compound_value = last_total;
|
||||
xdata.add_flags(POST_EXT_COMPOUND);
|
||||
break;
|
||||
|
||||
case value_t::DATETIME:
|
||||
case value_t::DATE:
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
bind_scope_t inner_scope(report, temp);
|
||||
display_total = display_total_expr.calc(inner_scope);
|
||||
|
||||
DEBUG("filters.revalued", "intermediate display_total = " << display_total);
|
||||
}
|
||||
|
||||
switch (display_total.type()) {
|
||||
case value_t::VOID:
|
||||
case value_t::INTEGER:
|
||||
case value_t::SEQUENCE:
|
||||
break;
|
||||
|
||||
case value_t::AMOUNT:
|
||||
display_total.in_place_cast(value_t::BALANCE);
|
||||
// fall through...
|
||||
|
||||
case value_t::BALANCE: {
|
||||
commodity_t::history_map all_prices;
|
||||
|
||||
foreach (const balance_t::amounts_map::value_type& amt_comm,
|
||||
display_total.as_balance().amounts) {
|
||||
if (optional<commodity_t::varied_history_t&> hist =
|
||||
amt_comm.first->varied_history()) {
|
||||
foreach
|
||||
(const commodity_t::history_by_commodity_map::value_type& comm_hist,
|
||||
hist->histories) {
|
||||
foreach (const commodity_t::history_map::value_type& price,
|
||||
comm_hist.second.prices) {
|
||||
if (price.first.date() > post.date() &&
|
||||
price.first.date() < current) {
|
||||
DEBUG("filters.revalued", post.date() << " < "
|
||||
<< price.first.date() << " < " << current);
|
||||
DEBUG("filters.revalued", "inserting "
|
||||
<< price.second << " at " << price.first.date());
|
||||
all_prices.insert(price);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Choose the last price from each day as the price to use
|
||||
typedef std::map<const date_t, bool> date_map;
|
||||
date_map pricing_dates;
|
||||
|
||||
BOOST_REVERSE_FOREACH
|
||||
(const commodity_t::history_map::value_type& price, all_prices) {
|
||||
// This insert will fail if a later price has already been inserted
|
||||
// for that date.
|
||||
DEBUG("filters.revalued",
|
||||
"re-inserting " << price.second << " at " << price.first.date());
|
||||
pricing_dates.insert(date_map::value_type(price.first.date(), true));
|
||||
}
|
||||
|
||||
// Go through the time-sorted prices list, outputting a revaluation for
|
||||
// each price difference.
|
||||
foreach (const date_map::value_type& price, pricing_dates) {
|
||||
output_revaluation(post, price.first);
|
||||
last_total = repriced_total;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void changed_value_posts::output_rounding(post_t& post)
|
||||
{
|
||||
bind_scope_t bound_scope(report, post);
|
||||
value_t new_display_total(display_total_expr.calc(bound_scope));
|
||||
|
||||
DEBUG("filter.changed_value.rounding",
|
||||
DEBUG("filters.changed_value.rounding",
|
||||
"rounding.new_display_total = " << new_display_total);
|
||||
|
||||
if (! last_display_total.is_null()) {
|
||||
if (value_t repriced_amount = display_amount_expr.calc(bound_scope)) {
|
||||
DEBUG("filter.changed_value.rounding",
|
||||
DEBUG("filters.changed_value.rounding",
|
||||
"rounding.repriced_amount = " << repriced_amount);
|
||||
|
||||
value_t precise_display_total(new_display_total.truncated() -
|
||||
repriced_amount.truncated());
|
||||
|
||||
DEBUG("filter.changed_value.rounding",
|
||||
DEBUG("filters.changed_value.rounding",
|
||||
"rounding.precise_display_total = " << precise_display_total);
|
||||
DEBUG("filter.changed_value.rounding",
|
||||
DEBUG("filters.changed_value.rounding",
|
||||
"rounding.last_display_total = " << last_display_total);
|
||||
|
||||
if (value_t diff = precise_display_total - last_display_total) {
|
||||
DEBUG("filter.changed_value.rounding",
|
||||
DEBUG("filters.changed_value.rounding",
|
||||
"rounding.diff = " << diff);
|
||||
|
||||
xact_t& xact = temps.create_xact();
|
||||
|
|
@ -566,13 +730,16 @@ void changed_value_posts::output_rounding(post_t& post)
|
|||
|
||||
void changed_value_posts::operator()(post_t& post)
|
||||
{
|
||||
if (last_post)
|
||||
if (last_post) {
|
||||
if (! for_accounts_report)
|
||||
output_intermediate_prices(*last_post, post.date());
|
||||
output_revaluation(*last_post, post.date());
|
||||
}
|
||||
|
||||
if (changed_values_only)
|
||||
post.xdata().add_flags(POST_EXT_DISPLAYED);
|
||||
|
||||
if (! for_accounts_report)
|
||||
if (! for_accounts_report && show_rounding)
|
||||
output_rounding(post);
|
||||
|
||||
item_handler<post_t>::operator()(post);
|
||||
|
|
@ -793,41 +960,43 @@ void transfer_details::operator()(post_t& post)
|
|||
bind_scope_t bound_scope(scope, temp);
|
||||
value_t substitute(expr.calc(bound_scope));
|
||||
|
||||
switch (which_element) {
|
||||
case SET_DATE:
|
||||
temp.xdata().date = substitute.to_date();
|
||||
break;
|
||||
if (! substitute.is_null()) {
|
||||
switch (which_element) {
|
||||
case SET_DATE:
|
||||
temp.xdata().date = substitute.to_date();
|
||||
break;
|
||||
|
||||
case SET_ACCOUNT: {
|
||||
string account_name = substitute.to_string();
|
||||
if (! account_name.empty() &&
|
||||
account_name[account_name.length() - 1] != ':') {
|
||||
account_t * prev_account = temp.account;
|
||||
temp.account->remove_post(&temp);
|
||||
case SET_ACCOUNT: {
|
||||
string account_name = substitute.to_string();
|
||||
if (! account_name.empty() &&
|
||||
account_name[account_name.length() - 1] != ':') {
|
||||
account_t * prev_account = temp.account;
|
||||
temp.account->remove_post(&temp);
|
||||
|
||||
account_name += ':';
|
||||
account_name += prev_account->fullname();
|
||||
account_name += ':';
|
||||
account_name += prev_account->fullname();
|
||||
|
||||
std::list<string> account_names;
|
||||
split_string(account_name, ':', account_names);
|
||||
temp.account = create_temp_account_from_path(account_names, temps,
|
||||
xact.journal->master);
|
||||
temp.account->add_post(&temp);
|
||||
std::list<string> account_names;
|
||||
split_string(account_name, ':', account_names);
|
||||
temp.account = create_temp_account_from_path(account_names, temps,
|
||||
xact.journal->master);
|
||||
temp.account->add_post(&temp);
|
||||
|
||||
temp.account->add_flags(prev_account->flags());
|
||||
if (prev_account->has_xdata())
|
||||
temp.account->xdata().add_flags(prev_account->xdata().flags());
|
||||
temp.account->add_flags(prev_account->flags());
|
||||
if (prev_account->has_xdata())
|
||||
temp.account->xdata().add_flags(prev_account->xdata().flags());
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_PAYEE:
|
||||
xact.payee = substitute.to_string();
|
||||
break;
|
||||
case SET_PAYEE:
|
||||
xact.payee = substitute.to_string();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
item_handler<post_t>::operator()(temp);
|
||||
|
|
@ -869,7 +1038,8 @@ void budget_posts::report_budget_items(const date_t& date)
|
|||
optional<date_t> begin = pair.first.start;
|
||||
if (! begin) {
|
||||
if (! pair.first.find_period(date))
|
||||
throw_(std::runtime_error, _("Something odd has happened"));
|
||||
throw_(std::runtime_error,
|
||||
_("Something odd has happened at date %1") << date);
|
||||
begin = pair.first.start;
|
||||
}
|
||||
assert(begin);
|
||||
|
|
|
|||
224
src/filters.h
224
src/filters.h
|
|
@ -50,6 +50,56 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Posting collector
|
||||
//
|
||||
|
||||
class post_splitter : public item_handler<post_t>
|
||||
{
|
||||
public:
|
||||
typedef std::map<value_t, posts_list> value_to_posts_map;
|
||||
typedef function<void (const value_t&)> custom_flusher_t;
|
||||
|
||||
protected:
|
||||
value_to_posts_map posts_map;
|
||||
report_t& report;
|
||||
post_handler_ptr post_chain;
|
||||
expr_t group_by_expr;
|
||||
custom_flusher_t preflush_func;
|
||||
optional<custom_flusher_t> postflush_func;
|
||||
|
||||
public:
|
||||
post_splitter(report_t& _report,
|
||||
post_handler_ptr _post_chain,
|
||||
expr_t _group_by_expr)
|
||||
: report(_report), post_chain(_post_chain),
|
||||
group_by_expr(_group_by_expr),
|
||||
preflush_func(bind(&post_splitter::print_title, this, _1)) {
|
||||
TRACE_CTOR(post_splitter, "scope_t&, post_handler_ptr, expr_t");
|
||||
}
|
||||
virtual ~post_splitter() {
|
||||
TRACE_DTOR(post_splitter);
|
||||
}
|
||||
|
||||
void set_preflush_func(custom_flusher_t functor) {
|
||||
preflush_func = functor;
|
||||
}
|
||||
void set_postflush_func(custom_flusher_t functor) {
|
||||
postflush_func = functor;
|
||||
}
|
||||
|
||||
virtual void print_title(const value_t& val);
|
||||
|
||||
virtual void flush();
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
posts_map.clear();
|
||||
post_chain->clear();
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Posting filters
|
||||
|
|
@ -88,6 +138,11 @@ public:
|
|||
virtual void operator()(post_t& post) {
|
||||
posts.push_back(&post);
|
||||
}
|
||||
|
||||
virtual void clear() {
|
||||
posts.clear();
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class posts_iterator;
|
||||
|
|
@ -149,27 +204,34 @@ public:
|
|||
|
||||
virtual void flush();
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
completed = false;
|
||||
posts.clear();
|
||||
xacts_seen = 0;
|
||||
last_xact = NULL;
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class sort_posts : public item_handler<post_t>
|
||||
{
|
||||
typedef std::deque<post_t *> posts_deque;
|
||||
|
||||
posts_deque posts;
|
||||
const expr_t sort_order;
|
||||
posts_deque posts;
|
||||
expr_t sort_order;
|
||||
|
||||
sort_posts();
|
||||
|
||||
public:
|
||||
sort_posts(post_handler_ptr handler,
|
||||
const expr_t& _sort_order)
|
||||
sort_posts(post_handler_ptr handler, const expr_t& _sort_order)
|
||||
: item_handler<post_t>(handler),
|
||||
sort_order(_sort_order) {
|
||||
TRACE_CTOR(sort_posts,
|
||||
"post_handler_ptr, const value_expr&");
|
||||
}
|
||||
sort_posts(post_handler_ptr handler,
|
||||
const string& _sort_order)
|
||||
sort_posts(post_handler_ptr handler, const string& _sort_order)
|
||||
: item_handler<post_t>(handler),
|
||||
sort_order(_sort_order) {
|
||||
TRACE_CTOR(sort_posts,
|
||||
|
|
@ -189,6 +251,13 @@ public:
|
|||
virtual void operator()(post_t& post) {
|
||||
posts.push_back(&post);
|
||||
}
|
||||
|
||||
virtual void clear() {
|
||||
posts.clear();
|
||||
sort_order.mark_uncompiled();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class sort_xacts : public item_handler<post_t>
|
||||
|
|
@ -199,14 +268,12 @@ class sort_xacts : public item_handler<post_t>
|
|||
sort_xacts();
|
||||
|
||||
public:
|
||||
sort_xacts(post_handler_ptr handler,
|
||||
const expr_t& _sort_order)
|
||||
sort_xacts(post_handler_ptr handler, const expr_t& _sort_order)
|
||||
: sorter(handler, _sort_order) {
|
||||
TRACE_CTOR(sort_xacts,
|
||||
"post_handler_ptr, const value_expr&");
|
||||
}
|
||||
sort_xacts(post_handler_ptr handler,
|
||||
const string& _sort_order)
|
||||
sort_xacts(post_handler_ptr handler, const string& _sort_order)
|
||||
: sorter(handler, _sort_order) {
|
||||
TRACE_CTOR(sort_xacts,
|
||||
"post_handler_ptr, const string&");
|
||||
|
|
@ -228,6 +295,13 @@ public:
|
|||
|
||||
last_xact = post.xact;
|
||||
}
|
||||
|
||||
virtual void clear() {
|
||||
sorter.clear();
|
||||
last_xact = NULL;
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class filter_posts : public item_handler<post_t>
|
||||
|
|
@ -255,6 +329,11 @@ public:
|
|||
(*handler)(post);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void clear() {
|
||||
pred.mark_uncompiled();
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class anonymize_posts : public item_handler<post_t>
|
||||
|
|
@ -274,6 +353,13 @@ public:
|
|||
}
|
||||
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
temps.clear();
|
||||
last_xact = NULL;
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class calc_posts : public item_handler<post_t>
|
||||
|
|
@ -297,6 +383,13 @@ public:
|
|||
}
|
||||
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
last_post = NULL;
|
||||
amount_expr.mark_uncompiled();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class collapse_posts : public item_handler<post_t>
|
||||
|
|
@ -334,13 +427,29 @@ public:
|
|||
}
|
||||
|
||||
virtual void flush() {
|
||||
report_subtotal();
|
||||
report_subtotal();
|
||||
item_handler<post_t>::flush();
|
||||
}
|
||||
|
||||
void report_subtotal();
|
||||
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
amount_expr.mark_uncompiled();
|
||||
display_predicate.mark_uncompiled();
|
||||
only_predicate.mark_uncompiled();
|
||||
|
||||
subtotal = value_t();
|
||||
count = 0;
|
||||
last_xact = NULL;
|
||||
last_post = NULL;
|
||||
|
||||
temps.clear();
|
||||
component_posts.clear();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class related_posts : public item_handler<post_t>
|
||||
|
|
@ -367,6 +476,11 @@ public:
|
|||
post.xdata().add_flags(POST_EXT_RECEIVED);
|
||||
posts.push_back(&post);
|
||||
}
|
||||
|
||||
virtual void clear() {
|
||||
posts.clear();
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class changed_value_posts : public item_handler<post_t>
|
||||
|
|
@ -381,9 +495,11 @@ class changed_value_posts : public item_handler<post_t>
|
|||
bool changed_values_only;
|
||||
bool for_accounts_report;
|
||||
bool show_unrealized;
|
||||
bool show_rounding;
|
||||
post_t * last_post;
|
||||
value_t last_total;
|
||||
value_t last_display_total;
|
||||
value_t repriced_total;
|
||||
temporaries_t temps;
|
||||
account_t& revalued_account;
|
||||
account_t& rounding_account;
|
||||
|
|
@ -396,7 +512,8 @@ public:
|
|||
changed_value_posts(post_handler_ptr handler,
|
||||
report_t& _report,
|
||||
bool _for_accounts_report,
|
||||
bool _show_unrealized);
|
||||
bool _show_unrealized,
|
||||
bool _show_rounding);
|
||||
|
||||
virtual ~changed_value_posts() {
|
||||
TRACE_DTOR(changed_value_posts);
|
||||
|
|
@ -405,9 +522,24 @@ public:
|
|||
virtual void flush();
|
||||
|
||||
void output_revaluation(post_t& post, const date_t& current);
|
||||
void output_intermediate_prices(post_t& post, const date_t& current);
|
||||
void output_rounding(post_t& post);
|
||||
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
display_amount_expr.mark_uncompiled();
|
||||
total_expr.mark_uncompiled();
|
||||
display_total_expr.mark_uncompiled();
|
||||
|
||||
last_post = NULL;
|
||||
last_total = value_t();
|
||||
last_display_total = value_t();
|
||||
|
||||
temps.clear();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class subtotal_posts : public item_handler<post_t>
|
||||
|
|
@ -469,10 +601,20 @@ public:
|
|||
item_handler<post_t>::flush();
|
||||
}
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
amount_expr.mark_uncompiled();
|
||||
values.clear();
|
||||
temps.clear();
|
||||
component_posts.clear();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class interval_posts : public subtotal_posts
|
||||
{
|
||||
date_interval_t start_interval;
|
||||
date_interval_t interval;
|
||||
date_interval_t last_interval;
|
||||
post_t * last_post;
|
||||
|
|
@ -489,8 +631,9 @@ public:
|
|||
const date_interval_t& _interval,
|
||||
bool _exact_periods = false,
|
||||
bool _generate_empty_posts = false)
|
||||
: subtotal_posts(_handler, amount_expr), interval(_interval),
|
||||
last_post(NULL), empty_account(temps.create_account(_("<None>"))),
|
||||
: subtotal_posts(_handler, amount_expr), start_interval(_interval),
|
||||
interval(start_interval), last_post(NULL),
|
||||
empty_account(temps.create_account(_("<None>"))),
|
||||
exact_periods(_exact_periods),
|
||||
generate_empty_posts(_generate_empty_posts) {
|
||||
TRACE_CTOR(interval_posts,
|
||||
|
|
@ -510,6 +653,14 @@ public:
|
|||
}
|
||||
}
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
interval = start_interval;
|
||||
last_interval = date_interval_t();
|
||||
last_post = NULL;
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class posts_as_equity : public subtotal_posts
|
||||
|
|
@ -537,6 +688,11 @@ public:
|
|||
report_subtotal();
|
||||
subtotal_posts::flush();
|
||||
}
|
||||
|
||||
virtual void clear() {
|
||||
last_post = NULL;
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class by_payee_posts : public item_handler<post_t>
|
||||
|
|
@ -560,6 +716,13 @@ class by_payee_posts : public item_handler<post_t>
|
|||
|
||||
virtual void flush();
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
amount_expr.mark_uncompiled();
|
||||
payee_subtotals.clear();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class transfer_details : public item_handler<post_t>
|
||||
|
|
@ -593,6 +756,13 @@ public:
|
|||
}
|
||||
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
expr.mark_uncompiled();
|
||||
temps.clear();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class dow_posts : public subtotal_posts
|
||||
|
|
@ -614,6 +784,13 @@ public:
|
|||
virtual void operator()(post_t& post) {
|
||||
days_of_the_week[post.date().day_of_week()].push_back(&post);
|
||||
}
|
||||
|
||||
virtual void clear() {
|
||||
for (int i = 0; i < 7; i++)
|
||||
days_of_the_week[i].clear();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class generate_posts : public item_handler<post_t>
|
||||
|
|
@ -640,6 +817,13 @@ public:
|
|||
void add_period_xacts(period_xacts_list& period_xacts);
|
||||
|
||||
virtual void add_post(const date_interval_t& period, post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
pending_posts.clear();
|
||||
temps.clear();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class budget_posts : public generate_posts
|
||||
|
|
@ -690,6 +874,11 @@ class forecast_posts : public generate_posts
|
|||
|
||||
virtual void add_post(const date_interval_t& period, post_t& post);
|
||||
virtual void flush();
|
||||
|
||||
virtual void clear() {
|
||||
pred.mark_uncompiled();
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -715,6 +904,13 @@ public:
|
|||
virtual ~pass_down_accounts() {
|
||||
TRACE_DTOR(pass_down_accounts);
|
||||
}
|
||||
|
||||
virtual void clear() {
|
||||
if (pred)
|
||||
pred->mark_uncompiled();
|
||||
|
||||
item_handler<account_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ namespace {
|
|||
return NULL_VALUE;
|
||||
}
|
||||
value_t get_note(item_t& item) {
|
||||
return string_value(item.note ? *item.note : empty_string);
|
||||
return item.note ? string_value(*item.note) : NULL_VALUE;
|
||||
}
|
||||
|
||||
value_t has_tag(call_scope_t& args) {
|
||||
|
|
@ -260,7 +260,8 @@ namespace {
|
|||
return false;
|
||||
}
|
||||
|
||||
value_t get_tag(call_scope_t& args) {
|
||||
value_t get_tag(call_scope_t& args)
|
||||
{
|
||||
item_t& item(find_scope<item_t>(args));
|
||||
optional<string> str;
|
||||
|
||||
|
|
@ -292,14 +293,14 @@ namespace {
|
|||
if (str)
|
||||
return string_value(*str);
|
||||
else
|
||||
return string_value(empty_string);
|
||||
return NULL_VALUE;
|
||||
}
|
||||
|
||||
value_t get_pathname(item_t& item) {
|
||||
if (item.pos)
|
||||
return string_value(item.pos->pathname.string());
|
||||
else
|
||||
return string_value(empty_string);
|
||||
return NULL_VALUE;
|
||||
}
|
||||
|
||||
value_t get_beg_pos(item_t& item) {
|
||||
|
|
|
|||
35
src/option.h
35
src/option.h
|
|
@ -90,13 +90,16 @@ public:
|
|||
|
||||
void report(std::ostream& out) const {
|
||||
if (handled && source) {
|
||||
out.width(24);
|
||||
out << std::right << desc();
|
||||
if (wants_arg) {
|
||||
out << desc() << " => ";
|
||||
value.dump(out);
|
||||
out << " = ";
|
||||
value.print(out, 42);
|
||||
} else {
|
||||
out << desc();
|
||||
out.width(45);
|
||||
out << ' ';
|
||||
}
|
||||
out << " <" << *source << ">" << std::endl;
|
||||
out << std::left << *source << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,20 +205,20 @@ public:
|
|||
};
|
||||
|
||||
#define BEGIN(type, name) \
|
||||
struct name ## _option_t : public option_t<type>
|
||||
struct name ## option_t : public option_t<type>
|
||||
|
||||
#define CTOR(type, name) \
|
||||
name ## _option_t() : option_t<type>(#name)
|
||||
name ## option_t() : option_t<type>(#name)
|
||||
#define DECL1(type, name, vartype, var, value) \
|
||||
vartype var ; \
|
||||
name ## _option_t() : option_t<type>(#name), var(value)
|
||||
name ## option_t() : option_t<type>(#name), var(value)
|
||||
|
||||
#define DO() virtual void handler_thunk(call_scope_t&)
|
||||
#define DO_(var) virtual void handler_thunk(call_scope_t& var)
|
||||
|
||||
#define END(name) name ## _handler
|
||||
#define END(name) name ## handler
|
||||
|
||||
#define COPY_OPT(name, other) name ## _handler(other.name ## _handler)
|
||||
#define COPY_OPT(name, other) name ## handler(other.name ## handler)
|
||||
|
||||
#define MAKE_OPT_HANDLER(type, x) \
|
||||
expr_t::op_t::wrap_functor(bind(&option_t<type>::handler_wrapper, x, _1))
|
||||
|
|
@ -235,26 +238,26 @@ inline bool is_eq(const char * p, const char * n) {
|
|||
|
||||
#define OPT(name) \
|
||||
if (is_eq(p, #name)) \
|
||||
return ((name ## _handler).parent = this, &(name ## _handler))
|
||||
return ((name ## handler).parent = this, &(name ## handler))
|
||||
|
||||
#define OPT_ALT(name, alt) \
|
||||
if (is_eq(p, #name) || is_eq(p, #alt)) \
|
||||
return ((name ## _handler).parent = this, &(name ## _handler))
|
||||
return ((name ## handler).parent = this, &(name ## handler))
|
||||
|
||||
#define OPT_(name) \
|
||||
if (! *(p + 1) || \
|
||||
((name ## _handler).wants_arg && \
|
||||
((name ## handler).wants_arg && \
|
||||
*(p + 1) == '_' && ! *(p + 2)) || \
|
||||
is_eq(p, #name)) \
|
||||
return ((name ## _handler).parent = this, &(name ## _handler))
|
||||
return ((name ## handler).parent = this, &(name ## handler))
|
||||
|
||||
#define OPT_CH(name) \
|
||||
if (! *(p + 1) || \
|
||||
((name ## _handler).wants_arg && \
|
||||
((name ## handler).wants_arg && \
|
||||
*(p + 1) == '_' && ! *(p + 2))) \
|
||||
return ((name ## _handler).parent = this, &(name ## _handler))
|
||||
return ((name ## handler).parent = this, &(name ## handler))
|
||||
|
||||
#define HANDLER(name) name ## _handler
|
||||
#define HANDLER(name) name ## handler
|
||||
#define HANDLED(name) HANDLER(name)
|
||||
|
||||
#define OPTION(type, name) \
|
||||
|
|
|
|||
157
src/output.cc
157
src/output.cc
|
|
@ -42,8 +42,10 @@ namespace ledger {
|
|||
|
||||
format_posts::format_posts(report_t& _report,
|
||||
const string& format,
|
||||
const optional<string>& _prepend_format)
|
||||
: report(_report), last_xact(NULL), last_post(NULL)
|
||||
const optional<string>& _prepend_format,
|
||||
std::size_t _prepend_width)
|
||||
: report(_report), prepend_width(_prepend_width),
|
||||
last_xact(NULL), last_post(NULL), first_report_title(true)
|
||||
{
|
||||
TRACE_CTOR(format_posts, "report&, const string&, bool");
|
||||
|
||||
|
|
@ -76,14 +78,34 @@ void format_posts::flush()
|
|||
|
||||
void format_posts::operator()(post_t& post)
|
||||
{
|
||||
std::ostream& out(report.output_stream);
|
||||
|
||||
if (! post.has_xdata() ||
|
||||
! post.xdata().has_flags(POST_EXT_DISPLAYED)) {
|
||||
std::ostream& out(report.output_stream);
|
||||
|
||||
bind_scope_t bound_scope(report, post);
|
||||
|
||||
if (prepend_format)
|
||||
if (! report_title.empty()) {
|
||||
if (first_report_title)
|
||||
first_report_title = false;
|
||||
else
|
||||
out << '\n';
|
||||
|
||||
value_scope_t val_scope(string_value(report_title));
|
||||
bind_scope_t inner_scope(bound_scope, val_scope);
|
||||
|
||||
format_t group_title_format;
|
||||
group_title_format
|
||||
.parse_format(report.HANDLER(group_title_format_).str());
|
||||
|
||||
out << group_title_format(inner_scope);
|
||||
|
||||
report_title = "";
|
||||
}
|
||||
|
||||
if (prepend_format) {
|
||||
out.width(prepend_width);
|
||||
out << prepend_format(bound_scope);
|
||||
}
|
||||
|
||||
if (last_xact != post.xact) {
|
||||
if (last_xact) {
|
||||
|
|
@ -107,8 +129,10 @@ void format_posts::operator()(post_t& post)
|
|||
|
||||
format_accounts::format_accounts(report_t& _report,
|
||||
const string& format,
|
||||
const optional<string>& _prepend_format)
|
||||
: report(_report), disp_pred()
|
||||
const optional<string>& _prepend_format,
|
||||
std::size_t _prepend_width)
|
||||
: report(_report), prepend_width(_prepend_width), disp_pred(),
|
||||
first_report_title(true)
|
||||
{
|
||||
TRACE_CTOR(format_accounts, "report&, const string&");
|
||||
|
||||
|
|
@ -139,17 +163,37 @@ std::size_t format_accounts::post_account(account_t& account, const bool flat)
|
|||
|
||||
if (account.xdata().has_flags(ACCOUNT_EXT_TO_DISPLAY) &&
|
||||
! account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED)) {
|
||||
std::ostream& out(report.output_stream);
|
||||
|
||||
DEBUG("account.display", "Displaying account: " << account.fullname());
|
||||
account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED);
|
||||
|
||||
bind_scope_t bound_scope(report, account);
|
||||
|
||||
if (prepend_format)
|
||||
static_cast<std::ostream&>(report.output_stream)
|
||||
<< prepend_format(bound_scope);
|
||||
if (! report_title.empty()) {
|
||||
if (first_report_title)
|
||||
first_report_title = false;
|
||||
else
|
||||
out << '\n';
|
||||
|
||||
static_cast<std::ostream&>(report.output_stream)
|
||||
<< account_line_format(bound_scope);
|
||||
value_scope_t val_scope(string_value(report_title));
|
||||
bind_scope_t inner_scope(bound_scope, val_scope);
|
||||
|
||||
format_t group_title_format;
|
||||
group_title_format
|
||||
.parse_format(report.HANDLER(group_title_format_).str());
|
||||
|
||||
out << group_title_format(inner_scope);
|
||||
|
||||
report_title = "";
|
||||
}
|
||||
|
||||
if (prepend_format) {
|
||||
out.width(prepend_width);
|
||||
out << prepend_format(bound_scope);
|
||||
}
|
||||
|
||||
out << account_line_format(bound_scope);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -216,9 +260,11 @@ void format_accounts::flush()
|
|||
bind_scope_t bound_scope(report, *report.session.journal->master);
|
||||
out << separator_format(bound_scope);
|
||||
|
||||
if (prepend_format)
|
||||
if (prepend_format) {
|
||||
static_cast<std::ostream&>(report.output_stream).width(prepend_width);
|
||||
static_cast<std::ostream&>(report.output_stream)
|
||||
<< prepend_format(bound_scope);
|
||||
}
|
||||
|
||||
out << total_line_format(bound_scope);
|
||||
}
|
||||
|
|
@ -232,4 +278,89 @@ void format_accounts::operator()(account_t& account)
|
|||
posted_accounts.push_back(&account);
|
||||
}
|
||||
|
||||
void report_accounts::flush()
|
||||
{
|
||||
std::ostream& out(report.output_stream);
|
||||
|
||||
foreach (accounts_pair& entry, accounts) {
|
||||
if (report.HANDLED(count))
|
||||
out << entry.second << ' ';
|
||||
out << *entry.first << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void report_accounts::operator()(post_t& post)
|
||||
{
|
||||
std::map<account_t *, std::size_t>::iterator i = accounts.find(post.account);
|
||||
if (i == accounts.end())
|
||||
accounts.insert(accounts_pair(post.account, 1));
|
||||
else
|
||||
(*i).second++;
|
||||
}
|
||||
|
||||
void report_payees::flush()
|
||||
{
|
||||
std::ostream& out(report.output_stream);
|
||||
|
||||
foreach (payees_pair& entry, payees) {
|
||||
if (report.HANDLED(count))
|
||||
out << entry.second << ' ';
|
||||
out << entry.first << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void report_payees::operator()(post_t& post)
|
||||
{
|
||||
std::map<string, std::size_t>::iterator i = payees.find(post.xact->payee);
|
||||
if (i == payees.end())
|
||||
payees.insert(payees_pair(post.xact->payee, 1));
|
||||
else
|
||||
(*i).second++;
|
||||
}
|
||||
|
||||
void report_commodities::flush()
|
||||
{
|
||||
std::ostream& out(report.output_stream);
|
||||
|
||||
foreach (commodities_pair& entry, commodities) {
|
||||
if (report.HANDLED(count))
|
||||
out << entry.second << ' ';
|
||||
out << *entry.first << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void report_commodities::operator()(post_t& post)
|
||||
{
|
||||
amount_t temp(post.amount.strip_annotations(report.what_to_keep()));
|
||||
commodity_t& comm(temp.commodity());
|
||||
|
||||
std::map<commodity_t *, std::size_t>::iterator i = commodities.find(&comm);
|
||||
if (i == commodities.end())
|
||||
commodities.insert(commodities_pair(&comm, 1));
|
||||
else
|
||||
(*i).second++;
|
||||
|
||||
if (comm.has_annotation()) {
|
||||
annotated_commodity_t& ann_comm(as_annotated_commodity(comm));
|
||||
if (ann_comm.details.price) {
|
||||
std::map<commodity_t *, std::size_t>::iterator i =
|
||||
commodities.find(&ann_comm.details.price->commodity());
|
||||
if (i == commodities.end())
|
||||
commodities.insert
|
||||
(commodities_pair(&ann_comm.details.price->commodity(), 1));
|
||||
else
|
||||
(*i).second++;
|
||||
}
|
||||
}
|
||||
|
||||
if (post.cost) {
|
||||
amount_t temp_cost(post.cost->strip_annotations(report.what_to_keep()));
|
||||
i = commodities.find(&temp_cost.commodity());
|
||||
if (i == commodities.end())
|
||||
commodities.insert(commodities_pair(&temp_cost.commodity(), 1));
|
||||
else
|
||||
(*i).second++;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
130
src/output.h
130
src/output.h
|
|
@ -56,23 +56,40 @@ class report_t;
|
|||
class format_posts : public item_handler<post_t>
|
||||
{
|
||||
protected:
|
||||
report_t& report;
|
||||
format_t first_line_format;
|
||||
format_t next_lines_format;
|
||||
format_t between_format;
|
||||
format_t prepend_format;
|
||||
xact_t * last_xact;
|
||||
post_t * last_post;
|
||||
report_t& report;
|
||||
format_t first_line_format;
|
||||
format_t next_lines_format;
|
||||
format_t between_format;
|
||||
format_t prepend_format;
|
||||
std::size_t prepend_width;
|
||||
xact_t * last_xact;
|
||||
post_t * last_post;
|
||||
bool first_report_title;
|
||||
string report_title;
|
||||
|
||||
public:
|
||||
format_posts(report_t& _report, const string& format,
|
||||
const optional<string>& _prepend_format = none);
|
||||
const optional<string>& _prepend_format = none,
|
||||
std::size_t _prepend_width = 0);
|
||||
virtual ~format_posts() {
|
||||
TRACE_DTOR(format_posts);
|
||||
}
|
||||
|
||||
virtual void title(const string& str) {
|
||||
report_title = str;
|
||||
}
|
||||
|
||||
virtual void flush();
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
last_xact = NULL;
|
||||
last_post = NULL;
|
||||
|
||||
report_title = "";
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class format_accounts : public item_handler<account_t>
|
||||
|
|
@ -83,13 +100,17 @@ protected:
|
|||
format_t total_line_format;
|
||||
format_t separator_format;
|
||||
format_t prepend_format;
|
||||
std::size_t prepend_width;
|
||||
predicate_t disp_pred;
|
||||
bool first_report_title;
|
||||
string report_title;
|
||||
|
||||
std::list<account_t *> posted_accounts;
|
||||
|
||||
public:
|
||||
format_accounts(report_t& _report, const string& _format,
|
||||
const optional<string>& _prepend_format = none);
|
||||
const optional<string>& _prepend_format = none,
|
||||
std::size_t _prepend_width = 0);
|
||||
virtual ~format_accounts() {
|
||||
TRACE_DTOR(format_accounts);
|
||||
}
|
||||
|
|
@ -97,10 +118,101 @@ public:
|
|||
std::pair<std::size_t, std::size_t>
|
||||
mark_accounts(account_t& account, const bool flat);
|
||||
|
||||
virtual void title(const string& str) {
|
||||
report_title = str;
|
||||
}
|
||||
|
||||
virtual std::size_t post_account(account_t& account, const bool flat);
|
||||
virtual void flush();
|
||||
|
||||
virtual void operator()(account_t& account);
|
||||
|
||||
virtual void clear() {
|
||||
disp_pred.mark_uncompiled();
|
||||
posted_accounts.clear();
|
||||
|
||||
report_title = "";
|
||||
|
||||
item_handler<account_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class report_accounts : public item_handler<post_t>
|
||||
{
|
||||
protected:
|
||||
report_t& report;
|
||||
|
||||
std::map<account_t *, std::size_t> accounts;
|
||||
|
||||
typedef std::map<account_t *, std::size_t>::value_type accounts_pair;
|
||||
|
||||
public:
|
||||
report_accounts(report_t& _report) : report(_report) {
|
||||
TRACE_CTOR(report_accounts, "report&");
|
||||
}
|
||||
virtual ~report_accounts() {
|
||||
TRACE_DTOR(report_accounts);
|
||||
}
|
||||
|
||||
virtual void flush();
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
accounts.clear();
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class report_payees : public item_handler<post_t>
|
||||
{
|
||||
protected:
|
||||
report_t& report;
|
||||
|
||||
std::map<string, std::size_t> payees;
|
||||
|
||||
typedef std::map<string, std::size_t>::value_type payees_pair;
|
||||
|
||||
public:
|
||||
report_payees(report_t& _report) : report(_report) {
|
||||
TRACE_CTOR(report_payees, "report&");
|
||||
}
|
||||
virtual ~report_payees() {
|
||||
TRACE_DTOR(report_payees);
|
||||
}
|
||||
|
||||
virtual void flush();
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
payees.clear();
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class report_commodities : public item_handler<post_t>
|
||||
{
|
||||
protected:
|
||||
report_t& report;
|
||||
|
||||
std::map<commodity_t *, std::size_t> commodities;
|
||||
|
||||
typedef std::map<commodity_t *, std::size_t>::value_type commodities_pair;
|
||||
|
||||
public:
|
||||
report_commodities(report_t& _report) : report(_report) {
|
||||
TRACE_CTOR(report_commodities, "report&");
|
||||
}
|
||||
virtual ~report_commodities() {
|
||||
TRACE_DTOR(report_commodities);
|
||||
}
|
||||
|
||||
virtual void flush();
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
commodities.clear();
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
|
|
@ -79,9 +79,7 @@ expr_t::parser_t::parse_value_term(std::istream& in,
|
|||
case token_t::LPAREN:
|
||||
node = parse_value_expr(in, tflags.plus_flags(PARSE_PARTIAL)
|
||||
.minus_flags(PARSE_SINGLE));
|
||||
tok = next_token(in, tflags);
|
||||
if (tok.kind != token_t::RPAREN)
|
||||
tok.expected(')');
|
||||
tok = next_token(in, tflags, ')');
|
||||
|
||||
if (node->kind == op_t::O_CONS) {
|
||||
ptr_op_t prev(node);
|
||||
|
|
@ -383,10 +381,7 @@ expr_t::parser_t::parse_querycolon_expr(std::istream& in,
|
|||
throw_(parse_error,
|
||||
_("%1 operator not followed by argument") << tok.symbol);
|
||||
|
||||
token_t& next_tok = next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT));
|
||||
if (next_tok.kind != token_t::COLON)
|
||||
next_tok.expected(':');
|
||||
|
||||
next_token(in, tflags.plus_flags(PARSE_OP_CONTEXT), ':');
|
||||
prev = node->right();
|
||||
ptr_op_t subnode = new op_t(op_t::O_COLON);
|
||||
subnode->set_left(prev);
|
||||
|
|
|
|||
|
|
@ -52,11 +52,12 @@ class expr_t::parser_t : public noncopyable
|
|||
mutable token_t lookahead;
|
||||
mutable bool use_lookahead;
|
||||
|
||||
token_t& next_token(std::istream& in, const parse_flags_t& tflags) const {
|
||||
token_t& next_token(std::istream& in, const parse_flags_t& tflags,
|
||||
const char expecting = '\0') const {
|
||||
if (use_lookahead)
|
||||
use_lookahead = false;
|
||||
else
|
||||
lookahead.next(in, tflags);
|
||||
lookahead.next(in, tflags, expecting);
|
||||
return lookahead;
|
||||
}
|
||||
|
||||
|
|
|
|||
96
src/post.cc
96
src/post.cc
|
|
@ -142,11 +142,15 @@ namespace {
|
|||
return value_t(static_cast<scope_t *>(post.xact));
|
||||
}
|
||||
|
||||
value_t get_xact_id(post_t& post) {
|
||||
return static_cast<long>(post.xact_id());
|
||||
}
|
||||
|
||||
value_t get_code(post_t& post) {
|
||||
if (post.xact->code)
|
||||
return string_value(*post.xact->code);
|
||||
else
|
||||
return string_value(empty_string);
|
||||
return NULL_VALUE;
|
||||
}
|
||||
|
||||
value_t get_payee(post_t& post) {
|
||||
|
|
@ -154,9 +158,13 @@ namespace {
|
|||
}
|
||||
|
||||
value_t get_note(post_t& post) {
|
||||
string note = post.note ? *post.note : empty_string;
|
||||
note += post.xact->note ? *post.xact->note : empty_string;
|
||||
return string_value(note);
|
||||
if (post.note || post.xact->note) {
|
||||
string note = post.note ? *post.note : empty_string;
|
||||
note += post.xact->note ? *post.xact->note : empty_string;
|
||||
return string_value(note);
|
||||
} else {
|
||||
return NULL_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
value_t get_magnitude(post_t& post) {
|
||||
|
|
@ -183,11 +191,21 @@ namespace {
|
|||
}
|
||||
|
||||
value_t get_commodity(post_t& post) {
|
||||
return string_value(post.amount.commodity().symbol());
|
||||
if (post.has_xdata() &&
|
||||
post.xdata().has_flags(POST_EXT_COMPOUND))
|
||||
return string_value(post.xdata().compound_value.to_amount()
|
||||
.commodity().symbol());
|
||||
else
|
||||
return string_value(post.amount.commodity().symbol());
|
||||
}
|
||||
|
||||
value_t get_commodity_is_primary(post_t& post) {
|
||||
return post.amount.commodity().has_flags(COMMODITY_PRIMARY);
|
||||
if (post.has_xdata() &&
|
||||
post.xdata().has_flags(POST_EXT_COMPOUND))
|
||||
return post.xdata().compound_value.to_amount()
|
||||
.commodity().has_flags(COMMODITY_PRIMARY);
|
||||
else
|
||||
return post.amount.commodity().has_flags(COMMODITY_PRIMARY);
|
||||
}
|
||||
|
||||
value_t get_has_cost(post_t& post) {
|
||||
|
|
@ -222,7 +240,7 @@ namespace {
|
|||
return 1L;
|
||||
}
|
||||
|
||||
value_t get_account(call_scope_t& scope)
|
||||
value_t account_name(call_scope_t& scope)
|
||||
{
|
||||
in_context_t<post_t> env(scope, "&v");
|
||||
|
||||
|
|
@ -265,16 +283,34 @@ namespace {
|
|||
} else {
|
||||
name = env->reported_account()->fullname();
|
||||
}
|
||||
|
||||
if (env->has_flags(POST_VIRTUAL)) {
|
||||
if (env->must_balance())
|
||||
name = string("[") + name + "]";
|
||||
else
|
||||
name = string("(") + name + ")";
|
||||
}
|
||||
return string_value(name);
|
||||
}
|
||||
|
||||
value_t get_display_account(call_scope_t& scope)
|
||||
{
|
||||
in_context_t<post_t> env(scope, "&v");
|
||||
|
||||
value_t acct = account_name(scope);
|
||||
if (acct.is_string()) {
|
||||
if (env->has_flags(POST_VIRTUAL)) {
|
||||
if (env->must_balance())
|
||||
acct = string_value(string("[") + acct.as_string() + "]");
|
||||
else
|
||||
acct = string_value(string("(") + acct.as_string() + ")");
|
||||
}
|
||||
}
|
||||
return acct;
|
||||
}
|
||||
|
||||
value_t get_account(call_scope_t& scope)
|
||||
{
|
||||
return account_name(scope);
|
||||
}
|
||||
|
||||
value_t get_account_id(post_t& post) {
|
||||
return static_cast<long>(post.account_id());
|
||||
}
|
||||
|
||||
value_t get_account_base(post_t& post) {
|
||||
return string_value(post.reported_account()->name);
|
||||
}
|
||||
|
|
@ -351,6 +387,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
|
|||
return WRAP_FUNCTOR(get_account);
|
||||
else if (name == "account_base")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
|
||||
else if (name == "account_id")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_account_id>);
|
||||
else if (name == "any")
|
||||
return WRAP_FUNCTOR(&fn_any);
|
||||
else if (name == "all")
|
||||
|
|
@ -378,7 +416,9 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
|
|||
break;
|
||||
|
||||
case 'd':
|
||||
if (name == "depth")
|
||||
if (name == "display_account")
|
||||
return WRAP_FUNCTOR(get_display_account);
|
||||
else if (name == "depth")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_account_depth>);
|
||||
else if (name == "datetime")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_datetime>);
|
||||
|
|
@ -444,6 +484,8 @@ expr_t::ptr_op_t post_t::lookup(const symbol_t::kind_t kind,
|
|||
case 'x':
|
||||
if (name == "xact")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_xact>);
|
||||
else if (name == "xact_id")
|
||||
return WRAP_FUNCTOR(get_wrapper<&get_xact_id>);
|
||||
break;
|
||||
|
||||
case 'N':
|
||||
|
|
@ -479,6 +521,30 @@ amount_t post_t::resolve_expr(scope_t& scope, expr_t& expr)
|
|||
}
|
||||
}
|
||||
|
||||
std::size_t post_t::xact_id() const
|
||||
{
|
||||
std::size_t id = 1;
|
||||
foreach (post_t * p, xact->posts) {
|
||||
if (p == this)
|
||||
return id;
|
||||
id++;
|
||||
}
|
||||
assert(! "Failed to find posting within its transaction");
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t post_t::account_id() const
|
||||
{
|
||||
std::size_t id = 1;
|
||||
foreach (post_t * p, account->posts) {
|
||||
if (p == this)
|
||||
return id;
|
||||
id++;
|
||||
}
|
||||
assert(! "Failed to find posting within its transaction");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool post_t::valid() const
|
||||
{
|
||||
if (! xact) {
|
||||
|
|
|
|||
|
|
@ -118,6 +118,9 @@ public:
|
|||
|
||||
amount_t resolve_expr(scope_t& scope, expr_t& expr);
|
||||
|
||||
std::size_t xact_id() const;
|
||||
std::size_t account_id() const;
|
||||
|
||||
bool valid() const;
|
||||
|
||||
struct xdata_t : public supports_flags<uint_least16_t>
|
||||
|
|
|
|||
104
src/print.cc
104
src/print.cc
|
|
@ -42,15 +42,20 @@ namespace ledger {
|
|||
|
||||
print_xacts::print_xacts(report_t& _report,
|
||||
bool _print_raw)
|
||||
: report(_report), print_raw(_print_raw)
|
||||
: report(_report), print_raw(_print_raw), first_title(true)
|
||||
{
|
||||
TRACE_CTOR(print_xacts, "report&, bool");
|
||||
}
|
||||
|
||||
namespace {
|
||||
void print_note(std::ostream& out, const string& note)
|
||||
void print_note(std::ostream& out,
|
||||
const string& note,
|
||||
const std::size_t columns,
|
||||
const std::size_t prior_width)
|
||||
{
|
||||
if (note.length() > 15)
|
||||
// The 4 is for four leading spaces at the beginning of the posting, and
|
||||
// the 3 is for two spaces and a semi-colon before the note.
|
||||
if (columns > 0 && note.length() > columns - (prior_width + 3))
|
||||
out << "\n ;";
|
||||
else
|
||||
out << " ;";
|
||||
|
|
@ -71,23 +76,40 @@ namespace {
|
|||
|
||||
void print_xact(report_t& report, std::ostream& out, xact_t& xact)
|
||||
{
|
||||
out << format_date(item_t::use_effective_date ?
|
||||
xact.date() : xact.actual_date(),
|
||||
FMT_WRITTEN);
|
||||
if (! item_t::use_effective_date && xact.effective_date())
|
||||
out << '=' << format_date(*xact.effective_date(), FMT_WRITTEN);
|
||||
out << ' ';
|
||||
format_type_t format_type = FMT_WRITTEN;
|
||||
optional<const char *> format;
|
||||
|
||||
out << (xact.state() == item_t::CLEARED ? "* " :
|
||||
if (report.HANDLED(date_format_)) {
|
||||
format_type = FMT_CUSTOM;
|
||||
format = report.HANDLER(date_format_).str().c_str();
|
||||
}
|
||||
|
||||
std::ostringstream buf;
|
||||
|
||||
buf << format_date(item_t::use_effective_date ?
|
||||
xact.date() : xact.actual_date(),
|
||||
format_type, format);
|
||||
if (! item_t::use_effective_date && xact.effective_date())
|
||||
buf << '=' << format_date(*xact.effective_date(),
|
||||
format_type, format);
|
||||
buf << ' ';
|
||||
|
||||
buf << (xact.state() == item_t::CLEARED ? "* " :
|
||||
(xact.state() == item_t::PENDING ? "! " : ""));
|
||||
|
||||
if (xact.code)
|
||||
out << '(' << *xact.code << ") ";
|
||||
buf << '(' << *xact.code << ") ";
|
||||
|
||||
out << xact.payee;
|
||||
buf << xact.payee;
|
||||
|
||||
string leader = buf.str();
|
||||
out << leader;
|
||||
|
||||
std::size_t columns = (report.HANDLED(columns_) ?
|
||||
report.HANDLER(columns_).value.to_long() : 80);
|
||||
|
||||
if (xact.note)
|
||||
print_note(out, *xact.note);
|
||||
print_note(out, *xact.note, columns, unistring(leader).length());
|
||||
out << '\n';
|
||||
|
||||
if (xact.metadata) {
|
||||
|
|
@ -132,20 +154,37 @@ namespace {
|
|||
buf << ')';
|
||||
}
|
||||
|
||||
if (! post->has_flags(POST_CALCULATED) || report.HANDLED(print_virtual)) {
|
||||
unistring name(buf.str());
|
||||
unistring name(buf.str());
|
||||
|
||||
std::size_t account_width =
|
||||
(report.HANDLER(account_width_).specified ?
|
||||
report.HANDLER(account_width_).value.to_long() : 36);
|
||||
|
||||
if (account_width < name.length())
|
||||
account_width = name.length();
|
||||
|
||||
if (! post->has_flags(POST_CALCULATED) || report.HANDLED(print_virtual)) {
|
||||
out << name.extract();
|
||||
int slip = 36 - static_cast<int>(name.length());
|
||||
if (slip > 0)
|
||||
out << string(slip, ' ');
|
||||
int slip = (static_cast<int>(account_width) -
|
||||
static_cast<int>(name.length()));
|
||||
if (slip > 0) {
|
||||
out.width(slip);
|
||||
out << ' ';
|
||||
}
|
||||
|
||||
std::ostringstream amtbuf;
|
||||
|
||||
string amt;
|
||||
if (post->amount_expr) {
|
||||
amt = post->amount_expr->text();
|
||||
} else {
|
||||
std::size_t amount_width =
|
||||
(report.HANDLER(amount_width_).specified ?
|
||||
report.HANDLER(amount_width_).value.to_long() : 12);
|
||||
|
||||
std::ostringstream amt_str;
|
||||
report.scrub(post->amount).print(amt_str, 12, -1, true);
|
||||
report.scrub(post->amount)
|
||||
.print(amt_str, static_cast<int>(amount_width), -1, true);
|
||||
amt = amt_str.str();
|
||||
}
|
||||
|
||||
|
|
@ -154,29 +193,44 @@ namespace {
|
|||
int amt_slip = (static_cast<int>(amt.length()) -
|
||||
static_cast<int>(trimmed_amt.length()));
|
||||
if (slip + amt_slip < 2)
|
||||
out << string(2 - (slip + amt_slip), ' ');
|
||||
out << amt;
|
||||
amtbuf << string(2 - (slip + amt_slip), ' ');
|
||||
amtbuf << amt;
|
||||
|
||||
if (post->cost && ! post->has_flags(POST_CALCULATED)) {
|
||||
if (post->has_flags(POST_COST_IN_FULL))
|
||||
out << " @@ " << report.scrub(post->cost->abs());
|
||||
amtbuf << " @@ " << report.scrub(post->cost->abs());
|
||||
else
|
||||
out << " @ " << report.scrub((*post->cost / post->amount).abs());
|
||||
amtbuf << " @ " << report.scrub((*post->cost / post->amount).abs());
|
||||
}
|
||||
|
||||
if (post->assigned_amount)
|
||||
out << " = " << report.scrub(*post->assigned_amount);
|
||||
amtbuf << " = " << report.scrub(*post->assigned_amount);
|
||||
|
||||
string trailer = amtbuf.str();
|
||||
out << trailer;
|
||||
|
||||
account_width += unistring(trailer).length();
|
||||
} else {
|
||||
out << buf.str();
|
||||
}
|
||||
|
||||
if (post->note)
|
||||
print_note(out, *post->note);
|
||||
print_note(out, *post->note, columns, 4 + account_width);
|
||||
out << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void print_xacts::title(const string&)
|
||||
{
|
||||
if (first_title) {
|
||||
first_title = false;
|
||||
} else {
|
||||
std::ostream& out(report.output_stream);
|
||||
out << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void print_xacts::flush()
|
||||
{
|
||||
std::ostream& out(report.output_stream);
|
||||
|
|
|
|||
10
src/print.h
10
src/print.h
|
|
@ -62,6 +62,7 @@ protected:
|
|||
xacts_present_map xacts_present;
|
||||
xacts_list xacts;
|
||||
bool print_raw;
|
||||
bool first_title;
|
||||
|
||||
public:
|
||||
print_xacts(report_t& _report, bool _print_raw = false);
|
||||
|
|
@ -69,8 +70,17 @@ public:
|
|||
TRACE_DTOR(print_xacts);
|
||||
}
|
||||
|
||||
virtual void title(const string&);
|
||||
|
||||
virtual void flush();
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
xacts_present.clear();
|
||||
xacts.clear();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -207,11 +207,11 @@ void export_account()
|
|||
.def("__len__", accounts_len)
|
||||
.def("__getitem__", accounts_getitem, return_internal_reference<>())
|
||||
|
||||
.def("__iter__", range<return_internal_reference<> >
|
||||
.def("__iter__", python::range<return_internal_reference<> >
|
||||
(&account_t::accounts_begin, &account_t::accounts_end))
|
||||
.def("accounts", range<return_internal_reference<> >
|
||||
.def("accounts", python::range<return_internal_reference<> >
|
||||
(&account_t::accounts_begin, &account_t::accounts_end))
|
||||
.def("posts", range<return_internal_reference<> >
|
||||
.def("posts", python::range<return_internal_reference<> >
|
||||
(&account_t::posts_begin, &account_t::posts_end))
|
||||
|
||||
.def("has_xdata", &account_t::has_xdata)
|
||||
|
|
|
|||
|
|
@ -295,13 +295,16 @@ void export_commodity()
|
|||
.def("keys", py_pool_keys)
|
||||
.def("has_key", py_pool_contains)
|
||||
.def("__contains__", py_pool_contains)
|
||||
.def("__iter__", range<return_value_policy<reference_existing_object> >
|
||||
.def("__iter__",
|
||||
python::range<return_value_policy<reference_existing_object> >
|
||||
(py_pool_commodities_begin, py_pool_commodities_end))
|
||||
.def("iteritems", range<return_value_policy<reference_existing_object> >
|
||||
.def("iteritems",
|
||||
python::range<return_value_policy<reference_existing_object> >
|
||||
(py_pool_commodities_begin, py_pool_commodities_end))
|
||||
.def("iterkeys", range<>(py_pool_commodities_keys_begin,
|
||||
py_pool_commodities_keys_end))
|
||||
.def("itervalues", range<return_value_policy<reference_existing_object> >
|
||||
.def("iterkeys", python::range<>(py_pool_commodities_keys_begin,
|
||||
py_pool_commodities_keys_end))
|
||||
.def("itervalues",
|
||||
python::range<return_value_policy<reference_existing_object> >
|
||||
(py_pool_commodities_values_begin, py_pool_commodities_values_end))
|
||||
;
|
||||
|
||||
|
|
@ -309,16 +312,16 @@ void export_commodity()
|
|||
|
||||
scope().attr("commodities") = commodity_pool_t::current_pool;
|
||||
|
||||
scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS;
|
||||
scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED;
|
||||
scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED;
|
||||
scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN;
|
||||
scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS;
|
||||
scope().attr("COMMODITY_NOMARKET") = COMMODITY_NOMARKET;
|
||||
scope().attr("COMMODITY_BUILTIN") = COMMODITY_BUILTIN;
|
||||
scope().attr("COMMODITY_WALKED") = COMMODITY_WALKED;
|
||||
scope().attr("COMMODITY_KNOWN") = COMMODITY_KNOWN;
|
||||
scope().attr("COMMODITY_PRIMARY") = COMMODITY_PRIMARY;
|
||||
scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS;
|
||||
scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED;
|
||||
scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED;
|
||||
scope().attr("COMMODITY_STYLE_DECIMAL_COMMA") = COMMODITY_STYLE_DECIMAL_COMMA;
|
||||
scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS;
|
||||
scope().attr("COMMODITY_NOMARKET") = COMMODITY_NOMARKET;
|
||||
scope().attr("COMMODITY_BUILTIN") = COMMODITY_BUILTIN;
|
||||
scope().attr("COMMODITY_WALKED") = COMMODITY_WALKED;
|
||||
scope().attr("COMMODITY_KNOWN") = COMMODITY_KNOWN;
|
||||
scope().attr("COMMODITY_PRIMARY") = COMMODITY_PRIMARY;
|
||||
|
||||
class_< commodity_t, boost::noncopyable > ("Commodity", no_init)
|
||||
#if 1
|
||||
|
|
@ -331,9 +334,9 @@ void export_commodity()
|
|||
.def("drop_flags", &delegates_flags<uint_least16_t>::drop_flags)
|
||||
#endif
|
||||
|
||||
.add_static_property("european_by_default",
|
||||
make_getter(&commodity_t::european_by_default),
|
||||
make_setter(&commodity_t::european_by_default))
|
||||
.add_static_property("decimal_comma_by_default",
|
||||
make_getter(&commodity_t::decimal_comma_by_default),
|
||||
make_setter(&commodity_t::decimal_comma_by_default))
|
||||
|
||||
.def("__str__", &commodity_t::symbol)
|
||||
.def("__unicode__", py_commodity_unicode)
|
||||
|
|
|
|||
|
|
@ -226,8 +226,8 @@ void export_journal()
|
|||
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<1,
|
||||
with_custodian_and_ward_postcall<1, 0> > >
|
||||
.def("__iter__", python::range<return_internal_reference<1,
|
||||
with_custodian_and_ward_postcall<1, 0> > >
|
||||
(&collect_posts::begin, &collect_posts::end))
|
||||
;
|
||||
|
||||
|
|
@ -236,8 +236,9 @@ void export_journal()
|
|||
.def("__len__", &collector_wrapper::length)
|
||||
.def("__getitem__", posts_getitem, return_internal_reference<1,
|
||||
with_custodian_and_ward_postcall<0, 1> >())
|
||||
.def("__iter__", range<return_value_policy<reference_existing_object,
|
||||
with_custodian_and_ward_postcall<0, 1> > >
|
||||
.def("__iter__",
|
||||
python::range<return_value_policy<reference_existing_object,
|
||||
with_custodian_and_ward_postcall<0, 1> > >
|
||||
(&collector_wrapper::begin, &collector_wrapper::end))
|
||||
;
|
||||
|
||||
|
|
@ -296,15 +297,15 @@ void export_journal()
|
|||
with_custodian_and_ward_postcall<0, 1> >())
|
||||
#endif
|
||||
|
||||
.def("__iter__", range<return_internal_reference<> >
|
||||
.def("__iter__", python::range<return_internal_reference<> >
|
||||
(&journal_t::xacts_begin, &journal_t::xacts_end))
|
||||
.def("xacts", range<return_internal_reference<> >
|
||||
.def("xacts", python::range<return_internal_reference<> >
|
||||
(&journal_t::xacts_begin, &journal_t::xacts_end))
|
||||
.def("auto_xacts", range<return_internal_reference<> >
|
||||
.def("auto_xacts", python::range<return_internal_reference<> >
|
||||
(&journal_t::auto_xacts_begin, &journal_t::auto_xacts_end))
|
||||
.def("period_xacts", range<return_internal_reference<> >
|
||||
.def("period_xacts", python::range<return_internal_reference<> >
|
||||
(&journal_t::period_xacts_begin, &journal_t::period_xacts_end))
|
||||
.def("sources", range<return_internal_reference<> >
|
||||
.def("sources", python::range<return_internal_reference<> >
|
||||
(&journal_t::sources_begin, &journal_t::sources_end))
|
||||
|
||||
.def("read", py_read)
|
||||
|
|
|
|||
|
|
@ -98,9 +98,9 @@ void export_xact()
|
|||
|
||||
.def("finalize", &xact_base_t::finalize)
|
||||
|
||||
.def("__iter__", range<return_internal_reference<> >
|
||||
.def("__iter__", python::range<return_internal_reference<> >
|
||||
(&xact_t::posts_begin, &xact_t::posts_end))
|
||||
.def("posts", range<return_internal_reference<> >
|
||||
.def("posts", python::range<return_internal_reference<> >
|
||||
(&xact_t::posts_begin, &xact_t::posts_end))
|
||||
|
||||
.def("valid", &xact_base_t::valid)
|
||||
|
|
|
|||
61
src/query.cc
61
src/query.cc
|
|
@ -53,10 +53,39 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
|
|||
}
|
||||
}
|
||||
|
||||
if (consume_next_arg) {
|
||||
switch (*arg_i) {
|
||||
case '\'':
|
||||
case '"':
|
||||
case '/': {
|
||||
string pat;
|
||||
char closing = *arg_i;
|
||||
bool found_closing = false;
|
||||
for (++arg_i; arg_i != arg_end; ++arg_i) {
|
||||
if (*arg_i == '\\') {
|
||||
if (++arg_i == arg_end)
|
||||
throw_(parse_error, _("Unexpected '\\' at end of pattern"));
|
||||
}
|
||||
else if (*arg_i == closing) {
|
||||
++arg_i;
|
||||
found_closing = true;
|
||||
break;
|
||||
}
|
||||
pat.push_back(*arg_i);
|
||||
}
|
||||
if (! found_closing)
|
||||
throw_(parse_error, _("Expected '%1' at end of pattern") << closing);
|
||||
if (pat.empty())
|
||||
throw_(parse_error, _("Match pattern is empty"));
|
||||
|
||||
return token_t(token_t::TERM, pat);
|
||||
}
|
||||
}
|
||||
|
||||
if (multiple_args && consume_next_arg) {
|
||||
consume_next_arg = false;
|
||||
token_t tok(token_t::TERM, string(arg_i, arg_end));
|
||||
arg_i = arg_end;
|
||||
return token_t(token_t::TERM, (*begin).as_string());
|
||||
return tok;
|
||||
}
|
||||
|
||||
resume:
|
||||
|
|
@ -70,29 +99,6 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
|
|||
return next_token();
|
||||
goto resume;
|
||||
|
||||
case '/': {
|
||||
string pat;
|
||||
bool found_end_slash = false;
|
||||
for (++arg_i; arg_i != arg_end; ++arg_i) {
|
||||
if (*arg_i == '\\') {
|
||||
if (++arg_i == arg_end)
|
||||
throw_(parse_error, _("Unexpected '\\' at end of pattern"));
|
||||
}
|
||||
else if (*arg_i == '/') {
|
||||
++arg_i;
|
||||
found_end_slash = true;
|
||||
break;
|
||||
}
|
||||
pat.push_back(*arg_i);
|
||||
}
|
||||
if (! found_end_slash)
|
||||
throw_(parse_error, _("Expected '/' at end of pattern"));
|
||||
if (pat.empty())
|
||||
throw_(parse_error, _("Match pattern is empty"));
|
||||
|
||||
return token_t(token_t::TERM, pat);
|
||||
}
|
||||
|
||||
case '(': ++arg_i; return token_t(token_t::LPAREN);
|
||||
case ')': ++arg_i; return token_t(token_t::RPAREN);
|
||||
case '&': ++arg_i; return token_t(token_t::TOK_AND);
|
||||
|
|
@ -101,7 +107,10 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
|
|||
case '@': ++arg_i; return token_t(token_t::TOK_PAYEE);
|
||||
case '#': ++arg_i; return token_t(token_t::TOK_CODE);
|
||||
case '%': ++arg_i; return token_t(token_t::TOK_META);
|
||||
case '=': ++arg_i; return token_t(token_t::TOK_EQ);
|
||||
case '=':
|
||||
++arg_i;
|
||||
consume_next_arg = true;
|
||||
return token_t(token_t::TOK_EQ);
|
||||
|
||||
case '\\':
|
||||
consume_next = true;
|
||||
|
|
|
|||
27
src/query.h
27
src/query.h
|
|
@ -62,6 +62,7 @@ public:
|
|||
|
||||
bool consume_whitespace;
|
||||
bool consume_next_arg;
|
||||
bool multiple_args;
|
||||
|
||||
public:
|
||||
struct token_t
|
||||
|
|
@ -177,10 +178,11 @@ public:
|
|||
token_t token_cache;
|
||||
|
||||
lexer_t(value_t::sequence_t::const_iterator _begin,
|
||||
value_t::sequence_t::const_iterator _end)
|
||||
value_t::sequence_t::const_iterator _end,
|
||||
bool _multiple_args = true)
|
||||
: begin(_begin), end(_end),
|
||||
consume_whitespace(false),
|
||||
consume_next_arg(false)
|
||||
consume_whitespace(false), consume_next_arg(false),
|
||||
multiple_args(_multiple_args)
|
||||
{
|
||||
TRACE_CTOR(query_t::lexer_t, "");
|
||||
assert(begin != end);
|
||||
|
|
@ -192,6 +194,7 @@ public:
|
|||
arg_i(lexer.arg_i), arg_end(lexer.arg_end),
|
||||
consume_whitespace(lexer.consume_whitespace),
|
||||
consume_next_arg(lexer.consume_next_arg),
|
||||
multiple_args(lexer.multiple_args),
|
||||
token_cache(lexer.token_cache)
|
||||
{
|
||||
TRACE_CTOR(query_t::lexer_t, "copy");
|
||||
|
|
@ -227,8 +230,8 @@ protected:
|
|||
expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context);
|
||||
|
||||
public:
|
||||
parser_t(const value_t& _args)
|
||||
: args(_args), lexer(args.begin(), args.end()) {
|
||||
parser_t(const value_t& _args, bool multiple_args = true)
|
||||
: args(_args), lexer(args.begin(), args.end(), multiple_args) {
|
||||
TRACE_CTOR(query_t::parser_t, "");
|
||||
}
|
||||
parser_t(const parser_t& parser)
|
||||
|
|
@ -261,28 +264,30 @@ public:
|
|||
TRACE_CTOR(query_t, "copy");
|
||||
}
|
||||
query_t(const string& arg,
|
||||
const keep_details_t& _what_to_keep = keep_details_t())
|
||||
const keep_details_t& _what_to_keep = keep_details_t(),
|
||||
bool multiple_args = true)
|
||||
: predicate_t(_what_to_keep) {
|
||||
TRACE_CTOR(query_t, "string, keep_details_t");
|
||||
if (! arg.empty()) {
|
||||
value_t temp(string_value(arg));
|
||||
parse_args(temp.to_sequence());
|
||||
parse_args(temp.to_sequence(), multiple_args);
|
||||
}
|
||||
}
|
||||
query_t(const value_t& args,
|
||||
const keep_details_t& _what_to_keep = keep_details_t())
|
||||
const keep_details_t& _what_to_keep = keep_details_t(),
|
||||
bool multiple_args = true)
|
||||
: predicate_t(_what_to_keep) {
|
||||
TRACE_CTOR(query_t, "value_t, keep_details_t");
|
||||
if (! args.empty())
|
||||
parse_args(args);
|
||||
parse_args(args, multiple_args);
|
||||
}
|
||||
virtual ~query_t() {
|
||||
TRACE_DTOR(query_t);
|
||||
}
|
||||
|
||||
void parse_args(const value_t& args) {
|
||||
void parse_args(const value_t& args, bool multiple_args = true) {
|
||||
if (! parser)
|
||||
parser = parser_t(args);
|
||||
parser = parser_t(args, multiple_args);
|
||||
ptr = parser->parse(); // expr_t::ptr
|
||||
}
|
||||
|
||||
|
|
|
|||
201
src/report.cc
201
src/report.cc
|
|
@ -119,6 +119,8 @@ void report_t::normalize_options(const string& verb)
|
|||
HANDLER(meta_).str() + "\"))");
|
||||
}
|
||||
}
|
||||
if (! HANDLED(prepend_width_))
|
||||
HANDLER(prepend_width_).on_with(string("?normalize"), static_cast<long>(0));
|
||||
|
||||
if (verb == "print" || verb == "xact" || verb == "dump") {
|
||||
HANDLER(related).on_only(string("?normalize"));
|
||||
|
|
@ -143,9 +145,6 @@ void report_t::normalize_options(const string& verb)
|
|||
// 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());
|
||||
|
||||
optional<date_t> begin = interval.begin(session.current_year);
|
||||
|
|
@ -162,6 +161,8 @@ void report_t::normalize_options(const string& verb)
|
|||
|
||||
if (! interval.duration)
|
||||
HANDLER(period_).off();
|
||||
else if (! HANDLED(sort_all_))
|
||||
HANDLER(sort_xacts_).on_only(string("?normalize"));
|
||||
}
|
||||
|
||||
// If -j or -J were specified, set the appropriate format string now so as
|
||||
|
|
@ -273,77 +274,158 @@ void report_t::parse_query_args(const value_t& args, const string& whence)
|
|||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct posts_flusher
|
||||
{
|
||||
report_t& report;
|
||||
post_handler_ptr handler;
|
||||
|
||||
posts_flusher(report_t& _report, post_handler_ptr _handler)
|
||||
: report(_report), handler(_handler) {}
|
||||
|
||||
void operator()(const value_t&) {
|
||||
report.session.journal->clear_xdata();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void report_t::posts_report(post_handler_ptr handler)
|
||||
{
|
||||
handler = chain_post_handlers(*this, handler);
|
||||
if (HANDLED(group_by_)) {
|
||||
std::auto_ptr<post_splitter>
|
||||
splitter(new post_splitter(*this, handler, HANDLER(group_by_).expr));
|
||||
splitter->set_postflush_func(posts_flusher(*this, handler));
|
||||
handler = post_handler_ptr(splitter.release());
|
||||
}
|
||||
handler = chain_pre_post_handlers(*this, handler);
|
||||
|
||||
journal_posts_iterator walker(*session.journal.get());
|
||||
pass_down_posts(chain_post_handlers(*this, handler), walker);
|
||||
session.journal->clear_xdata();
|
||||
pass_down_posts(handler, walker);
|
||||
|
||||
if (! HANDLED(group_by_))
|
||||
posts_flusher(*this, handler)(value_t());
|
||||
}
|
||||
|
||||
void report_t::generate_report(post_handler_ptr handler)
|
||||
{
|
||||
HANDLER(limit_).on(string("#generate"), "actual");
|
||||
|
||||
handler = chain_handlers(*this, handler);
|
||||
|
||||
generate_posts_iterator walker
|
||||
(session, HANDLED(seed_) ?
|
||||
static_cast<unsigned int>(HANDLER(seed_).value.to_long()) : 0,
|
||||
HANDLED(head_) ?
|
||||
static_cast<unsigned int>(HANDLER(head_).value.to_long()) : 50);
|
||||
|
||||
pass_down_posts(chain_post_handlers(*this, handler), walker);
|
||||
pass_down_posts(handler, walker);
|
||||
}
|
||||
|
||||
void report_t::xact_report(post_handler_ptr handler, xact_t& xact)
|
||||
{
|
||||
handler = chain_handlers(*this, handler);
|
||||
|
||||
xact_posts_iterator walker(xact);
|
||||
pass_down_posts(chain_post_handlers(*this, handler), walker);
|
||||
pass_down_posts(handler, walker);
|
||||
|
||||
xact.clear_xdata();
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct accounts_title_printer
|
||||
{
|
||||
report_t& report;
|
||||
acct_handler_ptr handler;
|
||||
|
||||
accounts_title_printer(report_t& _report, acct_handler_ptr _handler)
|
||||
: report(_report), handler(_handler) {}
|
||||
|
||||
void operator()(const value_t& val)
|
||||
{
|
||||
if (! report.HANDLED(no_titles)) {
|
||||
std::ostringstream buf;
|
||||
val.print(buf);
|
||||
handler->title(buf.str());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct accounts_flusher
|
||||
{
|
||||
report_t& report;
|
||||
acct_handler_ptr handler;
|
||||
|
||||
accounts_flusher(report_t& _report, acct_handler_ptr _handler)
|
||||
: report(_report), handler(_handler) {}
|
||||
|
||||
void operator()(const value_t&)
|
||||
{
|
||||
report.HANDLER(amount_).expr.mark_uncompiled();
|
||||
report.HANDLER(total_).expr.mark_uncompiled();
|
||||
report.HANDLER(display_amount_).expr.mark_uncompiled();
|
||||
report.HANDLER(display_total_).expr.mark_uncompiled();
|
||||
report.HANDLER(revalued_total_).expr.mark_uncompiled();
|
||||
|
||||
scoped_ptr<accounts_iterator> iter;
|
||||
if (! report.HANDLED(sort_)) {
|
||||
iter.reset(new basic_accounts_iterator(*report.session.journal->master));
|
||||
} else {
|
||||
expr_t sort_expr(report.HANDLER(sort_).str());
|
||||
sort_expr.set_context(&report);
|
||||
iter.reset(new sorted_accounts_iterator(*report.session.journal->master,
|
||||
sort_expr, report.HANDLED(flat)));
|
||||
}
|
||||
|
||||
if (report.HANDLED(display_)) {
|
||||
DEBUG("report.predicate",
|
||||
"Display predicate = " << report.HANDLER(display_).str());
|
||||
pass_down_accounts(handler, *iter.get(),
|
||||
predicate_t(report.HANDLER(display_).str(),
|
||||
report.what_to_keep()),
|
||||
report);
|
||||
} else {
|
||||
pass_down_accounts(handler, *iter.get());
|
||||
}
|
||||
|
||||
report.session.journal->clear_xdata();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void report_t::accounts_report(acct_handler_ptr handler)
|
||||
{
|
||||
journal_posts_iterator walker(*session.journal.get());
|
||||
post_handler_ptr chain =
|
||||
chain_post_handlers(*this, post_handler_ptr(new ignore_posts),
|
||||
/* for_accounts_report= */ true);
|
||||
if (HANDLED(group_by_)) {
|
||||
std::auto_ptr<post_splitter>
|
||||
splitter(new post_splitter(*this, chain, HANDLER(group_by_).expr));
|
||||
|
||||
splitter->set_preflush_func(accounts_title_printer(*this, handler));
|
||||
splitter->set_postflush_func(accounts_flusher(*this, handler));
|
||||
|
||||
chain = post_handler_ptr(splitter.release());
|
||||
}
|
||||
chain = chain_pre_post_handlers(*this, chain);
|
||||
|
||||
// The lifetime of the chain object controls the lifetime of all temporary
|
||||
// objects created within it during the call to pass_down_posts, which will
|
||||
// be needed later by the pass_down_accounts.
|
||||
post_handler_ptr chain =
|
||||
chain_post_handlers(*this, post_handler_ptr(new ignore_posts), true);
|
||||
journal_posts_iterator walker(*session.journal.get());
|
||||
pass_down_posts(chain, walker);
|
||||
|
||||
HANDLER(amount_).expr.mark_uncompiled();
|
||||
HANDLER(total_).expr.mark_uncompiled();
|
||||
HANDLER(display_amount_).expr.mark_uncompiled();
|
||||
HANDLER(display_total_).expr.mark_uncompiled();
|
||||
HANDLER(revalued_total_).expr.mark_uncompiled();
|
||||
|
||||
scoped_ptr<accounts_iterator> iter;
|
||||
if (! HANDLED(sort_)) {
|
||||
iter.reset(new basic_accounts_iterator(*session.journal->master));
|
||||
} else {
|
||||
expr_t sort_expr(HANDLER(sort_).str());
|
||||
sort_expr.set_context(this);
|
||||
iter.reset(new sorted_accounts_iterator(*session.journal->master,
|
||||
sort_expr, HANDLED(flat)));
|
||||
}
|
||||
|
||||
if (HANDLED(display_)) {
|
||||
DEBUG("report.predicate",
|
||||
"Display predicate = " << HANDLER(display_).str());
|
||||
pass_down_accounts(handler, *iter.get(),
|
||||
predicate_t(HANDLER(display_).str(), what_to_keep()),
|
||||
*this);
|
||||
} else {
|
||||
pass_down_accounts(handler, *iter.get());
|
||||
}
|
||||
|
||||
session.journal->clear_xdata();
|
||||
if (! HANDLED(group_by_))
|
||||
accounts_flusher(*this, handler)(value_t());
|
||||
}
|
||||
|
||||
void report_t::commodities_report(post_handler_ptr handler)
|
||||
{
|
||||
handler = chain_handlers(*this, handler);
|
||||
|
||||
posts_commodities_iterator walker(*session.journal.get());
|
||||
pass_down_posts(chain_post_handlers(*this, handler), walker);
|
||||
pass_down_posts(handler, walker);
|
||||
|
||||
session.journal->clear_xdata();
|
||||
}
|
||||
|
||||
|
|
@ -853,6 +935,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
|
|||
else OPT(columns_);
|
||||
else OPT_ALT(basis, cost);
|
||||
else OPT_(current);
|
||||
else OPT(count);
|
||||
break;
|
||||
case 'd':
|
||||
OPT(daily);
|
||||
|
|
@ -886,6 +969,8 @@ option_t<report_t> * report_t::lookup_option(const char * p)
|
|||
break;
|
||||
case 'g':
|
||||
OPT(gain);
|
||||
else OPT(group_by_);
|
||||
else OPT(group_title_format_);
|
||||
break;
|
||||
case 'h':
|
||||
OPT(head_);
|
||||
|
|
@ -914,6 +999,8 @@ option_t<report_t> * report_t::lookup_option(const char * p)
|
|||
case 'n':
|
||||
OPT_CH(collapse);
|
||||
else OPT(no_color);
|
||||
else OPT(no_rounding);
|
||||
else OPT(no_titles);
|
||||
else OPT(no_total);
|
||||
else OPT(now_);
|
||||
break;
|
||||
|
|
@ -936,6 +1023,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
|
|||
else OPT(pricedb_format_);
|
||||
else OPT(payee_width_);
|
||||
else OPT(prepend_format_);
|
||||
else OPT(prepend_width_);
|
||||
else OPT(print_virtual);
|
||||
break;
|
||||
case 'q':
|
||||
|
|
@ -1222,12 +1310,19 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
|
|||
|
||||
case symbol_t::COMMAND:
|
||||
switch (*p) {
|
||||
case 'a':
|
||||
if (is_eq(p, "accounts"))
|
||||
return WRAP_FUNCTOR(reporter<>(new report_accounts(*this), *this,
|
||||
"#accounts"));
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
if (*(p + 1) == '\0' || is_eq(p, "bal") || is_eq(p, "balance")) {
|
||||
return expr_t::op_t::wrap_functor
|
||||
(reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
|
||||
(new format_accounts(*this, report_format(HANDLER(balance_format_)),
|
||||
maybe_format(HANDLER(prepend_format_))),
|
||||
maybe_format(HANDLER(prepend_format_)),
|
||||
HANDLER(prepend_width_).value.to_long()),
|
||||
*this, "#balance"));
|
||||
}
|
||||
else if (is_eq(p, "budget")) {
|
||||
|
|
@ -1240,7 +1335,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
|
|||
return expr_t::op_t::wrap_functor
|
||||
(reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
|
||||
(new format_accounts(*this, report_format(HANDLER(budget_format_)),
|
||||
maybe_format(HANDLER(prepend_format_))),
|
||||
maybe_format(HANDLER(prepend_format_)),
|
||||
HANDLER(prepend_width_).value.to_long()),
|
||||
*this, "#budget"));
|
||||
}
|
||||
break;
|
||||
|
|
@ -1250,7 +1346,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
|
|||
return WRAP_FUNCTOR
|
||||
(reporter<>
|
||||
(new format_posts(*this, report_format(HANDLER(csv_format_)),
|
||||
maybe_format(HANDLER(prepend_format_))),
|
||||
maybe_format(HANDLER(prepend_format_)),
|
||||
HANDLER(prepend_width_).value.to_long()),
|
||||
*this, "#csv"));
|
||||
}
|
||||
else if (is_eq(p, "cleared")) {
|
||||
|
|
@ -1259,11 +1356,17 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
|
|||
return expr_t::op_t::wrap_functor
|
||||
(reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
|
||||
(new format_accounts(*this, report_format(HANDLER(cleared_format_)),
|
||||
maybe_format(HANDLER(prepend_format_))),
|
||||
maybe_format(HANDLER(prepend_format_)),
|
||||
HANDLER(prepend_width_).value.to_long()),
|
||||
*this, "#cleared"));
|
||||
}
|
||||
else if (is_eq(p, "convert"))
|
||||
else if (is_eq(p, "convert")) {
|
||||
return WRAP_FUNCTOR(convert_command);
|
||||
}
|
||||
else if (is_eq(p, "commodities")) {
|
||||
return WRAP_FUNCTOR(reporter<>(new report_commodities(*this), *this,
|
||||
"#commodities"));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
|
|
@ -1288,14 +1391,19 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
|
|||
return expr_t::op_t::wrap_functor
|
||||
(reporter<post_t, post_handler_ptr, &report_t::commodities_report>
|
||||
(new format_posts(*this, report_format(HANDLER(prices_format_)),
|
||||
maybe_format(HANDLER(prepend_format_))),
|
||||
maybe_format(HANDLER(prepend_format_)),
|
||||
HANDLER(prepend_width_).value.to_long()),
|
||||
*this, "#prices"));
|
||||
else if (is_eq(p, "pricedb"))
|
||||
return expr_t::op_t::wrap_functor
|
||||
(reporter<post_t, post_handler_ptr, &report_t::commodities_report>
|
||||
(new format_posts(*this, report_format(HANDLER(pricedb_format_)),
|
||||
maybe_format(HANDLER(prepend_format_))),
|
||||
maybe_format(HANDLER(prepend_format_)),
|
||||
HANDLER(prepend_width_).value.to_long()),
|
||||
*this, "#pricedb"));
|
||||
else if (is_eq(p, "payees"))
|
||||
return WRAP_FUNCTOR(reporter<>(new report_payees(*this), *this,
|
||||
"#payees"));
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
|
|
@ -1303,7 +1411,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
|
|||
return WRAP_FUNCTOR
|
||||
(reporter<>
|
||||
(new format_posts(*this, report_format(HANDLER(register_format_)),
|
||||
maybe_format(HANDLER(prepend_format_))),
|
||||
maybe_format(HANDLER(prepend_format_)),
|
||||
HANDLER(prepend_width_).value.to_long()),
|
||||
*this, "#register"));
|
||||
else if (is_eq(p, "reload"))
|
||||
return MAKE_FUNCTOR(report_t::reload_command);
|
||||
|
|
|
|||
55
src/report.h
55
src/report.h
|
|
@ -256,6 +256,8 @@ public:
|
|||
HANDLER(forecast_years_).report(out);
|
||||
HANDLER(format_).report(out);
|
||||
HANDLER(gain).report(out);
|
||||
HANDLER(group_by_).report(out);
|
||||
HANDLER(group_title_format_).report(out);
|
||||
HANDLER(head_).report(out);
|
||||
HANDLER(invert).report(out);
|
||||
HANDLER(limit_).report(out);
|
||||
|
|
@ -267,6 +269,8 @@ public:
|
|||
HANDLER(market).report(out);
|
||||
HANDLER(meta_).report(out);
|
||||
HANDLER(monthly).report(out);
|
||||
HANDLER(no_rounding).report(out);
|
||||
HANDLER(no_titles).report(out);
|
||||
HANDLER(no_total).report(out);
|
||||
HANDLER(now_).report(out);
|
||||
HANDLER(only_).report(out);
|
||||
|
|
@ -280,6 +284,7 @@ public:
|
|||
HANDLER(plot_amount_format_).report(out);
|
||||
HANDLER(plot_total_format_).report(out);
|
||||
HANDLER(prepend_format_).report(out);
|
||||
HANDLER(prepend_width_).report(out);
|
||||
HANDLER(price).report(out);
|
||||
HANDLER(prices_format_).report(out);
|
||||
HANDLER(pricedb_format_).report(out);
|
||||
|
|
@ -372,7 +377,7 @@ public:
|
|||
|
||||
OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) {
|
||||
on(none,
|
||||
"%(justify(scrub(display_total), 20, -1, true, color))"
|
||||
"%(justify(scrub(display_total), 20, 20 + prepend_width, true, color))"
|
||||
" %(!options.flat ? depth_spacer : \"\")"
|
||||
"%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
|
||||
"%$1\n%/"
|
||||
|
|
@ -432,8 +437,9 @@ public:
|
|||
|
||||
OPTION__(report_t, cleared_format_, CTOR(report_t, cleared_format_) {
|
||||
on(none,
|
||||
"%(justify(scrub(get_at(total_expr, 0)), 16, -1, true, color))"
|
||||
" %(justify(scrub(get_at(total_expr, 1)), 16, -1, true, color))"
|
||||
"%(justify(scrub(get_at(total_expr, 0)), 16, 16 + prepend_width, "
|
||||
" true, color)) %(justify(scrub(get_at(total_expr, 1)), 18, "
|
||||
" 36 + prepend_width, true, color))"
|
||||
" %(latest_cleared ? format_date(latest_cleared) : \" \")"
|
||||
" %(!options.flat ? depth_spacer : \"\")"
|
||||
"%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
|
||||
|
|
@ -454,15 +460,17 @@ public:
|
|||
});
|
||||
|
||||
OPTION(report_t, columns_);
|
||||
OPTION(report_t, count);
|
||||
|
||||
OPTION__(report_t, csv_format_, CTOR(report_t, csv_format_) {
|
||||
on(none,
|
||||
"%(quoted(date)),"
|
||||
"%(quoted(payee)),"
|
||||
"%(quoted(account)),"
|
||||
"%(quoted(scrub(display_amount))),"
|
||||
"%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))),"
|
||||
"%(quoted(code)),"
|
||||
"%(quoted(payee)),"
|
||||
"%(quoted(display_account)),"
|
||||
"%(quoted(commodity)),"
|
||||
"%(quoted(quantity(scrub(display_amount)))),"
|
||||
"%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))),"
|
||||
"%(quoted(join(note | xact.note)))\n");
|
||||
});
|
||||
|
||||
|
|
@ -590,6 +598,22 @@ public:
|
|||
" - get_at(total_expr, 1)");
|
||||
});
|
||||
|
||||
OPTION__
|
||||
(report_t, group_by_,
|
||||
expr_t expr;
|
||||
CTOR(report_t, group_by_) {}
|
||||
void set_expr(const optional<string>& whence, const string& str) {
|
||||
expr = str;
|
||||
on(whence, str);
|
||||
}
|
||||
DO_(args) {
|
||||
set_expr(args[0].to_string(), args[1].to_string());
|
||||
});
|
||||
|
||||
OPTION__(report_t, group_title_format_, CTOR(report_t, group_title_format_) {
|
||||
on(none, "%(value)\n");
|
||||
});
|
||||
|
||||
OPTION(report_t, head_);
|
||||
|
||||
OPTION_(report_t, invert, DO() {
|
||||
|
|
@ -632,6 +656,8 @@ public:
|
|||
parent->HANDLER(color).off();
|
||||
});
|
||||
|
||||
OPTION(report_t, no_rounding);
|
||||
OPTION(report_t, no_titles);
|
||||
OPTION(report_t, no_total);
|
||||
|
||||
OPTION_(report_t, now_, DO_(args) {
|
||||
|
|
@ -735,6 +761,9 @@ public:
|
|||
});
|
||||
|
||||
OPTION(report_t, prepend_format_);
|
||||
OPTION_(report_t, prepend_width_, DO_(args) {
|
||||
value = args[1].to_long();
|
||||
});
|
||||
|
||||
OPTION_(report_t, price, DO() { // -I
|
||||
parent->HANDLER(display_amount_)
|
||||
|
|
@ -745,13 +774,13 @@ public:
|
|||
|
||||
OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
|
||||
on(none,
|
||||
"%(date) %-8(account) %(justify(scrub(display_amount), 12, "
|
||||
"%(date) %-8(display_account) %(justify(scrub(display_amount), 12, "
|
||||
" 2 + 9 + 8 + 12, true, color))\n");
|
||||
});
|
||||
|
||||
OPTION__(report_t, pricedb_format_, CTOR(report_t, pricedb_format_) {
|
||||
on(none,
|
||||
"P %(datetime) %(account) %(scrub(display_amount))\n");
|
||||
"P %(datetime) %(display_account) %(scrub(display_amount))\n");
|
||||
});
|
||||
|
||||
OPTION(report_t, print_virtual);
|
||||
|
|
@ -778,14 +807,14 @@ public:
|
|||
" if color & date > today))"
|
||||
" %(ansify_if(justify(truncated(payee, payee_width), payee_width), "
|
||||
" bold if color & !cleared & actual))"
|
||||
" %(ansify_if(justify(truncated(account, account_width, abbrev_len), "
|
||||
" account_width), blue if color))"
|
||||
" %(ansify_if(justify(truncated(display_account, account_width, "
|
||||
" abbrev_len), account_width), blue if color))"
|
||||
" %(justify(scrub(display_amount), amount_width, "
|
||||
" 3 + meta_width + date_width + payee_width + account_width"
|
||||
" + amount_width, true, color))"
|
||||
" + amount_width + prepend_width, true, color))"
|
||||
" %(justify(scrub(display_total), total_width, "
|
||||
" 4 + meta_width + date_width + payee_width + account_width"
|
||||
" + amount_width + total_width, true, color))\n%/"
|
||||
" + amount_width + total_width + prepend_width, true, color))\n%/"
|
||||
"%(justify(\" \", 2 + date_width + payee_width))"
|
||||
"%$3 %$4 %$5\n");
|
||||
});
|
||||
|
|
|
|||
24
src/scope.h
24
src/scope.h
|
|
@ -354,6 +354,30 @@ inline T& find_scope(child_scope_t& scope, bool skip_this = true)
|
|||
return reinterpret_cast<T&>(scope); // never executed
|
||||
}
|
||||
|
||||
class value_scope_t : public scope_t
|
||||
{
|
||||
value_t value;
|
||||
|
||||
value_t get_value(call_scope_t&) {
|
||||
return value;
|
||||
}
|
||||
|
||||
public:
|
||||
value_scope_t(const value_t& _value) : value(_value) {}
|
||||
|
||||
virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind,
|
||||
const string& name)
|
||||
{
|
||||
if (kind != symbol_t::FUNCTION)
|
||||
return NULL;
|
||||
|
||||
if (name == "value")
|
||||
return MAKE_FUNCTOR(value_scope_t::get_value);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
||||
#endif // _SCOPE_H
|
||||
|
|
|
|||
|
|
@ -196,9 +196,7 @@ option_t<session_t> * session_t::lookup_option(const char * p)
|
|||
break;
|
||||
case 'd':
|
||||
OPT(download); // -Q
|
||||
break;
|
||||
case 'e':
|
||||
OPT(european);
|
||||
else OPT(decimal_comma);
|
||||
break;
|
||||
case 'f':
|
||||
OPT_(file_); // -f
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ public:
|
|||
{
|
||||
HANDLER(cache_).report(out);
|
||||
HANDLER(download).report(out);
|
||||
HANDLER(european).report(out);
|
||||
HANDLER(decimal_comma).report(out);
|
||||
HANDLER(file_).report(out);
|
||||
HANDLER(input_date_format_).report(out);
|
||||
HANDLER(master_account_).report(out);
|
||||
|
|
@ -101,8 +101,8 @@ public:
|
|||
OPTION(session_t, cache_);
|
||||
OPTION(session_t, download); // -Q
|
||||
|
||||
OPTION_(session_t, european, DO() {
|
||||
commodity_t::european_by_default = true;
|
||||
OPTION_(session_t, decimal_comma, DO() {
|
||||
commodity_t::decimal_comma_by_default = true;
|
||||
});
|
||||
|
||||
OPTION__
|
||||
|
|
|
|||
51
src/temps.cc
51
src/temps.cc
|
|
@ -38,26 +38,6 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
temporaries_t::~temporaries_t()
|
||||
{
|
||||
if (post_temps) {
|
||||
foreach (post_t& post, *post_temps) {
|
||||
if (! post.xact->has_flags(ITEM_TEMP))
|
||||
post.xact->remove_post(&post);
|
||||
|
||||
if (post.account && ! post.account->has_flags(ACCOUNT_TEMP))
|
||||
post.account->remove_post(&post);
|
||||
}
|
||||
}
|
||||
|
||||
if (acct_temps) {
|
||||
foreach (account_t& acct, *acct_temps) {
|
||||
if (acct.parent && ! acct.parent->has_flags(ACCOUNT_TEMP))
|
||||
acct.parent->remove_account(&acct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xact_t& temporaries_t::copy_xact(xact_t& origin)
|
||||
{
|
||||
if (! xact_temps)
|
||||
|
|
@ -91,9 +71,9 @@ post_t& temporaries_t::copy_post(post_t& origin, xact_t& xact,
|
|||
post_temps->push_back(origin);
|
||||
post_t& temp(post_temps->back());
|
||||
|
||||
temp.add_flags(ITEM_TEMP);
|
||||
if (account)
|
||||
temp.account = account;
|
||||
temp.add_flags(ITEM_TEMP);
|
||||
|
||||
temp.account->add_post(&temp);
|
||||
xact.add_post(&temp);
|
||||
|
|
@ -109,8 +89,8 @@ post_t& temporaries_t::create_post(xact_t& xact, account_t * account)
|
|||
post_temps->push_back(post_t(account));
|
||||
post_t& temp(post_temps->back());
|
||||
|
||||
temp.account = account;
|
||||
temp.add_flags(ITEM_TEMP);
|
||||
temp.account = account;
|
||||
|
||||
temp.account->add_post(&temp);
|
||||
xact.add_post(&temp);
|
||||
|
|
@ -127,11 +107,36 @@ account_t& temporaries_t::create_account(const string& name,
|
|||
acct_temps->push_back(account_t(parent, name));
|
||||
account_t& temp(acct_temps->back());
|
||||
|
||||
temp.add_flags(ACCOUNT_TEMP);
|
||||
if (parent)
|
||||
parent->add_account(&temp);
|
||||
|
||||
temp.add_flags(ACCOUNT_TEMP);
|
||||
return temp;
|
||||
}
|
||||
|
||||
void temporaries_t::clear()
|
||||
{
|
||||
if (post_temps) {
|
||||
foreach (post_t& post, *post_temps) {
|
||||
if (! post.xact->has_flags(ITEM_TEMP))
|
||||
post.xact->remove_post(&post);
|
||||
|
||||
if (post.account && ! post.account->has_flags(ACCOUNT_TEMP))
|
||||
post.account->remove_post(&post);
|
||||
}
|
||||
post_temps->clear();
|
||||
}
|
||||
|
||||
if (xact_temps)
|
||||
xact_temps->clear();
|
||||
|
||||
if (acct_temps) {
|
||||
foreach (account_t& acct, *acct_temps) {
|
||||
if (acct.parent && ! acct.parent->has_flags(ACCOUNT_TEMP))
|
||||
acct.parent->remove_account(&acct);
|
||||
}
|
||||
acct_temps->clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ class temporaries_t
|
|||
optional<std::list<account_t> > acct_temps;
|
||||
|
||||
public:
|
||||
~temporaries_t();
|
||||
~temporaries_t() {
|
||||
clear();
|
||||
}
|
||||
|
||||
xact_t& copy_xact(xact_t& origin);
|
||||
xact_t& create_xact();
|
||||
|
|
@ -69,6 +71,8 @@ public:
|
|||
account_t& last_account() {
|
||||
return acct_temps->back();
|
||||
}
|
||||
|
||||
void clear();
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
|
|
@ -95,7 +95,6 @@ namespace {
|
|||
public:
|
||||
parse_context_t& context;
|
||||
instance_t * parent;
|
||||
account_t * master;
|
||||
accounts_map account_aliases;
|
||||
const path * original_file;
|
||||
path pathname;
|
||||
|
|
@ -109,7 +108,6 @@ namespace {
|
|||
|
||||
instance_t(parse_context_t& _context,
|
||||
std::istream& _in,
|
||||
account_t * _master = NULL,
|
||||
const path * _original_file = NULL,
|
||||
instance_t * _parent = NULL);
|
||||
|
||||
|
|
@ -200,30 +198,17 @@ namespace {
|
|||
|
||||
instance_t::instance_t(parse_context_t& _context,
|
||||
std::istream& _in,
|
||||
account_t * _master,
|
||||
const path * _original_file,
|
||||
instance_t * _parent)
|
||||
: context(_context), parent(_parent), master(_master),
|
||||
original_file(_original_file), in(_in)
|
||||
: context(_context), parent(_parent), original_file(_original_file),
|
||||
pathname(original_file ? *original_file : "/dev/stdin"), in(_in)
|
||||
{
|
||||
TRACE_CTOR(instance_t, "...");
|
||||
|
||||
if (! master)
|
||||
master = context.journal.master;
|
||||
context.state_stack.push_front(master);
|
||||
|
||||
if (_original_file)
|
||||
pathname = *_original_file;
|
||||
else
|
||||
pathname = "/dev/stdin";
|
||||
}
|
||||
|
||||
instance_t::~instance_t()
|
||||
{
|
||||
TRACE_DTOR(instance_t);
|
||||
|
||||
assert(! context.state_stack.empty());
|
||||
context.state_stack.pop_front();
|
||||
}
|
||||
|
||||
void instance_t::parse()
|
||||
|
|
@ -411,8 +396,7 @@ void instance_t::read_next_directive()
|
|||
|
||||
#if defined(TIMELOG_SUPPORT)
|
||||
|
||||
void instance_t::clock_in_directive(char * line,
|
||||
bool /*capitalized*/)
|
||||
void instance_t::clock_in_directive(char * line, bool /*capitalized*/)
|
||||
{
|
||||
string datetime(line, 2, 19);
|
||||
|
||||
|
|
@ -441,8 +425,7 @@ void instance_t::clock_in_directive(char * line,
|
|||
context.timelog.clock_in(event);
|
||||
}
|
||||
|
||||
void instance_t::clock_out_directive(char * line,
|
||||
bool /*capitalized*/)
|
||||
void instance_t::clock_out_directive(char * line, bool /*capitalized*/)
|
||||
{
|
||||
string datetime(line, 2, 19);
|
||||
|
||||
|
|
@ -543,7 +526,7 @@ void instance_t::automated_xact_directive(char * line)
|
|||
|
||||
std::auto_ptr<auto_xact_t> ae
|
||||
(new auto_xact_t(query_t(string(skip_ws(line + 1)),
|
||||
keep_details_t(true, true, true))));
|
||||
keep_details_t(true, true, true), false)));
|
||||
ae->pos = position_t();
|
||||
ae->pos->pathname = pathname;
|
||||
ae->pos->beg_pos = line_beg_pos;
|
||||
|
|
@ -680,7 +663,12 @@ void instance_t::include_directive(char * line)
|
|||
for (filesystem::directory_iterator iter(parent_path);
|
||||
iter != end;
|
||||
++iter) {
|
||||
if (is_regular_file(*iter)) {
|
||||
#if BOOST_VERSION <= 103500
|
||||
if (is_regular(*iter))
|
||||
#else
|
||||
if (is_regular_file(*iter))
|
||||
#endif
|
||||
{
|
||||
#if BOOST_VERSION >= 103700
|
||||
string base = (*iter).filename();
|
||||
#else // BOOST_VERSION >= 103700
|
||||
|
|
@ -689,7 +677,7 @@ void instance_t::include_directive(char * line)
|
|||
if (glob.match(base)) {
|
||||
path inner_file(*iter);
|
||||
ifstream stream(inner_file);
|
||||
instance_t instance(context, stream, master, &inner_file, this);
|
||||
instance_t instance(context, stream, &inner_file, this);
|
||||
instance.parse();
|
||||
files_found = true;
|
||||
}
|
||||
|
|
@ -713,7 +701,7 @@ void instance_t::master_account_directive(char * line)
|
|||
|
||||
void instance_t::end_directive(char * kind)
|
||||
{
|
||||
string name(kind);
|
||||
string name(kind ? kind : "");
|
||||
|
||||
if ((name.empty() || name == "account") && ! context.front_is_account())
|
||||
throw_(std::runtime_error,
|
||||
|
|
@ -1451,8 +1439,10 @@ std::size_t journal_t::parse(std::istream& in,
|
|||
|
||||
parse_context_t context(*this, scope);
|
||||
context.strict = strict;
|
||||
if (master || this->master)
|
||||
context.state_stack.push_front(master ? master : this->master);
|
||||
|
||||
instance_t instance(context, in, master, original_file);
|
||||
instance_t instance(context, in, original_file);
|
||||
instance.parse();
|
||||
|
||||
TRACE_STOP(parsing_total, 1);
|
||||
|
|
|
|||
59
src/times.cc
59
src/times.cc
|
|
@ -197,25 +197,33 @@ namespace {
|
|||
optional_year year,
|
||||
date_traits_t * traits = NULL)
|
||||
{
|
||||
date_t when;
|
||||
VERIFY(std::strlen(date_str) < 127);
|
||||
|
||||
if (std::strchr(date_str, '/')) {
|
||||
when = io.parse(date_str);
|
||||
} else {
|
||||
char buf[128];
|
||||
VERIFY(std::strlen(date_str) < 127);
|
||||
std::strcpy(buf, date_str);
|
||||
char buf[128];
|
||||
std::strcpy(buf, date_str);
|
||||
|
||||
for (char * p = buf; *p; p++)
|
||||
if (*p == '.' || *p == '-')
|
||||
*p = '/';
|
||||
for (char * p = buf; *p; p++)
|
||||
if (*p == '.' || *p == '-')
|
||||
*p = '/';
|
||||
|
||||
when = io.parse(buf);
|
||||
}
|
||||
date_t when = io.parse(buf);
|
||||
|
||||
if (! when.is_not_a_date()) {
|
||||
DEBUG("times.parse", "Parsed date string: " << date_str);
|
||||
DEBUG("times.parse", "Parsed result is: " << when);
|
||||
DEBUG("times.parse", "Passed date string: " << date_str);
|
||||
DEBUG("times.parse", "Parsed date string: " << buf);
|
||||
DEBUG("times.parse", "Parsed result is: " << when);
|
||||
DEBUG("times.parse", "Formatted result is: " << io.format(when));
|
||||
|
||||
string when_str = io.format(when);
|
||||
|
||||
const char * p = when_str.c_str();
|
||||
const char * q = buf;
|
||||
for (; *p && *q; p++, q++) {
|
||||
if (*p != *q && *p == '0') p++;
|
||||
if (! *p || *p != *q) break;
|
||||
}
|
||||
if (*p != '\0' || *q != '\0')
|
||||
throw_(date_error, _("Invalid date: %1") << date_str);
|
||||
|
||||
if (traits)
|
||||
*traits = io.traits;
|
||||
|
|
@ -1299,14 +1307,14 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token()
|
|||
// "2009/08/01", but also dates that fit the user's --input-date-format,
|
||||
// assuming their format fits in one argument and begins with a digit.
|
||||
if (std::isdigit(*begin)) {
|
||||
string::const_iterator i = begin;
|
||||
for (i = begin; i != end && ! std::isspace(*i); i++) {}
|
||||
assert(i != begin);
|
||||
|
||||
string possible_date(start, i);
|
||||
|
||||
try {
|
||||
string::const_iterator i = begin;
|
||||
for (i = begin; i != end && ! std::isspace(*i); i++) {}
|
||||
assert(i != begin);
|
||||
|
||||
string possible_date(start, i);
|
||||
date_traits_t traits;
|
||||
|
||||
date_t when = parse_date_mask(possible_date.c_str(), none, &traits);
|
||||
if (! when.is_not_a_date()) {
|
||||
begin = i;
|
||||
|
|
@ -1314,7 +1322,12 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token()
|
|||
token_t::content_t(date_specifier_t(when, traits)));
|
||||
}
|
||||
}
|
||||
catch (...) {}
|
||||
catch (date_error&) {
|
||||
if (contains(possible_date, "/") ||
|
||||
contains(possible_date, "-") ||
|
||||
contains(possible_date, "."))
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
start = begin;
|
||||
|
|
@ -1457,7 +1470,7 @@ std::string format_datetime(const datetime_t& when,
|
|||
if (format_type == FMT_WRITTEN) {
|
||||
return written_datetime_io->format(when);
|
||||
}
|
||||
else if (format_type == FMT_CUSTOM || format) {
|
||||
else if (format_type == FMT_CUSTOM && format) {
|
||||
datetime_io_map::iterator i = temp_datetime_io.find(*format);
|
||||
if (i != temp_datetime_io.end()) {
|
||||
return (*i).second->format(when);
|
||||
|
|
@ -1483,7 +1496,7 @@ std::string format_date(const date_t& when,
|
|||
if (format_type == FMT_WRITTEN) {
|
||||
return written_date_io->format(when);
|
||||
}
|
||||
else if (format_type == FMT_CUSTOM || format) {
|
||||
else if (format_type == FMT_CUSTOM && format) {
|
||||
date_io_map::iterator i = temp_date_io.find(*format);
|
||||
if (i != temp_date_io.end()) {
|
||||
return (*i).second->format(when);
|
||||
|
|
|
|||
47
src/token.cc
47
src/token.cc
|
|
@ -138,7 +138,8 @@ void expr_t::token_t::parse_ident(std::istream& in)
|
|||
value.set_string(buf);
|
||||
}
|
||||
|
||||
void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags)
|
||||
void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags,
|
||||
const char expecting)
|
||||
{
|
||||
if (in.eof()) {
|
||||
kind = TOK_EOF;
|
||||
|
|
@ -423,6 +424,13 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags)
|
|||
expected('\0', c);
|
||||
|
||||
parse_ident(in);
|
||||
|
||||
if (value.as_string().length() == 0) {
|
||||
kind = ERROR;
|
||||
symbol[0] = c;
|
||||
symbol[1] = '\0';
|
||||
unexpected(expecting);
|
||||
}
|
||||
} else {
|
||||
kind = VALUE;
|
||||
value = temp;
|
||||
|
|
@ -447,21 +455,38 @@ void expr_t::token_t::rewind(std::istream& in)
|
|||
}
|
||||
|
||||
|
||||
void expr_t::token_t::unexpected()
|
||||
void expr_t::token_t::unexpected(const char wanted)
|
||||
{
|
||||
kind_t prev_kind = kind;
|
||||
|
||||
kind = ERROR;
|
||||
|
||||
switch (prev_kind) {
|
||||
case TOK_EOF:
|
||||
throw_(parse_error, _("Unexpected end of expression"));
|
||||
case IDENT:
|
||||
throw_(parse_error, _("Unexpected symbol '%1'") << value);
|
||||
case VALUE:
|
||||
throw_(parse_error, _("Unexpected value '%1'") << value);
|
||||
default:
|
||||
throw_(parse_error, _("Unexpected token '%1'") << symbol);
|
||||
if (wanted == '\0') {
|
||||
switch (prev_kind) {
|
||||
case TOK_EOF:
|
||||
throw_(parse_error, _("Unexpected end of expression"));
|
||||
case IDENT:
|
||||
throw_(parse_error, _("Unexpected symbol '%1'") << value);
|
||||
case VALUE:
|
||||
throw_(parse_error, _("Unexpected value '%1'") << value);
|
||||
default:
|
||||
throw_(parse_error, _("Unexpected expression token '%1'") << symbol);
|
||||
}
|
||||
} else {
|
||||
switch (prev_kind) {
|
||||
case TOK_EOF:
|
||||
throw_(parse_error,
|
||||
_("Unexpected end of expression (wanted '%1')" << wanted));
|
||||
case IDENT:
|
||||
throw_(parse_error,
|
||||
_("Unexpected symbol '%1' (wanted '%2')") << value << wanted);
|
||||
case VALUE:
|
||||
throw_(parse_error,
|
||||
_("Unexpected value '%1' (wanted '%2')") << value << wanted);
|
||||
default:
|
||||
throw_(parse_error, _("Unexpected expression token '%1' (wanted '%2')")
|
||||
<< symbol << wanted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,10 +124,11 @@ struct expr_t::token_t : public noncopyable
|
|||
|
||||
int parse_reserved_word(std::istream& in);
|
||||
void parse_ident(std::istream& in);
|
||||
void next(std::istream& in, const parse_flags_t& flags);
|
||||
void next(std::istream& in, const parse_flags_t& flags,
|
||||
const char expecting = '\0');
|
||||
void rewind(std::istream& in);
|
||||
void unexpected();
|
||||
void expected(char wanted, char c = '\0');
|
||||
void unexpected(const char wanted = '\0');
|
||||
void expected(const char wanted, char c = '\0');
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
54
src/value.cc
54
src/value.cc
|
|
@ -832,7 +832,7 @@ bool value_t::is_equal_to(const value_t& val) const
|
|||
break;
|
||||
}
|
||||
|
||||
throw_(value_error, _("Cannot compare %1 by %2") << label() << val.label());
|
||||
throw_(value_error, _("Cannot compare %1 to %2") << label() << val.label());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -840,6 +840,23 @@ bool value_t::is_equal_to(const value_t& val) const
|
|||
bool value_t::is_less_than(const value_t& val) const
|
||||
{
|
||||
switch (type()) {
|
||||
case BOOLEAN:
|
||||
if (val.is_boolean()) {
|
||||
if (as_boolean()) {
|
||||
if (! val.as_boolean())
|
||||
return false;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else if (! as_boolean()) {
|
||||
if (! val.as_boolean())
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DATETIME:
|
||||
if (val.is_datetime())
|
||||
return as_datetime() < val.as_datetime();
|
||||
|
|
@ -935,6 +952,22 @@ bool value_t::is_less_than(const value_t& val) const
|
|||
bool value_t::is_greater_than(const value_t& val) const
|
||||
{
|
||||
switch (type()) {
|
||||
if (val.is_boolean()) {
|
||||
if (as_boolean()) {
|
||||
if (! val.as_boolean())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else if (! as_boolean()) {
|
||||
if (! val.as_boolean())
|
||||
return false;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DATETIME:
|
||||
if (val.is_datetime())
|
||||
return as_datetime() > val.as_datetime();
|
||||
|
|
@ -1042,8 +1075,27 @@ void value_t::in_place_cast(type_t cast_type)
|
|||
}
|
||||
|
||||
switch (type()) {
|
||||
case VOID:
|
||||
switch (cast_type) {
|
||||
case INTEGER:
|
||||
set_long(0L);
|
||||
return;
|
||||
case AMOUNT:
|
||||
set_amount(0L);
|
||||
return;
|
||||
case STRING:
|
||||
set_string("");
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case BOOLEAN:
|
||||
switch (cast_type) {
|
||||
case INTEGER:
|
||||
set_long(as_boolean() ? 1L : 0L);
|
||||
return;
|
||||
case AMOUNT:
|
||||
set_amount(as_boolean() ? 1L : 0L);
|
||||
return;
|
||||
|
|
|
|||
97
src/xact.cc
97
src/xact.cc
|
|
@ -172,44 +172,8 @@ bool xact_base_t::finalize()
|
|||
add_post(null_post);
|
||||
}
|
||||
|
||||
if (null_post != NULL) {
|
||||
// If one post has no value at all, its value will become the inverse of
|
||||
// the rest. If multiple commodities are involved, multiple posts are
|
||||
// generated to balance them all.
|
||||
|
||||
DEBUG("xact.finalize", "there was a null posting");
|
||||
|
||||
if (balance.is_balance()) {
|
||||
bool first = true;
|
||||
const balance_t& bal(balance.as_balance());
|
||||
foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) {
|
||||
if (first) {
|
||||
null_post->amount = pair.second.negated();
|
||||
null_post->add_flags(POST_CALCULATED);
|
||||
first = false;
|
||||
} else {
|
||||
post_t * p = new post_t(null_post->account, pair.second.negated(),
|
||||
ITEM_GENERATED | POST_CALCULATED);
|
||||
p->set_state(null_post->state());
|
||||
add_post(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (balance.is_amount()) {
|
||||
null_post->amount = balance.as_amount().negated();
|
||||
null_post->add_flags(POST_CALCULATED);
|
||||
}
|
||||
else if (balance.is_long()) {
|
||||
null_post->amount = amount_t(- balance.as_long());
|
||||
null_post->add_flags(POST_CALCULATED);
|
||||
}
|
||||
else if (! balance.is_null() && ! balance.is_realzero()) {
|
||||
throw_(balance_error, _("Transaction does not balance"));
|
||||
}
|
||||
balance = NULL_VALUE;
|
||||
}
|
||||
else if (balance.is_balance() &&
|
||||
balance.as_balance().amounts.size() == 2) {
|
||||
if (balance.is_balance() &&
|
||||
balance.as_balance().amounts.size() == 2) {
|
||||
// When an xact involves two different commodities (regardless of how
|
||||
// many posts there are) determine the conversion ratio by dividing the
|
||||
// total value of one commodity by the total value of the other. This
|
||||
|
|
@ -293,12 +257,6 @@ bool xact_base_t::finalize()
|
|||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
DEBUG("xact.finalize", "resolved balance = " << balance);
|
||||
|
||||
posts_list copy(posts);
|
||||
|
||||
foreach (post_t * post, copy) {
|
||||
|
|
@ -340,7 +298,44 @@ bool xact_base_t::finalize()
|
|||
}
|
||||
}
|
||||
|
||||
DEBUG("xact.finalize", "final balance = " << balance);
|
||||
if (null_post != NULL) {
|
||||
// If one post has no value at all, its value will become the inverse of
|
||||
// the rest. If multiple commodities are involved, multiple posts are
|
||||
// generated to balance them all.
|
||||
|
||||
DEBUG("xact.finalize", "there was a null posting");
|
||||
|
||||
if (balance.is_balance()) {
|
||||
bool first = true;
|
||||
const balance_t& bal(balance.as_balance());
|
||||
foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) {
|
||||
if (first) {
|
||||
null_post->amount = pair.second.negated();
|
||||
null_post->add_flags(POST_CALCULATED);
|
||||
first = false;
|
||||
} else {
|
||||
post_t * p = new post_t(null_post->account, pair.second.negated(),
|
||||
ITEM_GENERATED | POST_CALCULATED);
|
||||
p->set_state(null_post->state());
|
||||
add_post(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (balance.is_amount()) {
|
||||
null_post->amount = balance.as_amount().negated();
|
||||
null_post->add_flags(POST_CALCULATED);
|
||||
}
|
||||
else if (balance.is_long()) {
|
||||
null_post->amount = amount_t(- balance.as_long());
|
||||
null_post->add_flags(POST_CALCULATED);
|
||||
}
|
||||
else if (! balance.is_null() && ! balance.is_realzero()) {
|
||||
throw_(balance_error, _("Transaction does not balance"));
|
||||
}
|
||||
balance = NULL_VALUE;
|
||||
|
||||
}
|
||||
DEBUG("xact.finalize", "resolved balance = " << balance);
|
||||
|
||||
if (! balance.is_null() && ! balance.is_zero()) {
|
||||
add_error_context(item_context(*this, _("While balancing transaction")));
|
||||
|
|
@ -473,7 +468,7 @@ namespace {
|
|||
if (xact.code)
|
||||
return string_value(*xact.code);
|
||||
else
|
||||
return string_value(empty_string);
|
||||
return NULL_VALUE;
|
||||
}
|
||||
|
||||
value_t get_payee(xact_t& xact) {
|
||||
|
|
@ -716,8 +711,14 @@ void auto_xact_t::extend_xact(xact_base_t& xact)
|
|||
account_t * account = post->account;
|
||||
string fullname = account->fullname();
|
||||
assert(! fullname.empty());
|
||||
if (fullname == "$account" || fullname == "@account")
|
||||
account = initial_post->account;
|
||||
|
||||
if (contains(fullname, "$account")) {
|
||||
fullname = regex_replace(fullname, regex("\\$account\\>"),
|
||||
initial_post->account->fullname());
|
||||
while (account->parent)
|
||||
account = account->parent;
|
||||
account = account->find_account(fullname);
|
||||
}
|
||||
|
||||
// Copy over details so that the resulting post is a mirror of
|
||||
// the automated xact's one.
|
||||
|
|
|
|||
|
|
@ -83,6 +83,14 @@ public:
|
|||
|
||||
virtual void flush();
|
||||
virtual void operator()(post_t& post);
|
||||
|
||||
virtual void clear() {
|
||||
commodities.clear();
|
||||
transactions_set.clear();
|
||||
transactions.clear();
|
||||
|
||||
item_handler<post_t>::clear();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ def confirm_report(command):
|
|||
return not failure
|
||||
|
||||
for cmd in commands:
|
||||
if confirm_report('$ledger $cmd ' + re.sub('\$tests', tests, cmd)):
|
||||
if confirm_report('$ledger --rounding $cmd ' + re.sub('\$tests', tests, cmd)):
|
||||
harness.success()
|
||||
else:
|
||||
harness.failure()
|
||||
|
|
|
|||
11
test/baseline/cmd-print.test
Normal file
11
test/baseline/cmd-print.test
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
print --decimal-comma
|
||||
<<<
|
||||
2008/12/31 Market
|
||||
Expenses:Food ($10,00 + $2,50)
|
||||
Assets:Cash
|
||||
>>>1
|
||||
2008/12/31 Market
|
||||
Expenses:Food ($10,00 + $2,50)
|
||||
Assets:Cash
|
||||
>>>2
|
||||
=== 0
|
||||
|
|
@ -28,7 +28,7 @@ reg --payee=code
|
|||
08-Feb-01 102 Assets:Cash $-20.00 0
|
||||
08-Feb-28 103 Expenses:Books $20.00 $20.00
|
||||
08-Feb-28 103 Assets:Cash $-20.00 0
|
||||
08-Mar-01 Expenses:Books $30.00 $30.00
|
||||
08-Mar-01 Assets:Cash $-30.00 0
|
||||
08-Mar-01 March Expenses:Books $30.00 $30.00
|
||||
08-Mar-01 March Assets:Cash $-30.00 0
|
||||
>>>2
|
||||
=== 0
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ P 2010/03/01 00:00:00 S 8 P
|
|||
P 2010/04/01 00:00:00 S 16 P
|
||||
>>>1
|
||||
09-Jan-01 Sample 1a As:Brokerage:Stocks 0 0
|
||||
09-Feb-01 Commodities revalued <Revalued> 300 P 300 P
|
||||
09-Jan-15 Commodities revalued <Revalued> 100 P 100 P
|
||||
09-Feb-01 Commodities revalued <Revalued> 200 P 300 P
|
||||
09-Feb-01 Sample 2a As:Brokerage:Stocks 300 P 600 P
|
||||
09-Mar-01 Commodities revalued <Revalued> 800 P 1400 P
|
||||
09-Mar-01 Sample 3a As:Brokerage:Stocks 700 P 2100 P
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ reg --end 2009/06/26 -V equities
|
|||
08-Jan-01 Purchase Apple shares Equities $2000 $2000
|
||||
08-Jun-30 Commodities revalued <Revalued> $500 $2500
|
||||
08-Jun-30 Sell some Apple sha.. Equities $-1250 $1250
|
||||
09-Jun-26 Commodities revalued <Revalued> $750 $2000
|
||||
09-Jan-31 Commodities revalued <Revalued> $250 $1500
|
||||
09-Jun-26 Commodities revalued <Revalued> $500 $2000
|
||||
>>>2
|
||||
=== 0
|
||||
reg --end 2009/06/26 -G equities
|
||||
|
|
@ -36,7 +37,8 @@ reg --end 2009/06/26 -G equities
|
|||
08-Jan-01 Purchase Apple shares Equities 0 0
|
||||
08-Jun-30 Commodities revalued <Revalued> $500 $500
|
||||
08-Jun-30 Sell some Apple sha.. Equities 0 $500
|
||||
09-Jun-26 Commodities revalued <Revalued> $750 $1250
|
||||
09-Jan-31 Commodities revalued <Revalued> $250 $750
|
||||
09-Jun-26 Commodities revalued <Revalued> $500 $1250
|
||||
>>>2
|
||||
=== 0
|
||||
reg -I equities
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@
|
|||
>>>2
|
||||
While parsing file "$sourcepath/src/amount.h", line 67:
|
||||
Error: No quantity specified for amount
|
||||
While parsing file "$sourcepath/src/amount.h", line 720:
|
||||
While parsing file "$sourcepath/src/amount.h", line 718:
|
||||
Error: Invalid date/time: line amount_t amoun
|
||||
While parsing file "$sourcepath/src/amount.h", line 726:
|
||||
While parsing file "$sourcepath/src/amount.h", line 724:
|
||||
Error: Invalid date/time: line string amount_
|
||||
While parsing file "$sourcepath/src/amount.h", line 732:
|
||||
While parsing file "$sourcepath/src/amount.h", line 730:
|
||||
Error: Invalid date/time: line string amount_
|
||||
While parsing file "$sourcepath/src/amount.h", line 738:
|
||||
While parsing file "$sourcepath/src/amount.h", line 736:
|
||||
Error: Invalid date/time: line string amount_
|
||||
While parsing file "$sourcepath/src/amount.h", line 744:
|
||||
While parsing file "$sourcepath/src/amount.h", line 742:
|
||||
Error: Invalid date/time: line std::ostream&
|
||||
While parsing file "$sourcepath/src/amount.h", line 751:
|
||||
While parsing file "$sourcepath/src/amount.h", line 749:
|
||||
Error: Invalid date/time: line std::istream&
|
||||
=== 7
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
reg -V --end=2009/06/16
|
||||
reg -V
|
||||
<<<
|
||||
D 1000.00 EUR
|
||||
|
||||
|
|
@ -10,6 +10,6 @@ P 2008/04/20 00:00:00 CAD 1.20 EUR
|
|||
>>>1
|
||||
08-Apr-15 Paid expenses back .. Ex:Cie-Reimbursements 2200.00 EUR 2200.00 EUR
|
||||
Assets:Checking -2200.00 EUR 0
|
||||
09-Jun-16 Commodities revalued <Revalued> 200.00 EUR 200.00 EUR
|
||||
08-Apr-20 Commodities revalued <Revalued> 200.00 EUR 200.00 EUR
|
||||
>>>2
|
||||
=== 0
|
||||
|
|
|
|||
|
|
@ -64,9 +64,9 @@ void AmountTestCase::testParser()
|
|||
x16.parse("$2000,00");
|
||||
assertEqual(string("$2.000,00"), x16.to_string());
|
||||
|
||||
// Since European-ness is an additive quality, we must switch back
|
||||
// to American-ness manually
|
||||
x15.commodity().drop_flags(COMMODITY_STYLE_EUROPEAN);
|
||||
// Since use of a decimal-comma is an additive quality, we must switch back
|
||||
// to decimal-period manually.
|
||||
x15.commodity().drop_flags(COMMODITY_STYLE_DECIMAL_COMMA);
|
||||
|
||||
amount_t x17("$1,000,000.00"); // parsing this switches back to American
|
||||
|
||||
|
|
|
|||
|
|
@ -158,8 +158,6 @@ void ValueExprTestCase::testPredicateTokenizer7()
|
|||
|
||||
assertEqual(query_t::lexer_t::token_t::TOK_EQ, tokens.next_token().kind);
|
||||
assertEqual(query_t::lexer_t::token_t::TERM, tokens.next_token().kind);
|
||||
assertEqual(query_t::lexer_t::token_t::TOK_AND, tokens.next_token().kind);
|
||||
assertEqual(query_t::lexer_t::token_t::TERM, tokens.next_token().kind);
|
||||
assertEqual(query_t::lexer_t::token_t::END_REACHED, tokens.next_token().kind);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -167,7 +165,7 @@ void ValueExprTestCase::testPredicateTokenizer7()
|
|||
void ValueExprTestCase::testPredicateTokenizer8()
|
||||
{
|
||||
value_t args;
|
||||
args.push_back(string_value("expr foo and bar"));
|
||||
args.push_back(string_value("expr 'foo and bar'"));
|
||||
|
||||
#ifndef NOT_FOR_PYTHON
|
||||
query_t::lexer_t tokens(args.begin(), args.end());
|
||||
|
|
@ -182,7 +180,7 @@ void ValueExprTestCase::testPredicateTokenizer9()
|
|||
{
|
||||
value_t args;
|
||||
args.push_back(string_value("expr"));
|
||||
args.push_back(string_value("foo and bar"));
|
||||
args.push_back(string_value("'foo and bar'"));
|
||||
|
||||
#ifndef NOT_FOR_PYTHON
|
||||
query_t::lexer_t tokens(args.begin(), args.end());
|
||||
|
|
|
|||
|
|
@ -249,11 +249,7 @@ endif
|
|||
|
||||
######################################################################
|
||||
|
||||
TESTS =
|
||||
|
||||
if HAVE_PYTHON
|
||||
TESTS += RegressTests BaselineTests ManualTests ConfirmTests GenerateTests
|
||||
endif
|
||||
TESTS = RegressTests BaselineTests ManualTests ConfirmTests GenerateTests
|
||||
|
||||
if HAVE_CPPUNIT
|
||||
TESTS += \
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue