Checked in all major updates.
This commit is contained in:
parent
4507573d4f
commit
7901598f1d
30 changed files with 1934 additions and 1050 deletions
|
|
@ -7,6 +7,7 @@ libledger_la_SOURCES = \
|
|||
config.cc \
|
||||
datetime.cc \
|
||||
derive.cc \
|
||||
emacs.cc \
|
||||
format.cc \
|
||||
journal.cc \
|
||||
mask.cc \
|
||||
|
|
@ -20,10 +21,6 @@ libledger_la_SOURCES = \
|
|||
valexpr.cc \
|
||||
value.cc \
|
||||
walk.cc
|
||||
if USE_EDITOR
|
||||
libledger_la_CXXFLAGS += -DUSE_EDITOR=1
|
||||
libledger_la_SOURCES += emacs.cc
|
||||
endif
|
||||
if HAVE_EXPAT
|
||||
libledger_la_CXXFLAGS += -DHAVE_EXPAT=1
|
||||
libledger_la_SOURCES += gnucash.cc xml.cc
|
||||
|
|
@ -77,9 +74,6 @@ bin_PROGRAMS = ledger
|
|||
ledger_CXXFLAGS =
|
||||
ledger_SOURCES = main.cc
|
||||
ledger_LDADD = $(LIBOBJS) libledger.la
|
||||
if USE_EDITOR
|
||||
ledger_CXXFLAGS += -DUSE_EDITOR=1
|
||||
endif
|
||||
if HAVE_EXPAT
|
||||
ledger_CXXFLAGS += -DHAVE_EXPAT=1
|
||||
ledger_LDADD += -lexpat
|
||||
|
|
|
|||
114
NEWS
114
NEWS
|
|
@ -3,18 +3,87 @@
|
|||
|
||||
* 2.5
|
||||
|
||||
- Added a new "csv" command, for outputting results in CSV format.
|
||||
- There have a few changes to value expression syntax. The most
|
||||
significant incompatibilities being:
|
||||
|
||||
* Equality is now ==, not =
|
||||
* The U, A, and S functions now requires parens around the argument.
|
||||
Whereas before At was acceptable, now it must be specified as
|
||||
A(t).
|
||||
* The P function now always requires two arguments. The old
|
||||
one-argument version P(x) is now the same as P(x,m).
|
||||
|
||||
The following value expression features are new:
|
||||
|
||||
* A C-like comma operator is supported, where all but the last term
|
||||
are ignored. The is significant for the next feature:
|
||||
* Function definitions are now supported. Scoping is governed
|
||||
by parentheses. For example:
|
||||
(x=100, x+10) ; yields 110 as the result
|
||||
(f(x)=x*2,f(100)) ; yields 200 as the result
|
||||
* Identifier names may be any length. Along with this support comes
|
||||
alternate, longer names for all of the current one-letter value
|
||||
expression variables:
|
||||
|
||||
Old New
|
||||
--- ---
|
||||
m now
|
||||
a amount
|
||||
a amount
|
||||
b cost
|
||||
i price
|
||||
d date
|
||||
X cleared
|
||||
Y pending
|
||||
R real
|
||||
L actual
|
||||
n index
|
||||
N count
|
||||
l depth
|
||||
O total
|
||||
B cost_total
|
||||
I price_total
|
||||
v market
|
||||
V market_total
|
||||
g gain
|
||||
G gain_total
|
||||
U(x) abs(x)
|
||||
S(x) quant(x), quantity(x)
|
||||
comm(x), commodity(x)
|
||||
setcomm(x,y), set_commodity(x,y)
|
||||
A(x) mean(x), avg(x), average(x)
|
||||
P(x,y) val(x,y), value(x,y)
|
||||
min(x,y)
|
||||
max(x,y)
|
||||
|
||||
- There are new "parse" and "expr" commands, whose argument is a
|
||||
single value expression. Ledger will simply print out the result of
|
||||
evaluating it. "parse" happens before parsing your ledger file,
|
||||
while "expr" happens afterward. Although "expr" is slower as a
|
||||
result, any commodities you use will be formatted based on patterns
|
||||
of usage seen in your ledger file.
|
||||
|
||||
These commands can be used to test value expressions, or for doing
|
||||
calculation of commoditized amounts from a script.
|
||||
|
||||
A new "--debug" will also dump the resulting parse tree, useful for
|
||||
submitting bug reports.
|
||||
|
||||
- Added new min(x,y) and max(x,y) value expression functions.
|
||||
|
||||
- Added a new value expression regexp command:
|
||||
C// compare against transaction amount's commodity symbol
|
||||
C// compare against a transaction amount's commodity symbol
|
||||
|
||||
- Added new @min(x,y) and @max(x,y) value expression functions.
|
||||
- Value expression function may now be defined within your ledger file
|
||||
(or initialization file) using the following syntax:
|
||||
|
||||
- A new configure option "--disable-emacs" will disable generation of
|
||||
transaction and entry location info, which is used by ledger.el and
|
||||
the "write" command. If you use neither of these, then disabling
|
||||
them will cut textual parsing time in half, and binary loading time
|
||||
(and cache file size) by a third.
|
||||
@def foo(x)=x*1000
|
||||
|
||||
This line makes the function "foo" available to all subsequent value
|
||||
expressions, to all command-line options taking a value expression,
|
||||
and to the new "expr" command (see above).
|
||||
|
||||
- Added a new "csv" command, for outputting results in CSV format.
|
||||
|
||||
- Effective dates may now be specified for entries:
|
||||
|
||||
|
|
@ -74,28 +143,27 @@
|
|||
transactions belong to the same entry.
|
||||
|
||||
- Individual transactions may now be cleared separately. The old
|
||||
syntax, which is still supported, clears all transactions in the
|
||||
syntax, which is still supported, clears all transactions in an
|
||||
entry:
|
||||
|
||||
2004/05/27 * Book Store
|
||||
Expenses:Dining $20.00
|
||||
Liabilities:MasterCard
|
||||
|
||||
The new (and optional) syntax allows clearing just the MasterCard
|
||||
transaction:
|
||||
The new syntax allows clearing of just the MasterCard transaction:
|
||||
|
||||
2004/05/27 Book Store
|
||||
Expenses:Dining $20.00
|
||||
* Liabilities:MasterCard
|
||||
|
||||
NOTE: This changes the output format of the "emacs" and "xml"
|
||||
reports. ledger.el will use the new syntax unless the Lisp variable
|
||||
NOTE: This changes the output format of both the "emacs" and "xml"
|
||||
reports. ledger.el uses the new syntax unless the Lisp variable
|
||||
`ledger-clear-whole-entries' is set to t.
|
||||
|
||||
- Removed Python integration support.
|
||||
|
||||
- Did much internal restructuring to allow the use of libledger.so in
|
||||
non-command-line environments.
|
||||
non-command-line environments (such as GUI tools).
|
||||
|
||||
* 2.4
|
||||
|
||||
|
|
@ -264,27 +332,27 @@ command-line driver.
|
|||
|
||||
-d now specifies the display predicate. To give a date mask similar
|
||||
to 1.7, use the -p (period) option.
|
||||
|
||||
|
||||
-P now generates the "by payee" report. To specify a price database
|
||||
to use, use --price-db.
|
||||
|
||||
|
||||
-G now generates a net gain report. To print totals in a format
|
||||
consumable by gnuplot, use -J.
|
||||
|
||||
|
||||
-l now specifies the calculation predicate. To emulate the old
|
||||
usage of "-l \$100", use: -d "AT>100".
|
||||
|
||||
|
||||
-N is gone. Instead of "-N REGEX", use: -d "/REGEX/?T>0:T".
|
||||
|
||||
|
||||
-F now specifies the report format string. The old meaning of -F
|
||||
now has little use.
|
||||
|
||||
|
||||
-S now takes a value expression as the sorting criterion. To get
|
||||
the old meaning of "-S", use "-S d".
|
||||
|
||||
|
||||
-n now means "collapse entries in the register report". The get the
|
||||
old meaning of -n in the balance report, use "-T a".
|
||||
|
||||
|
||||
-p now specifies the reporting period. You can convert commodities
|
||||
in a report using value expressions. For example, to display hours
|
||||
at $10 per hour:
|
||||
|
|
@ -329,7 +397,7 @@ command-line driver.
|
|||
|
||||
Note: This command is identical (and internally converted) to:
|
||||
ledger -l "/expenses/|//john/" register
|
||||
|
||||
|
||||
- To include entries from another file into a specific account, use:
|
||||
!account ACCOUNT
|
||||
!include FILE
|
||||
|
|
|
|||
188
amount.cc
188
amount.cc
|
|
@ -66,14 +66,14 @@ static struct _init_amounts {
|
|||
commodity_t * commodity;
|
||||
|
||||
commodity = commodity_t::find_commodity("s", true);
|
||||
commodity->flags |= COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN;
|
||||
commodity->flags() |= COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN;
|
||||
|
||||
parse_conversion("1.0m", "60s");
|
||||
parse_conversion("1.0h", "60m");
|
||||
|
||||
#if 0
|
||||
commodity = commodity_t::find_commodity("b", true);
|
||||
commodity->flags |= COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN;
|
||||
commodity->flags() |= COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN;
|
||||
|
||||
parse_conversion("1.00 Kb", "1024 b");
|
||||
parse_conversion("1.00 Mb", "1024 Kb");
|
||||
|
|
@ -417,7 +417,7 @@ amount_t& amount_t::operator*=(const amount_t& amt)
|
|||
mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
|
||||
quantity->prec += amt.quantity->prec;
|
||||
|
||||
unsigned int comm_prec = commodity().precision;
|
||||
unsigned int comm_prec = commodity().precision();
|
||||
if (quantity->prec > comm_prec + 6U) {
|
||||
mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U);
|
||||
quantity->prec = comm_prec + 6U;
|
||||
|
|
@ -442,7 +442,7 @@ amount_t& amount_t::operator/=(const amount_t& amt)
|
|||
mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
|
||||
quantity->prec += 6;
|
||||
|
||||
unsigned int comm_prec = commodity().precision;
|
||||
unsigned int comm_prec = commodity().precision();
|
||||
if (quantity->prec > comm_prec + 6U) {
|
||||
mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U);
|
||||
quantity->prec = comm_prec + 6U;
|
||||
|
|
@ -504,12 +504,12 @@ amount_t::operator bool() const
|
|||
if (! quantity)
|
||||
return false;
|
||||
|
||||
if (quantity->prec <= commodity().precision) {
|
||||
if (quantity->prec <= commodity().precision()) {
|
||||
return mpz_sgn(MPZ(quantity)) != 0;
|
||||
} else {
|
||||
mpz_set(temp, MPZ(quantity));
|
||||
if (commodity_)
|
||||
mpz_ui_pow_ui(divisor, 10, quantity->prec - commodity().precision);
|
||||
mpz_ui_pow_ui(divisor, 10, quantity->prec - commodity().precision());
|
||||
else
|
||||
mpz_ui_pow_ui(divisor, 10, quantity->prec);
|
||||
mpz_tdiv_q(temp, temp, divisor);
|
||||
|
|
@ -559,9 +559,9 @@ amount_t amount_t::value(const std::time_t moment) const
|
|||
{
|
||||
if (quantity) {
|
||||
commodity_t& comm = commodity();
|
||||
if (! (comm.flags & COMMODITY_STYLE_NOMARKET))
|
||||
if (! (comm.flags() & COMMODITY_STYLE_NOMARKET))
|
||||
if (amount_t amt = comm.value(moment))
|
||||
return (amt * *this).round(amt.commodity().precision);
|
||||
return (amt * *this).round(amt.commodity().precision());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -604,24 +604,23 @@ std::string amount_t::quantity_string() const
|
|||
unsigned short precision;
|
||||
|
||||
if (comm == *commodity_t::null_commodity ||
|
||||
comm.flags & COMMODITY_STYLE_VARIABLE) {
|
||||
comm.flags() & COMMODITY_STYLE_VARIABLE) {
|
||||
mpz_ui_pow_ui(divisor, 10, quantity->prec);
|
||||
mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor);
|
||||
precision = quantity->prec;
|
||||
}
|
||||
else if (comm.precision < quantity->prec) {
|
||||
mpz_round(rquotient, MPZ(quantity), quantity->prec,
|
||||
comm.precision);
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision);
|
||||
else if (comm.precision() < quantity->prec) {
|
||||
mpz_round(rquotient, MPZ(quantity), quantity->prec, comm.precision());
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision());
|
||||
mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
|
||||
precision = comm.precision;
|
||||
precision = comm.precision();
|
||||
}
|
||||
else if (comm.precision > quantity->prec) {
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision - quantity->prec);
|
||||
else if (comm.precision() > quantity->prec) {
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision() - quantity->prec);
|
||||
mpz_mul(rquotient, MPZ(quantity), divisor);
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision);
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision());
|
||||
mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
|
||||
precision = comm.precision;
|
||||
precision = comm.precision();
|
||||
}
|
||||
else if (quantity->prec) {
|
||||
mpz_ui_pow_ui(divisor, 10, quantity->prec);
|
||||
|
|
@ -682,11 +681,11 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt)
|
|||
}
|
||||
|
||||
amount_t base(amt);
|
||||
if (amt.commodity().larger) {
|
||||
if (amt.commodity().larger()) {
|
||||
amount_t last(amt);
|
||||
while (last.commodity().larger) {
|
||||
last /= *last.commodity().larger;
|
||||
last.commodity_ = last.commodity().larger->commodity_;
|
||||
while (last.commodity().larger()) {
|
||||
last /= *last.commodity().larger();
|
||||
last.commodity_ = last.commodity().larger()->commodity_;
|
||||
if (ledger::abs(last) < 1)
|
||||
break;
|
||||
base = last;
|
||||
|
|
@ -712,24 +711,24 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt)
|
|||
unsigned short precision;
|
||||
|
||||
if (comm == *commodity_t::null_commodity ||
|
||||
comm.flags & COMMODITY_STYLE_VARIABLE) {
|
||||
comm.flags() & COMMODITY_STYLE_VARIABLE) {
|
||||
mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
|
||||
mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor);
|
||||
precision = base.quantity->prec;
|
||||
}
|
||||
else if (comm.precision < base.quantity->prec) {
|
||||
else if (comm.precision() < base.quantity->prec) {
|
||||
mpz_round(rquotient, MPZ(base.quantity), base.quantity->prec,
|
||||
comm.precision);
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision);
|
||||
comm.precision());
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision());
|
||||
mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
|
||||
precision = comm.precision;
|
||||
precision = comm.precision();
|
||||
}
|
||||
else if (comm.precision > base.quantity->prec) {
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision - base.quantity->prec);
|
||||
else if (comm.precision() > base.quantity->prec) {
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision() - base.quantity->prec);
|
||||
mpz_mul(rquotient, MPZ(base.quantity), divisor);
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision);
|
||||
mpz_ui_pow_ui(divisor, 10, comm.precision());
|
||||
mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
|
||||
precision = comm.precision;
|
||||
precision = comm.precision();
|
||||
}
|
||||
else if (base.quantity->prec) {
|
||||
mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
|
||||
|
|
@ -755,12 +754,18 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt)
|
|||
return _out;
|
||||
}
|
||||
|
||||
if (! (comm.flags & COMMODITY_STYLE_SUFFIXED)) {
|
||||
if (comm.quote)
|
||||
out << "\"" << comm.symbol << "\"";
|
||||
else
|
||||
if (! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) {
|
||||
if (comm.quote) {
|
||||
std::string::size_type idx = comm.symbol.find(" {", 0);
|
||||
if (idx != std::string::npos)
|
||||
out << "\"" << comm.symbol.substr(0, idx) << "\""
|
||||
<< comm.symbol.substr(idx);
|
||||
else
|
||||
out << "\"" << comm.symbol << "\"";
|
||||
} else {
|
||||
out << comm.symbol;
|
||||
if (comm.flags & COMMODITY_STYLE_SEPARATED)
|
||||
}
|
||||
if (comm.flags() & COMMODITY_STYLE_SEPARATED)
|
||||
out << " ";
|
||||
}
|
||||
|
||||
|
|
@ -770,7 +775,7 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt)
|
|||
if (mpz_sgn(quotient) == 0) {
|
||||
out << '0';
|
||||
}
|
||||
else if (! (comm.flags & COMMODITY_STYLE_THOUSANDS)) {
|
||||
else if (! (comm.flags() & COMMODITY_STYLE_THOUSANDS)) {
|
||||
char * p = mpz_get_str(NULL, 10, quotient);
|
||||
out << p;
|
||||
std::free(p);
|
||||
|
|
@ -799,7 +804,7 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt)
|
|||
i != strs.rend();
|
||||
i++) {
|
||||
if (printed) {
|
||||
out << (comm.flags & COMMODITY_STYLE_EUROPEAN ? '.' : ',');
|
||||
out << (comm.flags() & COMMODITY_STYLE_EUROPEAN ? '.' : ',');
|
||||
out.width(3);
|
||||
out.fill('0');
|
||||
}
|
||||
|
|
@ -810,7 +815,7 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt)
|
|||
}
|
||||
|
||||
if (precision) {
|
||||
out << ((comm.flags & COMMODITY_STYLE_EUROPEAN) ? ',' : '.');
|
||||
out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.');
|
||||
|
||||
out.width(precision);
|
||||
out.fill('0');
|
||||
|
|
@ -820,13 +825,19 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt)
|
|||
std::free(p);
|
||||
}
|
||||
|
||||
if (comm.flags & COMMODITY_STYLE_SUFFIXED) {
|
||||
if (comm.flags & COMMODITY_STYLE_SEPARATED)
|
||||
if (comm.flags() & COMMODITY_STYLE_SUFFIXED) {
|
||||
if (comm.flags() & COMMODITY_STYLE_SEPARATED)
|
||||
out << " ";
|
||||
if (comm.quote)
|
||||
out << "\"" << comm.symbol << "\"";
|
||||
else
|
||||
if (comm.quote) {
|
||||
std::string::size_type idx = comm.symbol.find(" {", 0);
|
||||
if (idx != std::string::npos)
|
||||
out << "\"" << comm.symbol.substr(0, idx) << "\""
|
||||
<< comm.symbol.substr(idx);
|
||||
else
|
||||
out << "\"" << comm.symbol << "\"";
|
||||
} else {
|
||||
out << comm.symbol;
|
||||
}
|
||||
}
|
||||
|
||||
mpz_clear(quotient);
|
||||
|
|
@ -878,6 +889,7 @@ void amount_t::parse(std::istream& in, unsigned short flags)
|
|||
|
||||
std::string symbol;
|
||||
std::string quant;
|
||||
std::string price;
|
||||
unsigned int comm_flags = COMMODITY_STYLE_DEFAULTS;
|
||||
bool negative = false;
|
||||
|
||||
|
|
@ -923,6 +935,34 @@ void amount_t::parse(std::istream& in, unsigned short flags)
|
|||
|
||||
commodity_ = commodity_t::find_commodity(symbol, true);
|
||||
|
||||
// If a per-unit price is specified for this amount, record it by
|
||||
// creating a specialized commodity at that price. This is a
|
||||
// different from the whole transaction cost, which is associated
|
||||
// with the transaction and not with the amount. For example, a
|
||||
// sale of 10 AAPL shares for $100 (the cost) is a different thing
|
||||
// from selling 10 AAPL {$10} (where $10 is the commodity price) for
|
||||
// $50, which implies a capital loss of $50.
|
||||
|
||||
if (peek_next_nonws(in) == '{') {
|
||||
char c;
|
||||
char buf[256];
|
||||
in.get(c);
|
||||
READ_INTO(in, buf, 255, c, c != '}');
|
||||
if (c == '}')
|
||||
in.get(c);
|
||||
else
|
||||
throw amount_error("Commodity price lacks closing brace");
|
||||
|
||||
symbol = symbol + " {" + buf + "}";
|
||||
commodity_t * priced_commodity =
|
||||
commodity_t::find_commodity(symbol, true);
|
||||
|
||||
priced_commodity->price = new amount_t(buf);
|
||||
priced_commodity->base = commodity_;
|
||||
|
||||
commodity_ = priced_commodity;
|
||||
}
|
||||
|
||||
// Determine the precision of the amount, based on the usage of
|
||||
// comma or period.
|
||||
|
||||
|
|
@ -940,12 +980,12 @@ void amount_t::parse(std::istream& in, unsigned short flags)
|
|||
}
|
||||
else if (last_comma != std::string::npos &&
|
||||
(! commodity_t::default_commodity ||
|
||||
commodity_t::default_commodity->flags & COMMODITY_STYLE_EUROPEAN)) {
|
||||
commodity_t::default_commodity->flags() & COMMODITY_STYLE_EUROPEAN)) {
|
||||
comm_flags |= COMMODITY_STYLE_EUROPEAN;
|
||||
quantity->prec = quant.length() - last_comma - 1;
|
||||
}
|
||||
else if (last_period != std::string::npos &&
|
||||
! (commodity().flags & COMMODITY_STYLE_EUROPEAN)) {
|
||||
! (commodity().flags() & COMMODITY_STYLE_EUROPEAN)) {
|
||||
quantity->prec = quant.length() - last_period - 1;
|
||||
}
|
||||
else {
|
||||
|
|
@ -955,9 +995,9 @@ void amount_t::parse(std::istream& in, unsigned short flags)
|
|||
// Set the commodity's flags and precision accordingly
|
||||
|
||||
if (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE)) {
|
||||
commodity().flags |= comm_flags;
|
||||
if (quantity->prec > commodity().precision)
|
||||
commodity().precision = quantity->prec;
|
||||
commodity().flags() |= comm_flags;
|
||||
if (quantity->prec > commodity().precision())
|
||||
commodity().precision() = quantity->prec;
|
||||
}
|
||||
|
||||
// Now we have the final number. Remove commas and periods, if
|
||||
|
|
@ -991,9 +1031,9 @@ void amount_t::parse(std::istream& in, unsigned short flags)
|
|||
|
||||
void amount_t::reduce()
|
||||
{
|
||||
while (commodity_ && commodity().smaller) {
|
||||
*this *= *commodity().smaller;
|
||||
commodity_ = commodity().smaller->commodity_;
|
||||
while (commodity_ && commodity().smaller()) {
|
||||
*this *= *commodity().smaller();
|
||||
commodity_ = commodity().smaller()->commodity_;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1014,12 +1054,12 @@ void parse_conversion(const std::string& larger_str,
|
|||
larger *= smaller;
|
||||
|
||||
if (larger.commodity()) {
|
||||
larger.commodity().smaller = new amount_t(smaller);
|
||||
larger.commodity().flags = (smaller.commodity().flags |
|
||||
COMMODITY_STYLE_NOMARKET);
|
||||
larger.commodity().smaller() = new amount_t(smaller);
|
||||
larger.commodity().flags() = (smaller.commodity().flags() |
|
||||
COMMODITY_STYLE_NOMARKET);
|
||||
}
|
||||
if (smaller.commodity())
|
||||
smaller.commodity().larger = new amount_t(larger);
|
||||
smaller.commodity().larger() = new amount_t(larger);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1161,6 +1201,8 @@ void commodity_t::set_symbol(const std::string& sym)
|
|||
quote = false;
|
||||
for (const char * p = symbol.c_str(); *p; p++)
|
||||
if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.') {
|
||||
if (std::isspace(*p) && *(p + 1) == '{')
|
||||
return;
|
||||
quote = true;
|
||||
return;
|
||||
}
|
||||
|
|
@ -1168,15 +1210,15 @@ void commodity_t::set_symbol(const std::string& sym)
|
|||
|
||||
void commodity_t::add_price(const std::time_t date, const amount_t& price)
|
||||
{
|
||||
if (! history)
|
||||
history = new history_t;
|
||||
if (! history())
|
||||
history() = new history_t;
|
||||
|
||||
history_map::iterator i = history->prices.find(date);
|
||||
if (i != history->prices.end()) {
|
||||
history_map::iterator i = history()->prices.find(date);
|
||||
if (i != history()->prices.end()) {
|
||||
(*i).second = price;
|
||||
} else {
|
||||
std::pair<history_map::iterator, bool> result
|
||||
= history->prices.insert(history_pair(date, price));
|
||||
= history()->prices.insert(history_pair(date, price));
|
||||
assert(result.second);
|
||||
}
|
||||
}
|
||||
|
|
@ -1195,9 +1237,9 @@ commodity_t * commodity_t::find_commodity(const std::string& symbol,
|
|||
// Start out the new commodity with the default commodity's flags
|
||||
// and precision, if one has been defined.
|
||||
if (default_commodity)
|
||||
commodity->flags =
|
||||
(default_commodity->flags & ~(COMMODITY_STYLE_THOUSANDS |
|
||||
COMMODITY_STYLE_NOMARKET));
|
||||
commodity->flags() =
|
||||
(default_commodity->flags() & ~(COMMODITY_STYLE_THOUSANDS |
|
||||
COMMODITY_STYLE_NOMARKET));
|
||||
|
||||
return commodity;
|
||||
}
|
||||
|
|
@ -1210,23 +1252,23 @@ amount_t commodity_t::value(const std::time_t moment)
|
|||
std::time_t age = 0;
|
||||
amount_t price;
|
||||
|
||||
if (history) {
|
||||
assert(history->prices.size() > 0);
|
||||
if (history()) {
|
||||
assert(history()->prices.size() > 0);
|
||||
|
||||
if (moment == 0) {
|
||||
history_map::reverse_iterator r = history->prices.rbegin();
|
||||
history_map::reverse_iterator r = history()->prices.rbegin();
|
||||
age = (*r).first;
|
||||
price = (*r).second;
|
||||
} else {
|
||||
history_map::iterator i = history->prices.lower_bound(moment);
|
||||
if (i == history->prices.end()) {
|
||||
history_map::reverse_iterator r = history->prices.rbegin();
|
||||
history_map::iterator i = history()->prices.lower_bound(moment);
|
||||
if (i == history()->prices.end()) {
|
||||
history_map::reverse_iterator r = history()->prices.rbegin();
|
||||
age = (*r).first;
|
||||
price = (*r).second;
|
||||
} else {
|
||||
age = (*i).first;
|
||||
if (std::difftime(moment, age) != 0) {
|
||||
if (i != history->prices.begin()) {
|
||||
if (i != history()->prices.begin()) {
|
||||
--i;
|
||||
age = (*i).first;
|
||||
price = (*i).second;
|
||||
|
|
@ -1242,8 +1284,8 @@ amount_t commodity_t::value(const std::time_t moment)
|
|||
|
||||
if (updater)
|
||||
(*updater)(*this, moment, age,
|
||||
(history && history->prices.size() > 0 ?
|
||||
(*history->prices.rbegin()).first : 0), price);
|
||||
(history() && history()->prices.size() > 0 ?
|
||||
(*history()->prices.rbegin()).first : 0), price);
|
||||
|
||||
return price;
|
||||
}
|
||||
|
|
|
|||
71
amount.h
71
amount.h
|
|
@ -73,6 +73,7 @@ class amount_t
|
|||
void clear_commodity() {
|
||||
commodity_ = NULL;
|
||||
}
|
||||
amount_t base_amount() const;
|
||||
|
||||
bool null() const {
|
||||
return ! quantity && ! commodity_;
|
||||
|
|
@ -316,21 +317,47 @@ class commodity_t
|
|||
std::time_t last_lookup;
|
||||
};
|
||||
|
||||
history_t * history_;
|
||||
|
||||
history_t *& history() {
|
||||
return base ? base->history() : history_;
|
||||
}
|
||||
|
||||
const std::string symbol;
|
||||
bool quote;
|
||||
std::string name;
|
||||
std::string note;
|
||||
unsigned short precision;
|
||||
unsigned short flags;
|
||||
std::string name_;
|
||||
std::string note_;
|
||||
unsigned short precision_;
|
||||
unsigned short flags_;
|
||||
ident_t ident;
|
||||
history_t * history;
|
||||
amount_t * smaller;
|
||||
amount_t * larger;
|
||||
amount_t * smaller_;
|
||||
amount_t * larger_;
|
||||
commodity_t * base; // base commodity for AAPL {$10} is AAPL
|
||||
amount_t * price; // its price is therefore $10.00
|
||||
|
||||
std::string& name() {
|
||||
return base ? base->name() : name_;
|
||||
}
|
||||
std::string& note() {
|
||||
return base ? base->note() : note_;
|
||||
}
|
||||
unsigned short& precision() {
|
||||
return base ? base->precision() : precision_;
|
||||
}
|
||||
unsigned short& flags() {
|
||||
return base ? base->flags() : flags_;
|
||||
}
|
||||
amount_t *& smaller() {
|
||||
return base ? base->smaller() : smaller_;
|
||||
}
|
||||
amount_t *& larger() {
|
||||
return base ? base->larger() : larger_;
|
||||
}
|
||||
|
||||
// If set, this global function pointer is called to determine
|
||||
// whether prices have been updated in the meanwhile.
|
||||
|
||||
static updater_t * updater;
|
||||
static updater_t * updater;
|
||||
|
||||
// This map remembers all commodities that have been defined.
|
||||
|
||||
|
|
@ -361,14 +388,15 @@ class commodity_t
|
|||
commodity_t(const std::string& _symbol = "",
|
||||
unsigned int _precision = 0,
|
||||
unsigned int _flags = COMMODITY_STYLE_DEFAULTS)
|
||||
: precision(_precision), flags(_flags), history(NULL),
|
||||
smaller(NULL), larger(NULL) {
|
||||
: precision_(_precision), flags_(_flags), history_(NULL),
|
||||
smaller_(NULL), larger_(NULL), base(NULL), price(NULL) {
|
||||
set_symbol(_symbol);
|
||||
}
|
||||
~commodity_t() {
|
||||
if (history) delete history;
|
||||
if (smaller) delete smaller;
|
||||
if (larger) delete larger;
|
||||
if (history_) delete history_;
|
||||
if (smaller_) delete smaller_;
|
||||
if (larger_) delete larger_;
|
||||
if (price) delete price;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
|
|
@ -385,8 +413,8 @@ class commodity_t
|
|||
|
||||
void add_price(const std::time_t date, const amount_t& price);
|
||||
bool remove_price(const std::time_t date) {
|
||||
if (history) {
|
||||
history_map::size_type n = history->prices.erase(date);
|
||||
if (history_) {
|
||||
history_map::size_type n = history_->prices.erase(date);
|
||||
return n > 0;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -398,7 +426,7 @@ class commodity_t
|
|||
if (symbol.empty() && this != null_commodity)
|
||||
return false;
|
||||
|
||||
if (precision > 16)
|
||||
if (precision_ > 16)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
@ -417,6 +445,17 @@ inline commodity_t& amount_t::commodity() const {
|
|||
return *commodity_;
|
||||
}
|
||||
|
||||
inline amount_t amount_t::base_amount() const {
|
||||
if (commodity_ && commodity_->price) {
|
||||
amount_t temp(*this);
|
||||
assert(commodity_->base);
|
||||
temp.set_commodity(*(commodity_->base));
|
||||
return temp;
|
||||
} else {
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
class amount_error : public std::exception {
|
||||
std::string reason;
|
||||
public:
|
||||
|
|
|
|||
16
balance.cc
16
balance.cc
|
|
@ -34,6 +34,22 @@ balance_t balance_t::value(const std::time_t moment) const
|
|||
return temp;
|
||||
}
|
||||
|
||||
balance_t balance_t::factor_price() const
|
||||
{
|
||||
balance_t temp;
|
||||
|
||||
for (amounts_map::const_iterator i = amounts.begin();
|
||||
i != amounts.end();
|
||||
i++) {
|
||||
if ((*i).second.commodity().price)
|
||||
temp += *((*i).second.commodity().price) * (*i).second;
|
||||
else
|
||||
temp += (*i).second;
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
struct compare_amount_commodities {
|
||||
bool operator()(const amount_t * left, const amount_t * right) const {
|
||||
return left->commodity().symbol < right->commodity().symbol;
|
||||
|
|
|
|||
|
|
@ -426,6 +426,7 @@ class balance_t
|
|||
|
||||
amount_t amount(const commodity_t& commodity) const;
|
||||
balance_t value(const std::time_t moment) const;
|
||||
balance_t factor_price() const;
|
||||
|
||||
void write(std::ostream& out,
|
||||
const int first_width,
|
||||
|
|
@ -443,7 +444,7 @@ class balance_t
|
|||
i != amounts.end();
|
||||
i++)
|
||||
if ((*i).second.commodity())
|
||||
(*i).second = (*i).second.round((*i).second.commodity().precision);
|
||||
(*i).second = (*i).second.round((*i).second.commodity().precision());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
178
binary.cc
178
binary.cc
|
|
@ -11,18 +11,10 @@
|
|||
namespace ledger {
|
||||
|
||||
static unsigned long binary_magic_number = 0xFFEED765;
|
||||
#ifdef USE_EDITOR
|
||||
#ifdef DEBUG_ENABLED
|
||||
static unsigned long format_version = 0x00020589;
|
||||
static unsigned long format_version = 0x0002050b;
|
||||
#else
|
||||
static unsigned long format_version = 0x00020588;
|
||||
#endif
|
||||
#else
|
||||
#ifdef DEBUG_ENABLED
|
||||
static unsigned long format_version = 0x00020509;
|
||||
#else
|
||||
static unsigned long format_version = 0x00020508;
|
||||
#endif
|
||||
static unsigned long format_version = 0x0002050a;
|
||||
#endif
|
||||
|
||||
static account_t ** accounts;
|
||||
|
|
@ -272,8 +264,10 @@ inline void read_binary_value_expr(char *& data, value_expr_t *& expr)
|
|||
|
||||
expr = new value_expr_t(kind);
|
||||
|
||||
read_binary_value_expr(data, expr->left);
|
||||
read_binary_value_expr(data, expr->right);
|
||||
if (kind > value_expr_t::TERMINALS) {
|
||||
read_binary_value_expr(data, expr->left);
|
||||
if (expr->left) expr->left->acquire();
|
||||
}
|
||||
|
||||
switch (expr->kind) {
|
||||
case value_expr_t::CONSTANT_T:
|
||||
|
|
@ -283,15 +277,30 @@ inline void read_binary_value_expr(char *& data, value_expr_t *& expr)
|
|||
read_binary_long(data, expr->constant_i);
|
||||
break;
|
||||
case value_expr_t::CONSTANT_A:
|
||||
read_binary_amount(data, expr->constant_a);
|
||||
expr->constant_a = new amount_t();
|
||||
read_binary_amount(data, *(expr->constant_a));
|
||||
break;
|
||||
case value_expr_t::F_FUNC:
|
||||
read_binary_string(data, expr->constant_s);
|
||||
case value_expr_t::CONSTANT_V:
|
||||
assert(0);
|
||||
break;
|
||||
|
||||
case value_expr_t::F_CODE_MASK:
|
||||
case value_expr_t::F_PAYEE_MASK:
|
||||
case value_expr_t::F_NOTE_MASK:
|
||||
case value_expr_t::F_ACCOUNT_MASK:
|
||||
case value_expr_t::F_SHORT_ACCOUNT_MASK:
|
||||
case value_expr_t::F_COMMODITY_MASK:
|
||||
if (read_binary_number<unsigned char>(data) == 1)
|
||||
read_binary_mask(data, expr->mask);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (kind > value_expr_t::TERMINALS) {
|
||||
read_binary_value_expr(data, expr->right);
|
||||
if (expr->right) expr->right->acquire();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (read_binary_number<unsigned char>(data) == 1)
|
||||
read_binary_mask(data, expr->mask);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -301,18 +310,22 @@ inline void read_binary_transaction(char *& data, transaction_t * xact)
|
|||
read_binary_long(data, xact->_date_eff);
|
||||
xact->account = accounts[read_binary_long<account_t::ident_t>(data) - 1];
|
||||
|
||||
if (read_binary_number<char>(data) == 1)
|
||||
if (read_binary_number<char>(data) == 1) {
|
||||
read_binary_value_expr(data, xact->amount_expr);
|
||||
else
|
||||
if (xact->amount_expr) xact->amount_expr->acquire();
|
||||
} else {
|
||||
read_binary_amount(data, xact->amount);
|
||||
}
|
||||
|
||||
if (*data++ == 1) {
|
||||
xact->cost = new amount_t;
|
||||
|
||||
if (read_binary_number<char>(data) == 1)
|
||||
if (read_binary_number<char>(data) == 1) {
|
||||
read_binary_value_expr(data, xact->cost_expr);
|
||||
else
|
||||
if (xact->cost_expr) xact->cost_expr->acquire();
|
||||
} else {
|
||||
read_binary_amount(data, *xact->cost);
|
||||
}
|
||||
} else {
|
||||
xact->cost = NULL;
|
||||
}
|
||||
|
|
@ -322,12 +335,10 @@ inline void read_binary_transaction(char *& data, transaction_t * xact)
|
|||
xact->flags |= TRANSACTION_BULK_ALLOC;
|
||||
read_binary_string(data, &xact->note);
|
||||
|
||||
#ifdef USE_EDITOR
|
||||
xact->beg_pos = read_binary_long<unsigned long>(data);
|
||||
read_binary_long(data, xact->beg_line);
|
||||
xact->end_pos = read_binary_long<unsigned long>(data);
|
||||
read_binary_long(data, xact->end_line);
|
||||
#endif
|
||||
|
||||
xact->data = NULL;
|
||||
|
||||
|
|
@ -340,13 +351,11 @@ inline void read_binary_transaction(char *& data, transaction_t * xact)
|
|||
inline void read_binary_entry_base(char *& data, entry_base_t * entry,
|
||||
transaction_t *& xact_pool, bool& finalize)
|
||||
{
|
||||
#ifdef USE_EDITOR
|
||||
read_binary_long(data, entry->src_idx);
|
||||
entry->beg_pos = read_binary_long<unsigned long>(data);
|
||||
read_binary_long(data, entry->beg_line);
|
||||
entry->end_pos = read_binary_long<unsigned long>(data);
|
||||
read_binary_long(data, entry->end_line);
|
||||
#endif
|
||||
|
||||
bool ignore_calculated = read_binary_number<char>(data) == 1;
|
||||
|
||||
|
|
@ -376,8 +385,10 @@ inline void read_binary_auto_entry(char *& data, auto_entry_t * entry,
|
|||
{
|
||||
bool ignore;
|
||||
read_binary_entry_base(data, entry, xact_pool, ignore);
|
||||
read_binary_string(data, &entry->predicate_string);
|
||||
entry->predicate = new item_predicate<transaction_t>(entry->predicate_string);
|
||||
value_expr_t * expr;
|
||||
read_binary_value_expr(data, expr);
|
||||
// the item_predicate constructor will acquire the reference
|
||||
entry->predicate = new item_predicate<transaction_t>(expr);
|
||||
}
|
||||
|
||||
inline void read_binary_period_entry(char *& data, period_entry_t * entry,
|
||||
|
|
@ -396,10 +407,10 @@ inline commodity_t * read_binary_commodity(char *& data)
|
|||
|
||||
read_binary_string(data, *(const_cast<std::string *>(&commodity->symbol)));
|
||||
read_binary_number(data, commodity->quote);
|
||||
read_binary_string(data, commodity->name);
|
||||
read_binary_string(data, commodity->note);
|
||||
read_binary_number(data, commodity->precision);
|
||||
read_binary_number(data, commodity->flags);
|
||||
read_binary_string(data, commodity->name_);
|
||||
read_binary_string(data, commodity->note_);
|
||||
read_binary_number(data, commodity->precision_);
|
||||
read_binary_number(data, commodity->flags_);
|
||||
read_binary_long(data, commodity->ident);
|
||||
|
||||
return commodity;
|
||||
|
|
@ -421,12 +432,12 @@ inline void read_binary_commodity_extra(char *& data,
|
|||
// Upon insertion, amt will be copied, which will cause the amount
|
||||
// to be duplicated (and thus not lost when the journal's
|
||||
// item_pool is deleted.
|
||||
if (! commodity->history)
|
||||
commodity->history = new commodity_t::history_t;
|
||||
commodity->history->prices.insert(history_pair(when, amt));
|
||||
if (! commodity->history_)
|
||||
commodity->history_ = new commodity_t::history_t;
|
||||
commodity->history_->prices.insert(history_pair(when, amt));
|
||||
}
|
||||
if (commodity->history)
|
||||
read_binary_long(data, commodity->history->last_lookup);
|
||||
if (commodity->history_)
|
||||
read_binary_long(data, commodity->history_->last_lookup);
|
||||
|
||||
unsigned char flag;
|
||||
|
||||
|
|
@ -434,14 +445,23 @@ inline void read_binary_commodity_extra(char *& data,
|
|||
if (flag) {
|
||||
amount_t amt;
|
||||
read_binary_amount(data, amt);
|
||||
commodity->smaller = new amount_t(amt);
|
||||
commodity->smaller_ = new amount_t(amt);
|
||||
}
|
||||
|
||||
flag = read_binary_number<unsigned char>(data);
|
||||
if (flag) {
|
||||
amount_t amt;
|
||||
read_binary_amount(data, amt);
|
||||
commodity->larger = new amount_t(amt);
|
||||
commodity->larger_ = new amount_t(amt);
|
||||
}
|
||||
|
||||
flag = read_binary_number<unsigned char>(data);
|
||||
if (flag) {
|
||||
amount_t amt;
|
||||
read_binary_amount(data, amt);
|
||||
commodity->price = new amount_t(amt);
|
||||
commodity->base =
|
||||
commodities[read_binary_long<commodity_t::ident_t>(data) - 1];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -570,7 +590,7 @@ unsigned int read_binary_journal(std::istream& in,
|
|||
commodities = commodities_next = new commodity_t *[c_count];
|
||||
for (commodity_t::ident_t i = 0; i < c_count; i++) {
|
||||
commodity_t * commodity = read_binary_commodity(data);
|
||||
if (! (commodity->flags & COMMODITY_STYLE_BUILTIN)) {
|
||||
if (! (commodity->flags_ & COMMODITY_STYLE_BUILTIN)) {
|
||||
if (commodity->symbol == "") {
|
||||
commodity_t::commodities.erase(commodity->symbol);
|
||||
delete commodity_t::null_commodity;
|
||||
|
|
@ -737,7 +757,7 @@ void write_binary_mask(std::ostream& out, mask_t * mask)
|
|||
write_binary_string(out, mask->pattern);
|
||||
}
|
||||
|
||||
void write_binary_value_expr(std::ostream& out, value_expr_t * expr)
|
||||
void write_binary_value_expr(std::ostream& out, const value_expr_t * expr)
|
||||
{
|
||||
if (expr == NULL) {
|
||||
write_binary_number<unsigned char>(out, 0);
|
||||
|
|
@ -746,8 +766,9 @@ void write_binary_value_expr(std::ostream& out, value_expr_t * expr)
|
|||
write_binary_number<unsigned char>(out, 1);
|
||||
|
||||
write_binary_number(out, expr->kind);
|
||||
write_binary_value_expr(out, expr->left);
|
||||
write_binary_value_expr(out, expr->right);
|
||||
|
||||
if (expr->kind > value_expr_t::TERMINALS)
|
||||
write_binary_value_expr(out, expr->left);
|
||||
|
||||
switch (expr->kind) {
|
||||
case value_expr_t::CONSTANT_T:
|
||||
|
|
@ -757,19 +778,32 @@ void write_binary_value_expr(std::ostream& out, value_expr_t * expr)
|
|||
write_binary_long(out, expr->constant_i);
|
||||
break;
|
||||
case value_expr_t::CONSTANT_A:
|
||||
write_binary_amount(out, expr->constant_a);
|
||||
write_binary_amount(out, *(expr->constant_a));
|
||||
break;
|
||||
case value_expr_t::F_FUNC:
|
||||
write_binary_string(out, expr->constant_s);
|
||||
case value_expr_t::CONSTANT_V:
|
||||
assert(0);
|
||||
break;
|
||||
|
||||
case value_expr_t::F_CODE_MASK:
|
||||
case value_expr_t::F_PAYEE_MASK:
|
||||
case value_expr_t::F_NOTE_MASK:
|
||||
case value_expr_t::F_ACCOUNT_MASK:
|
||||
case value_expr_t::F_SHORT_ACCOUNT_MASK:
|
||||
case value_expr_t::F_COMMODITY_MASK:
|
||||
if (expr->mask) {
|
||||
write_binary_number<char>(out, 1);
|
||||
write_binary_mask(out, expr->mask);
|
||||
} else {
|
||||
write_binary_number<char>(out, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (expr->kind > value_expr_t::TERMINALS)
|
||||
write_binary_value_expr(out, expr->right);
|
||||
break;
|
||||
}
|
||||
|
||||
if (expr->mask) {
|
||||
write_binary_number<char>(out, 1);
|
||||
write_binary_mask(out, expr->mask);
|
||||
} else {
|
||||
write_binary_number<char>(out, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void write_binary_transaction(std::ostream& out, transaction_t * xact,
|
||||
|
|
@ -808,23 +842,19 @@ void write_binary_transaction(std::ostream& out, transaction_t * xact,
|
|||
write_binary_number(out, xact->flags);
|
||||
write_binary_string(out, xact->note);
|
||||
|
||||
#ifdef USE_EDITOR
|
||||
write_binary_long(out, xact->beg_pos);
|
||||
write_binary_long(out, xact->beg_line);
|
||||
write_binary_long(out, xact->end_pos);
|
||||
write_binary_long(out, xact->end_line);
|
||||
#endif
|
||||
}
|
||||
|
||||
void write_binary_entry_base(std::ostream& out, entry_base_t * entry)
|
||||
{
|
||||
#ifdef USE_EDITOR
|
||||
write_binary_long(out, entry->src_idx);
|
||||
write_binary_long(out, entry->beg_pos);
|
||||
write_binary_long(out, entry->beg_line);
|
||||
write_binary_long(out, entry->end_pos);
|
||||
write_binary_long(out, entry->end_line);
|
||||
#endif
|
||||
|
||||
bool ignore_calculated = false;
|
||||
for (transactions_list::const_iterator i = entry->transactions.begin();
|
||||
|
|
@ -856,7 +886,7 @@ void write_binary_entry(std::ostream& out, entry_t * entry)
|
|||
void write_binary_auto_entry(std::ostream& out, auto_entry_t * entry)
|
||||
{
|
||||
write_binary_entry_base(out, entry);
|
||||
write_binary_string(out, entry->predicate_string);
|
||||
write_binary_value_expr(out, entry->predicate->predicate);
|
||||
}
|
||||
|
||||
void write_binary_period_entry(std::ostream& out, period_entry_t * entry)
|
||||
|
|
@ -869,39 +899,47 @@ void write_binary_commodity(std::ostream& out, commodity_t * commodity)
|
|||
{
|
||||
write_binary_string(out, commodity->symbol);
|
||||
write_binary_number(out, commodity->quote);
|
||||
write_binary_string(out, commodity->name);
|
||||
write_binary_string(out, commodity->note);
|
||||
write_binary_number(out, commodity->precision);
|
||||
write_binary_number(out, commodity->flags);
|
||||
write_binary_string(out, commodity->name_);
|
||||
write_binary_string(out, commodity->note_);
|
||||
write_binary_number(out, commodity->precision_);
|
||||
write_binary_number(out, commodity->flags_);
|
||||
commodity->ident = ++commodity_index;
|
||||
write_binary_long(out, commodity->ident);
|
||||
}
|
||||
|
||||
void write_binary_commodity_extra(std::ostream& out, commodity_t * commodity)
|
||||
{
|
||||
if (! commodity->history) {
|
||||
if (! commodity->history_) {
|
||||
write_binary_long<unsigned long>(out, 0);
|
||||
} else {
|
||||
write_binary_long<unsigned long>(out, commodity->history->prices.size());
|
||||
for (history_map::const_iterator i = commodity->history->prices.begin();
|
||||
i != commodity->history->prices.end();
|
||||
write_binary_long<unsigned long>(out, commodity->history_->prices.size());
|
||||
for (history_map::const_iterator i = commodity->history_->prices.begin();
|
||||
i != commodity->history_->prices.end();
|
||||
i++) {
|
||||
write_binary_long(out, (*i).first);
|
||||
write_binary_amount(out, (*i).second);
|
||||
}
|
||||
write_binary_long(out, commodity->history->last_lookup);
|
||||
write_binary_long(out, commodity->history_->last_lookup);
|
||||
}
|
||||
|
||||
if (commodity->smaller) {
|
||||
if (commodity->smaller_) {
|
||||
write_binary_number<unsigned char>(out, 1);
|
||||
write_binary_amount(out, *commodity->smaller);
|
||||
write_binary_amount(out, *commodity->smaller_);
|
||||
} else {
|
||||
write_binary_number<unsigned char>(out, 0);
|
||||
}
|
||||
|
||||
if (commodity->larger) {
|
||||
if (commodity->larger_) {
|
||||
write_binary_number<unsigned char>(out, 1);
|
||||
write_binary_amount(out, *commodity->larger);
|
||||
write_binary_amount(out, *commodity->larger_);
|
||||
} else {
|
||||
write_binary_number<unsigned char>(out, 0);
|
||||
}
|
||||
|
||||
if (commodity->price) {
|
||||
write_binary_number<unsigned char>(out, 1);
|
||||
write_binary_amount(out, *commodity->price);
|
||||
write_binary_long(out, commodity->base->ident);
|
||||
} else {
|
||||
write_binary_number<unsigned char>(out, 0);
|
||||
}
|
||||
|
|
|
|||
190
config.cc
190
config.cc
|
|
@ -17,15 +17,43 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
std::list<option_t> config_options;
|
||||
namespace {
|
||||
config_t * config = NULL;
|
||||
|
||||
static config_t * config = NULL;
|
||||
void xact_amount(value_t& result, const details_t& details, value_expr_t *)
|
||||
{
|
||||
if (transaction_has_xdata(*details.xact) &&
|
||||
transaction_xdata_(*details.xact).dflags & TRANSACTION_COMPOSITE)
|
||||
result = transaction_xdata_(*details.xact).composite_amount;
|
||||
else
|
||||
result = details.xact->amount;
|
||||
}
|
||||
|
||||
void xact_running_total(value_t& result, const details_t& details,
|
||||
value_expr_t *)
|
||||
{
|
||||
result = transaction_xdata_(*details.xact).total;
|
||||
}
|
||||
|
||||
void account_amount(value_t& result, const details_t& details,
|
||||
value_expr_t *)
|
||||
{
|
||||
if (account_has_xdata(*details.account))
|
||||
result = account_xdata(*details.account).value;
|
||||
}
|
||||
|
||||
void account_total(value_t& result, const details_t& details,
|
||||
value_expr_t *)
|
||||
{
|
||||
if (account_has_xdata(*details.account))
|
||||
result = account_xdata(*details.account).total;
|
||||
}
|
||||
}
|
||||
|
||||
void config_t::reset()
|
||||
{
|
||||
amount_expr = "a";
|
||||
total_expr = "O";
|
||||
total_expr_template = "#";
|
||||
amount_expr = "a";
|
||||
total_expr = "O";
|
||||
pricing_leeway = 24 * 3600;
|
||||
budget_flags = BUDGET_NO_BUDGET;
|
||||
balance_format = "%20T %2_%-a\n";
|
||||
|
|
@ -34,8 +62,8 @@ void config_t::reset()
|
|||
wide_register_format = ("%D %-.35P %-.38A %22.108t %22.132T\n%/"
|
||||
"%48|%-.38A %22.108t %22.132T\n");
|
||||
csv_register_format = "\"%D\",\"%P\",\"%A\",\"%t\",\"%T\"\n";
|
||||
plot_amount_format = "%D %(St)\n";
|
||||
plot_total_format = "%D %(ST)\n";
|
||||
plot_amount_format = "%D %(S(t))\n";
|
||||
plot_total_format = "%D %(S(T))\n";
|
||||
print_format = "\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n";
|
||||
write_hdr_format = "%d %Y%C%P\n";
|
||||
write_xact_format = " %-34W %12o%n\n";
|
||||
|
|
@ -62,6 +90,7 @@ void config_t::reset()
|
|||
show_revalued = false;
|
||||
show_revalued_only = false;
|
||||
download_quotes = false;
|
||||
debug_mode = false;
|
||||
|
||||
use_cache = false;
|
||||
cache_dirty = false;
|
||||
|
|
@ -133,7 +162,7 @@ config_t::regexps_to_predicate(const std::string& command,
|
|||
if (! show_related && ! show_all_related) {
|
||||
if (! display_predicate.empty())
|
||||
display_predicate += "&";
|
||||
else if (! show_empty)
|
||||
if (! show_empty)
|
||||
display_predicate += "T&";
|
||||
|
||||
if (add_predicate == 2)
|
||||
|
|
@ -143,6 +172,8 @@ config_t::regexps_to_predicate(const std::string& command,
|
|||
display_predicate += ")/";
|
||||
}
|
||||
else if (! show_empty) {
|
||||
if (! display_predicate.empty())
|
||||
display_predicate += "&";
|
||||
display_predicate += "T";
|
||||
}
|
||||
}
|
||||
|
|
@ -178,6 +209,18 @@ void config_t::process_environment(char ** envp, const std::string& tag)
|
|||
config = NULL;
|
||||
}
|
||||
|
||||
static std::string expand_value_expr(const std::string& tmpl,
|
||||
const std::string& expr)
|
||||
{
|
||||
std::string xp = tmpl;
|
||||
for (std::string::size_type i = xp.find('#');
|
||||
i != std::string::npos;
|
||||
i = xp.find('#'))
|
||||
xp = (std::string(xp, 0, i) + "(" + expr + ")" +
|
||||
std::string(xp, i + 1));
|
||||
return xp;
|
||||
}
|
||||
|
||||
void config_t::process_options(const std::string& command,
|
||||
strings_list::iterator arg,
|
||||
strings_list::iterator args_end)
|
||||
|
|
@ -244,32 +287,8 @@ void config_t::process_options(const std::string& command,
|
|||
|
||||
// Setup the values of %t and %T, used in format strings
|
||||
|
||||
try {
|
||||
ledger::amount_expr.reset(parse_value_expr(amount_expr));
|
||||
}
|
||||
catch (const value_expr_error& err) {
|
||||
throw error(std::string("In amount expression '") + amount_expr +
|
||||
"': " + err.what());
|
||||
}
|
||||
|
||||
std::string expr = total_expr_template;
|
||||
for (std::string::size_type i = expr.find('#');
|
||||
i != std::string::npos;
|
||||
i = expr.find('#'))
|
||||
expr = (std::string(expr, 0, i) + "(" + total_expr + ")" +
|
||||
std::string(expr, i + 1));
|
||||
|
||||
DEBUG_PRINT("ledger.config.total_expr",
|
||||
"Total expression template = " << total_expr_template);
|
||||
DEBUG_PRINT("ledger.config.total_expr",
|
||||
"Total expression is now = " << expr);
|
||||
try {
|
||||
ledger::total_expr.reset(parse_value_expr(expr));
|
||||
}
|
||||
catch (const value_expr_error& err) {
|
||||
throw error(std::string("In total expression '") + expr + "': " +
|
||||
err.what());
|
||||
}
|
||||
ledger::amount_expr.reset(new value_expr(amount_expr));
|
||||
ledger::total_expr.reset(new value_expr(total_expr));
|
||||
|
||||
// If downloading is to be supported, configure the updater
|
||||
|
||||
|
|
@ -683,6 +702,10 @@ OPT_BEGIN(account, "a:") {
|
|||
config->account = optarg;
|
||||
} OPT_END(account);
|
||||
|
||||
OPT_BEGIN(debug, "") {
|
||||
config->debug_mode = true;
|
||||
} OPT_END(debug);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Report filtering
|
||||
|
|
@ -755,6 +778,10 @@ OPT_BEGIN(actual, "L") {
|
|||
config->predicate += "L";
|
||||
} OPT_END(actual);
|
||||
|
||||
OPT_BEGIN(lots, "") {
|
||||
show_lots = true;
|
||||
} OPT_END(lots);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Output customization
|
||||
|
|
@ -1013,6 +1040,12 @@ OPT_BEGIN(basis, "B") {
|
|||
config->total_expr = "B";
|
||||
} OPT_END(basis);
|
||||
|
||||
OPT_BEGIN(price, "I") {
|
||||
show_lots = true; // don't show them, but use in calculations
|
||||
config->amount_expr = "i";
|
||||
config->total_expr = "I";
|
||||
} OPT_END(price);
|
||||
|
||||
OPT_BEGIN(market, "V") {
|
||||
config->show_revalued = true;
|
||||
|
||||
|
|
@ -1034,15 +1067,98 @@ OPT_BEGIN(gain, "G") {
|
|||
} OPT_END(gain);
|
||||
|
||||
OPT_BEGIN(average, "A") {
|
||||
config->total_expr_template = "A#";
|
||||
config->total_expr = expand_value_expr("A(#)", config->total_expr);
|
||||
} OPT_END(average);
|
||||
|
||||
OPT_BEGIN(deviation, "D") {
|
||||
config->total_expr_template = "t-A#";
|
||||
config->total_expr = expand_value_expr("t-A(#)", config->total_expr);
|
||||
} OPT_END(deviation);
|
||||
|
||||
OPT_BEGIN(percentage, "%") {
|
||||
config->total_expr_template = "^#&{100.0%}*(#/^#)";
|
||||
config->total_expr = expand_value_expr("^#&{100.0%}*(#/^#)", config->total_expr);
|
||||
} OPT_END(percentage);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
option_t config_options[CONFIG_OPTIONS_SIZE] = {
|
||||
{ "account", 'a', true, opt_account, false },
|
||||
{ "actual", 'L', false, opt_actual, false },
|
||||
{ "add-budget", '\0', false, opt_add_budget, false },
|
||||
{ "amount", 't', true, opt_amount, false },
|
||||
{ "amount-data", 'j', false, opt_amount_data, false },
|
||||
{ "average", 'A', false, opt_average, false },
|
||||
{ "balance-format", '\0', true, opt_balance_format, false },
|
||||
{ "basis", 'B', false, opt_basis, false },
|
||||
{ "begin", 'b', true, opt_begin, false },
|
||||
{ "budget", '\0', false, opt_budget, false },
|
||||
{ "by-payee", 'P', false, opt_by_payee, false },
|
||||
{ "cache", '\0', true, opt_cache, false },
|
||||
{ "cleared", 'C', false, opt_cleared, false },
|
||||
{ "collapse", 'n', false, opt_collapse, false },
|
||||
{ "comm-as-payee", 'x', false, opt_comm_as_payee, false },
|
||||
{ "csv-register-format", '\0', true, opt_csv_register_format, false },
|
||||
{ "current", 'c', false, opt_current, false },
|
||||
{ "date-format", 'y', true, opt_date_format, false },
|
||||
{ "debug", '\0', false, opt_debug, false },
|
||||
{ "deviation", 'D', false, opt_deviation, false },
|
||||
{ "display", 'd', true, opt_display, false },
|
||||
{ "dow", '\0', false, opt_dow, false },
|
||||
{ "download", 'Q', false, opt_download, false },
|
||||
{ "effective", '\0', false, opt_effective, false },
|
||||
{ "empty", 'E', false, opt_empty, false },
|
||||
{ "end", 'e', true, opt_end, false },
|
||||
{ "equity-format", '\0', true, opt_equity_format, false },
|
||||
{ "file", 'f', true, opt_file, false },
|
||||
{ "forecast", '\0', true, opt_forecast, false },
|
||||
{ "format", 'F', true, opt_format, false },
|
||||
{ "full-help", 'H', false, opt_full_help, false },
|
||||
{ "gain", 'G', false, opt_gain, false },
|
||||
{ "head", '\0', true, opt_head, false },
|
||||
{ "help", 'h', false, opt_help, false },
|
||||
{ "help-calc", '\0', false, opt_help_calc, false },
|
||||
{ "help-comm", '\0', false, opt_help_comm, false },
|
||||
{ "help-disp", '\0', false, opt_help_disp, false },
|
||||
{ "init-file", 'i', true, opt_init_file, false },
|
||||
{ "input-date-format", '\0', true, opt_input_date_format, false },
|
||||
{ "limit", 'l', true, opt_limit, false },
|
||||
{ "lots", '\0', false, opt_lots, false },
|
||||
{ "market", 'V', false, opt_market, false },
|
||||
{ "monthly", 'M', false, opt_monthly, false },
|
||||
{ "no-cache", '\0', false, opt_no_cache, false },
|
||||
{ "output", 'o', true, opt_output, false },
|
||||
{ "pager", '\0', true, opt_pager, false },
|
||||
{ "percentage", '%', false, opt_percentage, false },
|
||||
{ "performance", 'g', false, opt_performance, false },
|
||||
{ "period", 'p', true, opt_period, false },
|
||||
{ "period-sort", '\0', true, opt_period_sort, false },
|
||||
{ "plot-amount-format", '\0', true, opt_plot_amount_format, false },
|
||||
{ "plot-total-format", '\0', true, opt_plot_total_format, false },
|
||||
{ "price", 'I', false, opt_price, false },
|
||||
{ "price-db", '\0', true, opt_price_db, false },
|
||||
{ "price-exp", 'Z', true, opt_price_exp, false },
|
||||
{ "prices-format", '\0', true, opt_prices_format, false },
|
||||
{ "print-format", '\0', true, opt_print_format, false },
|
||||
{ "quantity", 'O', false, opt_quantity, false },
|
||||
{ "real", 'R', false, opt_real, false },
|
||||
{ "reconcile", '\0', true, opt_reconcile, false },
|
||||
{ "reconcile-date", '\0', true, opt_reconcile_date, false },
|
||||
{ "register-format", '\0', true, opt_register_format, false },
|
||||
{ "related", 'r', false, opt_related, false },
|
||||
{ "sort", 'S', true, opt_sort, false },
|
||||
{ "subtotal", 's', false, opt_subtotal, false },
|
||||
{ "tail", '\0', true, opt_tail, false },
|
||||
{ "total", 'T', true, opt_total, false },
|
||||
{ "total-data", 'J', false, opt_total_data, false },
|
||||
{ "totals", '\0', false, opt_totals, false },
|
||||
{ "unbudgeted", '\0', false, opt_unbudgeted, false },
|
||||
{ "uncleared", 'U', false, opt_uncleared, false },
|
||||
{ "version", 'v', false, opt_version, false },
|
||||
{ "weekly", 'W', false, opt_weekly, false },
|
||||
{ "wide", 'w', false, opt_wide, false },
|
||||
{ "wide-register-format", '\0', true, opt_wide_register_format, false },
|
||||
{ "write-hdr-format", '\0', true, opt_write_hdr_format, false },
|
||||
{ "write-xact-format", '\0', true, opt_write_xact_format, false },
|
||||
{ "yearly", 'Y', false, opt_yearly, false },
|
||||
};
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
22
config.h
22
config.h
|
|
@ -42,7 +42,6 @@ class config_t
|
|||
std::string sort_string;
|
||||
std::string amount_expr;
|
||||
std::string total_expr;
|
||||
std::string total_expr_template;
|
||||
std::string forecast_limit;
|
||||
std::string reconcile_balance;
|
||||
std::string reconcile_date;
|
||||
|
|
@ -66,6 +65,7 @@ class config_t
|
|||
bool download_quotes;
|
||||
bool use_cache;
|
||||
bool cache_dirty;
|
||||
bool debug_mode;
|
||||
|
||||
config_t() {
|
||||
reset();
|
||||
|
|
@ -73,7 +73,6 @@ class config_t
|
|||
config_t(const config_t&) {
|
||||
assert(0);
|
||||
}
|
||||
|
||||
void reset();
|
||||
|
||||
void regexps_to_predicate(const std::string& command,
|
||||
|
|
@ -100,24 +99,15 @@ class config_t
|
|||
std::list<item_handler<transaction_t> *>& ptrs);
|
||||
};
|
||||
|
||||
extern std::list<option_t> config_options;
|
||||
#define CONFIG_OPTIONS_SIZE 78
|
||||
extern option_t config_options[CONFIG_OPTIONS_SIZE];
|
||||
|
||||
void option_help(std::ostream& out);
|
||||
|
||||
struct declared_option_handler : public option_handler {
|
||||
declared_option_handler(const std::string& label,
|
||||
const std::string& opt_chars) {
|
||||
add_option_handler(config_options, label, opt_chars, *this);
|
||||
}
|
||||
};
|
||||
#define OPT_BEGIN(tag, chars) \
|
||||
void opt_ ## tag(const char * optarg)
|
||||
|
||||
#define OPT_BEGIN(tag, chars) \
|
||||
static struct opt_ ## tag ## _handler \
|
||||
: public declared_option_handler { \
|
||||
opt_ ## tag ## _handler() : declared_option_handler(#tag, chars) {} \
|
||||
virtual void operator()(const char * optarg)
|
||||
|
||||
#define OPT_END(tag) } opt_ ## tag ## _handler_obj
|
||||
#define OPT_END(tag)
|
||||
|
||||
} // namespace ledger
|
||||
|
||||
|
|
|
|||
|
|
@ -200,15 +200,6 @@ AC_ARG_ENABLE(debug,
|
|||
esac],[debug=false])
|
||||
AM_CONDITIONAL(DEBUG, test x$debug = xtrue)
|
||||
|
||||
AC_ARG_ENABLE(emacs,
|
||||
[ --enable-emacs Turn on Emacs support],
|
||||
[case "${enableval}" in
|
||||
yes) emacs=true ;;
|
||||
no) emacs=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-emacs) ;;
|
||||
esac],[emacs=true])
|
||||
AM_CONDITIONAL(USE_EDITOR, test x$emacs = xtrue)
|
||||
|
||||
# Checks for header files.
|
||||
AC_STDC_HEADERS
|
||||
AC_HAVE_HEADERS(sys/stat.h)
|
||||
|
|
|
|||
5
debug.h
5
debug.h
|
|
@ -55,6 +55,9 @@ bool _debug_active(const char * const cls);
|
|||
#define DEBUG(cls) (_debug_active(cls))
|
||||
#define DEBUG_() DEBUG(_debug_cls)
|
||||
|
||||
#define DEBUG_IF(cls) if (_debug_active(cls))
|
||||
#define DEBUG_IF_() if (_debug_active(_debug_cls))
|
||||
|
||||
#define DEBUG_PRINT(cls, x) \
|
||||
if (_debug_stream && _debug_active(cls)) { \
|
||||
*_debug_stream << x << std::endl; \
|
||||
|
|
@ -91,6 +94,8 @@ void operator delete[](void*, const std::nothrow_t&) throw();
|
|||
#define DEBUG_CLASS(cls)
|
||||
#define DEBUG(cls) 0
|
||||
#define DEBUG_() 0
|
||||
#define DEBUG_IF(cls)
|
||||
#define DEBUG_IF_()
|
||||
#define DEBUG_PRINT(cls, x)
|
||||
#define DEBUG_PRINT_(x)
|
||||
#define DEBUG_PRINT_TIME(cls, x)
|
||||
|
|
|
|||
120
derive.cc
120
derive.cc
|
|
@ -23,7 +23,8 @@ entry_t * derive_new_entry(journal_t& journal,
|
|||
|
||||
mask_t regexp(*i++);
|
||||
|
||||
for (entries_list::reverse_iterator j = journal.entries.rbegin();
|
||||
entries_list::reverse_iterator j;
|
||||
for (j = journal.entries.rbegin();
|
||||
j != journal.entries.rend();
|
||||
j++)
|
||||
if (regexp.match((*j)->payee)) {
|
||||
|
|
@ -33,23 +34,42 @@ entry_t * derive_new_entry(journal_t& journal,
|
|||
|
||||
added->payee = matching ? matching->payee : regexp.pattern;
|
||||
|
||||
if (i == end) {
|
||||
if (! matching)
|
||||
throw error("Could not find a matching payee");
|
||||
if (! matching) {
|
||||
account_t * acct;
|
||||
if (i == end || ((*i)[0] == '-' || std::isdigit((*i)[0]))) {
|
||||
acct = journal.find_account("Expenses");
|
||||
}
|
||||
else if (i != end) {
|
||||
acct = journal.find_account_re(*i);
|
||||
if (! acct)
|
||||
acct = journal.find_account(*i);
|
||||
assert(acct);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == end)
|
||||
added->add_transaction(new transaction_t(acct));
|
||||
else
|
||||
added->add_transaction(new transaction_t(acct, amount_t(*i++)));
|
||||
|
||||
if (journal.basket)
|
||||
acct = journal.basket;
|
||||
else
|
||||
acct = journal.find_account("Equity");
|
||||
|
||||
added->add_transaction(new transaction_t(acct));
|
||||
}
|
||||
else if (i == end) {
|
||||
// If no argument were given but the payee, assume the user wants
|
||||
// to see the same transaction as last time.
|
||||
added->code = matching->code;
|
||||
|
||||
for (transactions_list::iterator j = matching->transactions.begin();
|
||||
j != matching->transactions.end();
|
||||
j++)
|
||||
added->add_transaction(new transaction_t(**j));
|
||||
for (transactions_list::iterator k = matching->transactions.begin();
|
||||
k != matching->transactions.end();
|
||||
k++)
|
||||
added->add_transaction(new transaction_t(**k));
|
||||
}
|
||||
else if ((*i)[0] == '-' || std::isdigit((*i)[0])) {
|
||||
if (! matching)
|
||||
throw error("Could not determine the account to draw from");
|
||||
|
||||
transaction_t * m_xact, * xact, * first;
|
||||
m_xact = matching->transactions.front();
|
||||
|
||||
|
|
@ -68,67 +88,65 @@ entry_t * derive_new_entry(journal_t& journal,
|
|||
account_t * acct = journal.find_account_re(*i);
|
||||
if (! acct)
|
||||
acct = journal.find_account(*i);
|
||||
if (acct)
|
||||
added->transactions.back()->account = acct;
|
||||
assert(acct);
|
||||
added->transactions.back()->account = acct;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (i != end) {
|
||||
std::string& re_pat(*i++);
|
||||
account_t * acct = NULL;
|
||||
commodity_t * cmdty = NULL;
|
||||
std::string& re_pat(*i++);
|
||||
account_t * acct = NULL;
|
||||
amount_t * amt = NULL;
|
||||
|
||||
if (matching) {
|
||||
mask_t acct_regex(re_pat);
|
||||
mask_t acct_regex(re_pat);
|
||||
|
||||
for (transactions_list::const_iterator x
|
||||
= matching->transactions.begin();
|
||||
x != matching->transactions.end();
|
||||
x++) {
|
||||
if (acct_regex.match((*x)->account->fullname())) {
|
||||
acct = (*x)->account;
|
||||
cmdty = &(*x)->amount.commodity();
|
||||
break;
|
||||
}
|
||||
for (; j != journal.entries.rend(); j++)
|
||||
if (regexp.match((*j)->payee)) {
|
||||
entry_t * entry = *j;
|
||||
for (transactions_list::const_iterator x =
|
||||
entry->transactions.begin();
|
||||
x != entry->transactions.end();
|
||||
x++)
|
||||
if (acct_regex.match((*x)->account->fullname())) {
|
||||
acct = (*x)->account;
|
||||
amt = &(*x)->amount;
|
||||
matching = entry;
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
found:
|
||||
if (! acct)
|
||||
acct = journal.find_account_re(re_pat);
|
||||
if (! acct)
|
||||
acct = journal.find_account(re_pat);
|
||||
|
||||
transaction_t * xact;
|
||||
if (i == end) {
|
||||
added->add_transaction(new transaction_t(acct));
|
||||
goto done;
|
||||
if (amt)
|
||||
xact = new transaction_t(acct, *amt);
|
||||
else
|
||||
xact = new transaction_t(acct);
|
||||
} else {
|
||||
xact = new transaction_t(acct, amount_t(*i++));
|
||||
if (! xact->amount.commodity()) {
|
||||
if (amt)
|
||||
xact->amount.set_commodity(amt->commodity());
|
||||
else if (commodity_t::default_commodity)
|
||||
xact->amount.set_commodity(*commodity_t::default_commodity);
|
||||
}
|
||||
}
|
||||
|
||||
transaction_t * xact = new transaction_t(acct, amount_t(*i++));
|
||||
if (! xact->amount.commodity()) {
|
||||
if (cmdty)
|
||||
xact->amount.set_commodity(*cmdty);
|
||||
else if (commodity_t::default_commodity)
|
||||
xact->amount.set_commodity(*commodity_t::default_commodity);
|
||||
}
|
||||
|
||||
added->add_transaction(xact);
|
||||
}
|
||||
|
||||
account_t * draw_acct;
|
||||
if (matching)
|
||||
draw_acct = matching->transactions.back()->account;
|
||||
else if (journal.basket)
|
||||
draw_acct = journal.basket;
|
||||
else
|
||||
throw error("Could not determine the account to draw from");
|
||||
|
||||
transaction_t * xact = new transaction_t(draw_acct);
|
||||
added->add_transaction(xact);
|
||||
assert(matching->transactions.back()->account);
|
||||
if (account_t * draw_acct = matching->transactions.back()->account)
|
||||
added->add_transaction(new transaction_t(draw_acct));
|
||||
}
|
||||
|
||||
done:
|
||||
if (! added->finalize() ||
|
||||
! run_hooks(journal.entry_finalize_hooks, *added))
|
||||
if (! run_hooks(journal.entry_finalize_hooks, *added) ||
|
||||
! added->finalize())
|
||||
throw error("Failed to finalize derived entry (check commodities)");
|
||||
|
||||
return added.release();
|
||||
|
|
|
|||
34
format.cc
34
format.cc
|
|
@ -162,13 +162,9 @@ element_t * format_t::parse_elements(const std::string& fmt)
|
|||
throw format_error("Missing ')'");
|
||||
|
||||
current->type = element_t::VALUE_EXPR;
|
||||
try {
|
||||
current->val_expr = parse_value_expr(std::string(b, p));
|
||||
}
|
||||
catch (value_expr_error& err) {
|
||||
throw value_expr_error(std::string("In format expression '") +
|
||||
std::string(b, p) + "': " + err.what());
|
||||
}
|
||||
|
||||
assert(current->val_expr == NULL);
|
||||
current->val_expr = new value_expr(std::string(b, p));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -293,22 +289,22 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
case element_t::AMOUNT:
|
||||
case element_t::TOTAL:
|
||||
case element_t::VALUE_EXPR: {
|
||||
value_expr_t * expr = NULL;
|
||||
value_calc * calc = NULL;
|
||||
switch (elem->type) {
|
||||
case element_t::AMOUNT: expr = amount_expr.get(); break;
|
||||
case element_t::TOTAL: expr = total_expr.get(); break;
|
||||
case element_t::VALUE_EXPR: expr = elem->val_expr; break;
|
||||
case element_t::AMOUNT: calc = amount_expr.get(); break;
|
||||
case element_t::TOTAL: calc = total_expr.get(); break;
|
||||
case element_t::VALUE_EXPR: calc = elem->val_expr; break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
if (! expr)
|
||||
if (! calc)
|
||||
break;
|
||||
|
||||
value_t value;
|
||||
balance_t * bal = NULL;
|
||||
|
||||
expr->compute(value, details);
|
||||
calc->compute(value, details);
|
||||
|
||||
switch (value.type) {
|
||||
case value_t::BOOLEAN:
|
||||
|
|
@ -348,9 +344,9 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
amount_t unit_cost = *details.xact->cost / details.xact->amount;
|
||||
|
||||
commodity_t& comm(unit_cost.commodity());
|
||||
bool has_flag = comm.flags & COMMODITY_STYLE_VARIABLE;
|
||||
bool has_flag = comm.flags() & COMMODITY_STYLE_VARIABLE;
|
||||
if (! has_flag)
|
||||
unit_cost.commodity().flags |= COMMODITY_STYLE_VARIABLE;
|
||||
unit_cost.commodity().flags() |= COMMODITY_STYLE_VARIABLE;
|
||||
|
||||
std::ostringstream stream;
|
||||
stream << details.xact->amount << " @ " << unit_cost;
|
||||
|
|
@ -358,7 +354,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
use_disp = true;
|
||||
|
||||
if (! has_flag)
|
||||
unit_cost.commodity().flags &= ~COMMODITY_STYLE_VARIABLE;
|
||||
unit_cost.commodity().flags() &= ~COMMODITY_STYLE_VARIABLE;
|
||||
}
|
||||
else if (details.entry) {
|
||||
unsigned int xacts_count = 0;
|
||||
|
|
@ -388,7 +384,6 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
}
|
||||
break;
|
||||
|
||||
#ifdef USE_EDITOR
|
||||
case element_t::SOURCE:
|
||||
if (details.entry && details.entry->journal) {
|
||||
int idx = details.entry->src_idx;
|
||||
|
|
@ -441,7 +436,6 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
if (details.xact)
|
||||
out << details.xact->end_line;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case element_t::DATE_STRING: {
|
||||
std::time_t date = 0;
|
||||
|
|
@ -617,7 +611,7 @@ void format_t::format(std::ostream& out_str, const details_t& details) const
|
|||
}
|
||||
}
|
||||
|
||||
format_transactions::format_transactions(std::ostream& _output_stream,
|
||||
format_transactions::format_transactions(std::ostream& _output_stream,
|
||||
const std::string& format)
|
||||
: output_stream(_output_stream), last_entry(NULL), last_xact(NULL)
|
||||
{
|
||||
|
|
@ -731,7 +725,7 @@ bool disp_subaccounts_p(const account_t& account,
|
|||
return display;
|
||||
}
|
||||
|
||||
bool display_account(const account_t& account,
|
||||
bool display_account(const account_t& account,
|
||||
const item_predicate<account_t>& disp_pred)
|
||||
{
|
||||
// Never display an account that has already been displayed.
|
||||
|
|
|
|||
2
format.h
2
format.h
|
|
@ -51,7 +51,7 @@ struct element_t
|
|||
|
||||
kind_t type;
|
||||
std::string chars;
|
||||
value_expr_t * val_expr;
|
||||
value_expr * val_expr;
|
||||
|
||||
struct element_t * next;
|
||||
|
||||
|
|
|
|||
24
gnucash.cc
24
gnucash.cc
|
|
@ -45,11 +45,9 @@ static std::istream * instreamp;
|
|||
static unsigned int offset;
|
||||
static XML_Parser parser;
|
||||
static std::string path;
|
||||
#ifdef USE_EDITOR
|
||||
static unsigned int src_idx;
|
||||
static istream_pos_type beg_pos;
|
||||
static unsigned long beg_line;
|
||||
#endif
|
||||
|
||||
static transaction_t::state_t curr_state;
|
||||
|
||||
|
|
@ -148,13 +146,11 @@ static void endElement(void *userData, const char *name)
|
|||
have_error = "The above entry does not balance";
|
||||
delete curr_entry;
|
||||
} else {
|
||||
#ifdef USE_EDITOR
|
||||
curr_entry->src_idx = src_idx;
|
||||
curr_entry->beg_pos = beg_pos;
|
||||
curr_entry->beg_line = beg_line;
|
||||
curr_entry->end_pos = instreamp->tellg();
|
||||
curr_entry->end_line = XML_GetCurrentLineNumber(parser) - offset;
|
||||
#endif
|
||||
count++;
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +176,7 @@ static void endElement(void *userData, const char *name)
|
|||
|
||||
if (default_commodity) {
|
||||
curr_quant.set_commodity(*default_commodity);
|
||||
value = curr_quant.round(default_commodity->precision);
|
||||
value = curr_quant.round(default_commodity->precision());
|
||||
|
||||
if (curr_value.commodity() == *default_commodity)
|
||||
curr_value = value;
|
||||
|
|
@ -193,12 +189,10 @@ static void endElement(void *userData, const char *name)
|
|||
if (value != curr_value)
|
||||
xact->cost = new amount_t(curr_value);
|
||||
|
||||
#ifdef USE_EDITOR
|
||||
xact->beg_pos = beg_pos;
|
||||
xact->beg_line = beg_line;
|
||||
xact->end_pos = instreamp->tellg();
|
||||
xact->end_line = XML_GetCurrentLineNumber(parser) - offset;
|
||||
#endif
|
||||
|
||||
// Clear the relevant variables for the next run
|
||||
curr_state = transaction_t::UNCLEARED;
|
||||
|
|
@ -264,23 +258,23 @@ static void dataHandler(void *userData, const char *s, int len)
|
|||
std::string symbol(s, len);
|
||||
commodity_t * comm = commodity_t::find_commodity(symbol, true);
|
||||
if (symbol != "$" && symbol != "USD")
|
||||
comm->flags |= COMMODITY_STYLE_SEPARATED;
|
||||
comm->flags() |= COMMODITY_STYLE_SEPARATED;
|
||||
account_comms.insert(account_comm_pair(curr_account, comm));
|
||||
}
|
||||
else if (curr_entry) {
|
||||
std::string symbol(s, len);
|
||||
entry_comm = commodity_t::find_commodity(symbol, true);
|
||||
if (symbol != "$" && symbol != "USD")
|
||||
entry_comm->flags |= COMMODITY_STYLE_SEPARATED;
|
||||
entry_comm->flags() |= COMMODITY_STYLE_SEPARATED;
|
||||
}
|
||||
break;
|
||||
|
||||
case COMM_NAME:
|
||||
curr_comm->name = std::string(s, len);
|
||||
curr_comm->name() = std::string(s, len);
|
||||
break;
|
||||
|
||||
case COMM_PREC:
|
||||
curr_comm->precision = len - 1;
|
||||
curr_comm->precision() = len - 1;
|
||||
break;
|
||||
|
||||
case ENTRY_NUM:
|
||||
|
|
@ -313,8 +307,8 @@ static void dataHandler(void *userData, const char *s, int len)
|
|||
curr_value = convert_number(std::string(s, len), &precision);
|
||||
curr_value.set_commodity(*entry_comm);
|
||||
|
||||
if (precision > entry_comm->precision)
|
||||
entry_comm->precision = precision;
|
||||
if (precision > entry_comm->precision())
|
||||
entry_comm->precision() = precision;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -382,9 +376,7 @@ unsigned int gnucash_parser_t::parse(std::istream& in,
|
|||
|
||||
instreamp = ∈
|
||||
path = original_file ? *original_file : "<gnucash>";
|
||||
#ifdef USE_EDITOR
|
||||
src_idx = journal->sources.size() - 1;
|
||||
#endif
|
||||
|
||||
// GnuCash uses the USD commodity without defining it, which really
|
||||
// means $.
|
||||
|
|
@ -401,10 +393,8 @@ unsigned int gnucash_parser_t::parse(std::istream& in,
|
|||
XML_SetCharacterDataHandler(parser, dataHandler);
|
||||
|
||||
while (in.good() && ! in.eof()) {
|
||||
#ifdef USE_EDITOR
|
||||
beg_pos = in.tellg();
|
||||
beg_line = (XML_GetCurrentLineNumber(parser) - offset) + 1;
|
||||
#endif
|
||||
|
||||
in.getline(buf, BUFSIZ - 1);
|
||||
std::strcat(buf, "\n");
|
||||
|
|
|
|||
12
journal.cc
12
journal.cc
|
|
@ -13,6 +13,14 @@ const std::string version = PACKAGE_VERSION;
|
|||
|
||||
bool transaction_t::use_effective_date = false;
|
||||
|
||||
transaction_t::~transaction_t()
|
||||
{
|
||||
DEBUG_PRINT("ledger.memory.dtors", "dtor transaction_t");
|
||||
if (cost) delete cost;
|
||||
if (amount_expr) amount_expr->release();
|
||||
if (cost_expr) cost_expr->release();
|
||||
}
|
||||
|
||||
std::time_t transaction_t::actual_date() const
|
||||
{
|
||||
if (_date == 0 && entry)
|
||||
|
|
@ -96,6 +104,10 @@ bool entry_base_t::finalize()
|
|||
} else {
|
||||
balance += *p;
|
||||
}
|
||||
|
||||
if ((*x)->cost && (*x)->amount.commodity().price)
|
||||
balance += (*((*x)->amount.commodity().price) * (*x)->amount -
|
||||
*((*x)->cost));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
32
journal.h
32
journal.h
|
|
@ -44,12 +44,10 @@ class transaction_t
|
|||
state_t state;
|
||||
unsigned short flags;
|
||||
std::string note;
|
||||
#ifdef USE_EDITOR
|
||||
istream_pos_type beg_pos;
|
||||
unsigned long beg_line;
|
||||
istream_pos_type end_pos;
|
||||
unsigned long end_line;
|
||||
#endif
|
||||
mutable void * data;
|
||||
|
||||
static bool use_effective_date;
|
||||
|
|
@ -58,13 +56,9 @@ class transaction_t
|
|||
: entry(NULL), _date(0), _date_eff(0), account(_account),
|
||||
amount_expr(NULL), cost(NULL), cost_expr(NULL),
|
||||
state(UNCLEARED), flags(TRANSACTION_NORMAL),
|
||||
#ifdef USE_EDITOR
|
||||
beg_pos(0), beg_line(0), end_pos(0), end_line(0),
|
||||
#endif
|
||||
data(NULL) {
|
||||
beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) {
|
||||
DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t");
|
||||
}
|
||||
|
||||
transaction_t(account_t * _account,
|
||||
const amount_t& _amount,
|
||||
unsigned int _flags = TRANSACTION_NORMAL,
|
||||
|
|
@ -72,30 +66,18 @@ class transaction_t
|
|||
: entry(NULL), _date(0), _date_eff(0), account(_account),
|
||||
amount(_amount), amount_expr(NULL), cost(NULL), cost_expr(NULL),
|
||||
state(UNCLEARED), flags(_flags), note(_note),
|
||||
#ifdef USE_EDITOR
|
||||
beg_pos(0), beg_line(0), end_pos(0), end_line(0),
|
||||
#endif
|
||||
data(NULL) {
|
||||
beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) {
|
||||
DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t");
|
||||
}
|
||||
|
||||
transaction_t(const transaction_t& xact)
|
||||
: entry(xact.entry), _date(0), _date_eff(0), account(xact.account),
|
||||
amount(xact.amount), amount_expr(NULL),
|
||||
cost(xact.cost ? new amount_t(*xact.cost) : NULL), cost_expr(NULL),
|
||||
state(xact.state), flags(xact.flags), note(xact.note),
|
||||
#ifdef USE_EDITOR
|
||||
beg_pos(0), beg_line(0), end_pos(0), end_line(0),
|
||||
#endif
|
||||
data(NULL) {
|
||||
beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) {
|
||||
DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t");
|
||||
}
|
||||
|
||||
~transaction_t() {
|
||||
DEBUG_PRINT("ledger.memory.dtors", "dtor transaction_t");
|
||||
if (cost)
|
||||
delete cost;
|
||||
}
|
||||
~transaction_t();
|
||||
|
||||
std::time_t actual_date() const;
|
||||
std::time_t effective_date() const;
|
||||
|
|
@ -124,26 +106,20 @@ class entry_base_t
|
|||
{
|
||||
public:
|
||||
journal_t * journal;
|
||||
#ifdef USE_EDITOR
|
||||
unsigned long src_idx;
|
||||
istream_pos_type beg_pos;
|
||||
unsigned long beg_line;
|
||||
istream_pos_type end_pos;
|
||||
unsigned long end_line;
|
||||
#endif
|
||||
transactions_list transactions;
|
||||
|
||||
entry_base_t() : journal(NULL),
|
||||
#ifdef USE_EDITOR
|
||||
beg_pos(0), beg_line(0), end_pos(0), end_line(0)
|
||||
#endif
|
||||
{
|
||||
DEBUG_PRINT("ledger.memory.ctors", "ctor entry_base_t");
|
||||
}
|
||||
entry_base_t(const entry_base_t& e) : journal(NULL),
|
||||
#ifdef USE_EDITOR
|
||||
beg_pos(0), beg_line(0), end_pos(0), end_line(0)
|
||||
#endif
|
||||
{
|
||||
DEBUG_PRINT("ledger.memory.ctors", "ctor entry_base_t");
|
||||
for (transactions_list::const_iterator i = e.transactions.begin();
|
||||
|
|
|
|||
34
main.cc
34
main.cc
|
|
@ -104,10 +104,10 @@ int parse_and_report(int argc, char * argv[], char * envp[])
|
|||
command = "p";
|
||||
else if (command == "output")
|
||||
command = "w";
|
||||
#ifdef USE_EDITOR
|
||||
else if (command == "emacs")
|
||||
command = "x";
|
||||
#endif
|
||||
else if (command == "lisp")
|
||||
command = "x";
|
||||
else if (command == "xml")
|
||||
command = "X";
|
||||
else if (command == "entry")
|
||||
|
|
@ -122,6 +122,20 @@ int parse_and_report(int argc, char * argv[], char * envp[])
|
|||
config.register_format = config.csv_register_format;
|
||||
command = "r";
|
||||
}
|
||||
else if (command == "parse") {
|
||||
value_auto_ptr expr(ledger::parse_value_expr(*arg));
|
||||
if (config.debug_mode) {
|
||||
ledger::dump_value_expr(std::cout, expr.get());
|
||||
std::cout << std::endl;
|
||||
}
|
||||
value_t result;
|
||||
expr->compute(result, details_t());
|
||||
std::cout << result << std::endl;
|
||||
return 0;
|
||||
}
|
||||
else if (command == "expr") {
|
||||
// this gets done below...
|
||||
}
|
||||
else
|
||||
throw error(std::string("Unrecognized command '") + command + "'");
|
||||
|
||||
|
|
@ -211,6 +225,20 @@ int parse_and_report(int argc, char * argv[], char * envp[])
|
|||
}
|
||||
#endif
|
||||
|
||||
// Are we handling the parse or expr commands? Do so now.
|
||||
|
||||
if (command == "expr") {
|
||||
value_auto_ptr expr(ledger::parse_value_expr(*arg));
|
||||
if (config.debug_mode) {
|
||||
ledger::dump_value_expr(std::cout, expr.get());
|
||||
std::cout << std::endl;
|
||||
}
|
||||
value_t result;
|
||||
expr->compute(result, details_t());
|
||||
std::cout << result << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Compile the format strings
|
||||
|
||||
const std::string * format;
|
||||
|
|
@ -245,10 +273,8 @@ int parse_and_report(int argc, char * argv[], char * envp[])
|
|||
formatter = new set_account_value;
|
||||
else if (command == "p" || command == "e")
|
||||
formatter = new format_entries(*out, *format);
|
||||
#ifdef USE_EDITOR
|
||||
else if (command == "x")
|
||||
formatter = new format_emacs_transactions(*out);
|
||||
#endif
|
||||
else if (command == "X") {
|
||||
#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
|
||||
formatter = new format_xml_entries(*out, config.show_totals);
|
||||
|
|
|
|||
187
option.cc
187
option.cc
|
|
@ -1,4 +1,5 @@
|
|||
#include "option.h"
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <iostream>
|
||||
|
|
@ -6,149 +7,125 @@
|
|||
|
||||
#include "util.h"
|
||||
|
||||
void add_option_handler(std::list<option_t>& options,
|
||||
const std::string& label,
|
||||
const std::string& opt_chars,
|
||||
option_handler& option)
|
||||
{
|
||||
option_t opt;
|
||||
|
||||
char buf[128];
|
||||
char * p = buf;
|
||||
for (const char * q = label.c_str();
|
||||
*q && p - buf < 128;
|
||||
q++)
|
||||
if (*q == '_')
|
||||
*p++ = '-';
|
||||
else
|
||||
*p++ = *q;
|
||||
*p = '\0';
|
||||
opt.long_opt = buf;
|
||||
|
||||
if (! opt_chars.empty()) {
|
||||
if (opt_chars[0] != ':')
|
||||
opt.short_opt = opt_chars[0];
|
||||
|
||||
if (opt_chars[opt_chars.length() - 1] == ':')
|
||||
opt.wants_arg = true;
|
||||
}
|
||||
|
||||
opt.handler = &option;
|
||||
|
||||
options.push_front(opt);
|
||||
}
|
||||
|
||||
namespace {
|
||||
inline void process_option(const option_t& opt,
|
||||
const char * arg = NULL) {
|
||||
if (! opt.handler->handled) {
|
||||
(*opt.handler)(arg);
|
||||
opt.handler->handled = true;
|
||||
inline void process_option(option_t * opt, const char * arg = NULL) {
|
||||
if (! opt->handled) {
|
||||
opt->handler(arg);
|
||||
opt->handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
option_t * search_options(option_t * array, const char * name)
|
||||
{
|
||||
int first = 0;
|
||||
int last = CONFIG_OPTIONS_SIZE;
|
||||
while (first <= last) {
|
||||
int mid = (first + last) / 2; // compute mid point.
|
||||
|
||||
int result;
|
||||
if ((result = (int)name[0] - (int)array[mid].long_opt[0]) == 0)
|
||||
result = std::strcmp(name, array[mid].long_opt);
|
||||
|
||||
if (result > 0)
|
||||
first = mid + 1; // repeat search in top half.
|
||||
else if (result < 0)
|
||||
last = mid - 1; // repeat search in bottom half.
|
||||
else
|
||||
return &array[mid];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
option_t * search_options(option_t * array, const char letter)
|
||||
{
|
||||
for (int i = 0; i < CONFIG_OPTIONS_SIZE; i++)
|
||||
if (letter == array[i].short_opt)
|
||||
return &array[i];
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool process_option(std::list<option_t>& options,
|
||||
const std::string& opt, const char * arg)
|
||||
bool process_option(option_t * options, const std::string& name,
|
||||
const char * arg)
|
||||
{
|
||||
for (std::list<option_t>::iterator i = options.begin();
|
||||
i != options.end();
|
||||
i++)
|
||||
if ((*i).long_opt == opt) {
|
||||
if (! (*i).handler->handled) {
|
||||
(*(*i).handler)(arg);
|
||||
(*i).handler->handled = true;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
option_t * opt = search_options(options, name.c_str());
|
||||
if (opt != NULL && ! opt->handled) {
|
||||
opt->handler(arg);
|
||||
opt->handled = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void process_arguments(std::list<option_t>& options,
|
||||
int argc, char ** argv, const bool anywhere,
|
||||
std::list<std::string>& args)
|
||||
void process_arguments(option_t * options, int argc, char ** argv,
|
||||
const bool anywhere, std::list<std::string>& args)
|
||||
{
|
||||
int index = 0;
|
||||
for (char ** i = argv; index < argc; i++, index++) {
|
||||
for (char ** i = argv; *i; i++) {
|
||||
if ((*i)[0] != '-') {
|
||||
if (anywhere) {
|
||||
args.push_back(*i);
|
||||
continue;
|
||||
} else {
|
||||
for (; index < argc; i++, index++)
|
||||
for (; *i; i++)
|
||||
args.push_back(*i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --long-option
|
||||
// --long-option or -s
|
||||
again:
|
||||
option_t * opt = NULL;
|
||||
char * value = NULL;
|
||||
|
||||
if ((*i)[1] == '-') {
|
||||
if ((*i)[2] == '\0')
|
||||
break;
|
||||
|
||||
for (std::list<option_t>::iterator j = options.begin();
|
||||
j != options.end();
|
||||
j++)
|
||||
if ((*j).wants_arg) {
|
||||
if (const char * p = std::strchr(*i + 2, '=')) {
|
||||
if ((*j).long_opt == std::string(*i + 2, p - (*i + 2))) {
|
||||
process_option(*j, p + 1);
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
else if ((*j).long_opt == *i + 2) {
|
||||
if (++index >= argc)
|
||||
throw option_error(std::string("missing option argument for ") +
|
||||
*i);
|
||||
process_option(*j, argv[index]);
|
||||
i++;
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
else if ((*j).long_opt == *i + 2) {
|
||||
process_option(*j);
|
||||
goto next;
|
||||
}
|
||||
char * name = *i + 2;
|
||||
if (char * p = std::strchr(name, '=')) {
|
||||
*p++ = '\0';
|
||||
value = p;
|
||||
}
|
||||
|
||||
throw option_error(std::string("illegal option ") + *i);
|
||||
opt = search_options(options, name);
|
||||
if (opt == NULL)
|
||||
throw option_error(std::string("illegal option --") + name);
|
||||
|
||||
if (opt->wants_arg && value == NULL) {
|
||||
value = *++i;
|
||||
if (value == NULL)
|
||||
throw option_error(std::string("missing option argument for --") +
|
||||
name);
|
||||
}
|
||||
process_option(opt, value);
|
||||
} else {
|
||||
for (std::list<option_t>::iterator j = options.begin();
|
||||
j != options.end();
|
||||
j++)
|
||||
if ((*i)[1] == (*j).short_opt) {
|
||||
if ((*j).wants_arg) {
|
||||
if (++index >= argc)
|
||||
throw option_error(std::string("missing argument for option ") +
|
||||
*i);
|
||||
process_option(*j, argv[index]);
|
||||
i++;
|
||||
goto next;
|
||||
} else {
|
||||
process_option(*j);
|
||||
if ((*i)[2]) {
|
||||
std::strcpy(*i + 1, *i + 2);
|
||||
goto again;
|
||||
}
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
char c = (*i)[1];
|
||||
opt = search_options(options, c);
|
||||
if (opt == NULL)
|
||||
throw option_error(std::string("illegal option -") + c);
|
||||
|
||||
throw option_error(std::string("illegal option -- ") + (*i)[1]);
|
||||
if (opt->wants_arg) {
|
||||
value = *++i;
|
||||
if (value == NULL)
|
||||
throw option_error(std::string("missing option argument for -") + c);
|
||||
}
|
||||
}
|
||||
|
||||
assert(opt);
|
||||
assert(value == NULL || opt->wants_arg);
|
||||
process_option(opt, value);
|
||||
|
||||
next:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
void process_environment(std::list<option_t>& options,
|
||||
char ** envp, const std::string& tag)
|
||||
void process_environment(option_t * options, char ** envp,
|
||||
const std::string& tag)
|
||||
{
|
||||
const char * tag_p = tag.c_str();
|
||||
int tag_len = tag.length();
|
||||
unsigned int tag_len = tag.length();
|
||||
|
||||
for (char ** p = envp; *p; p++)
|
||||
if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) {
|
||||
|
|
|
|||
33
option.h
33
option.h
|
|
@ -5,20 +5,14 @@
|
|||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
struct option_handler {
|
||||
bool handled;
|
||||
option_handler() : handled(false) {}
|
||||
virtual ~option_handler() {}
|
||||
virtual void operator()(const char * arg = NULL) = 0;
|
||||
};
|
||||
typedef void (*handler_t)(const char * arg);
|
||||
|
||||
struct option_t {
|
||||
char short_opt;
|
||||
std::string long_opt;
|
||||
bool wants_arg;
|
||||
option_handler * handler;
|
||||
|
||||
option_t() : short_opt(0), wants_arg(false), handler(NULL) {}
|
||||
const char * long_opt;
|
||||
char short_opt;
|
||||
bool wants_arg;
|
||||
handler_t handler;
|
||||
bool handled;
|
||||
};
|
||||
|
||||
class option_error : public std::exception {
|
||||
|
|
@ -32,14 +26,11 @@ class option_error : public std::exception {
|
|||
}
|
||||
};
|
||||
|
||||
void add_option_handler(std::list<option_t>& options, const std::string& label,
|
||||
const std::string& opt_chars, option_handler& option);
|
||||
bool process_option(std::list<option_t>& options,
|
||||
const std::string& opt, const char * arg = NULL);
|
||||
void process_arguments(std::list<option_t>& options,
|
||||
int argc, char ** argv, const bool anywhere,
|
||||
std::list<std::string>& args);
|
||||
void process_environment(std::list<option_t>& options,
|
||||
char ** envp, const std::string& tag);
|
||||
bool process_option(option_t * options, const std::string& opt,
|
||||
const char * arg = NULL);
|
||||
void process_arguments(option_t * options, int argc, char ** argv,
|
||||
const bool anywhere, std::list<std::string>& args);
|
||||
void process_environment(option_t * options, char ** envp,
|
||||
const std::string& tag);
|
||||
|
||||
#endif // _OPTION_H
|
||||
|
|
|
|||
18
qif.cc
18
qif.cc
|
|
@ -63,7 +63,6 @@ unsigned int qif_parser_t::parse(std::istream& in,
|
|||
src_idx = journal->sources.size() - 1;
|
||||
linenum = 1;
|
||||
|
||||
#ifdef USE_EDITOR
|
||||
istream_pos_type beg_pos = 0;
|
||||
unsigned long beg_line = 0;
|
||||
|
||||
|
|
@ -72,9 +71,6 @@ unsigned int qif_parser_t::parse(std::istream& in,
|
|||
beg_pos = in.tellg(); \
|
||||
beg_line = linenum; \
|
||||
}
|
||||
#else
|
||||
#define SET_BEG_POS_AND_LINE()
|
||||
#endif
|
||||
|
||||
while (in.good() && ! in.eof()) {
|
||||
char c;
|
||||
|
|
@ -119,16 +115,16 @@ unsigned int qif_parser_t::parse(std::istream& in,
|
|||
get_line(in);
|
||||
xact->amount.parse(line);
|
||||
|
||||
unsigned long flags = xact->amount.commodity().flags;
|
||||
unsigned short prec = xact->amount.commodity().precision;
|
||||
unsigned long flags = xact->amount.commodity().flags();
|
||||
unsigned short prec = xact->amount.commodity().precision();
|
||||
|
||||
if (! def_commodity)
|
||||
def_commodity = commodity_t::find_commodity("$", true);
|
||||
xact->amount.set_commodity(*def_commodity);
|
||||
|
||||
def_commodity->flags |= flags;
|
||||
if (prec > def_commodity->precision)
|
||||
def_commodity->precision = prec;
|
||||
def_commodity->flags() |= flags;
|
||||
if (prec > def_commodity->precision())
|
||||
def_commodity->precision() = prec;
|
||||
|
||||
if (c == '$') {
|
||||
saw_splits = true;
|
||||
|
|
@ -221,13 +217,11 @@ unsigned int qif_parser_t::parse(std::istream& in,
|
|||
}
|
||||
|
||||
if (journal->add_entry(entry.get())) {
|
||||
#ifdef USE_EDITOR
|
||||
entry->src_idx = src_idx;
|
||||
entry->beg_pos = beg_pos;
|
||||
entry->beg_line = beg_line;
|
||||
entry->end_pos = in.tellg();
|
||||
entry->end_line = linenum;
|
||||
#endif
|
||||
entry.release();
|
||||
count++;
|
||||
}
|
||||
|
|
@ -240,9 +234,7 @@ unsigned int qif_parser_t::parse(std::istream& in,
|
|||
saw_splits = false;
|
||||
saw_category = false;
|
||||
total = NULL;
|
||||
#ifdef USE_EDITOR
|
||||
beg_line = 0;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
10
quotes.cc
10
quotes.cc
|
|
@ -22,12 +22,12 @@ void quotes_by_script::operator()(commodity_t& commodity,
|
|||
DEBUG_PRINT_TIME_(moment);
|
||||
DEBUG_PRINT_TIME_(date);
|
||||
DEBUG_PRINT_TIME_(last);
|
||||
if (commodity.history)
|
||||
DEBUG_PRINT_TIME_(commodity.history->last_lookup);
|
||||
if (commodity.history())
|
||||
DEBUG_PRINT_TIME_(commodity.history()->last_lookup);
|
||||
DEBUG_PRINT_("pricing_leeway is " << pricing_leeway);
|
||||
|
||||
if ((commodity.history &&
|
||||
std::difftime(now, commodity.history->last_lookup) < pricing_leeway) ||
|
||||
if ((commodity.history() &&
|
||||
std::difftime(now, commodity.history()->last_lookup) < pricing_leeway) ||
|
||||
std::difftime(now, last) < pricing_leeway ||
|
||||
(price && std::difftime(moment, date) > 0 &&
|
||||
std::difftime(moment, date) <= pricing_leeway))
|
||||
|
|
@ -61,7 +61,7 @@ void quotes_by_script::operator()(commodity_t& commodity,
|
|||
price.parse(buf);
|
||||
commodity.add_price(now, price);
|
||||
|
||||
commodity.history->last_lookup = now;
|
||||
commodity.history()->last_lookup = now;
|
||||
cache_dirty = true;
|
||||
|
||||
if (price && ! price_db.empty()) {
|
||||
|
|
|
|||
81
textual.cc
81
textual.cc
|
|
@ -290,13 +290,49 @@ transaction_t * parse_transaction(char * line, account_t * account)
|
|||
if (amount) {
|
||||
xact->amount_expr = parse_amount(amount, xact->amount,
|
||||
AMOUNT_PARSE_NO_REDUCE, *xact);
|
||||
if (xact->amount_expr)
|
||||
xact->amount_expr->acquire();
|
||||
if (price) {
|
||||
xact->cost = new amount_t;
|
||||
xact->cost_expr = parse_amount(price, *xact->cost,
|
||||
AMOUNT_PARSE_NO_MIGRATE, *xact);
|
||||
if (xact->cost_expr)
|
||||
xact->cost_expr->acquire();
|
||||
if (per_unit) {
|
||||
if (xact->amount.commodity().base == NULL) {
|
||||
std::string symbol = xact->amount.commodity().symbol;
|
||||
symbol += " {";
|
||||
symbol += price;
|
||||
symbol += "}";
|
||||
|
||||
commodity_t * priced_commodity =
|
||||
commodity_t::find_commodity(symbol, true);
|
||||
|
||||
priced_commodity->price = new amount_t(*xact->cost);
|
||||
priced_commodity->base = &xact->amount.commodity();
|
||||
|
||||
xact->amount.set_commodity(*priced_commodity);
|
||||
}
|
||||
|
||||
*xact->cost *= xact->amount;
|
||||
*xact->cost = xact->cost->round(xact->cost->commodity().precision);
|
||||
*xact->cost = xact->cost->round(xact->cost->commodity().precision());
|
||||
}
|
||||
else if (xact->amount.commodity().base == NULL) {
|
||||
amount_t cost(*xact->cost);
|
||||
cost /= xact->amount;
|
||||
cost = cost.round(cost.commodity().precision());
|
||||
|
||||
std::string symbol;
|
||||
std::ostringstream symstr(symbol);
|
||||
symstr << xact->amount.commodity().symbol << " {" << cost << "}";
|
||||
|
||||
commodity_t * priced_commodity =
|
||||
commodity_t::find_commodity(symstr.str(), true);
|
||||
|
||||
priced_commodity->price = new amount_t(cost);
|
||||
priced_commodity->base = &xact->amount.commodity();
|
||||
|
||||
xact->amount.set_commodity(*priced_commodity);
|
||||
}
|
||||
}
|
||||
xact->amount.reduce();
|
||||
|
|
@ -318,10 +354,9 @@ bool parse_transactions(std::istream& in,
|
|||
in.getline(line, MAX_LINE);
|
||||
if (in.eof())
|
||||
break;
|
||||
#ifdef USE_EDITOR
|
||||
beg_pos += istream_pos_type(std::strlen(line) + 1);
|
||||
#endif
|
||||
linenum++;
|
||||
|
||||
if (line[0] == ' ' || line[0] == '\t' || line[0] == '\r') {
|
||||
char * p = skip_ws(line);
|
||||
if (! *p || *p == '\r')
|
||||
|
|
@ -403,20 +438,17 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master,
|
|||
|
||||
TIMER_START(entry_xacts);
|
||||
|
||||
#ifdef USE_EDITOR
|
||||
istream_pos_type end_pos;
|
||||
unsigned long beg_line = linenum;
|
||||
#endif
|
||||
|
||||
while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
|
||||
line[0] = '\0';
|
||||
in.getline(line, MAX_LINE);
|
||||
if (in.eof() && line[0] == '\0')
|
||||
break;
|
||||
#ifdef USE_EDITOR
|
||||
end_pos = beg_pos + istream_pos_type(std::strlen(line) + 1);
|
||||
#endif
|
||||
|
||||
linenum++;
|
||||
|
||||
if (line[0] == ' ' || line[0] == '\t' || line[0] == '\r') {
|
||||
char * p = skip_ws(line);
|
||||
if (! *p || *p == '\r')
|
||||
|
|
@ -547,20 +579,17 @@ unsigned int textual_parser_t::parse(std::istream& in,
|
|||
src_idx = journal->sources.size() - 1;
|
||||
linenum = 1;
|
||||
|
||||
#ifdef USE_EDITOR
|
||||
istream_pos_type beg_pos = in.tellg();
|
||||
istream_pos_type end_pos;
|
||||
unsigned long beg_line = linenum;
|
||||
#endif
|
||||
|
||||
while (in.good() && ! in.eof()) {
|
||||
try {
|
||||
in.getline(line, MAX_LINE);
|
||||
if (in.eof())
|
||||
break;
|
||||
linenum++;
|
||||
#ifdef USE_EDITOR
|
||||
end_pos = beg_pos + istream_pos_type(std::strlen(line) + 1);
|
||||
#endif
|
||||
linenum++;
|
||||
|
||||
switch (line[0]) {
|
||||
case '\0':
|
||||
|
|
@ -674,7 +703,7 @@ unsigned int textual_parser_t::parse(std::istream& in,
|
|||
parse_symbol(p, symbol);
|
||||
|
||||
commodity_t * commodity = commodity_t::find_commodity(symbol, true);
|
||||
commodity->flags |= COMMODITY_STYLE_NOMARKET;
|
||||
commodity->flags() |= COMMODITY_STYLE_NOMARKET;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -732,13 +761,11 @@ unsigned int textual_parser_t::parse(std::istream& in,
|
|||
if (pe->finalize()) {
|
||||
extend_entry_base(journal, *pe);
|
||||
journal->period_entries.push_back(pe);
|
||||
#ifdef USE_EDITOR
|
||||
pe->src_idx = src_idx;
|
||||
pe->beg_pos = beg_pos;
|
||||
pe->beg_line = beg_line;
|
||||
pe->end_pos = end_pos;
|
||||
pe->end_line = linenum;
|
||||
#endif
|
||||
} else {
|
||||
throw parse_error(path, linenum, "Period entry failed to balance");
|
||||
}
|
||||
|
|
@ -746,16 +773,15 @@ unsigned int textual_parser_t::parse(std::istream& in,
|
|||
break;
|
||||
}
|
||||
|
||||
case '@':
|
||||
case '!': { // directive
|
||||
char * p = next_element(line);
|
||||
std::string word(line + 1);
|
||||
if (word == "include") {
|
||||
push_var<std::string> save_path(path);
|
||||
push_var<unsigned int> save_src_idx(src_idx);
|
||||
#ifdef USE_EDITOR
|
||||
push_var<istream_pos_type> save_beg_pos(beg_pos);
|
||||
push_var<istream_pos_type> save_end_pos(end_pos);
|
||||
#endif
|
||||
push_var<unsigned int> save_linenum(linenum);
|
||||
|
||||
path = p;
|
||||
|
|
@ -799,26 +825,25 @@ unsigned int textual_parser_t::parse(std::istream& in,
|
|||
assert(result.second);
|
||||
}
|
||||
}
|
||||
else if (word == "def") {
|
||||
if (! global_scope.get())
|
||||
init_value_expr();
|
||||
value_auto_ptr expr(parse_boolean_expr(p, global_scope.get()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
unsigned int first_line = linenum;
|
||||
#ifdef USE_EDITOR
|
||||
istream_pos_type pos = end_pos;
|
||||
#else
|
||||
istream_pos_type pos;
|
||||
#endif
|
||||
if (entry_t * entry =
|
||||
parse_entry(in, line, account_stack.front(), *this, pos)) {
|
||||
if (journal->add_entry(entry)) {
|
||||
#ifdef USE_EDITOR
|
||||
entry->src_idx = src_idx;
|
||||
entry->beg_pos = beg_pos;
|
||||
entry->beg_line = beg_line;
|
||||
entry->end_pos = end_pos;
|
||||
entry->end_line = linenum;
|
||||
#endif
|
||||
count++;
|
||||
} else {
|
||||
print_entry(std::cerr, *entry);
|
||||
|
|
@ -833,9 +858,7 @@ unsigned int textual_parser_t::parse(std::istream& in,
|
|||
} else {
|
||||
throw parse_error(path, first_line, "Failed to parse entry");
|
||||
}
|
||||
#ifdef USE_EDITOR
|
||||
end_pos = pos;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -854,9 +877,7 @@ unsigned int textual_parser_t::parse(std::istream& in,
|
|||
<< err.what() << std::endl;;
|
||||
errors++;
|
||||
}
|
||||
#ifdef USE_EDITOR
|
||||
beg_pos = end_pos;
|
||||
#endif
|
||||
}
|
||||
|
||||
done:
|
||||
|
|
@ -876,8 +897,6 @@ unsigned int textual_parser_t::parse(std::istream& in,
|
|||
return count;
|
||||
}
|
||||
|
||||
#ifdef USE_EDITOR
|
||||
|
||||
void write_textual_journal(journal_t& journal, std::string path,
|
||||
item_handler<transaction_t>& formatter,
|
||||
const std::string& write_hdr_format,
|
||||
|
|
@ -964,6 +983,4 @@ void write_textual_journal(journal_t& journal, std::string path,
|
|||
}
|
||||
}
|
||||
|
||||
#endif // USE_EDITOR
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
1164
valexpr.cc
1164
valexpr.cc
File diff suppressed because it is too large
Load diff
292
valexpr.h
292
valexpr.h
|
|
@ -16,6 +16,7 @@ struct details_t
|
|||
const transaction_t * xact;
|
||||
const account_t * account;
|
||||
|
||||
details_t() : entry(NULL), xact(NULL), account(NULL) {}
|
||||
details_t(const entry_t& _entry)
|
||||
: entry(&_entry), xact(NULL), account(NULL) {
|
||||
DEBUG_PRINT("ledger.memory.ctors", "ctor details_t");
|
||||
|
|
@ -32,6 +33,29 @@ struct details_t
|
|||
#endif
|
||||
};
|
||||
|
||||
typedef void (*value_func_t)(value_t& result, const details_t& details,
|
||||
value_expr_t * context);
|
||||
|
||||
class value_calc
|
||||
{
|
||||
public:
|
||||
virtual ~value_calc() {}
|
||||
virtual void compute(value_t& result, const details_t& details,
|
||||
value_expr_t * context = NULL) = 0;
|
||||
};
|
||||
|
||||
class value_func : public value_calc
|
||||
{
|
||||
value_func_t func;
|
||||
public:
|
||||
value_func(value_func_t _func) : func(_func) {}
|
||||
|
||||
virtual void compute(value_t& result, const details_t& details,
|
||||
value_expr_t * context = NULL) {
|
||||
func(result, details, context);
|
||||
}
|
||||
};
|
||||
|
||||
struct value_expr_t
|
||||
{
|
||||
enum kind_t {
|
||||
|
|
@ -39,10 +63,14 @@ struct value_expr_t
|
|||
CONSTANT_I,
|
||||
CONSTANT_T,
|
||||
CONSTANT_A,
|
||||
CONSTANT_V,
|
||||
|
||||
CONSTANTS,
|
||||
|
||||
// Item details
|
||||
AMOUNT,
|
||||
COST,
|
||||
PRICE,
|
||||
DATE,
|
||||
CLEARED,
|
||||
PENDING,
|
||||
|
|
@ -55,6 +83,7 @@ struct value_expr_t
|
|||
COUNT,
|
||||
TOTAL,
|
||||
COST_TOTAL,
|
||||
PRICE_TOTAL,
|
||||
|
||||
// Relating to format_t
|
||||
VALUE_EXPR,
|
||||
|
|
@ -62,13 +91,12 @@ struct value_expr_t
|
|||
|
||||
// Functions
|
||||
F_NOW,
|
||||
F_PARENT,
|
||||
F_ARITH_MEAN,
|
||||
F_QUANTITY,
|
||||
F_COMMODITY,
|
||||
F_SET_COMMODITY,
|
||||
F_VALUE,
|
||||
F_FUNC,
|
||||
F_NEG,
|
||||
F_ABS,
|
||||
F_STRIP,
|
||||
F_CODE_MASK,
|
||||
F_PAYEE_MASK,
|
||||
F_NOTE_MASK,
|
||||
|
|
@ -76,11 +104,18 @@ struct value_expr_t
|
|||
F_SHORT_ACCOUNT_MASK,
|
||||
F_COMMODITY_MASK,
|
||||
|
||||
TERMINALS,
|
||||
|
||||
F_PARENT,
|
||||
|
||||
// Binary operators
|
||||
O_NEG,
|
||||
O_ADD,
|
||||
O_SUB,
|
||||
O_MUL,
|
||||
O_DIV,
|
||||
O_PERC,
|
||||
O_NEQ,
|
||||
O_EQ,
|
||||
O_LT,
|
||||
O_LTE,
|
||||
|
|
@ -91,88 +126,244 @@ struct value_expr_t
|
|||
O_OR,
|
||||
O_QUES,
|
||||
O_COL,
|
||||
O_COM,
|
||||
O_DEF,
|
||||
O_REF,
|
||||
O_ARG,
|
||||
|
||||
LAST
|
||||
};
|
||||
|
||||
kind_t kind;
|
||||
mutable short refc;
|
||||
value_expr_t * left;
|
||||
value_expr_t * right;
|
||||
|
||||
union {
|
||||
std::time_t constant_t;
|
||||
long constant_i;
|
||||
std::time_t constant_t;
|
||||
long constant_i;
|
||||
amount_t * constant_a;
|
||||
value_t * constant_v;
|
||||
mask_t * mask;
|
||||
value_expr_t * right;
|
||||
};
|
||||
std::string constant_s;
|
||||
amount_t constant_a;
|
||||
mask_t * mask;
|
||||
|
||||
value_expr_t(const kind_t _kind)
|
||||
: kind(_kind), left(NULL), right(NULL), mask(NULL) {
|
||||
: kind(_kind), refc(0), left(NULL), right(NULL) {
|
||||
DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr_t");
|
||||
}
|
||||
~value_expr_t();
|
||||
|
||||
~value_expr_t() {
|
||||
DEBUG_PRINT("ledger.memory.dtors", "dtor value_expr_t");
|
||||
if (mask) delete mask;
|
||||
if (left) delete left;
|
||||
if (right) delete right;
|
||||
void release() const {
|
||||
DEBUG_PRINT("ledger.valexpr.memory",
|
||||
"Releasing " << this << ", refc now " << refc - 1);
|
||||
assert(refc > 0);
|
||||
if (--refc == 0)
|
||||
delete this;
|
||||
}
|
||||
value_expr_t * acquire() {
|
||||
DEBUG_PRINT("ledger.valexpr.memory",
|
||||
"Acquiring " << this << ", refc now " << refc + 1);
|
||||
assert(refc >= 0);
|
||||
refc++;
|
||||
return this;
|
||||
}
|
||||
const value_expr_t * acquire() const {
|
||||
DEBUG_PRINT("ledger.valexpr.memory",
|
||||
"Acquiring " << this << ", refc now " << refc + 1);
|
||||
refc++;
|
||||
return this;
|
||||
}
|
||||
|
||||
void compute(value_t& result, const details_t& details) const;
|
||||
void set_left(value_expr_t * expr) {
|
||||
assert(kind > TERMINALS);
|
||||
if (left)
|
||||
left->release();
|
||||
left = expr ? expr->acquire() : NULL;
|
||||
}
|
||||
|
||||
void set_right(value_expr_t * expr) {
|
||||
assert(kind > TERMINALS);
|
||||
if (right)
|
||||
right->release();
|
||||
right = expr ? expr->acquire() : NULL;
|
||||
}
|
||||
|
||||
void compute(value_t& result, const details_t& details,
|
||||
value_expr_t * context = NULL) const;
|
||||
};
|
||||
|
||||
extern std::auto_ptr<value_expr_t> amount_expr;
|
||||
extern std::auto_ptr<value_expr_t> total_expr;
|
||||
struct scope_t
|
||||
{
|
||||
scope_t * parent;
|
||||
|
||||
typedef std::map<const std::string, value_expr_t *> symbol_map;
|
||||
typedef std::pair<const std::string, value_expr_t *> symbol_pair;
|
||||
|
||||
symbol_map symbols;
|
||||
|
||||
scope_t(scope_t * _parent = NULL) : parent(_parent) {}
|
||||
~scope_t() {
|
||||
for (symbol_map::iterator i = symbols.begin();
|
||||
i != symbols.end();
|
||||
i++)
|
||||
(*i).second->release();
|
||||
}
|
||||
|
||||
void define(const std::string& name, value_expr_t * def) {
|
||||
DEBUG_PRINT("ledger.valexpr.syms",
|
||||
"Defining '" << name << "' = " << def);
|
||||
std::pair<symbol_map::iterator, bool> result
|
||||
= symbols.insert(symbol_pair(name, def->acquire()));
|
||||
if (! result.second)
|
||||
throw value_expr_error(std::string("Redefinition of '") +
|
||||
name + "' in same scope");
|
||||
}
|
||||
value_expr_t * lookup(const std::string& name) {
|
||||
symbol_map::const_iterator i = symbols.find(name);
|
||||
if (i != symbols.end())
|
||||
return (*i).second;
|
||||
else if (parent)
|
||||
return parent->lookup(name);
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
extern std::auto_ptr<scope_t> global_scope;
|
||||
|
||||
extern std::time_t terminus;
|
||||
extern bool initialized;
|
||||
|
||||
void init_value_expr();
|
||||
|
||||
bool compute_amount(value_expr_t * expr, amount_t& amt, transaction_t& xact);
|
||||
|
||||
struct scope_t;
|
||||
value_expr_t * parse_boolean_expr(std::istream& in, scope_t * scope);
|
||||
|
||||
inline value_expr_t * parse_boolean_expr(const char * p,
|
||||
scope_t * scope = NULL) {
|
||||
std::istringstream stream(p);
|
||||
return parse_boolean_expr(stream, scope);
|
||||
}
|
||||
|
||||
inline value_expr_t * parse_boolean_expr(const std::string& str,
|
||||
scope_t * scope = NULL) {
|
||||
return parse_boolean_expr(str.c_str(), scope);
|
||||
}
|
||||
|
||||
value_expr_t * parse_value_expr(std::istream& in,
|
||||
scope_t * scope = NULL,
|
||||
const bool partial = false);
|
||||
|
||||
inline value_expr_t * parse_value_expr(const char * p,
|
||||
scope_t * scope = NULL,
|
||||
const bool partial = false) {
|
||||
std::istringstream stream(p);
|
||||
return parse_value_expr(stream, scope, partial);
|
||||
}
|
||||
|
||||
inline value_expr_t * parse_value_expr(const std::string& str,
|
||||
scope_t * scope = NULL,
|
||||
const bool partial = false) {
|
||||
return parse_value_expr(str.c_str(), scope);
|
||||
}
|
||||
|
||||
void dump_value_expr(std::ostream& out, const value_expr_t * node,
|
||||
const int depth = 0);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This class is used so that during the "in between" stages of value
|
||||
// expression parsing -- while no one yet holds a reference to the
|
||||
// value_expr_t object -- we can be assured of deletion should an
|
||||
// exception happen to whip by.
|
||||
|
||||
struct value_auto_ptr {
|
||||
value_expr_t * ptr;
|
||||
value_auto_ptr() : ptr(NULL) {}
|
||||
explicit value_auto_ptr(value_expr_t * _ptr) : ptr(_ptr) {}
|
||||
~value_auto_ptr() {
|
||||
if (ptr && ptr->refc == 0)
|
||||
delete ptr;
|
||||
}
|
||||
value_expr_t& operator*() const throw() {
|
||||
return *ptr;
|
||||
}
|
||||
value_expr_t * operator->() const throw() {
|
||||
return ptr;
|
||||
}
|
||||
value_expr_t * get() const throw() { return ptr; }
|
||||
value_expr_t * release() throw() {
|
||||
value_expr_t * tmp = ptr;
|
||||
ptr = 0;
|
||||
return tmp;
|
||||
}
|
||||
void reset(value_expr_t * p = 0) throw() {
|
||||
if (p != ptr) {
|
||||
if (ptr && ptr->refc == 0)
|
||||
delete ptr;
|
||||
ptr = p;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
class value_expr : public value_calc
|
||||
{
|
||||
std::string expr;
|
||||
value_expr_t * parsed;
|
||||
|
||||
public:
|
||||
value_expr(const std::string& _expr) : expr(_expr) {
|
||||
try {
|
||||
parsed = parse_value_expr(expr);
|
||||
parsed->acquire();
|
||||
}
|
||||
catch (const value_expr_error& err) {
|
||||
throw error(std::string("In value expression '") +
|
||||
expr + "': " + err.what());
|
||||
}
|
||||
}
|
||||
value_expr(value_expr_t * _parsed) : parsed(_parsed->acquire()) {}
|
||||
|
||||
virtual ~value_expr() {
|
||||
if (parsed != NULL)
|
||||
parsed->release();
|
||||
}
|
||||
|
||||
virtual void compute(value_t& result, const details_t& details,
|
||||
value_expr_t * context = NULL) {
|
||||
parsed->compute(result, details, context);
|
||||
}
|
||||
};
|
||||
|
||||
extern std::auto_ptr<value_calc> amount_expr;
|
||||
extern std::auto_ptr<value_calc> total_expr;
|
||||
|
||||
inline void compute_amount(value_t& result, const details_t& details) {
|
||||
if (amount_expr.get())
|
||||
if (amount_expr.get() != NULL)
|
||||
amount_expr->compute(result, details);
|
||||
}
|
||||
|
||||
inline void compute_total(value_t& result, const details_t& details) {
|
||||
if (total_expr.get())
|
||||
if (total_expr.get() != NULL)
|
||||
total_expr->compute(result, details);
|
||||
}
|
||||
|
||||
value_expr_t * parse_value_expr(std::istream& in,
|
||||
const bool partial = false);
|
||||
|
||||
inline value_expr_t * parse_value_expr(const char * p,
|
||||
const bool partial = false) {
|
||||
std::istringstream stream(p);
|
||||
return parse_value_expr(stream, partial);
|
||||
}
|
||||
|
||||
inline value_expr_t * parse_value_expr(const std::string& str,
|
||||
const bool partial = false) {
|
||||
return parse_value_expr(str.c_str());
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void dump_value_expr(std::ostream& out, const value_expr_t * node);
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T>
|
||||
class item_predicate
|
||||
{
|
||||
const value_expr_t * predicate;
|
||||
bool allocated;
|
||||
|
||||
public:
|
||||
item_predicate(const std::string& _predicate)
|
||||
: predicate(NULL), allocated(false) {
|
||||
const value_expr_t * predicate;
|
||||
|
||||
item_predicate(const std::string& _predicate) : predicate(NULL) {
|
||||
DEBUG_PRINT("ledger.memory.ctors", "ctor item_predicate<T>");
|
||||
if (! _predicate.empty()) {
|
||||
try {
|
||||
predicate = parse_value_expr(_predicate);
|
||||
allocated = true;
|
||||
predicate = parse_value_expr(_predicate)->acquire();
|
||||
}
|
||||
catch (value_expr_error& err) {
|
||||
throw value_expr_error(std::string("In predicate '") +
|
||||
|
|
@ -181,14 +372,14 @@ class item_predicate
|
|||
}
|
||||
}
|
||||
item_predicate(const value_expr_t * _predicate = NULL)
|
||||
: predicate(_predicate), allocated(false) {
|
||||
: predicate(_predicate->acquire()) {
|
||||
DEBUG_PRINT("ledger.memory.ctors", "ctor item_predicate<T>");
|
||||
}
|
||||
|
||||
~item_predicate() {
|
||||
DEBUG_PRINT("ledger.memory.dtors", "dtor item_predicate<T>");
|
||||
if (predicate && allocated)
|
||||
delete predicate;
|
||||
if (predicate)
|
||||
predicate->release();
|
||||
}
|
||||
|
||||
bool operator()(const T& item) const {
|
||||
|
|
@ -196,9 +387,8 @@ class item_predicate
|
|||
value_t result;
|
||||
predicate->compute(result, details_t(item));
|
||||
return result;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
32
value.cc
32
value.cc
|
|
@ -747,6 +747,38 @@ value_t value_t::cost() const
|
|||
return value_t();
|
||||
}
|
||||
|
||||
value_t value_t::factor_price() const
|
||||
{
|
||||
switch (type) {
|
||||
case BOOLEAN:
|
||||
case INTEGER:
|
||||
return *this;
|
||||
|
||||
case AMOUNT: {
|
||||
commodity_t& comm = ((amount_t *) data)->commodity();
|
||||
if (comm.price != NULL)
|
||||
return value_t(*comm.price * *((amount_t *) data));
|
||||
return *this;
|
||||
}
|
||||
|
||||
case BALANCE:
|
||||
return ((balance_t *) data)->factor_price();
|
||||
|
||||
case BALANCE_PAIR: {
|
||||
balance_pair_t temp(((balance_pair_t *) data)->quantity.factor_price());
|
||||
if (((balance_pair_t *) data)->cost)
|
||||
temp.cost = new balance_t(((balance_pair_t *) data)->cost);
|
||||
return temp;
|
||||
}
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
assert(0);
|
||||
return value_t();
|
||||
}
|
||||
|
||||
value_t& value_t::add(const amount_t& amount, const amount_t * cost)
|
||||
{
|
||||
switch (type) {
|
||||
|
|
|
|||
3
value.h
3
value.h
|
|
@ -266,6 +266,7 @@ class value_t
|
|||
void abs();
|
||||
void cast(type_t cast_type);
|
||||
value_t cost() const;
|
||||
value_t factor_price() const;
|
||||
value_t& add(const amount_t& amount, const amount_t * cost = NULL);
|
||||
|
||||
value_t value(const std::time_t moment) const {
|
||||
|
|
@ -290,7 +291,7 @@ class value_t
|
|||
case AMOUNT: {
|
||||
amount_t& amount = *((amount_t *) data);
|
||||
if (amount.commodity())
|
||||
amount = amount.round(amount.commodity().precision);
|
||||
amount = amount.round(amount.commodity().precision());
|
||||
break;
|
||||
}
|
||||
case BALANCE:
|
||||
|
|
|
|||
30
walk.cc
30
walk.cc
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace ledger {
|
||||
|
||||
bool show_lots = false;
|
||||
|
||||
template <>
|
||||
bool compare_items<transaction_t>::operator()(const transaction_t * left,
|
||||
const transaction_t * right)
|
||||
|
|
@ -39,12 +41,20 @@ transaction_xdata_t& transaction_xdata(const transaction_t& xact)
|
|||
void add_transaction_to(const transaction_t& xact, value_t& value)
|
||||
{
|
||||
if (transaction_has_xdata(xact) &&
|
||||
transaction_xdata_(xact).dflags & TRANSACTION_COMPOSITE)
|
||||
transaction_xdata_(xact).dflags & TRANSACTION_COMPOSITE) {
|
||||
value += transaction_xdata_(xact).composite_amount;
|
||||
else if (xact.cost || value)
|
||||
value.add(xact.amount, xact.cost);
|
||||
else
|
||||
value = xact.amount;
|
||||
}
|
||||
else if (xact.cost || value) {
|
||||
amount_t * cost = xact.cost;
|
||||
if (cost && cost->commodity().price)
|
||||
cost = new amount_t(cost->base_amount());
|
||||
value.add(translate_amount(xact.amount), cost);
|
||||
if (cost != xact.cost)
|
||||
delete cost;
|
||||
}
|
||||
else {
|
||||
value = translate_amount(xact.amount);
|
||||
}
|
||||
}
|
||||
|
||||
void truncate_entries::flush()
|
||||
|
|
@ -790,7 +800,7 @@ void walk_accounts(account_t& account,
|
|||
const std::string& sort_string)
|
||||
{
|
||||
if (! sort_string.empty()) {
|
||||
std::auto_ptr<value_expr_t> sort_order;
|
||||
value_auto_ptr sort_order;
|
||||
try {
|
||||
sort_order.reset(parse_value_expr(sort_string));
|
||||
}
|
||||
|
|
@ -814,15 +824,15 @@ void walk_commodities(commodities_map& commodities,
|
|||
for (commodities_map::iterator i = commodities.begin();
|
||||
i != commodities.end();
|
||||
i++) {
|
||||
if ((*i).second->flags & COMMODITY_STYLE_NOMARKET)
|
||||
if ((*i).second->flags() & COMMODITY_STYLE_NOMARKET)
|
||||
continue;
|
||||
|
||||
entry_temps.push_back(entry_t());
|
||||
acct_temps.push_back(account_t(NULL, (*i).second->symbol));
|
||||
|
||||
if ((*i).second->history)
|
||||
for (history_map::iterator j = (*i).second->history->prices.begin();
|
||||
j != (*i).second->history->prices.end();
|
||||
if ((*i).second->history())
|
||||
for (history_map::iterator j = (*i).second->history()->prices.begin();
|
||||
j != (*i).second->history()->prices.end();
|
||||
j++) {
|
||||
entry_temps.back()._date = (*j).first;
|
||||
|
||||
|
|
|
|||
15
walk.h
15
walk.h
|
|
@ -120,6 +120,11 @@ inline const account_t * xact_account(const transaction_t& xact) {
|
|||
return xact_account(const_cast<transaction_t&>(xact));
|
||||
}
|
||||
|
||||
extern bool show_lots;
|
||||
|
||||
#define translate_amount(amt) \
|
||||
((! show_lots && amt.commodity().price) ? amt.base_amount() : amt)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
inline void walk_transactions(transactions_list::iterator begin,
|
||||
|
|
@ -212,20 +217,19 @@ class sort_transactions : public item_handler<transaction_t>
|
|||
|
||||
transactions_deque transactions;
|
||||
const value_expr_t * sort_order;
|
||||
bool allocated;
|
||||
|
||||
public:
|
||||
sort_transactions(item_handler<transaction_t> * handler,
|
||||
const value_expr_t * _sort_order)
|
||||
: item_handler<transaction_t>(handler),
|
||||
sort_order(_sort_order), allocated(false) {}
|
||||
sort_order(_sort_order) {}
|
||||
|
||||
sort_transactions(item_handler<transaction_t> * handler,
|
||||
const std::string& _sort_order)
|
||||
: item_handler<transaction_t>(handler), allocated(false) {
|
||||
: item_handler<transaction_t>(handler) {
|
||||
try {
|
||||
sort_order = parse_value_expr(_sort_order);
|
||||
allocated = true;
|
||||
sort_order->acquire();
|
||||
}
|
||||
catch (value_expr_error& err) {
|
||||
throw value_expr_error(std::string("In sort string '") + _sort_order +
|
||||
|
|
@ -235,8 +239,7 @@ class sort_transactions : public item_handler<transaction_t>
|
|||
|
||||
virtual ~sort_transactions() {
|
||||
assert(sort_order);
|
||||
if (allocated)
|
||||
delete sort_order;
|
||||
sort_order->release();
|
||||
}
|
||||
|
||||
virtual void post_accumulated_xacts();
|
||||
|
|
|
|||
57
xml.cc
57
xml.cc
|
|
@ -115,18 +115,32 @@ static void endElement(void *userData, const char *name)
|
|||
else if (std::strcmp(name, "tr:generated") == 0) {
|
||||
curr_entry->transactions.back()->flags |= TRANSACTION_AUTO;
|
||||
}
|
||||
else if (std::strcmp(name, "commodity") == 0) {
|
||||
else if (std::strcmp(name, "symbol") == 0) {
|
||||
assert(! curr_comm);
|
||||
curr_comm = commodity_t::find_commodity(data, true);
|
||||
curr_comm->flags |= COMMODITY_STYLE_SUFFIXED;
|
||||
if (! comm_flags.empty())
|
||||
for (std::string::size_type i = 0, l = comm_flags.length(); i < l; i++)
|
||||
curr_comm->flags() |= COMMODITY_STYLE_SUFFIXED;
|
||||
if (! comm_flags.empty()) {
|
||||
for (std::string::size_type i = 0, l = comm_flags.length(); i < l; i++) {
|
||||
switch (comm_flags[i]) {
|
||||
case 'P': curr_comm->flags &= ~COMMODITY_STYLE_SUFFIXED; break;
|
||||
case 'S': curr_comm->flags |= COMMODITY_STYLE_SEPARATED; break;
|
||||
case 'T': curr_comm->flags |= COMMODITY_STYLE_THOUSANDS; break;
|
||||
case 'E': curr_comm->flags |= COMMODITY_STYLE_EUROPEAN; break;
|
||||
case 'P': curr_comm->flags() &= ~COMMODITY_STYLE_SUFFIXED; break;
|
||||
case 'S': curr_comm->flags() |= COMMODITY_STYLE_SEPARATED; break;
|
||||
case 'T': curr_comm->flags() |= COMMODITY_STYLE_THOUSANDS; break;
|
||||
case 'E': curr_comm->flags() |= COMMODITY_STYLE_EUROPEAN; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (std::strcmp(name, "price") == 0) {
|
||||
assert(curr_comm);
|
||||
amount_t * price = new amount_t(data);
|
||||
std::string symbol;
|
||||
std::ostringstream symstr(symbol);
|
||||
symstr << curr_comm->symbol << " {" << *price << "}";
|
||||
commodity_t * priced_comm =
|
||||
commodity_t::find_commodity(symstr.str(), true);
|
||||
priced_comm->price = price;
|
||||
priced_comm->base = curr_comm;
|
||||
curr_comm = priced_comm;
|
||||
}
|
||||
else if (std::strcmp(name, "quantity") == 0) {
|
||||
curr_entry->transactions.back()->amount.parse(data);
|
||||
|
|
@ -134,8 +148,8 @@ static void endElement(void *userData, const char *name)
|
|||
std::string::size_type i = data.find('.');
|
||||
if (i != std::string::npos) {
|
||||
int precision = data.length() - i - 1;
|
||||
if (precision > curr_comm->precision)
|
||||
curr_comm->precision = precision;
|
||||
if (precision > curr_comm->precision())
|
||||
curr_comm->precision() = precision;
|
||||
}
|
||||
curr_entry->transactions.back()->amount.set_commodity(*curr_comm);
|
||||
curr_comm = NULL;
|
||||
|
|
@ -240,11 +254,24 @@ void xml_write_amount(std::ostream& out, const amount_t& amount,
|
|||
commodity_t& c = amount.commodity();
|
||||
for (int i = 0; i < depth + 2; i++) out << ' ';
|
||||
out << "<commodity flags=\"";
|
||||
if (! (c.flags & COMMODITY_STYLE_SUFFIXED)) out << 'P';
|
||||
if (c.flags & COMMODITY_STYLE_SEPARATED) out << 'S';
|
||||
if (c.flags & COMMODITY_STYLE_THOUSANDS) out << 'T';
|
||||
if (c.flags & COMMODITY_STYLE_EUROPEAN) out << 'E';
|
||||
out << "\">" << c.symbol << "</commodity>\n";
|
||||
if (! (c.flags() & COMMODITY_STYLE_SUFFIXED)) out << 'P';
|
||||
if (c.flags() & COMMODITY_STYLE_SEPARATED) out << 'S';
|
||||
if (c.flags() & COMMODITY_STYLE_THOUSANDS) out << 'T';
|
||||
if (c.flags() & COMMODITY_STYLE_EUROPEAN) out << 'E';
|
||||
out << "\">\n";
|
||||
for (int i = 0; i < depth + 4; i++) out << ' ';
|
||||
if (c.price) {
|
||||
out << "<symbol>" << c.base->symbol << "</symbol>\n";
|
||||
for (int i = 0; i < depth + 4; i++) out << ' ';
|
||||
out << "<price>\n";
|
||||
xml_write_amount(out, *c.price, depth + 6);
|
||||
for (int i = 0; i < depth + 4; i++) out << ' ';
|
||||
out << "</price>\n";
|
||||
} else {
|
||||
out << "<symbol>" << c.symbol << "</symbol>\n";
|
||||
}
|
||||
for (int i = 0; i < depth + 2; i++) out << ' ';
|
||||
out << "</commodity>\n";
|
||||
|
||||
for (int i = 0; i < depth + 2; i++) out << ' ';
|
||||
out << "<quantity>";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue