Merge branch 'next'

This commit is contained in:
John Wiegley 2010-06-23 19:31:06 -04:00
commit eda6cbd014
91 changed files with 6112 additions and 4751 deletions

15
acprep
View file

@ -955,7 +955,10 @@ class PrepareBuild(CommandLineApp):
else:
gcc_version = "42"
if self.current_flavor == 'debug' or self.current_flavor == 'gcov':
if gcc_version != "42":
self.CPPFLAGS.append('-D_GLIBCXX_FULLY_DYNAMIC_STRING=1')
if self.current_flavor == 'debug':
if exists('/usr/local/stow/cppunit-gcc%s/include' % gcc_version):
self.sys_include_dirs.insert(
0, '/usr/local/stow/cppunit-gcc%s/include' % gcc_version)
@ -968,10 +971,11 @@ class PrepareBuild(CommandLineApp):
self.sys_library_dirs.insert(
0, '/usr/local/stow/icu-gcc%s/lib' % gcc_version)
self.CPPFLAGS.append('-D_GLIBCXX_FULLY_DYNAMIC_STRING=1')
self.configure_args.append('--disable-shared')
self.options.use_glibcxx_debug = True
elif self.current_flavor == 'gcov':
self.configure_args.append('--disable-shared')
else:
self.CXXFLAGS.append('-march=nocona')
self.CXXFLAGS.append('-msse3')
@ -1201,7 +1205,7 @@ class PrepareBuild(CommandLineApp):
self.log.debug('Using gcc version: %s' % gcc_version)
if self.current_flavor == 'debug' or self.current_flavor == 'gcov':
if self.current_flavor == 'debug':
if self.options.use_glibcxx_debug:
self.log.debug('We are using GLIBCXX_DEBUG, so setting up flags')
self.CPPFLAGS.append('-D_GLIBCXX_DEBUG=1')
@ -1282,6 +1286,9 @@ class PrepareBuild(CommandLineApp):
self.LDFLAGS.append(i)
def setup_flavor_gcov(self):
# NDEBUG is set so that branch coverage ignores the never-taken else
# branch inside assert statements.
self.CPPFLAGS.append('-DNDEBUG=1')
self.CXXFLAGS.append('-g')
self.CXXFLAGS.append('-fprofile-arcs')
self.CXXFLAGS.append('-ftest-coverage')
@ -1405,7 +1412,7 @@ class PrepareBuild(CommandLineApp):
else:
make_args.append(arg)
if self.options.jobs > 1:
if self.options.jobs > 1 and self.current_flavor != 'gcov':
make_args.append('-j%d' % self.options.jobs)
make_args.append('JOBS=%d' % self.options.jobs)

45
dist/Portfile vendored
View file

@ -4,26 +4,24 @@
PortSystem 1.0
name ledger-devel
version 3.0-20100611
version 3.0.0-20100615
homepage http://www.newartisans.com/software/ledger.html
categories finance accounting reporting
categories finance
description A command-line, double-entry accounting tool.
long_description Ledger is a powerful, double-entry accounting system that \
is accessed from the UNIX command-line.
maintainers johnw@newartisans.com
maintainers newartisans.com:johnw
platforms darwin
use_bzip2 yes
master_sites http://ftp.newartisans.com/pub/ledger/:source
distname ${name}-${version}
distfiles ${distname}${extract.suffix}:source
checksums ${distname}${extract.suffix} \
md5 0ab9a855719df536a85f7ea5238b8a6e \
sha1 e2ee9e2951fd37bac50c91046f097c11294a6e8e \
rmd160 72cdfe76add63425b1ade1d03479e837e9f2dafe
master_sites http://ftp.newartisans.com/pub/ledger/
distname ledger-${version}
checksums md5 980e819c4cb68b8777849a44316e0edc \
sha1 ff1b281ce6ddfeb5814ce59bd4d69b97ddb21f7e \
rmd160 a40e64bf21c9c132619b0921dee0e12299e3938a
depends_build port:automake \
port:autoconf \
@ -35,23 +33,15 @@ depends_lib port:gettext \
port:boost \
port:libedit
# gmp and mpfr are not universal
universal_variant no
configure.args --with-extra-includes=${prefix}/include \
--with-extra-libs=${prefix}/lib
build.args DYLD_LIBRARY_PATH=${worksrcpath}/ledger/.libs
platform darwin 9 {}
platform darwin 10 {}
destroot.args DESTDIR=${destroot}${prefix} \
DYLD_LIBRARY_PATH=${worksrcpath}/ledger/.libs \
docprefix=${destroot}/share/doc
post-destroot {}
variant debug description {Enable debug mode} {
configure.args-append --enable-debug=yes
}
@ -60,12 +50,10 @@ variant icu description {Enable full Unicode support} {
if {[variant_isset python25]} {
depends_lib-delete port:boost+python25
depends_lib-append port:boost+icu+python25
}
else if {[variant_isset python26]} {
} elsif {[variant_isset python26]} {
depends_lib-delete port:boost+python26
depends_lib-append port:boost+icu+python26
}
else {
} else {
depends_lib-delete port:boost
depends_lib-append port:boost+icu
}
@ -76,8 +64,7 @@ variant python25 description {build python 2.5 support} conflicts python26 {
if {[variant_isset icu]} {
depends_lib-delete port:boost+icu
depends_lib-append port:boost+icu+python25
}
else {
} else {
depends_lib-delete port:boost
depends_lib-append port:boost+python25
}
@ -89,15 +76,13 @@ variant python26 description {build python 2.6 support} conflicts python25 {
if {[variant_isset icu]} {
depends_lib-delete port:boost+icu
depends_lib-append port:boost+icu+python26
}
else {
} else {
depends_lib-delete port:boost
depends_lib-append port:boost+python26
}
depends_lib-append port:python26
}
#livecheck.check regex
#livecheck.url ${homepage}
#livecheck.regex "Latest Stable Ledger \\(Version (\\d+.\\d+.\\d+)\\)"
livecheck.check regex
livecheck.url [lindex ${master_sites} 0]
livecheck.regex ${name}-(\[0-9.-\]+)\\.tar

View file

@ -13,6 +13,23 @@ features, please see the manual.
To see 2.6 behavior, use "bal -n" in 3.0. The -s option no longer has any
effect on balance reports.
* 2.6.3
- Minor fixes to allow for compilation with gcc 4.4.
* 2.6.2
- Bug fix: Command-line options, such as -O, now override init-file options
such as -V.
- Bug fix: "cat data | ledger -f -" now works.
- Bug fix: --no-cache is now honored. Previously, it was writing out a cache
file named "<none>".
- Bug fix: Using %.2X in a format string now outputs 2 spaces if the state is
cleared.
* 2.6.1
- Added the concept of "balance setting transactions":

View file

@ -1,4 +1,4 @@
.Dd June 15, 2010
.Dd June 22, 2010
.Dt ledger 1
.Sh NAME
.Nm ledger
@ -275,6 +275,7 @@ transactions they are contained in. See the manual for more information.
.It Fl \-base
.It Fl \-basis Pq Fl B
.It Fl \-begin Ar DATE Pq Fl b
.It Fl \-bold-if Ar EXPR
.It Fl \-budget
.It Fl \-budget-format Ar FMT
.It Fl \-by-payee Pq Fl P
@ -336,6 +337,7 @@ See
.It Fl \-help-disp
.It Fl \-import Ar STR
.It Fl \-init-file Ar FILE
.It Fl \-inject Ar STR
.It Fl \-input-date-format Ar DATEFMT
.It Fl \-invert
.It Fl \-last Ar INT

322
doc/ledger3.texi Normal file
View file

@ -0,0 +1,322 @@
\input texinfo @c -*-texinfo-*-
@setfilename ledger.info
@settitle Ledger: Command-Line Accounting
@dircategory User Applications
@copying
Copyright (c) 2003-2010, John Wiegley. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of New Artisans LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@end copying
@documentencoding UTF-8
@iftex
@finalout
@end iftex
@titlepage
@title Ledger: Command-Line Accounting
@author John Wiegley
@end titlepage
@direntry
* Ledger: (ledger). Command-Line Accounting
@end direntry
@contents
@ifnottex
@node Top, , (dir), (dir)
@top Overview
@insertcopying
@end ifnottex
@ifnottex
@section Copyright
@insertcopying
@end ifnottex
@chapter Introduction
@chapter Principles of accounting
@chapter Keeping a journal
@chapter Basic reporting commands
@chapter Command-line syntax
@chapter Journal data format
This chapter offers a complete description of the journal data format,
suitable for implementors in other languages to follow. For users,
the chapter on keeping a journal is less extensive, but more typical
of common usage (@pxref{Keeping a journal}).
Data is collected in the form of @dfn{transactions} which occur in one
or more @dfn{journal files}. Each transaction, in turn, is made up of
one or more @dfn{postings}, which describe how @dfn{amounts} flow from
one @dfn{account} to another. Here is an example of the simplest of
journal files:
@example
2010/05/31 Just an example
Expenses:Some:Account $100.00
Income:Another:Account
@end example
In this example, there is a transaction date, a payee, or description
of the transaction, and two postings. The postings show movement of
one hundred dollars from an account within the Income hierarchy, to
the specified expense account. The name and meaning of these accounts
in arbitrary, with no preferences implied, although you will find it
useful to follow standard accounting practice (@pxref{Principles of
accounting}}.
Since an amount is missing from the second posting, it is assumed to
be the inverse of the first. This guarantee the cardinal rule of
double-entry accounting: the sum of every transaction must balance to
zero, or it is in error. Whenever Ledger encounters a @dfn{null
posting} in a transaction, it uses it to balance the remainder.
It is also typical---though not enforced---to think of the first
posting as the destination, and the final as the source. Thus, the
amount of the first posting is typically positive. Consider:
@example
2010/05/31 An income transaction
Assets:Checking $1,000.00
Income:Salary
2010/05/31 An expense transaction
Expenses:Dining $100.00
Assets:Checking
@end example
@section Specifying amounts
The heart of a journal is the amounts it records, and this fact is
reflected in the diversity of amount expressions allowed. All of them
are covered here, though it must be said that sometimes, there are
multiple ways to achieve a desired result.
@subsection Integer amounts
In the simplest form, bare decimal numbers are accepted:
@example
2010/05/31 An income transaction
Assets:Checking 1000.00
Income:Salary
@end example
Such amounts may only use an optional period for a decimal point.
These are referred to as @dfn{integer amounts} or @dfn{uncommoditized
amounts}. In most ways they are similar to @dfn{commoditized
amounts}, but for one signficant difference: They always display in
reports with @dfn{full precision}. More on this in a moment. For
now, a word must be said about how Ledger stores numbers.
Every number parsed by Ledger is stored internally as an
infinite-precision rational value. Floating-point math is never used,
as it cannot be trusted to maintain precision of values. So, in the
case of @samp{1000.00} above, the internal value is @samp{100000/100}.
While rational numbers are great at not losing precision, the question
arises: How should they be displayed? A number like @samp{100000/100}
is no problem, since it represents a clean decimal fraction. But what
about when the number @samp{1/1} is divided by three? How should one
print @samp{1/3}, an infinitely repeating decimal?
Ledger gets around this problem by rendering rationals into decimal at
the last possible moment, and only for display. As such, some
rounding must, at times, occur. If this rounding would affect the
calculation of a running total, special accommodation postings are
generated to make you aware it has happened. In practice, it happens
rarely, but even then it does not reflect adjustment of the
@emph{internal amount}, only the displayed amount.
What has still not been answered is how Ledger rounds values. Should
@samp{1/3} be printed as @samp{0.33} or @samp{0.33333}? For
commoditized amounts, the number of decimal places is decided by
observing how each commodity is used; but in the case of integer
amounts, an arbitrary factor must be chosen. Initially, this factor
is six. Thus, @samp{1/3} is printed back as @samp{0.333333}.
Further, this rounding factor becomes associated with each particular
value, and is carried through mathematical operations. For example,
if that particular number were multiplied by itself, the decimal
precision of the result would be twelve. Addition and subtraction do
not affect precision.
Since each integer amount retains its own display precision, this is
called @dfn{full precision}, as opposed to commoditized amounts, which
always look to their commodity to know what precision they should
round to, and so use @dfn{commodity precision}.
@subsection Commoditized amounts
A @dfn{commoditized amount} is an integer amount which has an
associated commodity. This commodity can appear before or after the
amount, and may or may not be separated from it by a space. Most
characters are allowed in a commodity name, except for the following:
@itemize
@item Any kind of whitespace
@item Numerical digits
@item Punctuation: @samp{.,;:?!}
@item Mathematical and logical operators: @samp{-+*/^&|=}
@item Bracketing characters: @samp{<>[](){}}
@item The at symbol: @samp{@}
@end itemize
And yet, any of these may appear in a commodity name if it is
surrounded by double quotes, for example:
@example
100 "EUN+133"
@end example
If a @dfn{quoted commodity} is found, it is displayed in quotes as
well, to avoid any confusion as to which part is the amount, and which
part is the commodity.
Another feature of commoditized amounts is that they are reported back
in the same form as parsed. If you specify dollar amounts using
@samp{$100}, they will print the same; likewise with @samp{100 $} or
@samp{$100.000}. You may even use decimal commas, such as
@samp{$100,00}, or thousand-marks, as in @samp{$10,000.00}.
These display characteristics become associated with the commodity,
with the result being that all amounts of the same commodity are
reported consistently. Where this is most noticeable is the
@dfn{display precision}, which is determined by the most precise value
seen for a given commodity. In most cases.
Ledger makes a distinction by @dfn{observed amounts} and unobserved
amounts. An observed amount is critiqued by Ledger to determine how
amounts using that commodity should be displayed; unobserved amounts
are significant in their value only---no matter how they are
specified, it does not change how other amounts in that commodity will
be displayed.
An example of this is found in cost expressions, covered next.
@section Posting costs
You have seen how to specify either a commoditized or an integer
amount for a posting. But what if the amount you paid for something
was in one commodity, and the amount received was another? There are
two main ways to express this:
@example
2010/05/31 Farmer's Market
Assets:My Larder 100 apples
Assets:Checking $20.00
@end example
In this example, you have paid twenty dollars for one hundred apples.
The cost to you is twenty cents per apple, and Ledger calculates this
implied cost for you. You can also make the cost explicit using a
@dfn{cost amount}:
@example
2010/05/31 Farmer's Market
Assets:My Larder 100 apples @ $0.200000
Assets:Checking
@end example
Here the @dfn{per-unit cost} is given explicitly in the form of a cost
amount; and since cost amount are @emph{unobserved}, the use of six
decimal places has no effect on how dollar amounts are displayed in
the final report. You can also specify the @dfn{total cost}:
@example
2010/05/31 Farmer's Market
Assets:My Larder 100 apples @@ $20
Assets:Checking
@end example
These three forms have identical meaning. In most cases the first is
preferred, but the second two are necessary when more than two
postings are involved:
@example
2010/05/31 Farmer's Market
Assets:My Larder 100 apples @ $0.200000
Assets:My Larder 100 pineapples @ $0.33
Assets:My Larder 100 "crab apples" @ $0.04
Assets:Checking
@end example
Here the implied cost is @samp{$57.00}, which is entered into the null
posting automatically so that the transaction balances.
@subsection Primary commodities
In every transaction involving more than one commodity, there is
always one which is the @dfn{primary commodity}. This commodity
should be thought of as the exchange commodity, or the commodity used
to buy and sells units of the other commodity. In the fruit examples
above, dollars are the primary commodity. This is decided by Ledger
on the placement of the commodity in the transaction:
@example
2010/05/31 Sample Transaction
Expenses 100 secondary
Assets 50 primary
2010/05/31 Sample Transaction
Expenses 100 secondary @ 0.5 primary
Assets
2010/05/31 Sample Transaction
Expenses 100 secondary @@ 50 primary
Assets
@end example
The only case where knowledge of primary versus secondary comes into
play is in reports that use the @option{-V} or @option{-B} options.
With these, only primary commodities are shown.
If a transaction uses only one commodity, this commodity is also
considered a primary. In fact, when Ledger goes about ensures that
all transactions balance to zero, it only ever asks this of primary
commodities.
@chapter Report queries
@chapter Value expressions
@chapter Format strings
@chapter Extending with Python
@bye

View file

@ -626,7 +626,7 @@ void account_t::xdata_t::details_t::update(post_t& post,
if (gather_all) {
accounts_referenced.insert(post.account->fullname());
payees_referenced.insert(post.xact->payee);
payees_referenced.insert(post.payee());
}
}

View file

@ -743,21 +743,24 @@ amount_t::value(const optional<datetime_t>& moment,
optional<price_point_t> point;
optional<commodity_t&> comm(in_terms_of);
if (comm && commodity().referent() == comm->referent()) {
return *this;
}
else if (has_annotation() && annotation().price) {
if (has_annotation() && annotation().price) {
if (annotation().has_flags(ANNOTATION_PRICE_FIXATED)) {
point = price_point_t();
point->price = *annotation().price;
DEBUG("commodity.prices.find",
"amount_t::value: fixated price = " << point->price);
}
else if (! in_terms_of) {
else if (! comm) {
comm = annotation().price->commodity();
}
}
if (! point) {
if (comm && commodity().referent() == comm->referent())
return *this;
point = commodity().find_price(comm, moment);
// Whether a price was found or not, check whether we should attempt
// to download a price from the Internet. This is done if (a) no
// price was found, or (b) the price is "stale" according to the

View file

@ -166,15 +166,27 @@ annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
commodity_t * new_comm;
bool keep_price = (what_to_keep.keep_price &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_PRICE_CALCULATED)));
bool keep_date = (what_to_keep.keep_date &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_DATE_CALCULATED)));
bool keep_tag = (what_to_keep.keep_tag &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_TAG_CALCULATED)));
bool keep_price =
((what_to_keep.keep_price ||
(details.has_flags(ANNOTATION_PRICE_FIXATED) &&
has_flags(COMMODITY_SAW_ANN_PRICE_FLOAT) &&
has_flags(COMMODITY_SAW_ANN_PRICE_FIXATED))) &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_PRICE_CALCULATED)));
bool keep_date =
(what_to_keep.keep_date &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_DATE_CALCULATED)));
bool keep_tag =
(what_to_keep.keep_tag &&
(! what_to_keep.only_actuals ||
! details.has_flags(ANNOTATION_TAG_CALCULATED)));
DEBUG("commodity.annotated.strip",
"Reducing commodity " << *this << std::endl
<< " keep price " << keep_price << " "
<< " keep date " << keep_date << " "
<< " keep tag " << keep_tag);
if ((keep_price && details.price) ||
(keep_date && details.date) ||
@ -184,12 +196,24 @@ annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep)
(referent(), annotation_t(keep_price ? details.price : none,
keep_date ? details.date : none,
keep_tag ? details.tag : none));
} else {
new_comm = &referent();
// Transfer over any relevant annotation flags, as they still apply.
if (new_comm->annotated) {
annotation_t& new_details(as_annotated_commodity(*new_comm).details);
if (keep_price)
new_details.add_flags(details.flags() &
(ANNOTATION_PRICE_CALCULATED |
ANNOTATION_PRICE_FIXATED));
if (keep_date)
new_details.add_flags(details.flags() & ANNOTATION_DATE_CALCULATED);
if (keep_tag)
new_details.add_flags(details.flags() & ANNOTATION_TAG_CALCULATED);
}
return *new_comm;
}
assert(new_comm);
return *new_comm;
return referent();
}
void annotated_commodity_t::write_annotations

View file

@ -67,8 +67,8 @@ post_handler_ptr chain_pre_post_handlers(post_handler_ptr base_handler,
// future balance.
if (report.budget_flags != BUDGET_NO_BUDGET) {
budget_posts * budget_handler = new budget_posts(handler,
report.budget_flags);
budget_posts * budget_handler =
new budget_posts(handler, report.terminus.date(), report.budget_flags);
budget_handler->add_period_xacts(report.session.journal->period_xacts);
handler.reset(budget_handler);
@ -202,8 +202,8 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler,
// period_posts is like subtotal_posts, but it subtotals according to time
// periods rather than totalling everything.
//
// dow_posts is like period_posts, except that it reports all the posts
// that fall on each subsequent day of the week.
// day_of_week_posts is like period_posts, except that it reports
// all the posts that fall on each subsequent day of the week.
if (report.HANDLED(equity))
handler.reset(new posts_as_equity(handler, expr));
else if (report.HANDLED(subtotal))
@ -211,7 +211,7 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler,
}
if (report.HANDLED(dow))
handler.reset(new dow_posts(handler, expr));
handler.reset(new day_of_week_posts(handler, expr));
else if (report.HANDLED(by_payee))
handler.reset(new by_payee_posts(handler, expr));
@ -258,6 +258,10 @@ post_handler_ptr chain_post_handlers(post_handler_ptr base_handler,
if (report.HANDLED(related))
handler.reset(new related_posts(handler, report.HANDLED(related_all)));
if (report.HANDLED(inject_))
handler.reset(new inject_posts(handler, report.HANDLED(inject_).str(),
report.session.journal->master));
return handler;
}

View file

@ -164,16 +164,19 @@ 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_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
#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
#define COMMODITY_SAW_ANNOTATED 0x200
#define COMMODITY_SAW_ANN_PRICE_FLOAT 0x400
#define COMMODITY_SAW_ANN_PRICE_FIXATED 0x800
string symbol;
amount_t::precision_t precision;

View file

@ -97,18 +97,20 @@ value_t convert_command(call_scope_t& args)
}
bool matched = false;
post_map_t::iterator i = post_map.find(- xact->posts.front()->amount);
if (i != post_map.end()) {
std::list<post_t *>& post_list((*i).second);
foreach (post_t * post, post_list) {
if (xact->code && post->xact->code &&
*xact->code == *post->xact->code) {
matched = true;
break;
}
else if (xact->actual_date() == post->actual_date()) {
matched = true;
break;
if (! xact->posts.front()->amount.is_null()) {
post_map_t::iterator i = post_map.find(- xact->posts.front()->amount);
if (i != post_map.end()) {
std::list<post_t *>& post_list((*i).second);
foreach (post_t * post, post_list) {
if (xact->code && post->xact->code &&
*xact->code == *post->xact->code) {
matched = true;
break;
}
else if (xact->actual_date() == post->actual_date()) {
matched = true;
break;
}
}
}
}

View file

@ -52,8 +52,9 @@ void expr_t::parse(std::istream& in, const parse_flags_t& flags,
in.seekg(start_pos, std::ios::beg);
scoped_array<char> buf
(new char[static_cast<std::size_t>(end_pos - start_pos) + 1]);
in.read(buf.get(), end_pos - start_pos);
buf[end_pos - start_pos] = '\0';
int len = static_cast<int>(end_pos) - static_cast<int>(start_pos);
in.read(buf.get(), len);
buf[len] = '\0';
set_text(buf.get());
}
else {

View file

@ -266,6 +266,7 @@ void anonymize_posts::operator()(post_t& post)
copy_xact_details = true;
}
xact_t& xact = temps.last_xact();
xact.code = none;
if (copy_xact_details) {
xact.copy_details(*post.xact);
@ -523,7 +524,7 @@ display_filter_posts::display_filter_posts(post_handler_ptr handler,
bool _show_rounding)
: item_handler<post_t>(handler), report(_report),
show_rounding(_show_rounding),
rounding_account(temps.create_account(_("<Rounding>"))),
rounding_account(temps.create_account(_("<Adjustment>"))),
revalued_account(temps.create_account(_("<Revalued>")))
{
TRACE_CTOR(display_filter_posts,
@ -1042,10 +1043,10 @@ void by_payee_posts::flush()
void by_payee_posts::operator()(post_t& post)
{
payee_subtotals_map::iterator i = payee_subtotals.find(post.xact->payee);
payee_subtotals_map::iterator i = payee_subtotals.find(post.payee());
if (i == payee_subtotals.end()) {
payee_subtotals_pair
temp(post.xact->payee,
temp(post.payee(),
shared_ptr<subtotal_posts>(new subtotal_posts(handler, amount_expr)));
std::pair<payee_subtotals_map::iterator, bool> result
= payee_subtotals.insert(temp);
@ -1073,7 +1074,7 @@ void transfer_details::operator()(post_t& post)
if (! substitute.is_null()) {
switch (which_element) {
case SET_DATE:
temp.xdata().date = substitute.to_date();
temp._date = substitute.to_date();
break;
case SET_ACCOUNT: {
@ -1112,7 +1113,7 @@ void transfer_details::operator()(post_t& post)
item_handler<post_t>::operator()(temp);
}
void dow_posts::flush()
void day_of_week_posts::flush()
{
for (int i = 0; i < 7; i++) {
foreach (post_t * post, days_of_the_week[i])
@ -1143,20 +1144,44 @@ void budget_posts::report_budget_items(const date_t& date)
bool reported;
do {
std::list<pending_posts_list::iterator> posts_to_erase;
reported = false;
foreach (pending_posts_list::value_type& pair, pending_posts) {
for (pending_posts_list::iterator i = pending_posts.begin();
i != pending_posts.end();
i++) {
pending_posts_list::value_type& pair(*i);
optional<date_t> begin = pair.first.start;
if (! begin) {
if (! pair.first.find_period(date))
optional<date_t> range_begin;
if (pair.first.range)
range_begin = pair.first.range->begin();
DEBUG("budget.generate", "Finding period for pending post");
if (! pair.first.find_period(range_begin ? *range_begin : date))
continue;
if (! pair.first.start)
throw_(std::logic_error,
_("Failed to find period for periodic transaction"));
begin = pair.first.start;
}
assert(begin);
#if defined(DEBUG_ON)
DEBUG("budget.generate", "begin = " << *begin);
DEBUG("budget.generate", "date = " << date);
if (pair.first.finish)
DEBUG("budget.generate", "pair.first.finish = " << *pair.first.finish);
#endif
if (*begin <= date &&
(! pair.first.finish || *begin < *pair.first.finish)) {
post_t& post = *pair.second;
++pair.first;
if (! pair.first.start)
posts_to_erase.push_back(i);
DEBUG("budget.generate", "Reporting budget for "
<< post.reported_account()->fullname());
@ -1176,14 +1201,14 @@ void budget_posts::report_budget_items(const date_t& date)
temp.xdata().add_flags(POST_EXT_COMPOUND);
}
++pair.first;
begin = *pair.first.start;
item_handler<post_t>::operator()(temp);
reported = true;
}
}
foreach (pending_posts_list::iterator& i, posts_to_erase)
pending_posts.erase(i);
} while (reported);
}
@ -1215,6 +1240,14 @@ void budget_posts::operator()(post_t& post)
}
}
void budget_posts::flush()
{
if (flags & BUDGET_BUDGETED)
report_budget_items(terminus);
item_handler<post_t>::flush();
}
void forecast_posts::add_post(const date_interval_t& period, post_t& post)
{
date_interval_t i(period);
@ -1274,17 +1307,16 @@ void forecast_posts::flush()
least = i;
}
date_t& begin = *(*least).first.start;
#if !defined(NO_ASSERTS)
if ((*least).first.finish)
assert(begin < *(*least).first.finish);
assert(*(*least).first.start < *(*least).first.finish);
#endif
// If the next date in the series for this periodic posting is more than 5
// years beyond the last valid post we generated, drop it from further
// consideration.
date_t next = *(*least).first.next;
assert(next > begin);
date_t& next(*(*least).first.next);
assert(next > *(*least).first.start);
if (static_cast<std::size_t>((next - last).days()) >
static_cast<std::size_t>(365U) * forecast_years) {
@ -1295,15 +1327,13 @@ void forecast_posts::flush()
continue;
}
begin = next;
// `post' refers to the posting defined in the period transaction. We
// make a copy of it within a temporary transaction with the payee
// "Forecast transaction".
post_t& post = *(*least).second;
xact_t& xact = temps.create_xact();
xact.payee = _("Forecast transaction");
xact._date = begin;
xact._date = next;
post_t& temp = temps.copy_post(post, xact);
// Submit the generated posting
@ -1338,6 +1368,60 @@ void forecast_posts::flush()
item_handler<post_t>::flush();
}
inject_posts::inject_posts(post_handler_ptr handler,
const string& tag_list,
account_t * master)
: item_handler<post_t>(handler)
{
TRACE_CTOR(inject_posts, "post_handler_ptr, string");
scoped_array<char> buf(new char[tag_list.length() + 1]);
std::strcpy(buf.get(), tag_list.c_str());
for (char * q = std::strtok(buf.get(), ",");
q;
q = std::strtok(NULL, ",")) {
std::list<string> account_names;
split_string(q, ':', account_names);
account_t * account =
create_temp_account_from_path(account_names, temps, master);
account->add_flags(ACCOUNT_GENERATED);
tags_list.push_back
(tags_list_pair(q, tag_mapping_pair(account, tag_injected_set())));
}
}
void inject_posts::operator()(post_t& post)
{
foreach (tags_list_pair& pair, tags_list) {
optional<value_t> tag_value = post.get_tag(pair.first, false);
if (! tag_value &&
pair.second.second.find(post.xact) == pair.second.second.end()) {
// When checking if the transaction has the tag, only inject once
// per transaction.
pair.second.second.insert(post.xact);
tag_value = post.xact->get_tag(pair.first);
}
if (tag_value) {
xact_t& xact = temps.copy_xact(*post.xact);
xact._date = post.date();
xact.add_flags(ITEM_GENERATED);
post_t& temp = temps.copy_post(post, xact);
temp.account = pair.second.first;
temp.amount = tag_value->to_amount();
temp.add_flags(ITEM_GENERATED);
item_handler<post_t>::operator()(temp);
}
}
item_handler<post_t>::operator()(post);
}
pass_down_accounts::pass_down_accounts(acct_handler_ptr handler,
accounts_iterator& iter,
const optional<predicate_t>& _pred,

View file

@ -813,19 +813,19 @@ public:
}
};
class dow_posts : public subtotal_posts
class day_of_week_posts : public subtotal_posts
{
posts_list days_of_the_week[7];
dow_posts();
day_of_week_posts();
public:
dow_posts(post_handler_ptr handler, expr_t& amount_expr)
day_of_week_posts(post_handler_ptr handler, expr_t& amount_expr)
: subtotal_posts(handler, amount_expr) {
TRACE_CTOR(dow_posts, "post_handler_ptr, bool");
TRACE_CTOR(day_of_week_posts, "post_handler_ptr, bool");
}
virtual ~dow_posts() throw() {
TRACE_DTOR(dow_posts);
virtual ~day_of_week_posts() throw() {
TRACE_DTOR(day_of_week_posts);
}
virtual void flush();
@ -882,14 +882,16 @@ class budget_posts : public generate_posts
#define BUDGET_WRAP_VALUES 0x04
uint_least8_t flags;
date_t terminus;
budget_posts();
public:
budget_posts(post_handler_ptr handler,
uint_least8_t _flags = BUDGET_BUDGETED)
: generate_posts(handler), flags(_flags) {
TRACE_CTOR(budget_posts, "post_handler_ptr, uint_least8_t");
date_t _terminus,
uint_least8_t _flags = BUDGET_BUDGETED)
: generate_posts(handler), flags(_flags), terminus(_terminus) {
TRACE_CTOR(budget_posts, "post_handler_ptr, date_t, uint_least8_t");
}
virtual ~budget_posts() throw() {
TRACE_DTOR(budget_posts);
@ -897,6 +899,7 @@ public:
void report_budget_items(const date_t& date);
virtual void flush();
virtual void operator()(post_t& post);
};
@ -929,6 +932,26 @@ class forecast_posts : public generate_posts
}
};
class inject_posts : public item_handler<post_t>
{
typedef std::set<xact_t *> tag_injected_set;
typedef std::pair<account_t *, tag_injected_set> tag_mapping_pair;
typedef std::pair<string, tag_mapping_pair> tags_list_pair;
std::list<tags_list_pair> tags_list;
temporaries_t temps;
public:
inject_posts(post_handler_ptr handler, const string& tag_list,
account_t * master);
virtual ~inject_posts() throw() {
TRACE_DTOR(inject_posts);
}
virtual void operator()(post_t& post);
};
//////////////////////////////////////////////////////////////////////
//
// Account filters

View file

@ -540,7 +540,7 @@ string format_t::truncate(const unistring& ustr,
else
adjust = std::size_t
(std::ceil(double(overflow) *
((double(*i + counter) * double(iteration)) /
((double(*i + counter*3) * double(iteration)) /
(double(len_minus_last) - double(counter)))));
DEBUG("format.abbrev", "Weight calc: (" << overflow
<< " * (((" << *i << " + " << counter << ") * "

View file

@ -37,7 +37,7 @@ namespace ledger {
bool item_t::use_effective_date = false;
bool item_t::has_tag(const string& tag) const
bool item_t::has_tag(const string& tag, bool) const
{
DEBUG("item.meta", "Checking if item has tag: " << tag);
if (! metadata) {
@ -57,7 +57,7 @@ bool item_t::has_tag(const string& tag) const
}
bool item_t::has_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask) const
const optional<mask_t>& value_mask, bool) const
{
if (metadata) {
foreach (const string_map::value_type& data, *metadata) {
@ -72,7 +72,7 @@ bool item_t::has_tag(const mask_t& tag_mask,
return false;
}
optional<value_t> item_t::get_tag(const string& tag) const
optional<value_t> item_t::get_tag(const string& tag, bool) const
{
DEBUG("item.meta", "Getting item tag: " << tag);
if (metadata) {
@ -87,7 +87,8 @@ optional<value_t> item_t::get_tag(const string& tag) const
}
optional<value_t> item_t::get_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask) const
const optional<mask_t>& value_mask,
bool) const
{
if (metadata) {
foreach (const string_map::value_type& data, *metadata) {
@ -138,26 +139,26 @@ void item_t::parse_tags(const char * p,
scope_t& scope,
bool overwrite_existing)
{
if (const char * b = std::strchr(p, '[')) {
if (*(b + 1) != '\0' &&
(std::isdigit(*(b + 1)) || *(b + 1) == '=')) {
if (const char * e = std::strchr(p, ']')) {
char buf[256];
std::strncpy(buf, b + 1, e - b - 1);
buf[e - b - 1] = '\0';
if (! std::strchr(p, ':')) {
if (const char * b = std::strchr(p, '[')) {
if (*(b + 1) != '\0' &&
(std::isdigit(*(b + 1)) || *(b + 1) == '=')) {
if (const char * e = std::strchr(p, ']')) {
char buf[256];
std::strncpy(buf, b + 1, e - b - 1);
buf[e - b - 1] = '\0';
if (char * p = std::strchr(buf, '=')) {
*p++ = '\0';
_date_eff = parse_date(p);
if (char * p = std::strchr(buf, '=')) {
*p++ = '\0';
_date_eff = parse_date(p);
}
if (buf[0])
_date = parse_date(buf);
}
if (buf[0])
_date = parse_date(buf);
}
}
}
if (! std::strchr(p, ':'))
return;
}
scoped_array<char> buf(new char[std::strlen(p) + 1]);
@ -165,6 +166,7 @@ void item_t::parse_tags(const char * p,
string tag;
bool by_value = false;
bool first = true;
for (char * q = std::strtok(buf.get(), " \t");
q;
q = std::strtok(NULL, " \t")) {
@ -190,7 +192,7 @@ void item_t::parse_tags(const char * p,
(*i).second.second = true;
}
}
else if (q[len - 1] == ':') { // a metadata setting
else if (first && q[len - 1] == ':') { // a metadata setting
int index = 1;
if (q[len - 2] == ':') {
by_value = true;
@ -198,6 +200,7 @@ void item_t::parse_tags(const char * p,
}
tag = string(q, len - index);
}
first = false;
}
}

View file

@ -149,13 +149,17 @@ public:
return ! (*this == xact);
}
virtual bool has_tag(const string& tag) const;
virtual bool has_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask = none) const;
virtual bool has_tag(const string& tag,
bool inherit = true) const;
virtual bool has_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask = none,
bool inherit = true) const;
virtual optional<value_t> get_tag(const string& tag) const;
virtual optional<value_t> get_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask = none) const;
virtual optional<value_t> get_tag(const string& tag,
bool inherit = true) const;
virtual optional<value_t> get_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask = none,
bool inherit = true) const;
virtual string_map::iterator
set_tag(const string& tag,
@ -171,6 +175,10 @@ public:
static bool use_effective_date;
virtual bool has_date() const {
return _date;
}
virtual date_t date() const {
assert(_date);
if (use_effective_date)

View file

@ -228,7 +228,8 @@ format_accounts::mark_accounts(account_t& account, const bool flat)
if ((! flat && to_display > 1) ||
((flat || to_display != 1 ||
account.has_xflags(ACCOUNT_EXT_VISITED)) &&
(report.HANDLED(empty) || report.fn_display_total(call_scope)) &&
(report.HANDLED(empty) ||
report.display_value(report.fn_display_total(call_scope))) &&
disp_pred(bound_scope))) {
account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY);
DEBUG("account.display", "Marking account as TO_DISPLAY");
@ -313,9 +314,9 @@ void report_payees::flush()
void report_payees::operator()(post_t& post)
{
std::map<string, std::size_t>::iterator i = payees.find(post.xact->payee);
std::map<string, std::size_t>::iterator i = payees.find(post.payee());
if (i == payees.end())
payees.insert(payees_pair(post.xact->payee, 1));
payees.insert(payees_pair(post.payee(), 1));
else
(*i).second++;
}

View file

@ -172,12 +172,21 @@ commodity_pool_t::create(commodity_t& comm,
const string& mapping_key)
{
assert(comm);
assert(! comm.has_annotation());
assert(details);
assert(! mapping_key.empty());
std::auto_ptr<commodity_t> commodity
(new annotated_commodity_t(&comm, details));
comm.add_flags(COMMODITY_SAW_ANNOTATED);
if (details.price) {
if (details.has_flags(ANNOTATION_PRICE_FIXATED))
comm.add_flags(COMMODITY_SAW_ANN_PRICE_FIXATED);
else
comm.add_flags(COMMODITY_SAW_ANN_PRICE_FLOAT);
}
commodity->qualified_symbol = comm.symbol();
assert(! commodity->qualified_symbol->empty());
@ -254,7 +263,13 @@ commodity_pool_t::exchange(const amount_t& amount,
DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost);
if (! per_unit_cost.is_realzero())
// Do not record commodity exchanges where amount's commodity has a
// fixated price, since this does not establish a market value for the
// base commodity.
if (! per_unit_cost.is_realzero() &&
(current_annotation == NULL ||
! (current_annotation->price &&
current_annotation->has_flags(ANNOTATION_PRICE_FIXATED))))
exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME());
cost_breakdown_t breakdown;
@ -276,6 +291,9 @@ commodity_pool_t::exchange(const amount_t& amount,
moment->date() : optional<date_t>(), tag);
annotation.add_flags(ANNOTATION_PRICE_CALCULATED);
if (current_annotation &&
current_annotation->has_flags(ANNOTATION_PRICE_FIXATED))
annotation.add_flags(ANNOTATION_PRICE_FIXATED);
if (moment)
annotation.add_flags(ANNOTATION_DATE_CALCULATED);
if (tag)

View file

@ -39,40 +39,42 @@
namespace ledger {
bool post_t::has_tag(const string& tag) const
bool post_t::has_tag(const string& tag, bool inherit) const
{
if (item_t::has_tag(tag))
return true;
if (xact)
if (inherit && xact)
return xact->has_tag(tag);
return false;
}
bool post_t::has_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask) const
const optional<mask_t>& value_mask,
bool inherit) const
{
if (item_t::has_tag(tag_mask, value_mask))
return true;
if (xact)
if (inherit && xact)
return xact->has_tag(tag_mask, value_mask);
return false;
}
optional<value_t> post_t::get_tag(const string& tag) const
optional<value_t> post_t::get_tag(const string& tag, bool inherit) const
{
if (optional<value_t> value = item_t::get_tag(tag))
return value;
if (xact)
if (inherit && xact)
return xact->get_tag(tag);
return none;
}
optional<value_t> post_t::get_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask) const
const optional<mask_t>& value_mask,
bool inherit) const
{
if (optional<value_t> value = item_t::get_tag(tag_mask, value_mask))
return value;
if (xact)
if (inherit && xact)
return xact->get_tag(tag_mask, value_mask);
return none;
}
@ -123,6 +125,14 @@ optional<date_t> post_t::effective_date() const
return date;
}
string post_t::payee() const
{
if (optional<value_t> post_payee = get_tag(_("Payee")))
return post_payee->as_string();
else
return xact->payee;
}
namespace {
value_t get_this(post_t& post) {
return scope_value(&post);
@ -160,7 +170,7 @@ namespace {
}
value_t get_payee(post_t& post) {
return string_value(post.xact->payee);
return string_value(post.payee());
}
value_t get_note(post_t& post) {

View file

@ -99,19 +99,25 @@ public:
TRACE_DTOR(post_t);
}
virtual bool has_tag(const string& tag) const;
virtual bool has_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask = none) const;
virtual bool has_tag(const string& tag,
bool inherit = true) const;
virtual bool has_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask = none,
bool inherit = true) const;
virtual optional<value_t> get_tag(const string& tag) const;
virtual optional<value_t> get_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask = none) const;
virtual optional<value_t> get_tag(const string& tag,
bool inherit = true) const;
virtual optional<value_t> get_tag(const mask_t& tag_mask,
const optional<mask_t>& value_mask = none,
bool inherit = true) const;
virtual date_t value_date() const;
virtual date_t date() const;
virtual date_t actual_date() const;
virtual optional<date_t> effective_date() const;
string payee() const;
bool must_balance() const {
return ! has_flags(POST_VIRTUAL) || has_flags(POST_MUST_BALANCE);
}

View file

@ -200,26 +200,23 @@ value_t query_command(call_scope_t& args)
args.value().dump(out);
out << std::endl << std::endl;
query_t query(args.value(), report.what_to_keep());
if (query) {
query_t query(args.value(), report.what_to_keep(),
! report.HANDLED(collapse));
if (query.has_query(query_t::QUERY_LIMIT)) {
call_scope_t sub_args(static_cast<scope_t&>(args));
sub_args.push_back(string_value(query.text()));
sub_args.push_back(string_value(query.get_query(query_t::QUERY_LIMIT)));
parse_command(sub_args);
}
if (query.tokens_remaining()) {
if (query.has_query(query_t::QUERY_SHOW)) {
out << std::endl << _("====== Display predicate ======")
<< std::endl << std::endl;
query.parse_again();
call_scope_t disp_sub_args(static_cast<scope_t&>(args));
disp_sub_args.push_back(string_value(query.get_query(query_t::QUERY_SHOW)));
if (query) {
call_scope_t disp_sub_args(static_cast<scope_t&>(args));
disp_sub_args.push_back(string_value(query.text()));
parse_command(disp_sub_args);
}
parse_command(disp_sub_args);
}
return NULL_VALUE;

View file

@ -37,10 +37,4 @@
namespace ledger {
predicate_t::predicate_t(const query_t& other)
: expr_t(other), what_to_keep(other.what_to_keep)
{
TRACE_CTOR(predicate_t, "query_t");
}
} // namespace ledger

View file

@ -48,8 +48,6 @@
namespace ledger {
class query_t;
class predicate_t : public expr_t
{
public:
@ -63,15 +61,21 @@ public:
: expr_t(other), what_to_keep(other.what_to_keep) {
TRACE_CTOR(predicate_t, "copy");
}
predicate_t(const query_t& other);
predicate_t(const string& str, const keep_details_t& _what_to_keep,
const parse_flags_t& flags = PARSE_DEFAULT)
predicate_t(ptr_op_t _ptr,
const keep_details_t& _what_to_keep,
scope_t * _context = NULL)
: expr_t(_ptr, _context), what_to_keep(_what_to_keep) {
TRACE_CTOR(predicate_t, "ptr_op_t, keep_details_t, scope_t *");
}
predicate_t(const string& str,
const keep_details_t& _what_to_keep,
const parse_flags_t& flags = PARSE_DEFAULT)
: expr_t(str, flags), what_to_keep(_what_to_keep) {
TRACE_CTOR(predicate_t, "string, keep_details_t, parse_flags_t");
}
predicate_t(std::istream& in, const keep_details_t& _what_to_keep,
const parse_flags_t& flags = PARSE_DEFAULT)
predicate_t(std::istream& in,
const keep_details_t& _what_to_keep,
const parse_flags_t& flags = PARSE_DEFAULT)
: expr_t(in, flags), what_to_keep(_what_to_keep) {
TRACE_CTOR(predicate_t, "std::istream&, keep_details_t, parse_flags_t");
}

View file

@ -201,7 +201,7 @@ namespace {
}
if (post->assigned_amount)
amtbuf << " = " << post->assigned_amount;
amtbuf << " = " << *post->assigned_amount;
string trailer = amtbuf.str();
out << trailer;

View file

@ -53,7 +53,12 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
}
}
resume:
switch (*arg_i) {
case '\0':
assert(false);
break;
case '\'':
case '"':
case '/': {
@ -84,13 +89,17 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
if (multiple_args && consume_next_arg) {
consume_next_arg = false;
token_t tok(token_t::TERM, string(arg_i, arg_end));
prev_arg_i = arg_i;
arg_i = arg_end;
return tok;
}
resume:
bool consume_next = false;
switch (*arg_i) {
case '\0':
assert(false);
break;
case ' ':
case '\t':
case '\r':
@ -121,15 +130,20 @@ query_t::lexer_t::token_t query_t::lexer_t::next_token()
string::const_iterator beg = arg_i;
for (; arg_i != arg_end; ++arg_i) {
switch (*arg_i) {
case '\0':
assert(false);
break;
case ' ':
case '\t':
case '\n':
case '\r':
if (! consume_whitespace)
if (! multiple_args && ! consume_whitespace)
goto test_ident;
else
ident.push_back(*arg_i);
break;
case '(':
case ')':
case '&':
@ -170,20 +184,16 @@ test_ident:
return token_t(token_t::TOK_META);
else if (ident == "data")
return token_t(token_t::TOK_META);
else if (ident == "show") {
// The "show" keyword is special, and separates a limiting predicate
// from a display predicate.
DEBUG("pred.show", "string = " << (*begin).as_string());
return token_t(token_t::END_REACHED);
}
#if 0
// jww (2009-11-06): This is disabled for the time being.
else if (ident == "date") {
// The date keyword takes the whole of the next string as its argument.
consume_whitespace = true;
return token_t(token_t::TOK_DATE);
}
#endif
else if (ident == "show")
return token_t(token_t::TOK_SHOW);
else if (ident == "bold")
return token_t(token_t::TOK_BOLD);
else if (ident == "for")
return token_t(token_t::TOK_FOR);
else if (ident == "since")
return token_t(token_t::TOK_SINCE);
else if (ident == "until")
return token_t(token_t::TOK_UNTIL);
else if (ident == "expr") {
// The expr keyword takes the whole of the next string as its argument.
consume_next_arg = true;
@ -238,10 +248,15 @@ query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_contex
lexer_t::token_t tok = lexer.next_token();
switch (tok.kind) {
case lexer_t::token_t::TOK_SHOW:
case lexer_t::token_t::TOK_BOLD:
case lexer_t::token_t::TOK_FOR:
case lexer_t::token_t::TOK_SINCE:
case lexer_t::token_t::TOK_UNTIL:
case lexer_t::token_t::END_REACHED:
lexer.push_token(tok);
break;
case lexer_t::token_t::TOK_DATE:
case lexer_t::token_t::TOK_CODE:
case lexer_t::token_t::TOK_PAYEE:
case lexer_t::token_t::TOK_NOTE:
@ -257,41 +272,6 @@ query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_contex
case lexer_t::token_t::TERM:
assert(tok.value);
switch (tok_context) {
case lexer_t::token_t::TOK_DATE: {
expr_t::ptr_op_t ident = new expr_t::op_t(expr_t::op_t::IDENT);
ident->set_ident("date");
date_interval_t interval(*tok.value);
if (interval.start) {
node = new expr_t::op_t(expr_t::op_t::O_GTE);
node->set_left(ident);
expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE);
arg1->set_value(*interval.start);
node->set_right(arg1);
}
if (interval.finish) {
expr_t::ptr_op_t lt = new expr_t::op_t(expr_t::op_t::O_LT);
lt->set_left(ident);
expr_t::ptr_op_t arg1 = new expr_t::op_t(expr_t::op_t::VALUE);
arg1->set_value(*interval.finish);
lt->set_right(arg1);
if (node) {
expr_t::ptr_op_t prev(node);
node = new expr_t::op_t(expr_t::op_t::O_AND);
node->set_left(prev);
node->set_right(lt);
} else {
node = lt;
}
}
break;
}
case lexer_t::token_t::TOK_EXPR:
node = expr_t(*tok.value).get_op();
break;
@ -357,7 +337,7 @@ query_t::parser_t::parse_query_term(query_t::lexer_t::token_t::kind_t tok_contex
break;
case lexer_t::token_t::LPAREN:
node = parse_query_expr(tok_context);
node = parse_query_expr(tok_context, true);
tok = lexer.next_token();
if (tok.kind != lexer_t::token_t::RPAREN)
tok.expected(')');
@ -447,18 +427,121 @@ query_t::parser_t::parse_or_expr(lexer_t::token_t::kind_t tok_context)
}
expr_t::ptr_op_t
query_t::parser_t::parse_query_expr(lexer_t::token_t::kind_t tok_context)
query_t::parser_t::parse_query_expr(lexer_t::token_t::kind_t tok_context,
bool subexpression)
{
if (expr_t::ptr_op_t node = parse_or_expr(tok_context)) {
if (expr_t::ptr_op_t next = parse_query_expr(tok_context)) {
expr_t::ptr_op_t prev(node);
node = new expr_t::op_t(expr_t::op_t::O_OR);
node->set_left(prev);
node->set_right(next);
expr_t::ptr_op_t limiter;
while (expr_t::ptr_op_t next = parse_or_expr(tok_context)) {
if (! limiter) {
limiter = next;
} else {
expr_t::ptr_op_t prev(limiter);
limiter = new expr_t::op_t(expr_t::op_t::O_OR);
limiter->set_left(prev);
limiter->set_right(next);
}
return node;
}
return expr_t::ptr_op_t();
if (! subexpression) {
if (limiter)
query_map.insert
(query_map_t::value_type
(QUERY_LIMIT, predicate_t(limiter, what_to_keep).print_to_str()));
lexer_t::token_t tok = lexer.peek_token();
while (tok.kind != lexer_t::token_t::END_REACHED) {
switch (tok.kind) {
case lexer_t::token_t::TOK_SHOW: {
lexer.next_token();
expr_t::ptr_op_t node;
while (expr_t::ptr_op_t next = parse_or_expr(tok_context)) {
if (! node) {
node = next;
} else {
expr_t::ptr_op_t prev(node);
node = new expr_t::op_t(expr_t::op_t::O_OR);
node->set_left(prev);
node->set_right(next);
}
}
if (node)
query_map.insert
(query_map_t::value_type
(QUERY_SHOW, predicate_t(node, what_to_keep).print_to_str()));
break;
}
case lexer_t::token_t::TOK_BOLD: {
lexer.next_token();
expr_t::ptr_op_t node = parse_or_expr(tok_context);
while (expr_t::ptr_op_t next = parse_or_expr(tok_context)) {
expr_t::ptr_op_t prev(node);
node = new expr_t::op_t(expr_t::op_t::O_OR);
node->set_left(prev);
node->set_right(next);
}
if (node)
query_map.insert
(query_map_t::value_type
(QUERY_BOLD, predicate_t(node, what_to_keep).print_to_str()));
break;
}
case lexer_t::token_t::TOK_FOR:
case lexer_t::token_t::TOK_SINCE:
case lexer_t::token_t::TOK_UNTIL: {
tok = lexer.next_token();
string for_string;
if (tok.kind == lexer_t::token_t::TOK_SINCE)
for_string = "since";
else if (tok.kind == lexer_t::token_t::TOK_UNTIL)
for_string = "until";
lexer.consume_next_arg = true;
tok = lexer.peek_token();
while (tok.kind != lexer_t::token_t::END_REACHED) {
tok = lexer.next_token();
assert(tok.kind == lexer_t::token_t::TERM);
if (*tok.value == "show" || *tok.value == "bold" ||
*tok.value == "for" || *tok.value == "since" ||
*tok.value == "until") {
lexer.token_cache = lexer_t::token_t();
lexer.arg_i = lexer.prev_arg_i;
lexer.consume_next_arg = false;
break;
}
if (! for_string.empty())
for_string += " ";
for_string += *tok.value;
lexer.consume_next_arg = true;
tok = lexer.peek_token();
}
if (! for_string.empty())
query_map.insert(query_map_t::value_type(QUERY_FOR, for_string));
break;
}
default:
break;
}
tok = lexer.peek_token();
}
}
return limiter;
}
} // namespace ledger

View file

@ -46,7 +46,7 @@
namespace ledger {
class query_t : public predicate_t
class query_t
{
protected:
class parser_t;
@ -60,6 +60,7 @@ public:
value_t::sequence_t::const_iterator begin;
value_t::sequence_t::const_iterator end;
string::const_iterator prev_arg_i;
string::const_iterator arg_i;
string::const_iterator arg_end;
@ -81,7 +82,6 @@ public:
TOK_OR,
TOK_EQ,
TOK_DATE,
TOK_CODE,
TOK_PAYEE,
TOK_NOTE,
@ -89,6 +89,12 @@ public:
TOK_META,
TOK_EXPR,
TOK_SHOW,
TOK_BOLD,
TOK_FOR,
TOK_SINCE,
TOK_UNTIL,
TERM,
END_REACHED
@ -131,13 +137,17 @@ public:
case TOK_AND: return "TOK_AND";
case TOK_OR: return "TOK_OR";
case TOK_EQ: return "TOK_EQ";
case TOK_DATE: return "TOK_DATE";
case TOK_CODE: return "TOK_CODE";
case TOK_PAYEE: return "TOK_PAYEE";
case TOK_NOTE: return "TOK_NOTE";
case TOK_ACCOUNT: return "TOK_ACCOUNT";
case TOK_META: return "TOK_META";
case TOK_EXPR: return "TOK_EXPR";
case TOK_SHOW: return "TOK_SHOW";
case TOK_BOLD: return "TOK_BOLD";
case TOK_FOR: return "TOK_FOR";
case TOK_SINCE: return "TOK_SINCE";
case TOK_UNTIL: return "TOK_UNTIL";
case TERM: return string("TERM(") + *value + ")";
case END_REACHED: return "END_REACHED";
}
@ -153,13 +163,17 @@ public:
case TOK_AND: return "and";
case TOK_OR: return "or";
case TOK_EQ: return "=";
case TOK_DATE: return "date";
case TOK_CODE: return "code";
case TOK_PAYEE: return "payee";
case TOK_NOTE: return "note";
case TOK_ACCOUNT: return "account";
case TOK_META: return "meta";
case TOK_EXPR: return "expr";
case TOK_SHOW: return "show";
case TOK_BOLD: return "bold";
case TOK_FOR: return "for";
case TOK_SINCE: return "since";
case TOK_UNTIL: return "until";
case END_REACHED: return "<EOF>";
@ -197,8 +211,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)
multiple_args(lexer.multiple_args), token_cache(lexer.token_cache)
{
TRACE_CTOR(query_t::lexer_t, "copy");
}
@ -218,24 +231,39 @@ public:
}
};
enum kind_t {
QUERY_LIMIT,
QUERY_SHOW,
QUERY_BOLD,
QUERY_FOR
};
typedef std::map<kind_t, string> query_map_t;
protected:
class parser_t
{
friend class query_t;
value_t args;
lexer_t lexer;
value_t args;
lexer_t lexer;
keep_details_t what_to_keep;
query_map_t query_map;
expr_t::ptr_op_t parse_query_term(lexer_t::token_t::kind_t tok_context);
expr_t::ptr_op_t parse_unary_expr(lexer_t::token_t::kind_t tok_context);
expr_t::ptr_op_t parse_and_expr(lexer_t::token_t::kind_t tok_context);
expr_t::ptr_op_t parse_or_expr(lexer_t::token_t::kind_t tok_context);
expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context);
expr_t::ptr_op_t parse_query_expr(lexer_t::token_t::kind_t tok_context,
bool subexpression = false);
public:
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 value_t& _args,
const keep_details_t& _what_to_keep = keep_details_t(),
bool multiple_args = true)
: args(_args), lexer(args.begin(), args.end(), multiple_args),
what_to_keep(_what_to_keep) {
TRACE_CTOR(query_t::parser_t, "value_t, keep_details_t, bool");
}
parser_t(const parser_t& parser)
: args(parser.args), lexer(parser.lexer) {
@ -245,8 +273,8 @@ protected:
TRACE_DTOR(query_t::parser_t);
}
expr_t::ptr_op_t parse() {
return parse_query_expr(lexer_t::token_t::TOK_ACCOUNT);
expr_t::ptr_op_t parse(bool subexpression = false) {
return parse_query_expr(lexer_t::token_t::TOK_ACCOUNT, subexpression);
}
bool tokens_remaining() {
@ -257,55 +285,61 @@ protected:
};
optional<parser_t> parser;
query_map_t predicates;
public:
query_t() {
TRACE_CTOR(query_t, "");
}
query_t(const query_t& other)
: predicate_t(other) {
: parser(other.parser), predicates(other.predicates) {
TRACE_CTOR(query_t, "copy");
}
query_t(const string& arg,
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");
query_t(const string& arg,
const keep_details_t& what_to_keep = keep_details_t(),
bool multiple_args = true) {
TRACE_CTOR(query_t, "string, keep_details_t, bool");
if (! arg.empty()) {
value_t temp(string_value(arg));
parse_args(temp.to_sequence(), multiple_args);
parse_args(temp.to_sequence(), what_to_keep, multiple_args);
}
}
query_t(const value_t& args,
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");
query_t(const value_t& args,
const keep_details_t& what_to_keep = keep_details_t(),
bool multiple_args = true) {
TRACE_CTOR(query_t, "value_t, keep_details_t, bool");
if (! args.empty())
parse_args(args, multiple_args);
parse_args(args, what_to_keep, multiple_args);
}
virtual ~query_t() {
TRACE_DTOR(query_t);
}
void parse_args(const value_t& args, bool multiple_args = true) {
expr_t::ptr_op_t
parse_args(const value_t& args,
const keep_details_t& what_to_keep = keep_details_t(),
bool multiple_args = true,
bool subexpression = false) {
if (! parser)
parser = parser_t(args, multiple_args);
ptr = parser->parse(); // expr_t::ptr
parser = parser_t(args, what_to_keep, multiple_args);
return parser->parse(subexpression);
}
void parse_again() {
assert(parser);
ptr = parser->parse(); // expr_t::ptr
bool has_query(const kind_t& id) const {
return parser && parser->query_map.find(id) != parser->query_map.end();
}
string get_query(const kind_t& id) const {
if (parser) {
query_map_t::const_iterator i = parser->query_map.find(id);
if (i != parser->query_map.end())
return (*i).second;
}
return empty_string;
}
bool tokens_remaining() {
return parser && parser->tokens_remaining();
}
virtual string text() {
return print_to_str();
}
};
} // namespace ledger

View file

@ -145,26 +145,8 @@ void report_t::normalize_options(const string& verb)
// using -b or -e). Then, if no _duration_ was specified (such as monthly),
// then ignore the period since the begin/end are the only interesting
// details.
if (HANDLED(period_)) {
date_interval_t interval(HANDLER(period_).str());
optional<date_t> begin = interval.begin();
optional<date_t> end = interval.end();
if (! HANDLED(begin_) && begin) {
string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
HANDLER(limit_).on(string("?normalize"), predicate);
}
if (! HANDLED(end_) && end) {
string predicate = "date<[" + to_iso_extended_string(*end) + "]";
HANDLER(limit_).on(string("?normalize"), predicate);
}
if (! interval.duration)
HANDLER(period_).off();
else if (! HANDLED(sort_all_))
HANDLER(sort_xacts_).on_only(string("?normalize"));
}
if (HANDLED(period_))
normalize_period();
// If -j or -J were specified, set the appropriate format string now so as
// to avoid option ordering issues were we to have done it during the
@ -254,24 +236,52 @@ void report_t::normalize_options(const string& verb)
}
}
void report_t::normalize_period()
{
date_interval_t interval(HANDLER(period_).str());
optional<date_t> begin = interval.begin();
optional<date_t> end = interval.end();
if (! HANDLED(begin_) && begin) {
string predicate = "date>=[" + to_iso_extended_string(*begin) + "]";
HANDLER(limit_).on(string("?normalize"), predicate);
}
if (! HANDLED(end_) && end) {
string predicate = "date<[" + to_iso_extended_string(*end) + "]";
HANDLER(limit_).on(string("?normalize"), predicate);
}
if (! interval.duration)
HANDLER(period_).off();
else if (! HANDLED(sort_all_))
HANDLER(sort_xacts_).on_only(string("?normalize"));
}
void report_t::parse_query_args(const value_t& args, const string& whence)
{
query_t query(args, what_to_keep());
if (query) {
HANDLER(limit_).on(whence, query.text());
DEBUG("report.predicate",
"Predicate = " << HANDLER(limit_).str());
if (query.has_query(query_t::QUERY_LIMIT)) {
HANDLER(limit_).on(whence, query.get_query(query_t::QUERY_LIMIT));
DEBUG("report.predicate", "Limit predicate = " << HANDLER(limit_).str());
}
if (query.tokens_remaining()) {
query.parse_again();
if (query) {
HANDLER(display_).on(whence, query.text());
if (query.has_query(query_t::QUERY_SHOW)) {
HANDLER(display_).on(whence, query.get_query(query_t::QUERY_SHOW));
DEBUG("report.predicate", "Display predicate = " << HANDLER(display_).str());
}
DEBUG("report.predicate",
"Display predicate = " << HANDLER(display_).str());
}
if (query.has_query(query_t::QUERY_BOLD)) {
HANDLER(bold_if_).set_expr(whence, query.get_query(query_t::QUERY_BOLD));
DEBUG("report.predicate", "Bolding predicate = " << HANDLER(bold_if_).str());
}
if (query.has_query(query_t::QUERY_FOR)) {
HANDLER(period_).on(whence, query.get_query(query_t::QUERY_FOR));
DEBUG("report.predicate", "Report period = " << HANDLER(period_).str());
normalize_period(); // it needs normalization
}
}
@ -428,6 +438,15 @@ void report_t::commodities_report(post_handler_ptr handler)
session.journal->clear_xdata();
}
value_t report_t::display_value(const value_t& val)
{
value_t temp(val.strip_annotations(what_to_keep()));
if (HANDLED(base))
return temp;
else
return temp.unreduced();
}
value_t report_t::fn_amount_expr(call_scope_t& scope)
{
return HANDLER(amount_).expr.calc(scope);
@ -448,6 +467,14 @@ value_t report_t::fn_display_total(call_scope_t& scope)
return HANDLER(display_total_).expr.calc(scope);
}
value_t report_t::fn_should_bold(call_scope_t& scope)
{
if (HANDLED(bold_if_))
return HANDLER(bold_if_).expr.calc(scope);
else
return false;
}
value_t report_t::fn_market(call_scope_t& args)
{
optional<datetime_t> moment = (args.has<datetime_t>(1) ?
@ -533,11 +560,7 @@ value_t report_t::fn_print(call_scope_t& args)
value_t report_t::fn_scrub(call_scope_t& args)
{
value_t temp(args.value().strip_annotations(what_to_keep()));
if (HANDLED(base))
return temp;
else
return temp.unreduced();
return display_value(args.value());
}
value_t report_t::fn_rounded(call_scope_t& args)
@ -900,6 +923,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(base);
else OPT_ALT(basis, cost);
else OPT_(begin_);
else OPT(bold_if_);
else OPT(budget);
else OPT(by_payee);
break;
@ -955,6 +979,7 @@ option_t<report_t> * report_t::lookup_option(const char * p)
break;
case 'i':
OPT(invert);
else OPT(inject_);
break;
case 'j':
OPT_CH(amount_data);
@ -1220,6 +1245,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return MAKE_FUNCTOR(report_t::fn_scrub);
else if (is_eq(p, "strip"))
return MAKE_FUNCTOR(report_t::fn_strip);
else if (is_eq(p, "should_bold"))
return MAKE_FUNCTOR(report_t::fn_should_bold);
break;
case 't':

View file

@ -126,6 +126,7 @@ public:
}
void normalize_options(const string& verb);
void normalize_period();
void parse_query_args(const value_t& args, const string& whence);
void posts_report(post_handler_ptr handler);
@ -134,10 +135,13 @@ public:
void accounts_report(acct_handler_ptr handler);
void commodities_report(post_handler_ptr handler);
value_t display_value(const value_t& val);
value_t fn_amount_expr(call_scope_t& scope);
value_t fn_total_expr(call_scope_t& scope);
value_t fn_display_amount(call_scope_t& scope);
value_t fn_display_total(call_scope_t& scope);
value_t fn_should_bold(call_scope_t& scope);
value_t fn_market(call_scope_t& scope);
value_t fn_get_at(call_scope_t& scope);
value_t fn_is_seq(call_scope_t& scope);
@ -260,6 +264,7 @@ public:
HANDLER(group_by_).report(out);
HANDLER(group_title_format_).report(out);
HANDLER(head_).report(out);
HANDLER(inject_).report(out);
HANDLER(invert).report(out);
HANDLER(limit_).report(out);
HANDLER(lot_dates).report(out);
@ -376,9 +381,13 @@ public:
OPTION__(report_t, balance_format_, CTOR(report_t, balance_format_) {
on(none,
"%(justify(scrub(display_total), 20, 20 + prepend_width, true, color))"
"%(ansify_if("
" justify(scrub(display_total), 20, 20 + prepend_width, true, color),"
" bold if should_bold))"
" %(!options.flat ? depth_spacer : \"\")"
"%-(ansify_if(partial_account(options.flat), blue if color))\n%/"
"%-(ansify_if("
" ansify_if(partial_account(options.flat), blue if color),"
" bold if should_bold))\n%/"
"%$1\n%/"
"--------------------\n");
});
@ -402,6 +411,18 @@ public:
parent->HANDLER(limit_).on(string("--begin"), predicate);
});
OPTION__
(report_t, bold_if_,
expr_t expr;
CTOR(report_t, bold_if_) {}
void set_expr(const optional<string>& whence, const string& str) {
expr = str;
on(whence, str);
}
DO_(args) {
set_expr(args.get<string>(0), args.get<string>(1));
});
OPTION_(report_t, budget, DO() {
parent->budget_flags |= BUDGET_BUDGETED;
});
@ -616,6 +637,7 @@ public:
});
OPTION(report_t, head_);
OPTION(report_t, inject_);
OPTION_(report_t, invert, DO() {
parent->HANDLER(amount_).set_expr(string("--invert"), "-amount");
@ -804,20 +826,37 @@ public:
OPTION__(report_t, register_format_, CTOR(report_t, register_format_) {
on(none,
"%(ansify_if(justify(format_date(date), date_width), green "
" if color & date > today))"
" %(ansify_if(justify(truncated(payee, payee_width), payee_width), "
" bold if color & !cleared & actual))"
" %(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 + prepend_width, true, color))"
" %(justify(scrub(display_total), total_width, "
" 4 + meta_width + date_width + payee_width + account_width"
" + amount_width + total_width + prepend_width, true, color))\n%/"
"%(justify(\" \", 2 + date_width + payee_width))"
"%$3 %$4 %$5\n");
"%(ansify_if("
" ansify_if(justify(format_date(date), date_width),"
" green if color and date > today),"
" bold if should_bold))"
" %(ansify_if("
" ansify_if(justify(truncated(payee, payee_width), payee_width), "
" bold if color and !cleared and actual),"
" bold if should_bold))"
" %(ansify_if("
" ansify_if(justify(truncated(display_account, account_width, "
" abbrev_len), account_width),"
" blue if color),"
" bold if should_bold))"
" %(ansify_if("
" justify(scrub(display_amount), amount_width, "
" 3 + meta_width + date_width + payee_width"
" + account_width + amount_width + prepend_width,"
" true, color),"
" bold if should_bold))"
" %(ansify_if("
" justify(scrub(display_total), total_width, "
" 4 + meta_width + date_width + payee_width"
" + account_width + amount_width + total_width"
" + prepend_width, true, color),"
" bold if should_bold))\n%/"
"%(justify(\" \", date_width))"
" %(ansify_if("
" justify(truncated(has_tag(\"Payee\") ? payee : \" \", "
" payee_width), payee_width),"
" bold if should_bold))"
" %$3 %$4 %$5\n");
});
OPTION(report_t, related); // -r

View file

@ -602,7 +602,7 @@ inline scope_t * call_scope_t::get<scope_t *>(std::size_t index, bool) {
template <>
inline expr_t::ptr_op_t
call_scope_t::get<expr_t::ptr_op_t>(std::size_t index, bool) {
return resolve(index, value_t::ANY).as_any<expr_t::ptr_op_t>();
return args[index].as_any<expr_t::ptr_op_t>();
}
class value_scope_t : public scope_t

View file

@ -161,14 +161,16 @@ void session_t::read_journal_files()
if (HANDLED(master_account_))
master_account = HANDLER(master_account_).str();
std::size_t count = read_data(master_account);
if (count == 0)
throw_(parse_error,
_("Failed to locate any transactions; did you specify a valid file with -f?"));
#if defined(DEBUG_ON)
std::size_t count =
#endif
read_data(master_account);
INFO_FINISH(journal);
#if defined(DEBUG_ON)
INFO("Found " << count << " transactions");
#endif
}
void session_t::close_journal_files()

View file

@ -534,9 +534,13 @@ void instance_t::automated_xact_directive(char * line)
bool reveal_context = true;
try {
std::auto_ptr<auto_xact_t> ae
(new auto_xact_t(query_t(string(skip_ws(line + 1)),
keep_details_t(true, true, true), false)));
query_t query;
keep_details_t keeper(true, true, true);
expr_t::ptr_op_t expr =
query.parse_args(string_value(skip_ws(line + 1)).to_sequence(),
keeper, false, true);
std::auto_ptr<auto_xact_t> ae(new auto_xact_t(predicate_t(expr, keeper)));
ae->pos = position_t();
ae->pos->pathname = pathname;
ae->pos->beg_pos = line_beg_pos;
@ -715,10 +719,10 @@ void instance_t::include_directive(char * line)
mask_t glob;
#if BOOST_VERSION >= 103700
path parent_path = filename.parent_path();
glob.assign_glob(filename.filename());
glob.assign_glob('^' + filename.filename() + '$');
#else // BOOST_VERSION >= 103700
path parent_path = filename.branch_path();
glob.assign_glob(filename.leaf());
glob.assign_glob('^' + filename.leaf() + '$');
#endif // BOOST_VERSION >= 103700
bool files_found = false;

View file

@ -310,7 +310,14 @@ string_to_month_of_year(const std::string& str)
datetime_t parse_datetime(const char * str)
{
datetime_t when = input_datetime_io->parse(str);
char buf[128];
std::strcpy(buf, str);
for (char * p = buf; *p; p++)
if (*p == '.' || *p == '-')
*p = '/';
datetime_t when = input_datetime_io->parse(buf);
if (when.is_not_a_date_time())
throw_(date_error, _("Invalid date/time: %1") << str);
return when;
@ -401,6 +408,8 @@ class date_parser_t
TOK_A_MONTH,
TOK_A_WDAY,
TOK_AGO,
TOK_HENCE,
TOK_SINCE,
TOK_UNTIL,
TOK_IN,
@ -498,6 +507,8 @@ class date_parser_t
out << date_specifier_t::day_of_week_type
(boost::get<date_time::weekdays>(*value));
break;
case TOK_AGO: return "ago";
case TOK_HENCE: return "hence";
case TOK_SINCE: return "since";
case TOK_UNTIL: return "until";
case TOK_IN: return "in";
@ -545,6 +556,8 @@ class date_parser_t
case TOK_A_YEAR: out << "TOK_A_YEAR"; break;
case TOK_A_MONTH: out << "TOK_A_MONTH"; break;
case TOK_A_WDAY: out << "TOK_A_WDAY"; break;
case TOK_AGO: out << "TOK_AGO"; break;
case TOK_HENCE: out << "TOK_HENCE"; break;
case TOK_SINCE: out << "TOK_SINCE"; break;
case TOK_UNTIL: out << "TOK_UNTIL"; break;
case TOK_IN: out << "TOK_IN"; break;
@ -638,17 +651,182 @@ private:
void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok,
date_specifier_t& specifier)
{
date_t today = CURRENT_DATE();
switch (tok.kind) {
case lexer_t::token_t::TOK_DATE:
specifier = boost::get<date_specifier_t>(*tok.value);
break;
case lexer_t::token_t::TOK_INT:
specifier.day =
date_specifier_t::day_type(boost::get<unsigned short>(*tok.value));
case lexer_t::token_t::TOK_INT: {
unsigned short amount = boost::get<unsigned short>(*tok.value);
int8_t adjust = 0;
tok = lexer.peek_token();
lexer_t::token_t::kind_t kind = tok.kind;
switch (kind) {
case lexer_t::token_t::TOK_YEAR:
case lexer_t::token_t::TOK_YEARS:
case lexer_t::token_t::TOK_QUARTER:
case lexer_t::token_t::TOK_QUARTERS:
case lexer_t::token_t::TOK_MONTH:
case lexer_t::token_t::TOK_MONTHS:
case lexer_t::token_t::TOK_WEEK:
case lexer_t::token_t::TOK_WEEKS:
case lexer_t::token_t::TOK_DAY:
case lexer_t::token_t::TOK_DAYS:
lexer.next_token();
tok = lexer.next_token();
switch (tok.kind) {
case lexer_t::token_t::TOK_AGO:
adjust = -1;
break;
case lexer_t::token_t::TOK_HENCE:
adjust = 1;
break;
default:
tok.unexpected();
break;
}
break;
default:
break;
}
date_t when(today);
switch (kind) {
case lexer_t::token_t::TOK_YEAR:
case lexer_t::token_t::TOK_YEARS:
when += gregorian::years(amount * adjust);
break;
case lexer_t::token_t::TOK_QUARTER:
case lexer_t::token_t::TOK_QUARTERS: {
date_t temp =
date_duration_t::find_nearest(today, date_duration_t::QUARTERS);
when += gregorian::months(amount * 3 * adjust);
break;
}
case lexer_t::token_t::TOK_MONTH:
case lexer_t::token_t::TOK_MONTHS:
when += gregorian::months(amount * adjust);
break;
case lexer_t::token_t::TOK_WEEK:
case lexer_t::token_t::TOK_WEEKS:
when += gregorian::weeks(amount * adjust);
break;
case lexer_t::token_t::TOK_DAY:
case lexer_t::token_t::TOK_DAYS:
when += gregorian::days(amount * adjust);
break;
default:
specifier.day = date_specifier_t::day_type(amount);
break;
}
if (adjust)
specifier = date_specifier_t(when);
break;
}
case lexer_t::token_t::TOK_THIS:
case lexer_t::token_t::TOK_NEXT:
case lexer_t::token_t::TOK_LAST: {
int8_t adjust = 0;
if (tok.kind == lexer_t::token_t::TOK_NEXT)
adjust = 1;
else if (tok.kind == lexer_t::token_t::TOK_LAST)
adjust = -1;
tok = lexer.next_token();
switch (tok.kind) {
case lexer_t::token_t::TOK_A_MONTH: {
date_t temp(today.year(),
boost::get<date_time::months_of_year>(*tok.value), 1);
temp += gregorian::years(adjust);
specifier =
date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
temp.month());
break;
}
case lexer_t::token_t::TOK_A_WDAY: {
date_t temp =
date_duration_t::find_nearest(today, date_duration_t::WEEKS);
while (temp.day_of_week() !=
boost::get<date_time::months_of_year>(*tok.value))
temp += gregorian::days(1);
temp += gregorian::days(7 * adjust);
specifier = date_specifier_t(temp);
break;
}
case lexer_t::token_t::TOK_YEAR: {
date_t temp(today);
temp += gregorian::years(adjust);
specifier =
date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()));
break;
}
case lexer_t::token_t::TOK_QUARTER: {
date_t base =
date_duration_t::find_nearest(today, date_duration_t::QUARTERS);
date_t temp;
if (adjust < 0) {
temp = base + gregorian::months(3 * adjust);
}
else if (adjust == 0) {
temp = base + gregorian::months(3);
}
else if (adjust > 0) {
base += gregorian::months(3 * adjust);
temp = base + gregorian::months(3 * adjust);
}
specifier = date_specifier_t(adjust < 0 ? temp : base);
break;
}
case lexer_t::token_t::TOK_WEEK: {
date_t base =
date_duration_t::find_nearest(today, date_duration_t::WEEKS);
date_t temp;
if (adjust < 0) {
temp = base + gregorian::days(7 * adjust);
}
else if (adjust == 0) {
temp = base + gregorian::days(7);
}
else if (adjust > 0) {
base += gregorian::days(7 * adjust);
temp = base + gregorian::days(7 * adjust);
}
specifier = date_specifier_t(adjust < 0 ? temp : base);
break;
}
case lexer_t::token_t::TOK_DAY: {
date_t temp(today);
temp += gregorian::days(adjust);
specifier = date_specifier_t(temp);
break;
}
default:
case lexer_t::token_t::TOK_MONTH: {
date_t temp(today);
temp += gregorian::months(adjust);
specifier =
date_specifier_t(static_cast<date_specifier_t::year_type>(temp.year()),
temp.month());
break;
}
}
break;
}
case lexer_t::token_t::TOK_A_YEAR:
specifier.year = boost::get<date_specifier_t::year_type>(*tok.value);
specifier.year = boost::get<date_specifier_t::year_type>(*tok.value);
break;
case lexer_t::token_t::TOK_A_MONTH:
specifier.month =
@ -662,13 +840,13 @@ void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok,
break;
case lexer_t::token_t::TOK_TODAY:
specifier = date_specifier_t(CURRENT_DATE());
specifier = date_specifier_t(today);
break;
case lexer_t::token_t::TOK_TOMORROW:
specifier = date_specifier_t(CURRENT_DATE() + gregorian::days(1));
specifier = date_specifier_t(today + gregorian::days(1));
break;
case lexer_t::token_t::TOK_YESTERDAY:
specifier = date_specifier_t(CURRENT_DATE() - gregorian::days(1));
specifier = date_specifier_t(today - gregorian::days(1));
break;
default:
@ -784,6 +962,9 @@ date_interval_t date_parser_t::parse()
date_t base(today);
date_t end(today);
if (! adjust)
adjust = 1;
tok = lexer.next_token();
switch (tok.kind) {
case lexer_t::token_t::TOK_YEARS:
@ -1231,6 +1412,8 @@ bool date_interval_t::find_period(const date_t& date)
DEBUG("times.interval", "true: start = " << *start);
DEBUG("times.interval", "true: end_of_duration = " << *end_of_duration);
resolve_end();
return true;
}
@ -1281,7 +1464,11 @@ void date_interval_t::dump(std::ostream& out)
if (duration)
out << _("duration: ") << duration->to_string() << std::endl;
stabilize(begin());
optional<date_t> when(begin());
if (! when)
when = CURRENT_DATE();
stabilize(when);
out << std::endl
<< _("--- After stabilization ---") << std::endl;
@ -1401,6 +1588,10 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token()
string_to_day_of_week(term)) {
return token_t(token_t::TOK_A_WDAY, token_t::content_t(*wday));
}
else if (term == _("ago"))
return token_t(token_t::TOK_AGO);
else if (term == _("hence"))
return token_t(token_t::TOK_HENCE);
else if (term == _("from") || term == _("since"))
return token_t(token_t::TOK_SINCE);
else if (term == _("to") || term == _("until"))

View file

@ -68,14 +68,14 @@ inline bool is_valid(const date_t& moment) {
extern optional<datetime_t> epoch;
#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK
#define TRUE_CURRENT_TIME() (boost::posix_time::microsec_clock::universal_time())
#define TRUE_CURRENT_TIME() (boost::posix_time::microsec_clock::local_time())
#define CURRENT_TIME() (epoch ? *epoch : TRUE_CURRENT_TIME())
#else
#define TRUE_CURRENT_TIME() (boost::posix_time::second_clock::universal_time())
#define TRUE_CURRENT_TIME() (boost::posix_time::second_clock::local_time())
#define CURRENT_TIME() (epoch ? *epoch : TRUE_CURRENT_TIME())
#endif
#define CURRENT_DATE() \
(epoch ? epoch->date() : boost::gregorian::day_clock::universal_day())
(epoch ? epoch->date() : boost::gregorian::day_clock::local_day())
extern date_time::weekdays start_of_week;

View file

@ -901,13 +901,10 @@ bool value_t::is_less_than(const value_t& val) const
switch (val.type()) {
case INTEGER:
case AMOUNT: {
if (val.is_nonzero())
break;
bool no_amounts = true;
foreach (const balance_t::amounts_map::value_type& pair,
as_balance().amounts) {
if (pair.second >= 0L)
if (pair.second >= val)
return false;
no_amounts = false;
}
@ -927,12 +924,9 @@ bool value_t::is_less_than(const value_t& val) const
switch (val.type()) {
case INTEGER:
case AMOUNT: {
if (val.is_nonzero())
break;
bool no_amounts = true;
foreach (const value_t& value, as_sequence()) {
if (value >= 0L)
if (value >= val)
return false;
no_amounts = false;
}
@ -1023,13 +1017,10 @@ bool value_t::is_greater_than(const value_t& val) const
switch (val.type()) {
case INTEGER:
case AMOUNT: {
if (val.is_nonzero())
break;
bool no_amounts = true;
foreach (const balance_t::amounts_map::value_type& pair,
as_balance().amounts) {
if (pair.second <= 0L)
if (pair.second <= val)
return false;
no_amounts = false;
}
@ -1049,12 +1040,9 @@ bool value_t::is_greater_than(const value_t& val) const
switch (val.type()) {
case INTEGER:
case AMOUNT: {
if (val.is_nonzero())
break;
bool no_amounts = true;
foreach (const value_t& value, as_sequence()) {
if (value <= 0L)
if (value <= val)
return false;
no_amounts = false;
}

View file

@ -124,27 +124,13 @@ bool xact_base_t::finalize()
amount_t& p(post->cost ? *post->cost : post->amount);
if (! p.is_null()) {
DEBUG("xact.finalize", "post must balance = " << p.reduced());
if (! post->cost && post->amount.has_annotation() &&
post->amount.annotation().price) {
// If the amount has no cost, but is annotated with a per-unit
// price, use the price times the amount as the cost
post->cost = (*post->amount.annotation().price *
post->amount).unrounded();
DEBUG("xact.finalize",
"annotation price = " << *post->amount.annotation().price);
DEBUG("xact.finalize", "amount = " << post->amount);
DEBUG("xact.finalize", "priced cost = " << *post->cost);
post->add_flags(POST_COST_CALCULATED);
add_or_set_value(balance, post->cost->rounded().reduced());
} else {
// If the amount was a cost, it very likely has the "keep_precision"
// flag set, meaning commodity display precision is ignored when
// displaying the amount. We never want this set for the balance,
// so we must clear the flag in a temporary to avoid it propagating
// into the balance.
add_or_set_value(balance, p.keep_precision() ?
p.rounded().reduced() : p.reduced());
}
// If the amount was a cost, it very likely has the
// "keep_precision" flag set, meaning commodity display precision
// is ignored when displaying the amount. We never want this set
// for the balance, so we must clear the flag in a temporary to
// avoid it propagating into the balance.
add_or_set_value(balance, p.keep_precision() ?
p.rounded().reduced() : p.reduced());
}
else if (null_post) {
throw_(std::logic_error,
@ -173,14 +159,15 @@ bool xact_base_t::finalize()
add_post(null_post);
}
if (balance.is_balance() &&
if (! null_post && 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
// establishes the per-unit cost for this post for both commodities.
DEBUG("xact.finalize", "there were exactly two commodities");
DEBUG("xact.finalize",
"there were exactly two commodities, and no null post");
bool saw_cost = false;
post_t * top_post = NULL;
@ -268,55 +255,65 @@ bool xact_base_t::finalize()
posts_list copy(posts);
foreach (post_t * post, copy) {
if (! post->cost)
continue;
if (has_date()) {
foreach (post_t * post, copy) {
if (! post->cost)
continue;
if (post->amount.commodity() == post->cost->commodity())
throw_(balance_error,
_("A posting's cost must be of a different commodity than its amount"));
if (post->amount.commodity() == post->cost->commodity())
throw_(balance_error,
_("A posting's cost must be of a different commodity than its amount"));
cost_breakdown_t breakdown =
commodity_pool_t::current_pool->exchange
cost_breakdown_t breakdown =
commodity_pool_t::current_pool->exchange
(post->amount, *post->cost, false,
datetime_t(date(), time_duration(0, 0, 0, 0)));
if (post->amount.has_annotation() &&
breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) {
if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) {
DEBUG("xact.finalize", "gain_loss = " << gain_loss);
gain_loss.in_place_round();
DEBUG("xact.finalize", "gain_loss rounds to = " << gain_loss);
if (post->amount.has_annotation() && post->amount.annotation().price) {
if (breakdown.basis_cost.commodity() == breakdown.final_cost.commodity()) {
if (amount_t gain_loss = breakdown.basis_cost - breakdown.final_cost) {
DEBUG("xact.finalize", "gain_loss = " << gain_loss);
gain_loss.in_place_round();
DEBUG("xact.finalize", "gain_loss rounds to = " << gain_loss);
if (post->must_balance())
add_or_set_value(balance, gain_loss.reduced());
if (post->must_balance())
add_or_set_value(balance, gain_loss.reduced());
account_t * account;
if (gain_loss.sign() > 0)
account = journal->find_account(_("Equity:Capital Gains"));
else
account = journal->find_account(_("Equity:Capital Losses"));
account_t * account;
if (gain_loss.sign() > 0)
account = journal->find_account(_("Equity:Capital Gains"));
else
account = journal->find_account(_("Equity:Capital Losses"));
post_t * p = new post_t(account, gain_loss, ITEM_GENERATED);
p->set_state(post->state());
if (post->has_flags(POST_VIRTUAL)) {
DEBUG("xact.finalize", "gain_loss came from a virtual post");
p->add_flags(post->flags() & (POST_VIRTUAL | POST_MUST_BALANCE));
post_t * p = new post_t(account, gain_loss, ITEM_GENERATED);
p->set_state(post->state());
if (post->has_flags(POST_VIRTUAL)) {
DEBUG("xact.finalize", "gain_loss came from a virtual post");
p->add_flags(post->flags() & (POST_VIRTUAL | POST_MUST_BALANCE));
}
add_post(p);
DEBUG("xact.finalize", "added gain_loss, balance = " << balance);
} else {
DEBUG("xact.finalize", "gain_loss would have displayed as zero");
}
}
add_post(p);
DEBUG("xact.finalize", "added gain_loss, balance = " << balance);
} else {
DEBUG("xact.finalize", "gain_loss would have display as zero");
if (post->amount.has_annotation()) {
if (breakdown.amount.has_annotation())
breakdown.amount.annotation().tag = post->amount.annotation().tag;
else
breakdown.amount.annotate
(annotation_t(none, none, post->amount.annotation().tag));
}
post->amount = breakdown.amount;
DEBUG("xact.finalize", "added breakdown, balance = " << balance);
}
} else {
post->amount = breakdown.amount;
DEBUG("xact.finalize", "added breakdown, balance = " << balance);
}
if (post->has_flags(POST_COST_FIXATED) &&
post->amount.has_annotation() && post->amount.annotation().price) {
DEBUG("xact.finalize", "fixating annotation price");
post->amount.annotation().add_flags(ANNOTATION_PRICE_FIXATED);
if (post->has_flags(POST_COST_FIXATED) &&
post->amount.has_annotation() && post->amount.annotation().price) {
DEBUG("xact.finalize", "fixating annotation price");
post->amount.annotation().add_flags(ANNOTATION_PRICE_FIXATED);
}
}
}

121
test/CheckComments.py Normal file
View file

@ -0,0 +1,121 @@
import sys
import re
import os
ok = 100.0
if sys.argv[1] == '-l':
ok = float(sys.argv[2])
sys.argv = [sys.argv[0]] + sys.argv[3:]
debug = False
if sys.argv[1] == '-v':
debug = True
sys.argv = [sys.argv[0]] + sys.argv[2:]
errors = 0
for path in sys.argv[1:]:
other_depth = 0
brace_depth = 0
code_count = 0
comment_count = 0
long_comment = None
long_comments = {}
kind = "function"
function_name = "<unknown>"
ctor_dtor = False
linenum = 0
fd = open(path, 'r')
for line in fd.readlines():
linenum += 1
match = re.search('/\*\*(.*)', line)
if match:
long_comment = re.sub('\s+', '', match.group(1))
continue
elif long_comment:
match = re.search('(.*)\*/', line)
if match:
long_comment += re.sub('\s+', '', match.group(1))
comment_count = len(long_comment)
long_comment = None
else:
long_comment += re.sub('\s+', '', line[:-1])
continue
if brace_depth == 0:
match = re.search('(namespace|enum|class|struct|union)', line)
if match:
kind = match.group(1)
if debug: print "kind =", kind
elif kind == "function":
match = re.search('(\S+)\(', line)
if match:
function_name = match.group(1)
long_comments[function_name] = comment_count
comment_count = 0
if debug: print "name found %s" % function_name
if re.search('{', line) and not re.search('@{', line):
if kind == "function":
brace_depth += 1
if debug: print "brace_depth =", brace_depth
else:
other_depth += 1
kind = "function"
if debug: print "other_depth =", other_depth
if re.search('}', line) and not re.search('@}', line):
if brace_depth > 0:
brace_depth -= 1
if debug: print "brace_depth =", brace_depth
if brace_depth == 0:
if debug: print "function done"
if function_name in long_comments:
comment_count += long_comments[function_name]
if code_count == 0:
percent = ok
print "%7s %4d/%4d %s:%d: %s" % \
("empty", comment_count, code_count,
os.path.basename(path), linenum,
function_name)
errors += 1
else:
percent = 100.0 * (float(comment_count) /
float(code_count))
if percent < ok and not ctor_dtor:
print "%6.0f%% %4d/%4d %s:%d: %s" % \
(percent, comment_count, code_count,
os.path.basename(path), linenum,
function_name)
errors += 1
code_count = 0
comment_count = 0
kind = "function"
function_name = "<unknown>"
ctor_dtor = False
else:
other_depth -= 1
if debug: print "other_depth =", other_depth
if brace_depth > 0:
if re.search("TRACE_[CD]TOR", line):
ctor_dtor = True
line = re.sub('\s+', '', line[:-1])
match = re.search('//(.*)', line)
if match:
comment = match.group(1)
line = re.sub('//.*', '', line)
else:
comment = None
if line:
code_count += len(line)
if comment:
comment_count += len(comment)
sys.exit(errors)

87
test/CheckTests.py Executable file
View file

@ -0,0 +1,87 @@
#!/usr/bin/env python
import sys
import re
import os
from os.path import *
from subprocess import Popen, PIPE
ledger_binary = sys.argv[1]
source_topdir = sys.argv[2]
documented_options = []
for line in open(join(source_topdir, 'doc', 'ledger.1')):
match = re.match('\.It Fl \\\\-([-A-Za-z]+)', line)
if match:
option = match.group(1)
if option not in documented_options:
documented_options.append(option)
pipe = Popen('%s --debug option.names parse true' % ledger_binary,
shell=True, stdout=PIPE, stderr=PIPE)
errors = 0
untested_options = [
'anon',
'args-only',
'cache',
'debug',
'download',
'file',
'force-color',
'force-pager',
'full-help',
'help',
'help-calc',
'help-comm',
'help-disp',
'import',
'init-file',
'no-color',
'options',
'price-db',
'price-exp',
'revalued-total',
'script',
'seed',
'trace',
'verbose',
'verify',
'version'
]
for line in pipe.stderr:
match = re.search('\[DEBUG\] Option: (.*)', line)
if match:
option = match.group(1)
option = re.sub('_', '-', option)
option = re.sub('-$', '', option)
if option not in untested_options and \
not exists(join(source_topdir, 'test', 'baseline',
'opt-%s.test' % option)):
print "Baseline test missing for --%s" % option
errors += 1
if option not in documented_options:
print "Man page entry missing for --%s" % option
errors += 1
else:
documented_options.remove(option)
known_alternates = [
'cost',
'first',
'import',
'last',
'leeway',
'period-sort'
]
for option in documented_options:
if option not in known_alternates:
print "Man page entry for unknown option --%s" % option
sys.exit(errors)

View file

@ -130,6 +130,7 @@ def run_gen_test(i):
harness.success()
else:
harness.failure()
return harness.failed
if multiproc:
pool = Pool(jobs*2)
@ -137,7 +138,7 @@ else:
pool = None
if pool:
pool.map(run_gen_test, range(beg_range, end_range))
harness.failed = sum(pool.map(run_gen_test, range(beg_range, end_range)))
else:
for i in range(beg_range, end_range):
run_gen_test(i)
@ -145,4 +146,5 @@ else:
if pool:
pool.close()
pool.join()
harness.exit()

View file

@ -52,7 +52,7 @@ class RegressFile(object):
def read_section(self):
lines = []
line = self.fd.readline()
while not self.is_directive(line):
while line and not self.is_directive(line):
lines.append(self.transform_line(line))
line = self.fd.readline()
return (lines, line)
@ -154,18 +154,22 @@ class RegressFile(object):
if not use_stdin:
os.remove(tempdata[1])
def run_tests(self, pool):
def run_tests(self):
test = self.read_test()
while test:
if pool:
pool.apply_async(RegressFile.run_test, (self, test,))
else:
self.run_test(test)
self.run_test(test)
test = self.read_test(test)
return harness.failed
def close(self):
self.fd.close()
def do_test(path):
entry = RegressFile(path)
failed = entry.run_tests()
entry.close()
return failed
if __name__ == '__main__':
if multiproc:
pool = Pool(jobs*2)
@ -173,17 +177,19 @@ if __name__ == '__main__':
pool = None
if os.path.isdir(tests):
for test_file in os.listdir(tests):
if re.search('\.test$', test_file):
entry = RegressFile(os.path.join(tests, test_file))
entry.run_tests(pool)
entry.close()
tests = [os.path.join(tests, x)
for x in os.listdir(tests) if x.endswith('.test')]
if pool:
harness.failed = sum(pool.map(do_test, tests, 1))
else:
harness.failed = sum(map(do_test, tests))
else:
entry = RegressFile(tests)
entry.run_tests(pool)
entry.run_tests()
entry.close()
if pool:
pool.close()
pool.join()
harness.exit()

View file

@ -22,3 +22,15 @@ P 1990/02/15 12:00:00 FOO $3
90-Feb-20 Payee Expenses:Gas $300 $800
>>>2
=== 0
reg
<<<
fixed XCD $0.374531835206
2008/04/08 KFC
Expenses:Food XCD 43.00
Assets:Cash
end fixed
>>>
08-Apr-08 KFC Expenses:Food XCD 43.00 XCD 43.00
Assets:Cash XCD -43.00 0

View file

View file

@ -1,4 +1,4 @@
reg --add-budget books cards
reg --add-budget books cards --now=2009/12/31
<<<
~ monthly
Expenses:Books $10.00

View file

View file

View file

@ -1,4 +1,4 @@
reg --budget books
reg --budget books --now=2009/12/31
<<<
~ monthly
Expenses:Books $10.00

View file

@ -0,0 +1,22 @@
reg income --budget --now=2010/06/20
<<<
~ Monthly since 2010/01/01
Expenses:Bills:Rent $873.00
Expenses:Household $200.00
Income:Salary -$2491.60
Assets:Bank:Checking
~ biweekly from 2010/02/23
Expenses:Bills:Housecleaning $85.00
Assets:Bank:Checking
2010/06/22 c897683b
Expenses:Household $100.00
Assets:Bank:Checking
>>>
10-Jan-01 Budget transaction Income:Salary $2491.60 $2491.60
10-Feb-01 Budget transaction Income:Salary $2491.60 $4983.20
10-Mar-01 Budget transaction Income:Salary $2491.60 $7474.80
10-Apr-01 Budget transaction Income:Salary $2491.60 $9966.40
10-May-01 Budget transaction Income:Salary $2491.60 $12458.00
10-Jun-01 Budget transaction Income:Salary $2491.60 $14949.60

View file

@ -0,0 +1,111 @@
reg --now=2010/02 --sort=date exp --budget
<<<
~ monthly
Expenses:Food $100
Expenses:Movies $20
Assets:Cash
~ monthly from 2009
Expenses:Food $101
Expenses:Movies $21
Assets:Cash
~ monthly to 2010
Expenses:Food $102
Expenses:Movies $22
Assets:Cash
~ monthly from 2009 to 2010
Expenses:Food $103
Expenses:Movies $23
Assets:Cash
2009/06/05 Grocery
Expenses:Food $5
Assets:Cash
>>>
09-Jan-01 Budget transaction Expenses:Food $-101 $-101
09-Jan-01 Budget transaction Expenses:Movies $-21 $-122
09-Jan-01 Budget transaction Expenses:Food $-103 $-225
09-Jan-01 Budget transaction Expenses:Movies $-23 $-248
09-Feb-01 Budget transaction Expenses:Food $-101 $-349
09-Feb-01 Budget transaction Expenses:Movies $-21 $-370
09-Feb-01 Budget transaction Expenses:Food $-103 $-473
09-Feb-01 Budget transaction Expenses:Movies $-23 $-496
09-Mar-01 Budget transaction Expenses:Food $-101 $-597
09-Mar-01 Budget transaction Expenses:Movies $-21 $-618
09-Mar-01 Budget transaction Expenses:Food $-103 $-721
09-Mar-01 Budget transaction Expenses:Movies $-23 $-744
09-Apr-01 Budget transaction Expenses:Food $-101 $-845
09-Apr-01 Budget transaction Expenses:Movies $-21 $-866
09-Apr-01 Budget transaction Expenses:Food $-103 $-969
09-Apr-01 Budget transaction Expenses:Movies $-23 $-992
09-May-01 Budget transaction Expenses:Food $-101 $-1093
09-May-01 Budget transaction Expenses:Movies $-21 $-1114
09-May-01 Budget transaction Expenses:Food $-103 $-1217
09-May-01 Budget transaction Expenses:Movies $-23 $-1240
09-Jun-01 Budget transaction Expenses:Food $-100 $-1340
09-Jun-01 Budget transaction Expenses:Movies $-20 $-1360
09-Jun-01 Budget transaction Expenses:Food $-102 $-1462
09-Jun-01 Budget transaction Expenses:Movies $-22 $-1484
09-Jun-01 Budget transaction Expenses:Food $-101 $-1585
09-Jun-01 Budget transaction Expenses:Movies $-21 $-1606
09-Jun-01 Budget transaction Expenses:Food $-103 $-1709
09-Jun-01 Budget transaction Expenses:Movies $-23 $-1732
09-Jun-05 Grocery Expenses:Food $5 $-1727
09-Jul-01 Budget transaction Expenses:Food $-100 $-1827
09-Jul-01 Budget transaction Expenses:Movies $-20 $-1847
09-Jul-01 Budget transaction Expenses:Food $-101 $-1948
09-Jul-01 Budget transaction Expenses:Movies $-21 $-1969
09-Jul-01 Budget transaction Expenses:Food $-102 $-2071
09-Jul-01 Budget transaction Expenses:Movies $-22 $-2093
09-Jul-01 Budget transaction Expenses:Food $-103 $-2196
09-Jul-01 Budget transaction Expenses:Movies $-23 $-2219
09-Aug-01 Budget transaction Expenses:Food $-100 $-2319
09-Aug-01 Budget transaction Expenses:Movies $-20 $-2339
09-Aug-01 Budget transaction Expenses:Food $-101 $-2440
09-Aug-01 Budget transaction Expenses:Movies $-21 $-2461
09-Aug-01 Budget transaction Expenses:Food $-102 $-2563
09-Aug-01 Budget transaction Expenses:Movies $-22 $-2585
09-Aug-01 Budget transaction Expenses:Food $-103 $-2688
09-Aug-01 Budget transaction Expenses:Movies $-23 $-2711
09-Sep-01 Budget transaction Expenses:Food $-100 $-2811
09-Sep-01 Budget transaction Expenses:Movies $-20 $-2831
09-Sep-01 Budget transaction Expenses:Food $-101 $-2932
09-Sep-01 Budget transaction Expenses:Movies $-21 $-2953
09-Sep-01 Budget transaction Expenses:Food $-102 $-3055
09-Sep-01 Budget transaction Expenses:Movies $-22 $-3077
09-Sep-01 Budget transaction Expenses:Food $-103 $-3180
09-Sep-01 Budget transaction Expenses:Movies $-23 $-3203
09-Oct-01 Budget transaction Expenses:Food $-100 $-3303
09-Oct-01 Budget transaction Expenses:Movies $-20 $-3323
09-Oct-01 Budget transaction Expenses:Food $-101 $-3424
09-Oct-01 Budget transaction Expenses:Movies $-21 $-3445
09-Oct-01 Budget transaction Expenses:Food $-102 $-3547
09-Oct-01 Budget transaction Expenses:Movies $-22 $-3569
09-Oct-01 Budget transaction Expenses:Food $-103 $-3672
09-Oct-01 Budget transaction Expenses:Movies $-23 $-3695
09-Nov-01 Budget transaction Expenses:Food $-100 $-3795
09-Nov-01 Budget transaction Expenses:Movies $-20 $-3815
09-Nov-01 Budget transaction Expenses:Food $-101 $-3916
09-Nov-01 Budget transaction Expenses:Movies $-21 $-3937
09-Nov-01 Budget transaction Expenses:Food $-102 $-4039
09-Nov-01 Budget transaction Expenses:Movies $-22 $-4061
09-Nov-01 Budget transaction Expenses:Food $-103 $-4164
09-Nov-01 Budget transaction Expenses:Movies $-23 $-4187
09-Dec-01 Budget transaction Expenses:Food $-100 $-4287
09-Dec-01 Budget transaction Expenses:Movies $-20 $-4307
09-Dec-01 Budget transaction Expenses:Food $-101 $-4408
09-Dec-01 Budget transaction Expenses:Movies $-21 $-4429
09-Dec-01 Budget transaction Expenses:Food $-102 $-4531
09-Dec-01 Budget transaction Expenses:Movies $-22 $-4553
09-Dec-01 Budget transaction Expenses:Food $-103 $-4656
09-Dec-01 Budget transaction Expenses:Movies $-23 $-4679
10-Jan-01 Budget transaction Expenses:Food $-100 $-4779
10-Jan-01 Budget transaction Expenses:Movies $-20 $-4799
10-Jan-01 Budget transaction Expenses:Food $-101 $-4900
10-Jan-01 Budget transaction Expenses:Movies $-21 $-4921
10-Feb-01 Budget transaction Expenses:Food $-100 $-5021
10-Feb-01 Budget transaction Expenses:Movies $-20 $-5041
10-Feb-01 Budget transaction Expenses:Food $-101 $-5142
10-Feb-01 Budget transaction Expenses:Movies $-21 $-5163

View file

View file

@ -4,8 +4,8 @@ reg --account=commodity
Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
Income:Dividends:Vanguard:VMMXX $-0.35
>>>1
07-Feb-02 RD VMMXX VM:As:Inve:Vangu:VMMXX 0.350 VMMXX 0.350 VMMXX
07-Feb-02 RD VMMXX $:In:Divi:Vangua:VMMXX $-0.35 $-0.35
07-Feb-02 RD VMMXX VM:As:Inv:Vangua:VMMXX 0.350 VMMXX 0.350 VMMXX
07-Feb-02 RD VMMXX $:In:Div:Vanguar:VMMXX $-0.35 $-0.35
0.350 VMMXX
>>>2
=== 0

View file

View file

View file

View file

View file

@ -24,13 +24,13 @@ reg --empty
Assets:Cash $-10.00 0
08-Jan-01 January Expenses:One:Books $10.00 $10.00
Expenses:One:Two:Books $10.00 $20.00
Expe:On:Tw:Three:Books $10.00 $30.00
Ex:One:Two:Three:Books $10.00 $30.00
Assets:Cash $-30.00 0
08-Jan-01 January Assets:Cash 0 0
Income:Books 0 0
08-Jan-01 January Assets:Cash $30.00 $30.00
Income:One:Books $-10.00 $20.00
Income:One:Two:Books $-10.00 $10.00
Inc:On:Two:Three:Books $-10.00 0
In:One:Two:Three:Books $-10.00 0
>>>2
=== 0

View file

View file

@ -0,0 +1,64 @@
reg --forecast 'date <[2011]' --now=2010/06/21
<<<
~ Monthly since 2010/01/01
Expenses:Bills:Rent $873.00
Expenses:Household $200.00
Income:Salary -$2491.60
Assets:Bank:Checking
~ biweekly from 2010/02/23
Expenses:Bills:Housecleaning $85.00
Assets:Bank:Checking
>>>
10-Jul-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Jul-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Jul-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Jul-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Jun-27 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Jun-27 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Jul-11 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Jul-11 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Aug-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Aug-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Aug-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Aug-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Jul-25 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Jul-25 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Aug-08 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Aug-08 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Sep-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Sep-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Sep-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Sep-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Aug-22 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Aug-22 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Sep-05 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Sep-05 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Oct-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Oct-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Oct-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Oct-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Sep-19 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Sep-19 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Oct-03 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Oct-03 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Nov-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Nov-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Nov-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Nov-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Oct-17 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Oct-17 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Oct-31 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Oct-31 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Nov-14 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Nov-14 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Dec-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Dec-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Dec-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Dec-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Nov-28 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Nov-28 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Dec-12 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Dec-12 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Dec-26 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Dec-26 Forecast transaction Assets:Bank:Checking $-85.00 0

View file

View file

View file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -4,24 +4,22 @@ D 1.0000s
2006/03/14 Opening Balances
Assets:Tajer 1339829c @ 1.86590975416s
Assets:Gruulmorg 248720c {10.051463493s}
Assets:Gruulmorg 248720c @ 10.051463493s
Equity:Gold -5000000s
>>>1
1339829c {1.86590975416s} [2006/03/14]
1339829c {1.86590975416s} [2006/03/14]
248720c {10.051463493s}
248720c {10.051463493s} [2006/03/14]
1339829c {1.86590975416s} [2006/03/14]
248720c {10.051463493s}
248720c {10.051463493s} [2006/03/14]
-1388.9h
>>>2
=== 0
reg --format '%(justify(scrub(total_expr), 40, 40, true))\n' --lots-actual
>>>1
1339829c
1339829c
248720c {10.051463493s}
1339829c
248720c {10.051463493s}
1588549c
1588549c
-1388.9h
>>>2
=== 0

File diff suppressed because it is too large Load diff

View file

@ -356,8 +356,8 @@ D 1.00G
Assets:Tajer
2006/03/17 Player: raev
Assets:Tajer:Items "Wildheart Belt" 1 {30G}
Assets:Tajer:Items "Ace of Warlords" -2 {15G}
Assets:Tajer:Items "Wildheart Belt" 1 @ 30G
Assets:Tajer:Items "Ace of Warlords" -2 @ 15G
2006/03/17 Auction House
Expenses:Fees:Auction 7482c
@ -609,8 +609,8 @@ D 1.00G
Assets:Tajer -1.20s 0
06-Mar-14 Puldoost Assets:Tajer 8.00G 8.00G
Expenses:Items -8.00G 0
06-Mar-14 Auction House Assets:Wyshona:Items 1.25G 1.25G
Assets:Tajer:Items -1.25G 0
06-Mar-14 Auction House Assets:Wyshona:Items "Plans: Wildthorn Mail" 1 "Plans: Wildthorn Mail" 1
Assets:Tajer:Items "Plans: Wildthorn Mail" -1 0
06-Mar-15 Auction House Assets:Tajer 45.00s 45.00s
Assets:Tajer 2.59s 47.59s
Assets:Bids -47.59s 0
@ -680,11 +680,17 @@ D 1.00G
Assets:Tajer:Items -119.58G 0
Income:Brokering -54.58G -54.58G
Equity:Capital Gains 54.58G 0
06-Mar-16 Auction House Assets:Wyshona:Items 2.11G 2.11G
Assets:Wyshona:Items 2.30G 4.40G
Assets:Wyshona:Items 1.00G 5.40G
Assets:Wyshona:Items 1.50G 6.90G
Assets:Tajer:Items -6.90G 0
06-Mar-16 Auction House Assets:Wyshona:Items "Plans: Mithril Shield Spike" 1 "Plans: Mithril Shield Spike" 1
Assets:Wyshona:Items "Plans: Mithril Shield Spike" 1 "Plans: Mithril Shield Spike" 2
Assets:Wyshona:Items "Recipe: Elixir of Giant Growth" 1 "Plans: Mithril Shield Spike" 2
"Recipe: Elixir of Giant Growth" 1
Assets:Wyshona:Items "Recipe: Elixir of Giant Growth" 1 "Plans: Mithril Shield Spike" 2
"Recipe: Elixir of Giant Growth" 2
Assets:Tajer:Items "Plans: Mithril Shield Spike" -1 "Plans: Mithril Shield Spike" 1
"Recipe: Elixir of Giant Growth" 2
Assets:Tajer:Items "Plans: Mithril Shield Spike" -1 "Recipe: Elixir of Giant Growth" 2
Assets:Tajer:Items "Recipe: Elixir of Giant Growth" -1 "Recipe: Elixir of Giant Growth" 1
Assets:Tajer:Items "Recipe: Elixir of Giant Growth" -1 0
06-Mar-16 Player Assets:Tajer 4.00G 4.00G
Equity:Gold -4.00G 0
06-Mar-16 Auction House Assets:Wyshona 13.41G 13.41G
@ -718,8 +724,8 @@ D 1.00G
Assets:Tajer -30.00G 0
06-Mar-16 Auction House Assets:Gruulmorg:Items 30.00G 30.00G
Assets:Gruulmorg -30.00G 0
06-Mar-16 Transfer Assets:Tajer:Items 30.00G 30.00G
Assets:Gruulmorg:Items -30.00G 0
06-Mar-16 Transfer Assets:Tajer:Items "Ace of Warlords" 2 "Ace of Warlords" 2
Assets:Gruulmorg:Items "Ace of Warlords" -2 0
06-Mar-16 Post Expenses:Fees:Mail 60c 60c
Assets:Gruulmorg -60c 0
06-Mar-16 Post Expenses:Fees:Mail 1.20s 1.20s

View file

@ -356,8 +356,8 @@ D 1.00G
Assets:Tajer
2006/03/17 Player: raev
Assets:Tajer:Items "Wildheart Belt" 1 {30G}
Assets:Tajer:Items "Ace of Warlords" -2 {15G}
Assets:Tajer:Items "Wildheart Belt" 1 @ 30G
Assets:Tajer:Items "Ace of Warlords" -2 @ 15G
2006/03/17 Auction House
Expenses:Fees:Auction 7482c
@ -610,8 +610,8 @@ D 1.00G
Assets:Tajer -120c 0
06-Mar-14 Puldoost Assets:Tajer 80000c 80000c
Expenses:Items -80000c 0
06-Mar-14 Auction House Assets:Wyshona:Items 12500c 12500c
Assets:Tajer:Items -12500c 0
06-Mar-14 Auction House Assets:Wyshona:Items "Plans: Wildthorn Mail" 1 "Plans: Wildthorn Mail" 1
Assets:Tajer:Items "Plans: Wildthorn Mail" -1 0
06-Mar-15 Auction House Assets:Tajer 4500c 4500c
Assets:Tajer 259c 4759c
Assets:Bids -4759c 0
@ -681,11 +681,17 @@ D 1.00G
Assets:Tajer:Items -1195768c 0
Income:Brokering -545768c -545768c
Equity:Capital Gains 545768c 0
06-Mar-16 Auction House Assets:Wyshona:Items 21050c 21050c
Assets:Wyshona:Items 23000c 44050c
Assets:Wyshona:Items 10000c 54050c
Assets:Wyshona:Items 15000c 69050c
Assets:Tajer:Items -69050c 0
06-Mar-16 Auction House Assets:Wyshona:Items "Plans: Mithril Shield Spike" 1 "Plans: Mithril Shield Spike" 1
Assets:Wyshona:Items "Plans: Mithril Shield Spike" 1 "Plans: Mithril Shield Spike" 2
Assets:Wyshona:Items "Recipe: Elixir of Giant Growth" 1 "Plans: Mithril Shield Spike" 2
"Recipe: Elixir of Giant Growth" 1
Assets:Wyshona:Items "Recipe: Elixir of Giant Growth" 1 "Plans: Mithril Shield Spike" 2
"Recipe: Elixir of Giant Growth" 2
Assets:Tajer:Items "Plans: Mithril Shield Spike" -1 "Plans: Mithril Shield Spike" 1
"Recipe: Elixir of Giant Growth" 2
Assets:Tajer:Items "Plans: Mithril Shield Spike" -1 "Recipe: Elixir of Giant Growth" 2
Assets:Tajer:Items "Recipe: Elixir of Giant Growth" -1 "Recipe: Elixir of Giant Growth" 1
Assets:Tajer:Items "Recipe: Elixir of Giant Growth" -1 0
06-Mar-16 Player Assets:Tajer 40000c 40000c
Equity:Gold -40000c 0
06-Mar-16 Auction House Assets:Wyshona 134100c 134100c
@ -719,8 +725,8 @@ D 1.00G
Assets:Tajer -300030c 0
06-Mar-16 Auction House Assets:Gruulmorg:Items 300000c 300000c
Assets:Gruulmorg -300000c 0
06-Mar-16 Transfer Assets:Tajer:Items 300000c 300000c
Assets:Gruulmorg:Items -300000c 0
06-Mar-16 Transfer Assets:Tajer:Items "Ace of Warlords" 2 "Ace of Warlords" 2
Assets:Gruulmorg:Items "Ace of Warlords" -2 0
06-Mar-16 Post Expenses:Fees:Mail 60c 60c
Assets:Gruulmorg -60c 0
06-Mar-16 Post Expenses:Fees:Mail 120c 120c

View file

View file

View file

View file

View file

View file

@ -22,11 +22,11 @@ reg --account=payee
>>>1
08-Jan-01 January January:Expenses:Books $10.00 $10.00
08-Jan-01 January January:Assets:Cash $-10.00 0
08-Jan-31 End of January End of J:Expense:Books $10.00 $10.00
08-Jan-31 End of January End of :Expenses:Books $10.00 $10.00
08-Jan-31 End of January End of Jan:Assets:Cash $-10.00 0
08-Feb-01 February Februar:Expenses:Books $20.00 $20.00
08-Feb-01 February February:Assets:Cash $-20.00 0
08-Feb-28 End of February End of F:Expense:Books $20.00 $20.00
08-Feb-28 End of February End of :Expenses:Books $20.00 $20.00
08-Feb-28 End of February End of Feb:Assets:Cash $-20.00 0
08-Mar-01 March March:Expenses:Books $30.00 $30.00
08-Mar-01 March March:Assets:Cash $-30.00 0

View file

View file

View file

View file

@ -0,0 +1,12 @@
reg
<<<
2010-04-02 Opening balance
Assets:A 14.75 EUR
Assets:B 2.84 GBP
Equity:Opening balance
>>>
10-Apr-02 Opening balance Assets:A 14.75 EUR 14.75 EUR
Assets:B 2.84 GBP 14.75 EUR
2.84 GBP
Equity:Opening balance -14.75 EUR 2.84 GBP
Equity:Opening balance -2.84 GBP 0

View file

@ -0,0 +1,17 @@
bal
<<<
D 1000.00 USD
2010-01-07 * Put money in
Assets:A -20.00 EUR
Equity:Opening balances
2010-01-11 * Purchase
Assets:A 20.00 EUR @@ 25.00 USD
Expenses:B
>>>
20.00 EUR Equity:Opening balances
-25.00 USD Expenses:B
--------------------
20.00 EUR
-25.00 USD

View file

@ -0,0 +1,24 @@
reg bank --forecast "d<=[next year]" -d "d>=[this month] & d<=[next year]" --sort d --now=2010/06/20
<<<
~ Monthly since 2010/01/01
Expenses:Bills:Rent $873.00
Expenses:Household $200.00
Income:Salary -$2491.60
Assets:Bank:Checking
~ biweekly from 2010/02/23
Expenses:Bills:Housecleaning $85.00
Assets:Bank:Checking
2010/06/22 c897683b
ad738623:d317da42:0e30a690 A2079.00
208b135f:c84cc2a7:a336b63a A199.00
45435ee9:2d8ee712:ee7e46b1:0f0e7e54:f5dbec59
>>>
10-Jul-01 Forecast transaction Assets:Bank:Checking $1418.60 $1418.60
10-Aug-01 Forecast transaction Assets:Bank:Checking $1418.60 $2837.20
10-Sep-01 Forecast transaction Assets:Bank:Checking $1418.60 $4255.80
10-Oct-01 Forecast transaction Assets:Bank:Checking $1418.60 $5674.40
10-Nov-01 Forecast transaction Assets:Bank:Checking $1418.60 $7093.00
10-Dec-01 Forecast transaction Assets:Bank:Checking $1418.60 $8511.60
11-Jan-01 Forecast transaction Assets:Bank:Checking $1418.60 $9930.20

View file

@ -1,4 +1,4 @@
bal --flat food:out
bal --flat food:out --now=2009/12/31
<<<
~ Monthly
Expenses:Auto:Fuel $120.00
@ -13,8 +13,8 @@ bal --flat food:out
$50.00 Expenses:Food:Out
>>>2
=== 0
bal --flat --budget food:out
bal --flat --budget food:out --now=2009/12/31
>>>1
$-50.00 Expenses:Food:Out
$-150.00 Expenses:Food:Out
>>>2
=== 0

View file

@ -5,7 +5,7 @@ reg -B
Expenses:Bank:Fees 2.73
Liabilities:Mastercard
>>>1
09-Jun-03 Westjet Expen:Transportati:Air 676.017377 676.017377
09-Jun-03 Westjet Expe:Transportatio:Air 676.017377 676.017377
Expenses:Bank:Fees 2.73 678.747377
Liabilities:Mastercard -678.747377 0
>>>2

View file

@ -0,0 +1,33 @@
reg
<<<
2007-01-01 Opening balances
Assets:Cash 10.00 EUR
Equity:Opening balances
2008-01-01 Buy 5.00 GBP
Assets:Cash 5.00 GBP @ 1.4 EUR
Assets:Checking
2009-01-01 Sell 5.00 GBP for 7.50 EUR that I bought for 7.00 EUR
Assets:Cash -5.00 GBP {=1.4 EUR} @ 1.5 EUR
Assets:Checking 7.50 EUR
Income:Gain
P 2009-02-01 00:00:00 GBP 1.5 EUR
>>>
07-Jan-01 Opening balances Assets:Cash 10.00 EUR 10.00 EUR
Equit:Opening balances -10.00 EUR 0
08-Jan-01 Buy 5.00 GBP Assets:Cash 5.00 GBP 5.00 GBP
Assets:Checking -7.00 EUR -7.00 EUR
5.00 GBP
09-Jan-01 Sell 5.00 GBP for 7.. Assets:Cash -5.00 GBP {=1.40 EUR} -7.00 EUR
5.00 GBP
-5.00 GBP {=1.40 EUR}
Assets:Checking 7.50 EUR 0.50 EUR
5.00 GBP
-5.00 GBP {=1.40 EUR}
Income:Gain -0.50 EUR 5.00 GBP
-5.00 GBP {=1.40 EUR}
Equity:Capital Gains 0.50 EUR 0.50 EUR
5.00 GBP
-5.00 GBP {=1.40 EUR}

View file

@ -0,0 +1,72 @@
reg --forecast 'date<[2011]' --now=2010/06/20
<<<
~ Monthly since 2010/01/01
Expenses:Bills:Rent $873.00
Expenses:Household $200.00
Income:Salary -$2491.60
Assets:Bank:Checking
~ biweekly from 2010/02/23
Expenses:Bills:Housecleaning $85.00
Assets:Bank:Checking
2010/06/22 c897683b
ad738623:d317da42:0e30a690 A2079.00
208b135f:c84cc2a7:a336b63a A199.00
45435ee9:2d8ee712:ee7e46b1:0f0e7e54:f5dbec59
>>>
10-Jun-22 c897683b ad738:d317da4:0e30a690 A2079.00 A2079.00
208b1:c84cc2a:a336b63a A199.00 A2278.00
45:2d:ee:0f0e:f5dbec59 A-2278.00 0
10-Jul-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Jul-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Jul-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Jul-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Jun-27 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Jun-27 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Jul-11 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Jul-11 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Aug-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Aug-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Aug-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Aug-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Jul-25 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Jul-25 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Aug-08 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Aug-08 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Sep-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Sep-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Sep-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Sep-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Aug-22 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Aug-22 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Sep-05 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Sep-05 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Oct-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Oct-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Oct-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Oct-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Sep-19 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Sep-19 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Oct-03 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Oct-03 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Nov-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Nov-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Nov-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Nov-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Oct-17 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Oct-17 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Oct-31 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Oct-31 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Nov-14 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Nov-14 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Dec-01 Forecast transaction Expenses:Bills:Rent $873.00 $873.00
10-Dec-01 Forecast transaction Expenses:Household $200.00 $1073.00
10-Dec-01 Forecast transaction Income:Salary $-2491.60 $-1418.60
10-Dec-01 Forecast transaction Assets:Bank:Checking $1418.60 0
10-Nov-28 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Nov-28 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Dec-12 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Dec-12 Forecast transaction Assets:Bank:Checking $-85.00 0
10-Dec-26 Forecast transaction Exp:Bill:Housecleaning $85.00 $85.00
10-Dec-26 Forecast transaction Assets:Bank:Checking $-85.00 0

View file

@ -11,7 +11,7 @@ reg
Expenses:Financial:Fees
>>>1
07-Dec-31 Cost basis for: RED.. As:In:RBC-:Account-RSP 4.00 RHT 4.00 RHT
Equ:Opening-Balan:Cost -689.87 CAD -689.87 CAD
Eq:Opening-Balanc:Cost -689.87 CAD -689.87 CAD
4.00 RHT
08-Jan-03 Sell -- RHT -- RED .. As:In:RBC-:Account-RSP -4.00 RHT -689.87 CAD
Ex:Financi:Commissions 9.95 USD -689.87 CAD

45
test/run Executable file
View file

@ -0,0 +1,45 @@
#!/bin/bash
LEDGER=ledger
ARGS="--args-only --no-color --columns=80"
output_only=false
update_test=false
if [[ "$1" == "-v" ]]; then
output_only=true
shift 1
elif [[ "$1" == "-u" ]]; then
update_test=true
shift 1
fi
COMMAND=$(perl -ne 'print unless /^<<</ .. eof();' $1)
if [[ $output_only == false && $update_test == false ]]; then
perl -ne 'print unless 1 .. /^>>>/ or /^(===|>>>2)/ .. eof();' $1 > /tmp/expected.$$
fi
perl -ne 'print unless 1 .. /^<<</ or /^>>>/ .. eof();' $1 \
| eval "$LEDGER -f - -o /tmp/received.$$ $ARGS $COMMAND"
if [[ $update_test == true ]]; then
if [[ -f /tmp/received.$$ ]]; then
perl -ne 'print if 1 .. /^>>>/;' $1 > /tmp/command.$$
perl -ne 'print if /^(===|>>>2)/ .. eof();' $1 > /tmp/epilog.$$
cat /tmp/command.$$ /tmp/received.$$ /tmp/epilog.$$ > replace.$$
mv replace.$$ $1
/bin/rm -f /tmp/command.$$ /tmp/received.$$ /tmp/epilog.$$
echo Test updated.
fi
elif [[ $output_only == false ]]; then
if [[ -f /tmp/expected.$$ && -f /tmp/received.$$ ]]; then
diff -w -U3 /tmp/expected.$$ /tmp/received.$$ && echo Test passed.
fi
else
if [[ -f /tmp/received.$$ ]]; then
cat /tmp/received.$$
fi
fi
/bin/rm -f /tmp/expected.$$ /tmp/received.$$

View file

@ -63,7 +63,7 @@ void ValueExprTestCase::testPredicateTokenizer2()
args.push_back(string_value("foo and bar"));
#ifndef NOT_FOR_PYTHON
query_t::lexer_t tokens(args.begin(), args.end());
query_t::lexer_t tokens(args.begin(), args.end(), false);
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);
@ -119,7 +119,7 @@ void ValueExprTestCase::testPredicateTokenizer5()
args.push_back(string_value("bar)"));
#ifndef NOT_FOR_PYTHON
query_t::lexer_t tokens(args.begin(), args.end());
query_t::lexer_t tokens(args.begin(), args.end(), false);
assertEqual(query_t::lexer_t::token_t::LPAREN, tokens.next_token().kind);
assertEqual(query_t::lexer_t::token_t::TERM, tokens.next_token().kind);
@ -168,7 +168,7 @@ void ValueExprTestCase::testPredicateTokenizer8()
args.push_back(string_value("expr 'foo and bar'"));
#ifndef NOT_FOR_PYTHON
query_t::lexer_t tokens(args.begin(), args.end());
query_t::lexer_t tokens(args.begin(), args.end(), false);
assertEqual(query_t::lexer_t::token_t::TOK_EXPR, tokens.next_token().kind);
assertEqual(query_t::lexer_t::token_t::TERM, tokens.next_token().kind);
@ -318,7 +318,7 @@ void ValueExprTestCase::testPredicateTokenizer16()
args.push_back(string_value("and bar|baz"));
#ifndef NOT_FOR_PYTHON
query_t::lexer_t tokens(args.begin(), args.end());
query_t::lexer_t tokens(args.begin(), args.end(), false);
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);

View file

@ -247,7 +247,8 @@ endif
######################################################################
TESTS = RegressTests BaselineTests ManualTests ConfirmTests GenerateTests
TESTS = RegressTests BaselineTests ManualTests ConfirmTests \
GenerateTests
if HAVE_CPPUNIT
TESTS += \
@ -258,6 +259,10 @@ TESTS += \
ReportTests
endif
if DEBUG
TESTS += CheckTests
endif
if HAVE_BOOST_PYTHON
TESTS += PyUnitTests
endif
@ -409,6 +414,12 @@ GenerateTests: $(srcdir)/test/GenerateTests.py
echo "$(PYTHON) $(srcdir)/test/GenerateTests.py -j$(JOBS) $(top_builddir)/ledger$(EXEEXT) $(srcdir) 1 ${1:-20} \"\$$@\"" > $@
chmod 755 $@
CheckTests_SOURCES = test/CheckTests.py
CheckTests:
echo "$(PYTHON) $(srcdir)/test/CheckTests.py $(top_builddir)/ledger$(EXEEXT) $(srcdir) \"\$$@\"" > $@
chmod 755 $@
FULLCHECK=$(srcdir)/test/fullcheck.sh
if HAVE_CPPUNIT

View file

@ -14,8 +14,7 @@ fi
rm -fr ~/Products/ledger-proof
time ./acprep --enable-cache --enable-doxygen \
--universal -j16 --warn proof 2>&1 | \
time ./acprep --enable-doxygen --universal -j16 --warn proof 2>&1 | \
tee ~/Desktop/proof.log
if egrep -q '(ERROR|CRITICAL)' ~/Desktop/proof.log; then
@ -26,9 +25,13 @@ if egrep -q '(ERROR|CRITICAL)' ~/Desktop/proof.log; then
exit 1
fi
else
echo "Ledger proof build succeeded"
echo $VERSION > ~/Products/last-proofed
mv ~/Desktop/proof.log /tmp
cd ~/Products/ledger-proof/debug; make docs
cd ~/Products/ledger-proof/gcov; make report
echo "Ledger proof build succeeded"
fi
exit 0

View file

@ -2,17 +2,38 @@
set -e
ACPREP="./acprep --universal -j16 --warn opt"
(cd plan/data; git push)
(cd plan; git commit -a -m "Update TODO files" && git push)
git checkout next
perl -i -pe "s/([-abgrc][0-9]*)?\\]\\)/-$(date +%Y%m%d)])/;" version.m4
git add version.m4
echo git commit -m "v$(cat version.m4 | sed 's/.*\[//' | sed 's/\].*//')"
git checkout master
git merge --no-ff next
git checkout next
git rebase master
git push
git checkout master
./acprep --enable-cache --universal -j16 --warn opt upload
cp -p ~/Products/ledger/opt/ledger ~/bin
./acprep --enable-cache --universal -j16 --warn opt make speedtest 2>&1 \
| tee build/last-speed.txt
mv *.dmg* build
$ACPREP upload
$ACPREP make dist
scp ~/Products/ledger/opt/ledger-*.tar.* jw:/srv/ftp/pub/ledger
openssl md5 *.dmg* ~/Products/ledger/opt/ledger-*.tar.* > build/CHECKSUMS.txt
openssl sha1 *.dmg* ~/Products/ledger/opt/ledger-*.tar.* >> build/CHECKSUMS.txt
openssl rmd160 *.dmg* ~/Products/ledger/opt/ledger-*.tar.* >> build/CHECKSUMS.txt
perl -i -pe 's/\/.*\///;' build/CHECKSUMS.txt
scp build/CHECKSUMS.txt jw:/srv/ftp/pub/ledger
rsync -az --delete ~/Products/ledger-proof/gcov/doc/report/ jw:/srv/ftp/pub/ledger/lcov/
$ACPREP make speedtest 2>&1 | tee build/last-speed.txt
mv *.dmg* ~/Products/ledger/opt/ledger-*.tar.* build
git checkout next

View file

@ -1,18 +1,16 @@
#!/bin/bash
set -e
cd ~/src/ledger
/bin/rm -fr ~/Products/ledger/opt
./acprep --no-python -j16 opt make check
./acprep -j16 opt update || exit 0
COMMIT=$(git describe --long --all)
SPEEDS=$(./acprep --no-python -j16 opt make speedtest 2>&1 \
SPEEDS=$(./acprep -j16 opt make speedtest 2>&1 \
| grep "Finished executing command" \
| awk '{print $1}' \
| xargs)
echo $COMMIT,$(echo $SPEEDS | sed 's/ /,/g') >> speed.log
echo $COMMIT,$(echo $SPEEDS | sed 's/ /,/g') >> ~/src/ledger/speed.log
exit 0

View file

@ -1 +1 @@
m4_define([VERSION_NUMBER], [3.0.0-20100615])
m4_define([VERSION_NUMBER], [3.0.0-20100623])