summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore62
-rw-r--r--.gitmodules12
-rw-r--r--Makefile.am415
-rwxr-xr-xacprep339
-rw-r--r--amount.cc2017
-rw-r--r--amount.h631
-rw-r--r--amounts.cc14
-rw-r--r--balance.cc529
-rw-r--r--balance.h961
-rw-r--r--binary.cc1338
-rw-r--r--binary.h26
-rw-r--r--config.cc132
-rw-r--r--config.h79
-rw-r--r--configure.ac418
-rw-r--r--configure.in280
-rw-r--r--contrib/CSVReader.cs165
-rw-r--r--contrib/Makefile4
-rw-r--r--contrib/ParseCcStmt.cs184
-rw-r--r--contrib/ledger.vim (renamed from ledger.vim)0
-rw-r--r--contrib/scripts/README (renamed from scripts/README)0
-rwxr-xr-xcontrib/scripts/bal (renamed from scripts/bal)0
-rwxr-xr-xcontrib/scripts/bal-huquq (renamed from scripts/bal-huquq)0
-rwxr-xr-xcontrib/scripts/entry (renamed from scripts/entry)0
-rwxr-xr-xcontrib/scripts/getquote (renamed from scripts/getquote)0
-rwxr-xr-xcontrib/scripts/ledger-du (renamed from scripts/ledger-du)0
-rwxr-xr-xcontrib/scripts/report (renamed from scripts/report)0
-rwxr-xr-xcontrib/scripts/tc (renamed from scripts/tc)0
-rwxr-xr-xcontrib/scripts/ti (renamed from scripts/ti)0
-rwxr-xr-xcontrib/scripts/to (renamed from scripts/to)0
-rwxr-xr-xcontrib/scripts/trend (renamed from scripts/trend)0
-rw-r--r--csv.cc105
-rw-r--r--csv.h24
-rw-r--r--datetime.cc372
-rw-r--r--datetime.h310
-rw-r--r--debug.cc125
-rw-r--r--debug.h146
-rw-r--r--derive.cc202
-rw-r--r--derive.h14
-rw-r--r--doc/Doxyfile1406
-rw-r--r--doc/INSTALL234
-rw-r--r--doc/LICENSE (renamed from LICENSE)0
-rw-r--r--doc/NEWS (renamed from NEWS)0
-rw-r--r--doc/README (renamed from README)0
-rw-r--r--doc/ledger.texi (renamed from ledger.texi)0
-rw-r--r--doc/sample.dat (renamed from sample.dat)0
-rw-r--r--emacs.cc80
-rw-r--r--emacs.h30
-rw-r--r--error.h125
-rw-r--r--format.cc985
-rw-r--r--format.h218
-rw-r--r--gnucash.h22
-rw-r--r--journal.cc674
-rw-r--r--journal.h467
-rw-r--r--ledger.h51
-rw-r--r--lib/Makefile42
m---------lib/boost0
m---------lib/cppunit0
m---------lib/gdtoa0
m---------lib/libofx0
-rw-r--r--lisp/ledger.el (renamed from ledger.el)2
-rw-r--r--lisp/timeclock.el (renamed from timeclock.el)0
-rw-r--r--main.cc494
-rw-r--r--mask.cc53
-rw-r--r--mask.h29
-rw-r--r--ofx.cc226
-rw-r--r--ofx.h22
-rw-r--r--option.cc1065
-rw-r--r--option.h53
-rw-r--r--parser.cc197
-rw-r--r--parser.h62
-rw-r--r--python/amounts.cc45
-rw-r--r--python/py_amount.cc362
-rw-r--r--python/py_commodity.cc63
-rw-r--r--python/py_times.cc132
-rw-r--r--python/py_utils.cc174
-rw-r--r--python/pyfstream.h203
-rw-r--r--python/pyinterp.cc235
-rw-r--r--python/pyinterp.h118
-rw-r--r--python/pyledger.cc46
-rw-r--r--python/pyledger.h38
-rw-r--r--python/pyutils.h113
-rwxr-xr-xpython/setup.py18
-rw-r--r--python/tuples.h281
-rw-r--r--qif.h22
-rw-r--r--quotes.cc82
-rw-r--r--quotes.h30
-rw-r--r--reconcile.cc88
-rw-r--r--reconcile.h33
-rw-r--r--report.cc413
-rw-r--r--report.h79
-rwxr-xr-xsetup.py18
-rw-r--r--src/account.cc198
-rw-r--r--src/account.h155
-rw-r--r--src/amount.cc1433
-rw-r--r--src/amount.h754
-rw-r--r--src/balance.cc255
-rw-r--r--src/balance.h532
-rw-r--r--src/balpair.h363
-rw-r--r--src/binary.cc188
-rw-r--r--src/binary.h270
-rw-r--r--src/cache.cc871
-rw-r--r--src/cache.h142
-rw-r--r--src/commodity.cc667
-rw-r--r--src/commodity.h426
-rw-r--r--src/compare.cc85
-rw-r--r--src/compare.h77
-rw-r--r--src/csv.cc148
-rw-r--r--src/csv.h63
-rw-r--r--src/derive.cc228
-rw-r--r--src/derive.h45
-rw-r--r--src/emacs.cc110
-rw-r--r--src/emacs.h68
-rw-r--r--src/entry.cc497
-rw-r--r--src/entry.h239
-rw-r--r--src/error.h85
-rw-r--r--src/expr.cc203
-rw-r--r--src/expr.h125
-rw-r--r--src/fdstream.h (renamed from fdstream.hpp)0
-rw-r--r--src/filters.cc755
-rw-r--r--src/filters.h702
-rw-r--r--src/flags.h117
-rw-r--r--src/format.cc391
-rw-r--r--src/format.h144
-rw-r--r--src/gnucash.cc (renamed from gnucash.cc)168
-rw-r--r--src/gnucash.h53
-rw-r--r--src/handler.h72
-rw-r--r--src/help.cc205
-rw-r--r--src/help.h49
-rw-r--r--src/hooks.h71
-rw-r--r--src/iterators.cc200
-rw-r--r--src/iterators.h228
-rw-r--r--src/journal.cc150
-rw-r--r--src/journal.h128
-rw-r--r--src/ledger.h81
-rw-r--r--src/main.cc593
-rw-r--r--src/mask.cc59
-rw-r--r--src/mask.h67
-rw-r--r--src/ofx.cc263
-rw-r--r--src/ofx.h53
-rw-r--r--src/op.cc1130
-rw-r--r--src/op.h330
-rw-r--r--src/option.cc212
-rw-r--r--src/option.h52
-rw-r--r--src/output.cc310
-rw-r--r--src/output.h140
-rw-r--r--src/parser.cc409
-rw-r--r--src/parser.h103
-rw-r--r--src/predicate.h69
-rw-r--r--src/pushvar.h80
-rw-r--r--src/qif.cc (renamed from qif.cc)106
-rw-r--r--src/qif.h53
-rw-r--r--src/quotes.cc109
-rw-r--r--src/quotes.h70
-rw-r--r--src/reconcile.cc116
-rw-r--r--src/reconcile.h72
-rw-r--r--src/report.cc432
-rw-r--r--src/report.h757
-rw-r--r--src/scope.cc123
-rw-r--r--src/scope.h280
-rw-r--r--src/session.cc391
-rw-r--r--src/session.h242
-rw-r--r--src/system.hh167
-rw-r--r--src/textual.cc1124
-rw-r--r--src/textual.h81
-rw-r--r--src/times.cc350
-rw-r--r--src/times.h154
-rw-r--r--src/token.cc378
-rw-r--r--src/token.h116
-rw-r--r--src/utils.cc716
-rw-r--r--src/utils.h578
-rw-r--r--src/value.cc1777
-rw-r--r--src/value.h928
-rw-r--r--src/xact.cc278
-rw-r--r--src/xact.h228
-rw-r--r--src/xml.cc (renamed from xml.cc)293
-rw-r--r--src/xml.h85
-rw-r--r--startup.cc56
-rwxr-xr-xtest.py22
-rwxr-xr-xtest/PyUnitTests.py5
-rw-r--r--test/UnitTests.cc112
-rw-r--r--test/UnitTests.h24
-rw-r--r--test/UnitTests.py9
-rw-r--r--test/__init__.py0
-rw-r--r--test/python/__init__.py0
-rw-r--r--test/python/numerics/__init__.py0
-rw-r--r--test/python/numerics/t_amount.py1469
-rwxr-xr-xtest/regress.py133
-rw-r--r--test/regress/205.test13
-rw-r--r--test/unit/t_amount.cc1576
-rw-r--r--test/unit/t_amount.h110
-rw-r--r--test/unit/t_balance.cc25
-rw-r--r--test/unit/t_balance.h30
-rw-r--r--test/unit/t_commodity.cc64
-rw-r--r--test/unit/t_commodity.h36
-rw-r--r--test/unit/t_expr.cc25
-rw-r--r--test/unit/t_expr.h30
-rw-r--r--test/unit/t_times.cc81
-rw-r--r--test/unit/t_times.h28
-rw-r--r--test/unit/t_utils.cc10
-rw-r--r--test/unit/t_utils.h28
-rw-r--r--test/unit/t_valexpr.cc25
-rw-r--r--test/unit/t_valexpr.h30
-rw-r--r--tests/amounts.h169
-rw-r--r--tests/baseline/10011
-rw-r--r--tests/baseline/10024
-rw-r--r--tests/baseline/10034
-rw-r--r--tests/baseline/10044
-rw-r--r--tests/baseline/10054
-rw-r--r--tests/baseline/10064
-rw-r--r--tests/baseline/10074
-rw-r--r--tests/baseline/10084
-rw-r--r--tests/baseline/10094
-rw-r--r--tests/baseline/10104
-rw-r--r--tests/baseline/10114
-rw-r--r--tests/baseline/10124
-rw-r--r--tests/baseline/10134
-rw-r--r--tests/baseline/10144
-rw-r--r--tests/baseline/10154
-rw-r--r--tests/baseline/10164
-rw-r--r--tests/baseline/10174
-rw-r--r--tests/baseline/10184
-rw-r--r--tests/baseline/10194
-rw-r--r--tests/baseline/10204
-rw-r--r--tests/baseline/10214
-rw-r--r--tests/baseline/10224
-rw-r--r--tests/baseline/10234
-rw-r--r--tests/baseline/10244
-rw-r--r--tests/baseline/10254
-rw-r--r--tests/baseline/10264
-rw-r--r--tests/baseline/10274
-rw-r--r--tests/baseline/10284
-rw-r--r--tests/baseline/10294
-rw-r--r--tests/cases/1001.dat10
-rw-r--r--tests/cases/1002.dat25
-rw-r--r--tests/cases/1030.dat24
-rw-r--r--tests/cases/1032.dat834
-rwxr-xr-xtests/confirm.py58
-rw-r--r--tests/parser.h65
-rwxr-xr-xtests/regress95
-rwxr-xr-xtests/regtest27
-rwxr-xr-xtests/runtests.py184
-rw-r--r--tests/textual.h34
-rw-r--r--textual.cc1117
-rw-r--r--textual.h46
-rw-r--r--timing.h62
-rw-r--r--util.h62
-rw-r--r--valexpr.cc1995
-rw-r--r--valexpr.h518
-rw-r--r--value.cc1696
-rw-r--r--value.h444
-rw-r--r--walk.cc911
-rw-r--r--walk.h751
-rw-r--r--xml.h46
253 files changed, 34151 insertions, 22725 deletions
diff --git a/.gitignore b/.gitignore
index a76cf356..6f5877d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,66 +1,76 @@
*.[oa]
+*.aux
+*.cp
*.dSYM
*.elc
+*.exe
+*.fn
+*.ky
*.la
*.lo
+*.loT
+*.log
*.o
+*.pg
+*.pkg
+*.pyc
+*.tar.bz2
+*.tar.gz
+*.toc
+*.tp
+*.vr
*~
.deps/
.libs/
.svn
/.deps
/.libs
+/3.0
+/AUTHORS
/AUTHORS
/COPYING
/ChangeLog
+/Doxyfile.gen
+/INSTALL
/Makefile
/Makefile.in
+/PyUnitTests
+/RegressionTests
/TAGS
+/UnitTests
/acconf.h
/acconf.h.in
/acconf.h.in~
/aclocal.m4
-/autom4te.cache
+/autom4te.cache/
/config.guess
/config.log
/config.status
/config.sub
/configure
+/configure.in
/depcomp
+/doc/*.info
+/doc/*.pdf
+/doc/.dirstamp
+/doc/Doxyfile.bak
+/doc/Doxyfile.gen
+/doc/html
+/doc/latex
/elc-stamp
/elisp-comp
/install-sh
/ledger
+/ledger-*/
/ledger.info
+/ledger.so
/libtool
/ltmain.sh
/make.deps
+/make.sh
/missing
/pending
/stamp-h1
+/system.hh.gch
/texinfo.tex
-AUTHORS
-INSTALL
-Makefile
-Makefile.in
-acconf.h
-acconf.h.in
-aclocal.m4
-autom4te.cache/
-config.guess
-config.log
-config.status
-config.sub
-configure
-depcomp
-doc/*.info
-elc-stamp
-elisp-comp
-install-sh
-ledger
-ledger.info
-libtool
-ltmain.sh
-missing
-stamp-h1
-texinfo.tex
+/version.m4
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..326da1f8
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,12 @@
+[submodule "lib/boost"]
+ path = lib/boost
+ url = git://repo.or.cz/boost.git
+[submodule "lib/gdtoa"]
+ path = lib/gdtoa
+ url = git://newartisans.com/gdtoa.git
+[submodule "lib/cppunit"]
+ path = lib/cppunit
+ url = git://newartisans.com/cppunit.git
+[submodule "lib/libofx"]
+ path = lib/libofx
+ url = git://newartisans.com/libofx.git
diff --git a/Makefile.am b/Makefile.am
index e97b56a0..e8f18a25 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,154 +1,347 @@
-EXTRA_DIST = LICENSE scripts ledger.vim sample.dat
+EXTRA_DIST = contrib
lib_LTLIBRARIES = libamounts.la libledger.la
-libamounts_la_CXXFLAGS =
-libamounts_la_SOURCES = \
- amount.cc \
- balance.cc \
- datetime.cc \
- value.cc
-if HAVE_BOOST_PYTHON
-libamounts_la_CXXFLAGS += -DUSE_BOOST_PYTHON=1
-endif
-if DEBUG
-libamounts_la_CXXFLAGS += -DDEBUG_LEVEL=4
-libamounts_la_SOURCES += debug.cc
-endif
+libamounts_la_CPPFLAGS = -I$(srcdir)/src
+libamounts_la_SOURCES = \
+ src/utils.cc \
+ src/times.cc \
+ src/mask.cc \
+ src/binary.cc \
+ \
+ src/amount.cc \
+ src/commodity.cc \
+ src/balance.cc \
+ src/value.cc \
+ \
+ src/token.cc \
+ src/parser.cc \
+ src/op.cc \
+ src/expr.cc \
+ src/scope.cc \
+ src/format.cc \
+ src/option.cc
-libledger_la_CXXFLAGS =
-libledger_la_SOURCES = \
- binary.cc \
- config.cc \
- csv.cc \
- derive.cc \
- emacs.cc \
- format.cc \
- journal.cc \
- mask.cc \
- option.cc \
- parser.cc \
- qif.cc \
- quotes.cc \
- reconcile.cc \
- report.cc \
- startup.cc \
- textual.cc \
- valexpr.cc \
- walk.cc \
- xml.cc
if HAVE_EXPAT
-libledger_la_CXXFLAGS += -DHAVE_EXPAT=1
-libledger_la_SOURCES += gnucash.cc
-endif
+libamounts_la_CPPFLAGS += -DHAVE_EXPAT=1
+else
if HAVE_XMLPARSE
-libledger_la_CXXFLAGS += -DHAVE_XMLPARSE=1
-libledger_la_SOURCES += gnucash.cc
+libamounts_la_CPPFLAGS += -DHAVE_XMLPARSE=1
+endif
endif
if HAVE_LIBOFX
-libledger_la_CXXFLAGS += -DHAVE_LIBOFX=1
-libledger_la_SOURCES += ofx.cc
+libamounts_la_CPPFLAGS += -DHAVE_LIBOFX=1
endif
if DEBUG
-libledger_la_CXXFLAGS += -DDEBUG_LEVEL=4
+libamounts_la_CPPFLAGS += -DDEBUG_MODE
endif
-libledger_la_LDFLAGS = -release 2.6.1
-
-pkginclude_HEADERS = \
- acconf.h \
- \
- amount.h \
- balance.h \
- datetime.h \
- value.h \
- debug.h \
- util.h \
- \
- binary.h \
- config.h \
- csv.h \
- derive.h \
- emacs.h \
- error.h \
- format.h \
- gnucash.h \
- journal.h \
- ledger.h \
- mask.h \
- option.h \
- parser.h \
- qif.h \
- ofx.h \
- quotes.h \
- reconcile.h \
- report.h \
- textual.h \
- timing.h \
- valexpr.h \
- walk.h \
- xml.h
-######################################################################
+libledger_la_CPPFLAGS = $(libamounts_la_CPPFLAGS)
+libledger_la_LDFLAGS = -release 3.0.0
+libledger_la_SOURCES = \
+ src/journal.cc \
+ src/entry.cc \
+ src/xact.cc \
+ src/account.cc \
+ src/iterators.cc \
+ src/compare.cc \
+ \
+ src/textual.cc \
+ src/cache.cc \
+ src/emacs.cc \
+ src/qif.cc \
+ src/xml.cc \
+ src/csv.cc \
+ \
+ src/session.cc \
+ src/report.cc \
+ src/filters.cc \
+ src/output.cc \
+ src/help.cc \
+ \
+ src/derive.cc \
+ src/reconcile.cc \
+ src/quotes.cc
-bin_PROGRAMS = ledger
-ledger_CXXFLAGS =
-ledger_SOURCES = main.cc
-ledger_LDADD = $(LIBOBJS) libamounts.la libledger.la
if HAVE_EXPAT
-ledger_CXXFLAGS += -DHAVE_EXPAT=1
-endif
+libledger_la_SOURCES += src/gnucash.cc
+else
if HAVE_XMLPARSE
-ledger_CXXFLAGS += -DHAVE_XMLPARSE=1
+libledger_la_SOURCES += src/gnucash.cc
endif
-if HAVE_LIBOFX
-ledger_CXXFLAGS += -DHAVE_LIBOFX=1
endif
-if DEBUG
-ledger_CXXFLAGS += -DDEBUG_LEVEL=4
+if HAVE_LIBOFX
+libledger_la_SOURCES += src/ofx.cc
endif
-ledger_LDFLAGS = -static # for the sake of command-line speed
-info_TEXINFOS = ledger.texi
+pkginclude_HEADERS = \
+ acconf.h \
+ src/system.hh \
+ src/fdstream.h \
+ src/utils.h \
+ src/flags.h \
+ src/hooks.h \
+ src/pushvar.h \
+ src/error.h \
+ src/times.h \
+ src/mask.h \
+ src/binary.h \
+ \
+ src/amount.h \
+ src/commodity.h \
+ src/balance.h \
+ src/balpair.h \
+ src/value.h \
+ \
+ src/token.h \
+ src/parser.h \
+ src/op.h \
+ src/expr.h \
+ src/scope.h \
+ src/predicate.h \
+ src/format.h \
+ src/option.h \
+ \
+ src/journal.h \
+ src/entry.h \
+ src/xact.h \
+ src/account.h \
+ src/iterators.h \
+ src/compare.h \
+ \
+ src/textual.h \
+ src/cache.h \
+ src/emacs.h \
+ src/qif.h \
+ src/xml.h \
+ src/csv.h \
+ src/gnucash.h \
+ src/ofx.h \
+ \
+ src/session.h \
+ src/report.h \
+ src/handler.h \
+ src/filters.h \
+ src/output.h \
+ src/help.h \
+ \
+ src/derive.h \
+ src/reconcile.h \
+ src/quotes.h \
+ \
+ src/ledger.h \
+ \
+ python/pyledger.h \
+ python/pyinterp.h
-######################################################################
+CLEANFILES =
-lisp_LISP = ledger.el timeclock.el
-dist_lisp_LISP = ledger.el timeclock.el
+if USE_PCH
+nodist_libledger_la_SOURCES = src/system.hh.gch
+
+BUILT_SOURCES = src/system.hh.gch
+CLEANFILES += src/system.hh.gch
+
+$(srcdir)/src/system.hh.gch: $(srcdir)/src/system.hh $(top_builddir)/acconf.h
+ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(libledger_la_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) \
+ -g -o $@ $(srcdir)/src/system.hh
+endif
######################################################################
-EXTRA_DIST += setup.py
+bin_PROGRAMS = ledger
+
+ledger_CPPFLAGS = $(libledger_la_CPPFLAGS)
+ledger_SOURCES = src/main.cc
+ledger_LDADD = $(LIBOBJS) libamounts.la libledger.la
+ledger_LDFLAGS = -static
+
+info_TEXINFOS = doc/ledger.texi
+
+dist_lisp_LISP = lisp/ledger.el lisp/timeclock.el
+ELCFILES =
+DISTCLEANFILES = ledger.elc timeclock.elc
+
+######################################################################
if HAVE_BOOST_PYTHON
-noinst_PROGRAMS = amounts.so
-amounts_so_SOURCES = amounts.cc fdstream.hpp
+lib_LTLIBRARIES += libpyledger.la
+
+libpyledger_la_SOURCES = \
+ python/py_amount.cc \
+ python/py_commodity.cc \
+ python/py_times.cc \
+ python/py_utils.cc \
+ python/pyfstream.h \
+ python/pyinterp.cc \
+ python/pyutils.h \
+ python/tuples.h \
+ python/setup.py
+
+libpyledger_la_CPPFLAGS = $(libledger_la_CPPFLAGS) -I$(srcdir)/python
-amounts.so: amounts.cc libamounts.la
- CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L. -L.libs" \
- python setup.py build --build-lib=.
+pyexec_PROGRAMS = ledger.so
-install-exec-hook:
- CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L. -L.libs" \
- python setup.py install --prefix=$(prefix)
+clean-local:
+ rm -fr build
+
+ledger_so_SOURCES = $(libamounts_la_SOURCES) $(libledger_la_SOURCES) \
+ $(libpyledger_la_SOURCES) python/pyledger.cc
+ledger_so_DEPENDENCIES = $(lib_LTLIBRARIES)
+
+PYLIBS = amounts ledger pyledger gmp \
+ boost_system$(BOOST_SUFFIX) \
+ boost_date_time$(BOOST_SUFFIX) \
+ boost_filesystem$(BOOST_SUFFIX) \
+ boost_regex$(BOOST_SUFFIX) \
+ boost_python$(BOOST_SUFFIX)
+
+if HAVE_LIBOFX
+PYLIBS += ofx
+endif
+
+ledger.so: $(ledger_so_SOURCES) $(ledger_so_DEPENDENCIES)
+ BUILD_DIR=`cd $(top_builddir); pwd`; \
+ (cd $(srcdir); \
+ CFLAGS="$(CPPFLAGS) -I$(srcdir) $(libpyledger_la_CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)" \
+ LDFLAGS="$(LDFLAGS) -L$$BUILD_DIR -L$$BUILD_DIR/.libs" \
+ ARCHFLAGS="$(ARCHFLAGS)" PYLIBS="$(PYLIBS)" \
+ python python/setup.py build --build-lib=$$BUILD_DIR/ \
+ --build-temp=$$BUILD_DIR/build -f)
endif
######################################################################
TESTS = RegressionTests
+XFAIL_TESTS = RegressionTests # jww (2008-08-07): for now
+
+if HAVE_CPPUNIT
+TESTS += UnitTests
+endif
+
+if HAVE_BOOST_PYTHON
+TESTS += PyUnitTests
+endif
+
+check_PROGRAMS = $(TESTS)
+
+UnitTests_SOURCES = \
+ test/UnitTests.cc \
+ test/UnitTests.h \
+ test/unit/t_utils.cc \
+ test/unit/t_utils.h \
+ test/unit/t_times.cc \
+ test/unit/t_times.h \
+ test/unit/t_commodity.cc \
+ test/unit/t_commodity.h \
+ test/unit/t_amount.cc \
+ test/unit/t_amount.h \
+ test/unit/t_balance.cc \
+ test/unit/t_balance.h \
+ test/unit/t_expr.cc \
+ test/unit/t_expr.h
+
+UnitTests_CPPFLAGS = -I$(srcdir)/test $(libledger_la_CPPFLAGS)
+UnitTests_LDFLAGS = $(LIBADD_DL)
+UnitTests_LDADD = $(lib_LTLIBRARIES) -lcppunit
+
+EXTRA_DIST += test/python
-DISTCLEANFILES = RegressionTests
+PyUnitTests_SOURCES = test/__init__.py test/PyUnitTests.py test/UnitTests.py
-RegressionTests:
- echo "exit 0" > $@
+ESC_srcdir=`echo "$(srcdir)" | sed 's/\//\\\\\//g'`
+ESC_builddir=`echo "$(top_builddir)" | sed 's/\//\\\\\//g'`
+ESC_distdir=`echo "$(distdir)" | sed 's/\//\\\\\//g'`
+
+# jww (2007-05-10): This rule will not be triggered on systems that
+# define an EXEEXT.
+PyUnitTests: $(srcdir)/test/PyUnitTests.py
+ cat $(srcdir)/test/PyUnitTests.py \
+ | sed "s/%srcdir%/$(ESC_srcdir)/g" \
+ | sed "s/%builddir%/$(ESC_builddir)/g" > $@
chmod 755 $@
+RegressionTests_SOURCES = test/regress.py
+
+EXTRA_DIST += test/regress
+
+RegressionTests: $(srcdir)/test/regress.py
+ echo "python $(srcdir)/test/regress.py $(top_builddir)/ledger$(EXEEXT) $(srcdir)/test/regress" > $@
+ chmod 755 $@
+
+if HAVE_VALGRIND
+VALGRIND = valgrind
+else
+VALGRIND =
+endif
+
+fullcheck: check
+ MallocGuardEdges=1 \
+ MallocScribble=1 \
+ MallocPreScribble=1 \
+ MallocCheckHeapStart=100 \
+ MallocCheckHeapEach=100 \
+ DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib \
+ $(VALGRIND) $(top_builddir)/UnitTests$(EXEEXT) --verify
+
+######################################################################
+
+EXTRA_DIST += doc/LICENSE doc/NEWS doc/README
+EXTRA_DIST += doc/Doxyfile doc/ledger.pdf doc/refman.pdf
+
+DISTCLEANFILES += doc/ledger.info doc/ledger.pdf \
+ Doxyfile.gen doc/Doxyfile.bak doc/refman.pdf
+
+dist-hook:
+ find $(distdir) -name .DS_Store -delete
+ find $(distdir) -name .localized -delete
+ find $(distdir)/doc -name .dirstamp -delete
+ rm -fr $(distdir)/doc/latex \
+ $(distdir)/doc/Doxyfile.bak \
+ $(distdir)/doc/Doxyfile.gen
+ cp -pR $(srcdir)/doc/html $(distdir)/doc
+
+distclean-local:
+ rm -fr doc/html doc/latex doc/refman.pdf
+
+$(top_builddir)/Doxyfile.gen: $(srcdir)/doc/Doxyfile
+ cat $(srcdir)/doc/Doxyfile \
+ | sed "s/%srcdir%/$(ESC_srcdir)/g" \
+ | sed "s/%builddir%/$(ESC_builddir)/g" > $@
+
+$(top_builddir)/doc/html/index.html: $(top_builddir)/Doxyfile.gen
+ doxygen $(top_builddir)/Doxyfile.gen
+
+# The intention with the following rules is that all of the Doxygen
+# documentation (both HTML and PDF) is built locally before distcheck is
+# run, since it's quite possible that the user will not have a complete
+# TeX + Doxygen + dot environment on their own system.
+
+$(top_builddir)/doc/refman.pdf: $(top_builddir)/doc/html/index.html
+ (cd $(top_builddir)/doc/latex && make)
+ cp $(top_builddir)/doc/latex/refman.pdf $@
+
+docs: pdf $(top_builddir)/doc/refman.pdf
+
######################################################################
-release:
- make -j3 distcheck \
- CPPFLAGS="-I/usr/local/include -I/opt/local/include " \
- LDFLAGS="-L/usr/local/lib -L/opt/local/lib"
+copy-sources:
+ -mkdir /tmp/ledger
+ sudo rsync -av --delete \
+ --exclude=lib/ \
+ --exclude=.libs/ \
+ --exclude=.deps/ \
+ ./ /tmp/ledger/
+ (cd /tmp/ledger; sudo git clean -x -d -f)
+
+release: copy-sources
+ (cd /tmp/ledger; ./acprep --release --build -j3)
+
+release-distcheck: copy-sources
+ (cd /tmp/ledger; ./acprep --release --build -j3 distcheck)
# Makefile.am ends here
diff --git a/acprep b/acprep
index 0d8ed00e..f8054848 100755
--- a/acprep
+++ b/acprep
@@ -1,75 +1,312 @@
#!/bin/sh
-export AUTOCONF_VERSION=2.61
-export AUTOMAKE_VERSION=1.9
+# acprep, version 3.0
+#
+# This script configures my ledger source tree on my Mac OS/X machine. This
+# is not necessary, however, since I keep all the files necessary for building
+# checked in to the source tree. Users can just type './configure && make'.
+# This script simply sets up the compiler and linker flags for all the various
+# build permutations I use for testing and profiling.
-touch AUTHORS COPYING ChangeLog
+export AUTOCONF_VERSION=2.61
+export AUTOMAKE_VERSION=1.10
cmd=$(which glibtoolize 2>&1)
if [ -x "$cmd" ]; then
export LIBTOOLIZE="$cmd"
fi
+
+COMMIT=$(git describe --all | sed 's/heads\///')
+echo "m4_define([VERSION_NUMBER], [$COMMIT])" > version.m4
+
autoreconf --force --install
-HERE="$PWD"
-if [ ! "$1" = "--local" ]; then
- if [ -d "$HOME/Products" ]; then
- projdir="$HOME/Products/$(basename $HERE)"
- if [ ! -d "$projdir" ]; then
- mkdir -p "$projdir"
- fi
- cd "$projdir" || (echo "Cannot change to $projdir"; exit 1)
- fi
-else
- shift 1
+SWITCHES="--enable-python --disable-shared"
+
+if [ -z "$PYTHON_HOME" ]; then
+ PYTHON_HOME="/usr"
+fi
+if [ -z "$PYTHON_VERSION" ]; then
+ PYTHON_VERSION="2.5"
fi
-INCDIRS="-I/opt/local/include -I/usr/local/include -I/usr/include/httpd/xml"
-INCDIRS="$INCDIRS -I/usr/include/python2.5"
-LIBDIRS="-L/opt/local/lib -L/usr/local/lib"
+BOOST_SUFFIX=""
+
+INCDIRS="-isystem /usr/local/include"
+INCDIRS="$INCDIRS -isystem /opt/local/include"
+INCDIRS="$INCDIRS -isystem /opt/local/include/libofx"
+INCDIRS="$INCDIRS -isystem /usr/local/include/boost-1_35"
+INCDIRS="$INCDIRS -isystem $PYTHON_HOME/include/python$PYTHON_VERSION"
-SYSTEM=`uname -s`
+CXXFLAGS=""
+ARCHFLAGS=""
+LDARCHFLAGS=""
+LDFLAGS=""
+LIBDIRS="-L/usr/local/lib -L/opt/local/lib"
+LIBDIRS="$LIBDIRS -L$PYTHON_HOME/lib/python$PYTHON_VERSION/config"
+
+SYSTEM=$(uname -s)
if [ $SYSTEM = Linux ]; then
CXXFLAGS="-pthread"
elif [ $SYSTEM = Solaris ]; then
CXXFLAGS="-pthreads"
elif [ $SYSTEM = Darwin ]; then
- #CXXFLAGS="-arch i386 -arch ppc -isysroot /Developer/SDKs/MacOSX10.4u.sdk"
- CXXFLAGS="$CXXFLAGS -Wno-long-double"
- #LIBDIRS="$LIBDIRS -arch i386 -arch ppc -isysroot /Developer/SDKs/MacOSX10.4u.sdk"
-else
- CXXFLAGS=""
+ ARCHFLAGS="-arch i386 -arch ppc -isysroot /Developer/SDKs/MacOSX10.5.sdk"
+ LDARCHFLAGS="$ARCHFLAGS -Wl,-syslibroot,/Developer/SDKs/MacOSX10.5.sdk"
+fi
+
+# Building the command-line tool as a shared library is a luxury,
+# since there are no clients except a GUI tool which might use it (and
+# that is built again anyway by Xcode).
+CPPFLAGS="$INCDIRS"
+CXXFLAGS="$CXXFLAGS -pipe"
+LDFLAGS="$LDFLAGS $LIBDIRS"
+BUILD_DIR=false
+
+# Warning flags
+CXXFLAGS="$CXXFLAGS -Wall -ansi -Winvalid-pch"
+CXXFLAGS="$CXXFLAGS -Wextra"
+#CXXFLAGS="$CXXFLAGS -Weffc++"
+CXXFLAGS="$CXXFLAGS -Wcast-align"
+CXXFLAGS="$CXXFLAGS -Wcast-qual"
+CXXFLAGS="$CXXFLAGS -Wconversion"
+CXXFLAGS="$CXXFLAGS -Wfloat-equal"
+CXXFLAGS="$CXXFLAGS -Wmissing-field-initializers"
+CXXFLAGS="$CXXFLAGS -Wno-endif-labels"
+CXXFLAGS="$CXXFLAGS -Wold-style-cast"
+CXXFLAGS="$CXXFLAGS -Woverloaded-virtual"
+CXXFLAGS="$CXXFLAGS -Wshorten-64-to-32"
+CXXFLAGS="$CXXFLAGS -Wsign-compare"
+CXXFLAGS="$CXXFLAGS -Wsign-promo"
+CXXFLAGS="$CXXFLAGS -Wstrict-null-sentinel"
+CXXFLAGS="$CXXFLAGS -Wwrite-strings"
+
+
+# The following are options to prepare a developer tree of Ledger for
+# building:
+#
+# --debug
+#
+# Build with debugging information. This doesn't slow things down by much,
+# but gives you useful stack traces to mention in your bug reports.
+# Recommended if you're not running a release version.
+#
+# --dir PATH
+#
+# Building the sources in PATH instead of in the source directory. This
+# breaks pre-compiled headers, but keeps your source tree clean.
+#
+# --boost SUFFIX
+#
+# Use the boost library with the given SUFFIX. Check the Boost "Getting
+# Started" documentation for what the different suffixes are and what they
+# mean. Usually you can see the available suffixes on your system using
+# something like this command:
+#
+# $ ls /usr/local/lib/libboost_date_time*
+#
+# Here's everything that's available on my machine right now:
+#
+# "" - dynamic optimized Boost library
+# d - dynamic debug
+# s - static optimized
+# sd - static debug
+# mt - multi-threaded optimized
+# mt-d - multi-threaded debug
+# mt-s - multi-threaded static optimized
+# mt-sd - multi-threaded static debug
+#
+# Since Ledger does not use threading, I recommend using the static
+# optimized library unless you wish to build with debugging enabled. If you
+# want to do that, see the --devel switch below.
+#
+# --devel
+#
+# This means you want to build like the developer does, which means:
+#
+# * using pre-compiled headers
+# * with glibc debugging enabled
+# * static linking as much as possible
+#
+# The glibc debugging is the only tricky part, since you must have Boost
+# compiled with _GLIBCXX_DEBUG defined also -- which it won't be on your
+# system by default.
+#
+# So, you have to roll your own set of Boost debug libraries in order to
+# support this. I like this because it gives me the most amount of safety
+# and checking possible, which is great for testing. Here's how I build a
+# super-debugging Boost:
+#
+# src $ git clone git://repo.or.cz/boost.git
+# src $ git checkout -b v1.35.0 svn/Version_1_35_0
+# src $ cd boost
+# boost $ sudo bjam release --prefix=/usr/local/stow/boost_1_35_0 \
+# --build-dir=$HOME/Products/boost_1_35_0 --toolset=darwin \
+# architecture=combined install
+# boost $ sudo bjam debug --prefix=/usr/local/stow/boost_1_35_0 \
+# --build-dir=$HOME/Products/boost_1_35_0 --toolset=darwin \
+# architecture=combined define=_GLIBCXX_DEBUG=1 install
+# boost $ cd /usr/local/stow
+# stow $ stow boost_1_35_0
+#
+# Of course, you'll need MacPorts to do this, with both the "bjam" and "stow"
+# packages installed.
+#
+# Lastly, you need to build cppunit by hand with GLIBCXX_DEBUG also, or else
+# you'll see UniTests crash in flames and none of the unit tests will run.
+#
+# Now you're ready to run acprep like this:
+#
+# $ ./acprep --devel --debug --boost sd
+#
+# Or, as I do it:
+#
+# $ ./myacprep
+#
+# --release
+#
+# This is the opposite of --devel: it means you wish to build in a release
+# scenario, preparing a universal binary and building against the non-debug
+# versions of Boost and CppUnit.
+#
+# NOTE: I do not expect anyone but me to use --devel or --release, so don't be
+# surprised if it doesn't work as advertised. In that case, look for me in
+# the #ledger channel on the IRC server irc.freenode.net.
+
+DO_BUILD=false
+
+while [ -n "$1" ]; do
+ case "$1" in
+ --devel)
+ # jww (2008-08-07): Sadly, PCH does not work with Boost+gcc4.0 on
+ # OS X. It does work with gcc4.2, but then _GLIBCXX_DEBUG fails.
+ #SWITCHES="$SWITCHES --enable-pch"
+
+ # The use of this flag requires that Boost be also build with
+ # _GLIBCXX_DEBUG.
+ CPPFLAGS="$CPPFLAGS -D_GLIBCXX_DEBUG=1"
+ CPPFLAGS="-isystem /usr/local/stow/cppunit-debug/include $CPPFLAGS"
+ LDFLAGS="-L/usr/local/stow/cppunit-debug/lib $LDFLAGS"
+
+ BOOST_SUFFIX="-d-1_35" # I built mine with _GLIBCXX_DEBUG
+
+ # Do the same thing as --debug below
+ SWITCHES="$SWITCHES --enable-debug"
+ CPPFLAGS="$CPPFLAGS -DBOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING=1"
+ CPPFLAGS="$CPPFLAGS -DBOOST_MULTI_INDEX_ENABLE_SAFE_MODE=1"
+ CXXFLAGS="$CXXFLAGS -g"
+ LDFLAGS="$LDFLAGS -g"
+
+ #LDFLAGS="-Wl,-read_only_relocs,suppress"
+ #LIBS=""
+ #if [ -f /opt/local/lib/libexpat.a ]; then
+ # LIBS="$LIBS /opt/local/lib/libexpat.a"
+ #fi
+ #if [ -f /opt/local/lib/libgmp.a ]; then
+ # LIBS="$LIBS /opt/local/lib/libgmp.a"
+ #fi
+ #if [ -f /usr/lib/gcc/i686-apple-darwin9/4.2.1/libgcc_static.a ]; then
+ # LIBS="$LIBS /usr/lib/gcc/i686-apple-darwin9/4.2.1/libgcc_static.a"
+ #fi
+
+ shift 1 ;;
+
+ --debug)
+ SWITCHES="$SWITCHES --enable-debug"
+ CPPFLAGS="$CPPFLAGS -DBOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING=1"
+ CPPFLAGS="$CPPFLAGS -DBOOST_MULTI_INDEX_ENABLE_SAFE_MODE=1"
+ CXXFLAGS="$CXXFLAGS -g"
+ LDFLAGS="$LDFLAGS -g"
+ shift 1 ;;
+
+ --boost)
+ shift 1
+ BOOST_SUFFIX="-$1"
+ shift 1 ;;
+
+ --gcov)
+ CXXFLAGS="$CXXFLAGS -fprofile-arcs -ftest-coverage"
+ shift 1 ;;
+
+ --gprof)
+ CXXFLAGS="$CXXFLAGS -g -pg"
+ shift 1 ;;
+
+ --pic)
+ CXXFLAGS="$CXXFLAGS -fPIC"
+ shift 1 ;;
+
+ --opt)
+ CXXFLAGS="$CXXFLAGS -fomit-frame-pointer -O3"
+ shift 1 ;;
+
+ --dir)
+ shift 1
+ BUILD_DIR="$1"
+ shift 1 ;;
+
+ --build)
+ DO_BUILD=true
+ shift 1 ;;
+
+ --local)
+ shift 1 ;;
+
+ --release)
+ SWITCHES="$SWITCHES --disable-dependency-tracking"
+ CPPFLAGS="-isystem /usr/local/stow/cppunit/include $CPPFLAGS"
+ CXXFLAGS="$CXXFLAGS $ARCHFLAGS"
+ LDFLAGS="$LDFLAGS $LDARCHFLAGS"
+ LDFLAGS="-L/usr/local/stow/cppunit/lib $LDFLAGS"
+
+ shift 1 ;;
+
+ *)
+ break ;;
+ esac
+done
+
+
+HERE="$PWD"
+
+if [ ! "$BUILD_DIR" = "false" ]; then
+ if [ ! -d "$BUILD_DIR" ]; then
+ mkdir -p "$BUILD_DIR"
+ fi
+ cd "$BUILD_DIR" || (echo "Cannot change to $BUILD_DIR"; exit 1)
fi
-if [ "$1" = "--debug" ]; then
- shift 1
- $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \
- CXXFLAGS="$CXXFLAGS -g" --enable-debug --enable-python "$@"
-elif [ "$1" = "--opt" ]; then
- shift 1
- $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \
- CXXFLAGS="$CXXFLAGS -fomit-frame-pointer -O3 -mcpu=7450 -fPIC" "$@"
-elif [ "$1" = "--flat-opt" ]; then
- shift 1
- $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \
- CXXFLAGS="$CXXFLAGS -fomit-frame-pointer -O3 -mcpu=7450" "$@"
-elif [ "$1" = "--safe-opt" ]; then
- shift 1
- $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \
- CXXFLAGS="$CXXFLAGS -fomit-frame-pointer -O3 -mcpu=7450 -fPIC -DDEBUG_LEVEL=1" "$@"
-elif [ "$1" = "--perf" ]; then
- shift 1
- $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \
- CXXFLAGS="$CXXFLAGS -g -pg" "$@"
-elif [ "$1" = "--python" ]; then
- shift 1
- $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \
- CXXFLAGS="$CXXFLAGS -g" --enable-python "$@"
-else
- $HERE/configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \
- CXXFLAGS="$CXXFLAGS -g" "$@"
+SWITCHES="$SWITCHES --with-boost-suffix=$BOOST_SUFFIX"
+
+PATH="$PYTHON_HOME/bin:$PATH" \
+ "$HERE/configure" --srcdir="$HERE" \
+ CXX="$CXX" CPPFLAGS="$CPPFLAGS" CXXFLAGS="$CXXFLAGS" \
+ LDFLAGS="$LDFLAGS" LIBS="$LIBS" \
+ $SWITCHES
+
+# Alter the Makefile so that it's not nearly so verbose. This makes errors
+# and warnings much easier to spot.
+
+if [ -f Makefile ]; then
+ perl -i -pe 's/^\t(\$\((LIBTOOL|CXX)\).*?\.cc)$/\t\@echo " " CXX \$\@;$1 > \/dev\/null/;' Makefile
+ perl -i -pe 's/^\tmv -f/\t\@mv -f/;' Makefile
+ perl -i -pe 's/^\t(\$\((.*?)LINK\).*)/\t\@echo " " LD \$\@;$1 > \/dev\/null/;' Makefile
fi
-rm -f AUTHORS COPYING
+# If the --build flag was passed, start a build right away with the right
+# options.
+
+echo '#!/bin/bash' > make.sh
+MAKE_VARS="ARCHFLAGS=\"$ARCHFLAGS\""
+MAKE_VARS="$MAKE_VARS CPPFLAGS=\"$CPPFLAGS\""
+MAKE_VARS="$MAKE_VARS LDFLAGS=\"$LDFLAGS\""
+MAKE_VARS="$MAKE_VARS CXXFLAGS=\"$CXXFLAGS\""
+MAKE_VARS="$MAKE_VARS DISTCHECK_CONFIGURE_FLAGS=\"$SWITCHES\""
+echo "make $MAKE_VARS \"\$@\"" >> make.sh
+chmod u+x make.sh
+
+if [ $DO_BUILD = true ]; then
+ sh -x make.sh "$@"
+fi
diff --git a/amount.cc b/amount.cc
deleted file mode 100644
index 087a9bc5..00000000
--- a/amount.cc
+++ /dev/null
@@ -1,2017 +0,0 @@
-#include "amount.h"
-#include "util.h"
-
-#include <list>
-#include <sstream>
-#include <cstdlib>
-
-#include <gmp.h>
-
-namespace ledger {
-
-bool do_cleanup = true;
-
-bool amount_t::keep_price = false;
-bool amount_t::keep_date = false;
-bool amount_t::keep_tag = false;
-bool amount_t::keep_base = false;
-
-#define BIGINT_BULK_ALLOC 0x0001
-#define BIGINT_KEEP_PREC 0x0002
-
-class amount_t::bigint_t {
- public:
- mpz_t val;
- unsigned char prec;
- unsigned char flags;
- unsigned int ref;
- unsigned int index;
-
- bigint_t() : prec(0), flags(0), ref(1), index(0) {
- mpz_init(val);
- }
- bigint_t(mpz_t _val) : prec(0), flags(0), ref(1), index(0) {
- mpz_init_set(val, _val);
- }
- bigint_t(const bigint_t& other)
- : prec(other.prec), flags(other.flags & BIGINT_KEEP_PREC),
- ref(1), index(0) {
- mpz_init_set(val, other.val);
- }
- ~bigint_t();
-};
-
-unsigned int sizeof_bigint_t() {
- return sizeof(amount_t::bigint_t);
-}
-
-#define MPZ(x) ((x)->val)
-
-static mpz_t temp; // these are the global temp variables
-static mpz_t divisor;
-
-static amount_t::bigint_t true_value;
-
-inline amount_t::bigint_t::~bigint_t() {
- assert(ref == 0 || (! do_cleanup && this == &true_value));
- mpz_clear(val);
-}
-
-base_commodities_map commodity_base_t::commodities;
-
-commodity_base_t::updater_t * commodity_base_t::updater = NULL;
-
-commodities_map commodity_t::commodities;
-bool commodity_t::commodities_sorted = false;
-commodity_t * commodity_t::null_commodity;
-commodity_t * commodity_t::default_commodity = NULL;
-
-static struct _init_amounts {
- _init_amounts() {
- mpz_init(temp);
- mpz_init(divisor);
-
- mpz_set_ui(true_value.val, 1);
-
- commodity_base_t::updater = NULL;
- commodity_t::null_commodity = commodity_t::create("");
- commodity_t::default_commodity = NULL;
-
- commodity_t::null_commodity->add_flags(COMMODITY_STYLE_NOMARKET |
- COMMODITY_STYLE_BUILTIN);
-
- // Add time commodity conversions, so that timelog's may be parsed
- // in terms of seconds, but reported as minutes or hours.
- commodity_t * commodity;
-
- commodity = commodity_t::create("s");
- commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN);
-
- parse_conversion("1.0m", "60s");
- parse_conversion("1.0h", "60m");
-
-#if 0
- commodity = commodity_t::create("b");
- commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN);
-
- parse_conversion("1.00 Kb", "1024 b");
- parse_conversion("1.00 Mb", "1024 Kb");
- parse_conversion("1.00 Gb", "1024 Mb");
- parse_conversion("1.00 Tb", "1024 Gb");
-#endif
- }
-
- ~_init_amounts() {
- if (! do_cleanup)
- return;
-
- mpz_clear(temp);
- mpz_clear(divisor);
-
- if (commodity_base_t::updater) {
- delete commodity_base_t::updater;
- commodity_base_t::updater = NULL;
- }
-
- for (commodities_map::iterator i = commodity_t::commodities.begin();
- i != commodity_t::commodities.end();
- i++)
- delete (*i).second;
-
- commodity_t::commodities.clear();
-
- true_value.ref--;
- }
-} _init_obj;
-
-static void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec)
-{
- // Round `value', with an encoding precision of `value_prec', to a
- // rounded value with precision `round_prec'. Result is stored in
- // `out'.
-
- assert(value_prec > round_prec);
-
- mpz_t quotient;
- mpz_t remainder;
-
- mpz_init(quotient);
- mpz_init(remainder);
-
- mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
- mpz_tdiv_qr(quotient, remainder, value, divisor);
- mpz_divexact_ui(divisor, divisor, 10);
- mpz_mul_ui(divisor, divisor, 5);
-
- if (mpz_sgn(remainder) < 0) {
- mpz_neg(divisor, divisor);
- if (mpz_cmp(remainder, divisor) < 0) {
- mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
- mpz_add(remainder, divisor, remainder);
- mpz_ui_sub(remainder, 0, remainder);
- mpz_add(out, value, remainder);
- } else {
- mpz_sub(out, value, remainder);
- }
- } else {
- if (mpz_cmp(remainder, divisor) >= 0) {
- mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
- mpz_sub(remainder, divisor, remainder);
- mpz_add(out, value, remainder);
- } else {
- mpz_sub(out, value, remainder);
- }
- }
- mpz_clear(quotient);
- mpz_clear(remainder);
-
- // chop off the rounded bits
- mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
- mpz_tdiv_q(out, out, divisor);
-}
-
-amount_t::amount_t(const bool value)
-{
- if (value) {
- quantity = &true_value;
- quantity->ref++;
- } else {
- quantity = NULL;
- }
- commodity_ = NULL;
-}
-
-amount_t::amount_t(const long value)
-{
- if (value != 0) {
- quantity = new bigint_t;
- mpz_set_si(MPZ(quantity), value);
- } else {
- quantity = NULL;
- }
- commodity_ = NULL;
-}
-
-amount_t::amount_t(const unsigned long value)
-{
- if (value != 0) {
- quantity = new bigint_t;
- mpz_set_ui(MPZ(quantity), value);
- } else {
- quantity = NULL;
- }
- commodity_ = NULL;
-}
-
-amount_t::amount_t(const double value)
-{
- if (value != 0.0) {
- quantity = new bigint_t;
- mpz_set_d(MPZ(quantity), value);
- } else {
- quantity = NULL;
- }
- commodity_ = NULL;
-}
-
-void amount_t::_release()
-{
- DEBUG_PRINT("amounts.refs",
- quantity << " ref--, now " << (quantity->ref - 1));
- if (--quantity->ref == 0) {
- if (! (quantity->flags & BIGINT_BULK_ALLOC))
- delete quantity;
- else
- quantity->~bigint_t();
- }
-}
-
-void amount_t::_init()
-{
- if (! quantity) {
- quantity = new bigint_t;
- }
- else if (quantity->ref > 1) {
- _release();
- quantity = new bigint_t;
- }
-}
-
-void amount_t::_dup()
-{
- if (quantity->ref > 1) {
- bigint_t * q = new bigint_t(*quantity);
- _release();
- quantity = q;
- }
-}
-
-void amount_t::_copy(const amount_t& amt)
-{
- if (quantity != amt.quantity) {
- if (quantity)
- _release();
-
- // Never maintain a pointer into a bulk allocation pool; such
- // pointers are not guaranteed to remain.
- if (amt.quantity->flags & BIGINT_BULK_ALLOC) {
- quantity = new bigint_t(*amt.quantity);
- } else {
- quantity = amt.quantity;
- DEBUG_PRINT("amounts.refs",
- quantity << " ref++, now " << (quantity->ref + 1));
- quantity->ref++;
- }
- }
- commodity_ = amt.commodity_;
-}
-
-amount_t& amount_t::operator=(const std::string& value)
-{
- std::istringstream str(value);
- parse(str);
- return *this;
-}
-
-amount_t& amount_t::operator=(const char * value)
-{
- std::string valstr(value);
- std::istringstream str(valstr);
- parse(str);
- return *this;
-}
-
-// assignment operator
-amount_t& amount_t::operator=(const amount_t& amt)
-{
- if (this != &amt) {
- if (amt.quantity)
- _copy(amt);
- else if (quantity)
- _clear();
- }
- return *this;
-}
-
-amount_t& amount_t::operator=(const bool value)
-{
- if (! value) {
- if (quantity)
- _clear();
- } else {
- commodity_ = NULL;
- if (quantity)
- _release();
- quantity = &true_value;
- quantity->ref++;
- }
- return *this;
-}
-
-amount_t& amount_t::operator=(const long value)
-{
- if (value == 0) {
- if (quantity)
- _clear();
- } else {
- commodity_ = NULL;
- _init();
- mpz_set_si(MPZ(quantity), value);
- }
- return *this;
-}
-
-amount_t& amount_t::operator=(const unsigned long value)
-{
- if (value == 0) {
- if (quantity)
- _clear();
- } else {
- commodity_ = NULL;
- _init();
- mpz_set_ui(MPZ(quantity), value);
- }
- return *this;
-}
-
-amount_t& amount_t::operator=(const double value)
-{
- if (value == 0.0) {
- if (quantity)
- _clear();
- } else {
- commodity_ = NULL;
- _init();
- mpz_set_d(MPZ(quantity), value);
- }
- return *this;
-}
-
-
-void amount_t::_resize(unsigned int prec)
-{
- assert(prec < 256);
-
- if (! quantity || prec == quantity->prec)
- return;
-
- _dup();
-
- if (prec < quantity->prec) {
- mpz_ui_pow_ui(divisor, 10, quantity->prec - prec);
- mpz_tdiv_q(MPZ(quantity), MPZ(quantity), divisor);
- } else {
- mpz_ui_pow_ui(divisor, 10, prec - quantity->prec);
- mpz_mul(MPZ(quantity), MPZ(quantity), divisor);
- }
-
- quantity->prec = prec;
-}
-
-
-amount_t& amount_t::operator+=(const amount_t& amt)
-{
- if (! amt.quantity)
- return *this;
-
- if (! quantity) {
- _copy(amt);
- return *this;
- }
-
- _dup();
-
- if (commodity() != amt.commodity())
- throw new amount_error
- (std::string("Adding amounts with different commodities: ") +
- commodity_->qualified_symbol + " != " +
- amt.commodity_->qualified_symbol);
-
- if (quantity->prec == amt.quantity->prec) {
- mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
- }
- else if (quantity->prec < amt.quantity->prec) {
- _resize(amt.quantity->prec);
- mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
- }
- else {
- amount_t temp = amt;
- temp._resize(quantity->prec);
- mpz_add(MPZ(quantity), MPZ(quantity), MPZ(temp.quantity));
- }
-
- return *this;
-}
-
-amount_t& amount_t::operator-=(const amount_t& amt)
-{
- if (! amt.quantity)
- return *this;
-
- if (! quantity) {
- quantity = new bigint_t(*amt.quantity);
- commodity_ = amt.commodity_;
- mpz_neg(MPZ(quantity), MPZ(quantity));
- return *this;
- }
-
- _dup();
-
- if (commodity() != amt.commodity())
- throw new amount_error
- (std::string("Subtracting amounts with different commodities: ") +
- commodity_->qualified_symbol + " != " +
- amt.commodity_->qualified_symbol);
-
- if (quantity->prec == amt.quantity->prec) {
- mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
- }
- else if (quantity->prec < amt.quantity->prec) {
- _resize(amt.quantity->prec);
- mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
- }
- else {
- amount_t temp = amt;
- temp._resize(quantity->prec);
- mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(temp.quantity));
- }
-
- return *this;
-}
-
-amount_t& amount_t::operator*=(const amount_t& amt)
-{
- if (! amt.quantity)
- return (*this = amt);
- else if (! quantity)
- return *this;
-
- _dup();
-
- mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
- quantity->prec += amt.quantity->prec;
-
- 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;
- }
-
- return *this;
-}
-
-amount_t& amount_t::operator/=(const amount_t& amt)
-{
- if (! amt.quantity || ! amt)
- throw new amount_error("Divide by zero");
- else if (! quantity)
- return *this;
-
- _dup();
-
- // Increase the value's precision, to capture fractional parts after
- // the divide.
- mpz_ui_pow_ui(divisor, 10, amt.quantity->prec + 6U);
- mpz_mul(MPZ(quantity), MPZ(quantity), divisor);
- mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
- quantity->prec += 6U;
-
- 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;
- }
-
- return *this;
-}
-
-// unary negation
-void amount_t::negate()
-{
- if (quantity) {
- _dup();
- mpz_neg(MPZ(quantity), MPZ(quantity));
- }
-}
-
-int amount_t::sign() const
-{
- return quantity ? mpz_sgn(MPZ(quantity)) : 0;
-}
-
-int amount_t::compare(const amount_t& amt) const
-{
- if (! quantity) {
- if (! amt.quantity)
- return 0;
- return - amt.sign();
- }
- if (! amt.quantity)
- return sign();
-
- if (commodity() && amt.commodity() && commodity() != amt.commodity())
- throw new amount_error
- (std::string("Cannot compare amounts with different commodities: ") +
- commodity().symbol() + " and " + amt.commodity().symbol());
-
- if (quantity->prec == amt.quantity->prec) {
- return mpz_cmp(MPZ(quantity), MPZ(amt.quantity));
- }
- else if (quantity->prec < amt.quantity->prec) {
- amount_t temp = *this;
- temp._resize(amt.quantity->prec);
- return mpz_cmp(MPZ(temp.quantity), MPZ(amt.quantity));
- }
- else {
- amount_t temp = amt;
- temp._resize(quantity->prec);
- return mpz_cmp(MPZ(quantity), MPZ(temp.quantity));
- }
-}
-
-bool amount_t::operator==(const amount_t& amt) const
-{
- if (commodity() != amt.commodity())
- return false;
- return compare(amt) == 0;
-}
-
-bool amount_t::operator!=(const amount_t& amt) const
-{
- if (commodity() != amt.commodity())
- return true;
- return compare(amt) != 0;
-}
-
-amount_t::operator bool() const
-{
- if (! quantity)
- return false;
-
- if (quantity->prec <= commodity().precision() ||
- (quantity->flags & BIGINT_KEEP_PREC)) {
- return mpz_sgn(MPZ(quantity)) != 0;
- } else {
- mpz_set(temp, MPZ(quantity));
- mpz_ui_pow_ui(divisor, 10, quantity->prec - commodity().precision());
- mpz_tdiv_q(temp, temp, divisor);
- bool zero = mpz_sgn(temp) == 0;
- return ! zero;
- }
-}
-
-amount_t::operator long() const
-{
- if (! quantity)
- return 0;
-
- mpz_set(temp, MPZ(quantity));
- mpz_ui_pow_ui(divisor, 10, quantity->prec);
- mpz_tdiv_q(temp, temp, divisor);
- return mpz_get_si(temp);
-}
-
-amount_t::operator double() const
-{
- if (! quantity)
- return 0.0;
-
- mpz_t remainder;
- mpz_init(remainder);
-
- mpz_set(temp, MPZ(quantity));
- mpz_ui_pow_ui(divisor, 10, quantity->prec);
- mpz_tdiv_qr(temp, remainder, temp, divisor);
-
- char * quotient_s = mpz_get_str(NULL, 10, temp);
- char * remainder_s = mpz_get_str(NULL, 10, remainder);
-
- std::ostringstream num;
- num << quotient_s << '.' << remainder_s;
-
- std::free(quotient_s);
- std::free(remainder_s);
-
- mpz_clear(remainder);
-
- return std::atof(num.str().c_str());
-}
-
-bool amount_t::realzero() const
-{
- if (! quantity)
- return true;
- return mpz_sgn(MPZ(quantity)) == 0;
-}
-
-amount_t amount_t::value(const datetime_t& moment) const
-{
- if (quantity) {
- amount_t amt(commodity().value(moment));
- if (! amt.realzero())
- return (amt * *this).round();
- }
- return *this;
-}
-
-amount_t amount_t::round(unsigned int prec) const
-{
- amount_t temp = *this;
-
- if (! quantity || quantity->prec <= prec) {
- if (quantity && quantity->flags & BIGINT_KEEP_PREC) {
- temp._dup();
- temp.quantity->flags &= ~BIGINT_KEEP_PREC;
- }
- return temp;
- }
-
- temp._dup();
-
- mpz_round(MPZ(temp.quantity), MPZ(temp.quantity), temp.quantity->prec, prec);
-
- temp.quantity->prec = prec;
- temp.quantity->flags &= ~BIGINT_KEEP_PREC;
-
- return temp;
-}
-
-amount_t amount_t::unround() const
-{
- if (! quantity) {
- amount_t temp(0L);
- assert(temp.quantity);
- temp.quantity->flags |= BIGINT_KEEP_PREC;
- return temp;
- }
- else if (quantity->flags & BIGINT_KEEP_PREC) {
- return *this;
- }
-
- amount_t temp = *this;
- temp._dup();
- temp.quantity->flags |= BIGINT_KEEP_PREC;
-
- return temp;
-}
-
-std::string amount_t::quantity_string() const
-{
- if (! quantity)
- return "0";
-
- std::ostringstream out;
-
- mpz_t quotient;
- mpz_t rquotient;
- mpz_t remainder;
-
- mpz_init(quotient);
- mpz_init(rquotient);
- mpz_init(remainder);
-
- bool negative = false;
-
- // Ensure the value is rounded to the commodity's precision before
- // outputting it. NOTE: `rquotient' is used here as a temp variable!
-
- commodity_t& comm(commodity());
- unsigned char precision;
-
- if (! comm || quantity->flags & BIGINT_KEEP_PREC) {
- 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());
- mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
- precision = comm.precision();
- }
- 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_tdiv_qr(quotient, remainder, rquotient, divisor);
- precision = comm.precision();
- }
- else if (quantity->prec) {
- mpz_ui_pow_ui(divisor, 10, quantity->prec);
- mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor);
- precision = quantity->prec;
- }
- else {
- mpz_set(quotient, MPZ(quantity));
- mpz_set_ui(remainder, 0);
- precision = 0;
- }
-
- if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) {
- negative = true;
-
- mpz_abs(quotient, quotient);
- mpz_abs(remainder, remainder);
- }
- mpz_set(rquotient, remainder);
-
- if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0)
- return "0";
-
- if (negative)
- out << "-";
-
- if (mpz_sgn(quotient) == 0) {
- out << '0';
- } else {
- char * p = mpz_get_str(NULL, 10, quotient);
- out << p;
- std::free(p);
- }
-
- if (precision) {
- out << '.';
-
- out.width(precision);
- out.fill('0');
-
- char * p = mpz_get_str(NULL, 10, rquotient);
- out << p;
- std::free(p);
- }
-
- mpz_clear(quotient);
- mpz_clear(rquotient);
- mpz_clear(remainder);
-
- return out.str();
-}
-
-std::ostream& operator<<(std::ostream& _out, const amount_t& amt)
-{
- if (! amt.quantity) {
- _out << "0";
- return _out;
- }
-
- amount_t base(amt);
- if (! amount_t::keep_base && amt.commodity().larger()) {
- amount_t last(amt);
- while (last.commodity().larger()) {
- last /= *last.commodity().larger();
- last.commodity_ = last.commodity().larger()->commodity_;
- if (ledger::abs(last) < 1)
- break;
- base = last.round();
- }
- }
-
- std::ostringstream out;
-
- mpz_t quotient;
- mpz_t rquotient;
- mpz_t remainder;
-
- mpz_init(quotient);
- mpz_init(rquotient);
- mpz_init(remainder);
-
- bool negative = false;
-
- // Ensure the value is rounded to the commodity's precision before
- // outputting it. NOTE: `rquotient' is used here as a temp variable!
-
- commodity_t& comm(base.commodity());
- unsigned char precision;
-
- if (! comm || base.quantity->flags & BIGINT_KEEP_PREC) {
- 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) {
- mpz_round(rquotient, MPZ(base.quantity), base.quantity->prec,
- comm.precision());
- mpz_ui_pow_ui(divisor, 10, comm.precision());
- mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
- precision = comm.precision();
- }
- 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_tdiv_qr(quotient, remainder, rquotient, divisor);
- precision = comm.precision();
- }
- else if (base.quantity->prec) {
- mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
- mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor);
- precision = base.quantity->prec;
- }
- else {
- mpz_set(quotient, MPZ(base.quantity));
- mpz_set_ui(remainder, 0);
- precision = 0;
- }
-
- if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) {
- negative = true;
-
- mpz_abs(quotient, quotient);
- mpz_abs(remainder, remainder);
- }
- mpz_set(rquotient, remainder);
-
- if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) {
- _out << "0";
- return _out;
- }
-
- if (! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) {
- comm.write(out);
-
- if (comm.flags() & COMMODITY_STYLE_SEPARATED)
- out << " ";
- }
-
- if (negative)
- out << "-";
-
- if (mpz_sgn(quotient) == 0) {
- out << '0';
- }
- else if (! (comm.flags() & COMMODITY_STYLE_THOUSANDS)) {
- char * p = mpz_get_str(NULL, 10, quotient);
- out << p;
- std::free(p);
- }
- else {
- std::list<std::string> strs;
- char buf[4];
-
- for (int powers = 0; true; powers += 3) {
- if (powers > 0) {
- mpz_ui_pow_ui(divisor, 10, powers);
- mpz_tdiv_q(temp, quotient, divisor);
- if (mpz_sgn(temp) == 0)
- break;
- mpz_tdiv_r_ui(temp, temp, 1000);
- } else {
- mpz_tdiv_r_ui(temp, quotient, 1000);
- }
- mpz_get_str(buf, 10, temp);
- strs.push_back(buf);
- }
-
- bool printed = false;
-
- for (std::list<std::string>::reverse_iterator i = strs.rbegin();
- i != strs.rend();
- i++) {
- if (printed) {
- out << (comm.flags() & COMMODITY_STYLE_EUROPEAN ? '.' : ',');
- out.width(3);
- out.fill('0');
- }
- out << *i;
-
- printed = true;
- }
- }
-
- if (precision) {
- std::ostringstream final;
- final.width(precision);
- final.fill('0');
- char * p = mpz_get_str(NULL, 10, rquotient);
- final << p;
- std::free(p);
-
- const std::string& str(final.str());
- int i, len = str.length();
- const char * q = str.c_str();
- for (i = len; i > 0; i--)
- if (q[i - 1] != '0')
- break;
-
- std::string ender;
- if (i == len)
- ender = str;
- else if (i < comm.precision())
- ender = std::string(str, 0, comm.precision());
- else
- ender = std::string(str, 0, i);
-
- if (! ender.empty()) {
- out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.');
- out << ender;
- }
- }
-
- if (comm.flags() & COMMODITY_STYLE_SUFFIXED) {
- if (comm.flags() & COMMODITY_STYLE_SEPARATED)
- out << " ";
-
- comm.write(out);
- }
-
- mpz_clear(quotient);
- mpz_clear(rquotient);
- mpz_clear(remainder);
-
- // If there are any annotations associated with this commodity,
- // output them now.
-
- if (comm.annotated) {
- annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm));
- assert(&ann.price != &amt);
- ann.write_annotations(out);
- }
-
- // Things are output to a string first, so that if anyone has
- // specified a width or fill for _out, it will be applied to the
- // entire amount string, and not just the first part.
-
- _out << out.str();
-
- return _out;
-}
-
-void parse_quantity(std::istream& in, std::string& value)
-{
- char buf[256];
- char c = peek_next_nonws(in);
- READ_INTO(in, buf, 255, c,
- std::isdigit(c) || c == '-' || c == '.' || c == ',');
-
- int len = std::strlen(buf);
- while (len > 0 && ! std::isdigit(buf[len - 1])) {
- buf[--len] = '\0';
- in.unget();
- }
-
- value = buf;
-}
-
-// Invalid commodity characters:
-// SPACE, TAB, NEWLINE, RETURN
-// 0-9 . , ; - + * / ^ ? : & | ! =
-// < > { } [ ] ( ) @
-
-int invalid_chars[256] = {
- /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
-/* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
-/* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
-/* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-/* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
-/* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
-/* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-/* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-};
-
-void parse_commodity(std::istream& in, std::string& symbol)
-{
- char buf[256];
- char c = peek_next_nonws(in);
- if (c == '"') {
- in.get(c);
- READ_INTO(in, buf, 255, c, c != '"');
- if (c == '"')
- in.get(c);
- else
- throw new amount_error("Quoted commodity symbol lacks closing quote");
- } else {
- READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]);
- }
- symbol = buf;
-}
-
-void parse_annotations(std::istream& in, amount_t& price,
- datetime_t& date, std::string& tag)
-{
- do {
- char buf[256];
- char c = peek_next_nonws(in);
- if (c == '{') {
- if (price)
- throw new amount_error("Commodity specifies more than one price");
-
- in.get(c);
- READ_INTO(in, buf, 255, c, c != '}');
- if (c == '}')
- in.get(c);
- else
- throw new amount_error("Commodity price lacks closing brace");
-
- price.parse(buf, AMOUNT_PARSE_NO_MIGRATE);
- price.reduce();
-
- // Since this price will maintain its own precision, make sure
- // it is at least as large as the base commodity, since the user
- // may have only specified {$1} or something similar.
-
- if (price.quantity->prec < price.commodity().precision())
- price = price.round(); // no need to retain individual precision
- }
- else if (c == '[') {
- if (date)
- throw new amount_error("Commodity specifies more than one date");
-
- in.get(c);
- READ_INTO(in, buf, 255, c, c != ']');
- if (c == ']')
- in.get(c);
- else
- throw new amount_error("Commodity date lacks closing bracket");
-
- date = buf;
- }
- else if (c == '(') {
- if (! tag.empty())
- throw new amount_error("Commodity specifies more than one tag");
-
- in.get(c);
- READ_INTO(in, buf, 255, c, c != ')');
- if (c == ')')
- in.get(c);
- else
- throw new amount_error("Commodity tag lacks closing parenthesis");
-
- tag = buf;
- }
- else {
- break;
- }
- } while (true);
-
- DEBUG_PRINT("amounts.commodities",
- "Parsed commodity annotations: "
- << " price " << price << " "
- << " date " << date << " "
- << " tag " << tag);
-}
-
-bool amount_t::parse(std::istream& in, unsigned char flags)
-{
- // The possible syntax for an amount is:
- //
- // [-]NUM[ ]SYM [@ AMOUNT]
- // SYM[ ][-]NUM [@ AMOUNT]
-
- std::string symbol;
- std::string quant;
- amount_t price;
- datetime_t date;
- std::string tag;
- unsigned int comm_flags = COMMODITY_STYLE_DEFAULTS;
- bool negative = false;
-
- char c = peek_next_nonws(in);
- if (c == '-') {
- negative = true;
- in.get(c);
- c = peek_next_nonws(in);
- }
-
- char n;
- if (std::isdigit(c)) {
- parse_quantity(in, quant);
-
- if (! in.eof() && ((n = in.peek()) != '\n')) {
- if (std::isspace(n))
- comm_flags |= COMMODITY_STYLE_SEPARATED;
-
- parse_commodity(in, symbol);
-
- if (! symbol.empty())
- comm_flags |= COMMODITY_STYLE_SUFFIXED;
-
- if (! in.eof() && ((n = in.peek()) != '\n'))
- parse_annotations(in, price, date, tag);
- }
- } else {
- parse_commodity(in, symbol);
-
- if (! in.eof() && ((n = in.peek()) != '\n')) {
- if (std::isspace(in.peek()))
- comm_flags |= COMMODITY_STYLE_SEPARATED;
-
- parse_quantity(in, quant);
-
- if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n'))
- parse_annotations(in, price, date, tag);
- }
- }
-
- if (quant.empty()) {
- if (flags & AMOUNT_PARSE_SOFT_FAIL)
- return false;
- else
- throw new amount_error("No quantity specified for amount");
- }
-
- _init();
-
- // Create the commodity if has not already been seen, and update the
- // precision if something greater was used for the quantity.
-
- bool newly_created = false;
-
- if (symbol.empty()) {
- commodity_ = commodity_t::null_commodity;
- } else {
- commodity_ = commodity_t::find(symbol);
- if (! commodity_) {
- commodity_ = commodity_t::create(symbol);
- newly_created = true;
- }
- assert(commodity_);
-
- if (! price.realzero() || date || ! tag.empty())
- commodity_ =
- annotated_commodity_t::find_or_create(*commodity_, price, date, tag);
- }
-
- // Determine the precision of the amount, based on the usage of
- // comma or period.
-
- std::string::size_type last_comma = quant.rfind(',');
- std::string::size_type last_period = quant.rfind('.');
-
- if (last_comma != std::string::npos && last_period != std::string::npos) {
- comm_flags |= COMMODITY_STYLE_THOUSANDS;
- if (last_comma > last_period) {
- comm_flags |= COMMODITY_STYLE_EUROPEAN;
- quantity->prec = quant.length() - last_comma - 1;
- } else {
- quantity->prec = quant.length() - last_period - 1;
- }
- }
- else if (last_comma != std::string::npos &&
- (! commodity_t::default_commodity ||
- 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)) {
- quantity->prec = quant.length() - last_period - 1;
- }
- else {
- quantity->prec = 0;
- }
-
- // Set the commodity's flags and precision accordingly
-
- if (! (flags & AMOUNT_PARSE_NO_MIGRATE)) {
- commodity().add_flags(comm_flags);
- if (quantity->prec > commodity().precision())
- commodity().set_precision(quantity->prec);
- } else {
- quantity->flags |= BIGINT_KEEP_PREC;
- }
-
- // Now we have the final number. Remove commas and periods, if
- // necessary.
-
- if (last_comma != std::string::npos || last_period != std::string::npos) {
- int len = quant.length();
- char * buf = new char[len + 1];
- const char * p = quant.c_str();
- char * t = buf;
-
- while (*p) {
- if (*p == ',' || *p == '.')
- p++;
- *t++ = *p++;
- }
- *t = '\0';
-
- mpz_set_str(MPZ(quantity), buf, 10);
- delete[] buf;
- } else {
- mpz_set_str(MPZ(quantity), quant.c_str(), 10);
- }
-
- if (negative)
- negate();
-
- if (! (flags & AMOUNT_PARSE_NO_REDUCE))
- reduce();
-
- return true;
-}
-
-void amount_t::reduce()
-{
- while (commodity_ && commodity().smaller()) {
- *this *= *commodity().smaller();
- commodity_ = commodity().smaller()->commodity_;
- }
-}
-
-bool amount_t::parse(const std::string& str, unsigned char flags)
-{
- std::istringstream stream(str);
- parse(stream, flags);
-}
-
-void parse_conversion(const std::string& larger_str,
- const std::string& smaller_str)
-{
- amount_t larger, smaller;
-
- larger.parse(larger_str.c_str(), AMOUNT_PARSE_NO_REDUCE);
- smaller.parse(smaller_str.c_str(), AMOUNT_PARSE_NO_REDUCE);
-
- larger *= smaller;
-
- if (larger.commodity()) {
- larger.commodity().set_smaller(smaller);
- larger.commodity().add_flags(smaller.commodity().flags() |
- COMMODITY_STYLE_NOMARKET);
- }
- if (smaller.commodity())
- smaller.commodity().set_larger(larger);
-}
-
-
-char * bigints;
-char * bigints_next;
-unsigned int bigints_index;
-unsigned int bigints_count;
-
-void amount_t::read_quantity(char *& data)
-{
- char byte = *data++;;
-
- if (byte == 0) {
- quantity = NULL;
- }
- else if (byte == 1) {
- quantity = new((bigint_t *)bigints_next) bigint_t;
- bigints_next += sizeof(bigint_t);
-
- unsigned short len = *((unsigned short *) data);
- data += sizeof(unsigned short);
- mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short),
- 0, 0, data);
- data += len;
-
- char negative = *data++;
- if (negative)
- mpz_neg(MPZ(quantity), MPZ(quantity));
-
- quantity->prec = *((unsigned char *) data);
- data += sizeof(unsigned char);
- quantity->flags = *((unsigned char *) data);
- data += sizeof(unsigned char);
- quantity->flags |= BIGINT_BULK_ALLOC;
- } else {
- unsigned int index = *((unsigned int *) data);
- data += sizeof(unsigned int);
-
- quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t));
- DEBUG_PRINT("amounts.refs",
- quantity << " ref++, now " << (quantity->ref + 1));
- quantity->ref++;
- }
-}
-
-static char buf[4096];
-
-void amount_t::read_quantity(std::istream& in)
-{
- char byte;
- in.read(&byte, sizeof(byte));
-
- if (byte == 0) {
- quantity = NULL;
- }
- else if (byte == 1) {
- quantity = new bigint_t;
-
- unsigned short len;
- in.read((char *)&len, sizeof(len));
- assert(len < 4096);
- in.read(buf, len);
- mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short),
- 0, 0, buf);
-
- char negative;
- in.read(&negative, sizeof(negative));
- if (negative)
- mpz_neg(MPZ(quantity), MPZ(quantity));
-
- in.read((char *)&quantity->prec, sizeof(quantity->prec));
- in.read((char *)&quantity->flags, sizeof(quantity->flags));
- }
- else {
- assert(0);
- }
-}
-
-void amount_t::write_quantity(std::ostream& out) const
-{
- char byte;
-
- if (! quantity) {
- byte = 0;
- out.write(&byte, sizeof(byte));
- return;
- }
-
- if (quantity->index == 0) {
- quantity->index = ++bigints_index;
- bigints_count++;
-
- byte = 1;
- out.write(&byte, sizeof(byte));
-
- std::size_t size;
- mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity));
- unsigned short len = size * sizeof(short);
- out.write((char *)&len, sizeof(len));
- if (len) {
- assert(len < 4096);
- out.write(buf, len);
- }
-
- byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0;
- out.write(&byte, sizeof(byte));
-
- out.write((char *)&quantity->prec, sizeof(quantity->prec));
- unsigned char flags = quantity->flags & ~BIGINT_BULK_ALLOC;
- assert(sizeof(flags) == sizeof(quantity->flags));
- out.write((char *)&flags, sizeof(flags));
- } else {
- assert(quantity->ref > 1);
-
- // Since this value has already been written, we simply write
- // out a reference to which one it was.
- byte = 2;
- out.write(&byte, sizeof(byte));
- out.write((char *)&quantity->index, sizeof(quantity->index));
- }
-}
-
-bool amount_t::valid() const
-{
- if (quantity) {
- if (quantity->ref == 0) {
- DEBUG_PRINT("ledger.validate", "amount_t: quantity->ref == 0");
- return false;
- }
- }
- else if (commodity_) {
- DEBUG_PRINT("ledger.validate", "amount_t: commodity_ != NULL");
- return false;
- }
- return true;
-}
-
-void amount_t::annotate_commodity(const amount_t& price,
- const datetime_t& date,
- const std::string& tag)
-{
- const commodity_t * this_base;
- annotated_commodity_t * this_ann = NULL;
-
- if (commodity().annotated) {
- this_ann = &static_cast<annotated_commodity_t&>(commodity());
- this_base = this_ann->ptr;
- } else {
- this_base = &commodity();
- }
- assert(this_base);
-
- DEBUG_PRINT("amounts.commodities", "Annotating commodity for amount "
- << *this << std::endl
- << " price " << price << " "
- << " date " << date << " "
- << " tag " << tag);
-
- commodity_t * ann_comm =
- annotated_commodity_t::find_or_create
- (*this_base, ! price && this_ann ? this_ann->price : price,
- ! date && this_ann ? this_ann->date : date,
- tag.empty() && this_ann ? this_ann->tag : tag);
- if (ann_comm)
- set_commodity(*ann_comm);
-
- DEBUG_PRINT("amounts.commodities", " Annotated amount is " << *this);
-}
-
-amount_t amount_t::strip_annotations(const bool _keep_price,
- const bool _keep_date,
- const bool _keep_tag) const
-{
- if (! commodity().annotated ||
- (_keep_price && _keep_date && _keep_tag))
- return *this;
-
- DEBUG_PRINT("amounts.commodities", "Reducing commodity for amount "
- << *this << std::endl
- << " keep price " << _keep_price << " "
- << " keep date " << _keep_date << " "
- << " keep tag " << _keep_tag);
-
- annotated_commodity_t&
- ann_comm(static_cast<annotated_commodity_t&>(commodity()));
- assert(ann_comm.base);
-
- commodity_t * new_comm;
-
- if ((_keep_price && ann_comm.price) ||
- (_keep_date && ann_comm.date) ||
- (_keep_tag && ! ann_comm.tag.empty()))
- {
- new_comm = annotated_commodity_t::find_or_create
- (*ann_comm.ptr, _keep_price ? ann_comm.price : amount_t(),
- _keep_date ? ann_comm.date : datetime_t(),
- _keep_tag ? ann_comm.tag : "");
- } else {
- new_comm = commodity_t::find_or_create(ann_comm.base_symbol());
- }
- assert(new_comm);
-
- amount_t temp(*this);
- temp.set_commodity(*new_comm);
-
- DEBUG_PRINT("amounts.commodities", " Reduced amount is " << temp);
-
- return temp;
-}
-
-amount_t amount_t::price() const
-{
- if (commodity_ && commodity_->annotated) {
- amount_t temp(((annotated_commodity_t *)commodity_)->price);
- temp *= *this;
- DEBUG_PRINT("amounts.commodities",
- "Returning price of " << *this << " = " << temp);
- return temp;
- }
- return *this;
-}
-
-datetime_t amount_t::date() const
-{
- if (commodity_ && commodity_->annotated) {
- DEBUG_PRINT("amounts.commodities",
- "Returning date of " << *this << " = "
- << ((annotated_commodity_t *)commodity_)->date);
- return ((annotated_commodity_t *)commodity_)->date;
- }
- return 0L;
-}
-
-
-void commodity_base_t::add_price(const datetime_t& date,
- const amount_t& price)
-{
- if (! history)
- history = new history_t;
-
- 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));
- assert(result.second);
- }
-}
-
-bool commodity_base_t::remove_price(const datetime_t& date)
-{
- if (history) {
- history_map::size_type n = history->prices.erase(date);
- if (n > 0) {
- if (history->prices.empty())
- history = NULL;
- return true;
- }
- }
- return false;
-}
-
-commodity_base_t * commodity_base_t::create(const std::string& symbol)
-{
- commodity_base_t * commodity = new commodity_base_t(symbol);
-
- DEBUG_PRINT("amounts.commodities", "Creating base commodity " << symbol);
-
- std::pair<base_commodities_map::iterator, bool> result
- = commodities.insert(base_commodities_pair(symbol, commodity));
- assert(result.second);
-
- return commodity;
-}
-
-bool commodity_t::needs_quotes(const std::string& symbol)
-{
- for (const char * p = symbol.c_str(); *p; p++)
- if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.')
- return true;
-
- return false;
-}
-
-bool commodity_t::valid() const
-{
- if (symbol().empty() && this != null_commodity) {
- DEBUG_PRINT("ledger.validate",
- "commodity_t: symbol().empty() && this != null_commodity");
- return false;
- }
-
- if (annotated && ! base) {
- DEBUG_PRINT("ledger.validate", "commodity_t: annotated && ! base");
- return false;
- }
-
- if (precision() > 16) {
- DEBUG_PRINT("ledger.validate", "commodity_t: precision() > 16");
- return false;
- }
-
- return true;
-}
-
-commodity_t * commodity_t::create(const std::string& symbol)
-{
- std::auto_ptr<commodity_t> commodity(new commodity_t);
-
- commodity->base = commodity_base_t::create(symbol);
-
- if (needs_quotes(symbol)) {
- commodity->qualified_symbol = "\"";
- commodity->qualified_symbol += symbol;
- commodity->qualified_symbol += "\"";
- } else {
- commodity->qualified_symbol = symbol;
- }
-
- DEBUG_PRINT("amounts.commodities",
- "Creating commodity " << commodity->qualified_symbol);
-
- std::pair<commodities_map::iterator, bool> result
- = commodities.insert(commodities_pair(symbol, commodity.get()));
- if (! result.second)
- return NULL;
-
- // Start out the new commodity with the default commodity's flags
- // and precision, if one has been defined.
- if (default_commodity)
- commodity->drop_flags(COMMODITY_STYLE_THOUSANDS |
- COMMODITY_STYLE_NOMARKET);
-
- return commodity.release();
-}
-
-commodity_t * commodity_t::find_or_create(const std::string& symbol)
-{
- DEBUG_PRINT("amounts.commodities", "Find-or-create commodity " << symbol);
-
- commodity_t * commodity = find(symbol);
- if (commodity)
- return commodity;
- return create(symbol);
-}
-
-commodity_t * commodity_t::find(const std::string& symbol)
-{
- DEBUG_PRINT("amounts.commodities", "Find commodity " << symbol);
-
- commodities_map::const_iterator i = commodities.find(symbol);
- if (i != commodities.end())
- return (*i).second;
- return NULL;
-}
-
-amount_t commodity_base_t::value(const datetime_t& moment)
-{
- datetime_t age;
- amount_t price;
-
- if (history) {
- assert(history->prices.size() > 0);
-
- if (! moment) {
- 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();
- age = (*r).first;
- price = (*r).second;
- } else {
- age = (*i).first;
- if (moment != age) {
- if (i != history->prices.begin()) {
- --i;
- age = (*i).first;
- price = (*i).second;
- } else {
- age = 0;
- }
- } else {
- price = (*i).second;
- }
- }
- }
- }
-
- if (updater && ! (flags & COMMODITY_STYLE_NOMARKET))
- (*updater)(*this, moment, age,
- (history && history->prices.size() > 0 ?
- (*history->prices.rbegin()).first : datetime_t()), price);
-
- return price;
-}
-
-bool annotated_commodity_t::operator==(const commodity_t& comm) const
-{
- // If the base commodities don't match, the game's up.
- if (base != comm.base)
- return false;
-
- if (price &&
- (! comm.annotated ||
- price != static_cast<const annotated_commodity_t&>(comm).price))
- return false;
-
- if (date &&
- (! comm.annotated ||
- date != static_cast<const annotated_commodity_t&>(comm).date))
- return false;
-
- if (! tag.empty() &&
- (! comm.annotated ||
- tag != static_cast<const annotated_commodity_t&>(comm).tag))
- return false;
-
- return true;
-}
-
-void
-annotated_commodity_t::write_annotations(std::ostream& out,
- const amount_t& price,
- const datetime_t& date,
- const std::string& tag)
-{
- if (price)
- out << " {" << price << '}';
-
- if (date)
- out << " [" << date << ']';
-
- if (! tag.empty())
- out << " (" << tag << ')';
-}
-
-commodity_t *
-annotated_commodity_t::create(const commodity_t& comm,
- const amount_t& price,
- const datetime_t& date,
- const std::string& tag,
- const std::string& mapping_key)
-{
- std::auto_ptr<annotated_commodity_t> commodity(new annotated_commodity_t);
-
- // Set the annotated bits
- commodity->price = price;
- commodity->date = date;
- commodity->tag = tag;
-
- commodity->ptr = &comm;
- assert(commodity->ptr);
- commodity->base = comm.base;
- assert(commodity->base);
-
- commodity->qualified_symbol = comm.symbol();
-
- DEBUG_PRINT("amounts.commodities", "Creating annotated commodity "
- << "symbol " << commodity->symbol()
- << " key " << mapping_key << std::endl
- << " price " << price << " "
- << " date " << date << " "
- << " tag " << tag);
-
- // Add the fully annotated name to the map, so that this symbol may
- // quickly be found again.
- std::pair<commodities_map::iterator, bool> result
- = commodities.insert(commodities_pair(mapping_key, commodity.get()));
- if (! result.second)
- return NULL;
-
- return commodity.release();
-}
-
-namespace {
- std::string make_qualified_name(const commodity_t& comm,
- const amount_t& price,
- const datetime_t& date,
- const std::string& tag)
- {
- if (price < 0)
- throw new amount_error("A commodity's price may not be negative");
-
- std::ostringstream name;
-
- comm.write(name);
- annotated_commodity_t::write_annotations(name, price, date, tag);
-
- DEBUG_PRINT("amounts.commodities", "make_qualified_name for "
- << comm.qualified_symbol << std::endl
- << " price " << price << " "
- << " date " << date << " "
- << " tag " << tag);
-
- DEBUG_PRINT("amounts.commodities", "qualified_name is " << name.str());
-
- return name.str();
- }
-}
-
-commodity_t *
-annotated_commodity_t::find_or_create(const commodity_t& comm,
- const amount_t& price,
- const datetime_t& date,
- const std::string& tag)
-{
- std::string name = make_qualified_name(comm, price, date, tag);
-
- commodity_t * ann_comm = commodity_t::find(name);
- if (ann_comm) {
- assert(ann_comm->annotated);
- return ann_comm;
- }
- return create(comm, price, date, tag, name);
-}
-
-bool compare_amount_commodities::operator()(const amount_t * left,
- const amount_t * right) const
-{
- commodity_t& leftcomm(left->commodity());
- commodity_t& rightcomm(right->commodity());
-
- int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol());
- if (cmp != 0)
- return cmp < 0;
-
- if (! leftcomm.annotated) {
- assert(rightcomm.annotated);
- return true;
- }
- else if (! rightcomm.annotated) {
- assert(leftcomm.annotated);
- return false;
- }
- else {
- annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm));
- annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm));
-
- if (! aleftcomm.price && arightcomm.price)
- return true;
- if (aleftcomm.price && ! arightcomm.price)
- return false;
-
- if (aleftcomm.price && arightcomm.price) {
- amount_t leftprice(aleftcomm.price);
- leftprice.reduce();
- amount_t rightprice(arightcomm.price);
- rightprice.reduce();
-
- if (leftprice.commodity() == rightprice.commodity()) {
- int diff = leftprice.compare(rightprice);
- if (diff)
- return diff;
- } else {
- // Since we have two different amounts, there's really no way
- // to establish a true sorting order; we'll just do it based
- // on the numerical values.
- leftprice.clear_commodity();
- rightprice.clear_commodity();
-
- int diff = leftprice.compare(rightprice);
- if (diff)
- return diff;
- }
- }
-
- if (! aleftcomm.date && arightcomm.date)
- return true;
- if (aleftcomm.date && ! arightcomm.date)
- return false;
-
- if (aleftcomm.date && arightcomm.date) {
- int diff = aleftcomm.date - arightcomm.date;
- if (diff)
- return diff < 0;
- }
-
- if (aleftcomm.tag.empty() && ! arightcomm.tag.empty())
- return true;
- if (! aleftcomm.tag.empty() && arightcomm.tag.empty())
- return false;
-
- if (! aleftcomm.tag.empty() && ! arightcomm.tag.empty())
- return aleftcomm.tag < arightcomm.tag;
-
- // The two annotated commodities don't differ enough to matter. This
- // should make this identical.
- return true;
- }
-}
-
-} // namespace ledger
-
-#ifdef USE_BOOST_PYTHON
-
-#include <boost/python.hpp>
-#include <Python.h>
-
-using namespace boost::python;
-using namespace ledger;
-
-int py_amount_quantity(amount_t& amount)
-{
- std::string quant = amount.quantity_string();
- return std::atol(quant.c_str());
-}
-
-void py_parse_1(amount_t& amount, const std::string& str,
- unsigned char flags) {
- amount.parse(str, flags);
-}
-void py_parse_2(amount_t& amount, const std::string& str) {
- amount.parse(str);
-}
-
-struct commodity_updater_wrap : public commodity_base_t::updater_t
-{
- PyObject * self;
- commodity_updater_wrap(PyObject * self_) : self(self_) {}
-
- virtual void operator()(commodity_base_t& commodity,
- const datetime_t& moment,
- const datetime_t& date,
- const datetime_t& last,
- amount_t& price) {
- call_method<void>(self, "__call__", commodity, moment, date, last, price);
- }
-};
-
-commodity_t * py_find_commodity(const std::string& symbol)
-{
- return commodity_t::find(symbol);
-}
-
-#define EXC_TRANSLATOR(type) \
- void exc_translate_ ## type(const type& err) { \
- PyErr_SetString(PyExc_RuntimeError, err.what()); \
- }
-
-EXC_TRANSLATOR(amount_error)
-
-void export_amount()
-{
- scope().attr("AMOUNT_PARSE_NO_MIGRATE") = AMOUNT_PARSE_NO_MIGRATE;
- scope().attr("AMOUNT_PARSE_NO_REDUCE") = AMOUNT_PARSE_NO_REDUCE;
-
- class_< amount_t > ("Amount")
- .def(init<amount_t>())
- .def(init<std::string>())
- .def(init<char *>())
- .def(init<bool>())
- .def(init<long>())
- .def(init<unsigned long>())
- .def(init<double>())
-
- .def(self += self)
- .def(self += long())
- .def(self + self)
- .def(self + long())
- .def(self -= self)
- .def(self -= long())
- .def(self - self)
- .def(self - long())
- .def(self *= self)
- .def(self *= long())
- .def(self * self)
- .def(self * long())
- .def(self /= self)
- .def(self /= long())
- .def(self / self)
- .def(self / long())
- .def(- self)
-
- .def(self < self)
- .def(self < long())
- .def(self <= self)
- .def(self <= long())
- .def(self > self)
- .def(self > long())
- .def(self >= self)
- .def(self >= long())
- .def(self == self)
- .def(self == long())
- .def(self != self)
- .def(self != long())
- .def(! self)
-
- .def(self_ns::int_(self))
- .def(self_ns::float_(self))
- .def(self_ns::str(self))
- .def(abs(self))
-
- .add_property("commodity",
- make_function(&amount_t::commodity,
- return_value_policy<reference_existing_object>()),
- make_function(&amount_t::set_commodity,
- with_custodian_and_ward<1, 2>()))
-
- .def("strip_annotations", &amount_t::strip_annotations)
-
- .def("negate", &amount_t::negate)
- .def("negated", &amount_t::negated)
- .def("parse", py_parse_1)
- .def("parse", py_parse_2)
- .def("reduce", &amount_t::reduce)
-
- .def("valid", &amount_t::valid)
- ;
-
- class_< commodity_base_t::updater_t, commodity_updater_wrap,
- boost::noncopyable >
- ("Updater")
- ;
-
- scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS;
- scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED;
- scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED;
- scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN;
- scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS;
- scope().attr("COMMODITY_STYLE_NOMARKET") = COMMODITY_STYLE_NOMARKET;
- scope().attr("COMMODITY_STYLE_BUILTIN") = COMMODITY_STYLE_BUILTIN;
-
- class_< commodity_t > ("Commodity")
- .add_property("symbol", &commodity_t::symbol)
-
-#if 0
- .add_property("name", &commodity_t::name, &commodity_t::set_name)
- .add_property("note", &commodity_t::note, &commodity_t::set_note)
- .add_property("precision", &commodity_t::precision,
- &commodity_t::set_precision)
- .add_property("flags", &commodity_t::flags, &commodity_t::set_flags)
- .add_property("add_flags", &commodity_t::add_flags)
- .add_property("drop_flags", &commodity_t::drop_flags)
-#if 0
- .add_property("updater", &commodity_t::updater)
-#endif
-
- .add_property("smaller",
- make_getter(&commodity_t::smaller,
- return_value_policy<reference_existing_object>()),
- make_setter(&commodity_t::smaller,
- return_value_policy<reference_existing_object>()))
- .add_property("larger",
- make_getter(&commodity_t::larger,
- return_value_policy<reference_existing_object>()),
- make_setter(&commodity_t::larger,
- return_value_policy<reference_existing_object>()))
-
- .def(self_ns::str(self))
-
- .def("find", py_find_commodity,
- return_value_policy<reference_existing_object>())
- .staticmethod("find")
-#endif
-
- .def("add_price", &commodity_t::add_price)
- .def("remove_price", &commodity_t::remove_price)
- .def("value", &commodity_t::value)
-
- .def("valid", &commodity_t::valid)
- ;
-
-#define EXC_TRANSLATE(type) \
- register_exception_translator<type>(&exc_translate_ ## type);
-
- EXC_TRANSLATE(amount_error);
-}
-
-#endif // USE_BOOST_PYTHON
diff --git a/amount.h b/amount.h
deleted file mode 100644
index ef1d2a3d..00000000
--- a/amount.h
+++ /dev/null
@@ -1,631 +0,0 @@
-#ifndef _AMOUNT_H
-#define _AMOUNT_H
-
-#include <map>
-#include <stack>
-#include <string>
-#include <memory>
-#include <cctype>
-#include <iostream>
-#include <sstream>
-#include <cassert>
-#include <exception>
-
-#include "datetime.h"
-#include "debug.h"
-#include "error.h"
-
-namespace ledger {
-
-extern bool do_cleanup;
-
-class commodity_t;
-
-class amount_t
-{
- public:
- class bigint_t;
-
- static bool keep_price;
- static bool keep_date;
- static bool keep_tag;
- static bool keep_base;
-
- protected:
- void _init();
- void _copy(const amount_t& amt);
- void _release();
- void _dup();
- void _resize(unsigned int prec);
-
- void _clear() {
- if (quantity) {
- assert(commodity_);
- _release();
- quantity = NULL;
- commodity_ = NULL;
- } else {
- assert(! commodity_);
- }
- }
-
- bigint_t * quantity;
- commodity_t * commodity_;
-
- public:
- // constructors
- amount_t() : quantity(NULL), commodity_(NULL) {}
- amount_t(const amount_t& amt) : quantity(NULL) {
- if (amt.quantity)
- _copy(amt);
- else
- commodity_ = NULL;
- }
- amount_t(const std::string& value) : quantity(NULL) {
- parse(value);
- }
- amount_t(const char * value) : quantity(NULL) {
- parse(value);
- }
- amount_t(const bool value);
- amount_t(const long value);
- amount_t(const unsigned long value);
- amount_t(const double value);
-
- // destructor
- ~amount_t() {
- if (quantity)
- _release();
- }
-
- commodity_t& commodity() const;
- void set_commodity(commodity_t& comm) {
- commodity_ = &comm;
- }
- void annotate_commodity(const amount_t& price,
- const datetime_t& date = datetime_t(),
- const std::string& tag = "");
- amount_t strip_annotations(const bool _keep_price = keep_price,
- const bool _keep_date = keep_date,
- const bool _keep_tag = keep_tag) const;
- void clear_commodity() {
- commodity_ = NULL;
- }
- amount_t price() const;
- datetime_t date() const;
-
- bool null() const {
- return ! quantity && ! commodity_;
- }
-
- std::string quantity_string() const;
-
- // assignment operator
- amount_t& operator=(const amount_t& amt);
- amount_t& operator=(const std::string& value);
- amount_t& operator=(const char * value);
- amount_t& operator=(const bool value);
- amount_t& operator=(const long value);
- amount_t& operator=(const unsigned long value);
- amount_t& operator=(const double value);
-
- // general methods
- amount_t round(unsigned int prec) const;
- amount_t round() const;
- amount_t unround() const;
-
- // in-place arithmetic
- amount_t& operator+=(const amount_t& amt);
- amount_t& operator-=(const amount_t& amt);
- amount_t& operator*=(const amount_t& amt);
- amount_t& operator/=(const amount_t& amt);
-
- template <typename T>
- amount_t& operator+=(T value) {
- return *this += amount_t(value);
- }
- template <typename T>
- amount_t& operator-=(T value) {
- return *this -= amount_t(value);
- }
- template <typename T>
- amount_t& operator*=(T value) {
- return *this *= amount_t(value);
- }
- template <typename T>
- amount_t& operator/=(T value) {
- return *this /= amount_t(value);
- }
-
- // simple arithmetic
- amount_t operator+(const amount_t& amt) const {
- amount_t temp = *this;
- temp += amt;
- return temp;
- }
- amount_t operator-(const amount_t& amt) const {
- amount_t temp = *this;
- temp -= amt;
- return temp;
- }
- amount_t operator*(const amount_t& amt) const {
- amount_t temp = *this;
- temp *= amt;
- return temp;
- }
- amount_t operator/(const amount_t& amt) const {
- amount_t temp = *this;
- temp /= amt;
- return temp;
- }
-
- template <typename T>
- amount_t operator+(T value) const {
- amount_t temp = *this;
- temp += value;
- return temp;
- }
- template <typename T>
- amount_t operator-(T value) const {
- amount_t temp = *this;
- temp -= value;
- return temp;
- }
- template <typename T>
- amount_t operator*(T value) const {
- amount_t temp = *this;
- temp *= value;
- return temp;
- }
- template <typename T>
- amount_t operator/(T value) const {
- amount_t temp = *this;
- temp /= value;
- return temp;
- }
-
- // unary negation
- void negate();
- amount_t negated() const {
- amount_t temp = *this;
- temp.negate();
- return temp;
- }
- amount_t operator-() const {
- return negated();
- }
-
- // test for non-zero (use ! for zero)
- operator bool() const;
- operator long() const;
- operator double() const;
-
- bool realzero() const;
-
- // comparisons between amounts
- int compare(const amount_t& amt) const;
-
- bool operator<(const amount_t& amt) const {
- return compare(amt) < 0;
- }
- bool operator<=(const amount_t& amt) const {
- return compare(amt) <= 0;
- }
- bool operator>(const amount_t& amt) const {
- return compare(amt) > 0;
- }
- bool operator>=(const amount_t& amt) const {
- return compare(amt) >= 0;
- }
- bool operator==(const amount_t& amt) const;
- bool operator!=(const amount_t& amt) const;
-
- template <typename T>
- void parse_num(T num) {
- std::ostringstream temp;
- temp << num;
- std::istringstream in(temp.str());
- parse(in);
- }
-
- int sign() const;
-
- // POD comparisons
-#define AMOUNT_CMP_INT(OP) \
- template <typename T> \
- bool operator OP (T num) const { \
- if (num == 0) { \
- return sign() OP 0; \
- } else { \
- amount_t amt; \
- amt.parse_num(num); \
- return *this OP amt; \
- } \
- }
-
- AMOUNT_CMP_INT(<)
- AMOUNT_CMP_INT(<=)
- AMOUNT_CMP_INT(>)
- AMOUNT_CMP_INT(>=)
- AMOUNT_CMP_INT(==)
-
- template <typename T>
- bool operator!=(T num) const {
- return ! (*this == num);
- }
-
- amount_t value(const datetime_t& moment) const;
-
- void abs() {
- if (*this < 0)
- negate();
- }
-
-#define AMOUNT_PARSE_NO_MIGRATE 0x01
-#define AMOUNT_PARSE_NO_REDUCE 0x02
-#define AMOUNT_PARSE_SOFT_FAIL 0x04
-
- bool parse(std::istream& in, unsigned char flags = 0);
- bool parse(const std::string& str, unsigned char flags = 0);
- void reduce();
-
- amount_t reduced() const {
- amount_t temp(*this);
- temp.reduce();
- return temp;
- }
-
- void read_quantity(char *& data);
- void read_quantity(std::istream& in);
- void write_quantity(std::ostream& out) const;
-
- bool valid() const;
-
- // Classes that are friends, and help to implement this class
-
- friend std::ostream& operator<<(std::ostream& out, const amount_t& amt);
- friend std::istream& operator>>(std::istream& in, amount_t& amt);
-
- friend unsigned int sizeof_bigint_t();
-
- friend void read_binary_amount(char *& data, amount_t& amt);
- friend void write_binary_amount(std::ostream& out, const amount_t& amt);
-
- // This function is special, and exists only to support a custom
- // optimization in binary.cc (which offers a significant enough gain
- // to be worth the trouble).
-
- friend void clean_commodity_history(char * item_pool,
- char * item_pool_end);
-
- friend void parse_annotations(std::istream& in, amount_t& price,
- datetime_t& date, std::string& tag);
-};
-
-unsigned int sizeof_bigint_t();
-
-void parse_quantity(std::istream& in, std::string& value);
-void parse_commodity(std::istream& in, std::string& symbol);
-void parse_annotations(std::istream& in, const std::string& symbol,
- std::string& name, std::string& price,
- std::string& date, std::string& tag);
-void parse_conversion(const std::string& larger,
- const std::string& smaller);
-
-inline bool is_quote_or_paren(char * p) {
- return *p == '"' || *p == '{' || *p == '[' || *p == '(';
-}
-
-inline char * scan_past_quotes_and_parens(char * expr)
-{
- std::stack<char> paren_stack;
-
- char * p;
- for (p = expr; *p; p++) {
- if (*p == '"' ||
- ((*p == '(' || ((*p == '{' || *p == '[') &&
- paren_stack.top() != '(')) &&
- paren_stack.top() != '"')) {
- paren_stack.push(*p);
- }
- else if ((*p == ')' && paren_stack.top() == '(') ||
- (*p == '}' && paren_stack.top() == '{') ||
- (*p == ']' && paren_stack.top() == '[') ||
- (*p == '"' && paren_stack.top() == '"')) {
- paren_stack.pop();
- if (paren_stack.size() == 0)
- break;
- }
- }
- return p;
-}
-
-inline amount_t abs(const amount_t& amt) {
- return amt < 0 ? amt.negated() : amt;
-}
-
-std::ostream& operator<<(std::ostream& out, const amount_t& amt);
-
-inline std::istream& operator>>(std::istream& in, amount_t& amt) {
- amt.parse(in);
- return in;
-}
-
-
-#define COMMODITY_STYLE_DEFAULTS 0x0000
-#define COMMODITY_STYLE_SUFFIXED 0x0001
-#define COMMODITY_STYLE_SEPARATED 0x0002
-#define COMMODITY_STYLE_EUROPEAN 0x0004
-#define COMMODITY_STYLE_THOUSANDS 0x0008
-#define COMMODITY_STYLE_NOMARKET 0x0010
-#define COMMODITY_STYLE_BUILTIN 0x0020
-
-typedef std::map<const datetime_t, amount_t> history_map;
-typedef std::pair<const datetime_t, amount_t> history_pair;
-
-class commodity_base_t;
-
-typedef std::map<const std::string, commodity_base_t *> base_commodities_map;
-typedef std::pair<const std::string, commodity_base_t *> base_commodities_pair;
-
-class commodity_base_t
-{
- public:
- friend class commodity_t;
- friend class annotated_commodity_t;
-
- typedef unsigned long ident_t;
-
- ident_t ident;
- std::string name;
- std::string note;
- unsigned char precision;
- unsigned char flags;
- amount_t * smaller;
- amount_t * larger;
-
- commodity_base_t()
- : precision(0), flags(COMMODITY_STYLE_DEFAULTS),
- smaller(NULL), larger(NULL), history(NULL) {}
-
- commodity_base_t(const std::string& _symbol,
- unsigned int _precision = 0,
- unsigned int _flags = COMMODITY_STYLE_DEFAULTS)
- : precision(_precision), flags(_flags),
- smaller(NULL), larger(NULL), symbol(_symbol), history(NULL) {}
-
- ~commodity_base_t() {
- if (history) delete history;
- if (smaller) delete smaller;
- if (larger) delete larger;
- }
-
- static base_commodities_map commodities;
- static commodity_base_t * create(const std::string& symbol);
-
- std::string symbol;
-
- struct history_t {
- history_map prices;
- datetime_t last_lookup;
- datetime_t bogus_time;
- history_t() : last_lookup(0), bogus_time(0) {}
- };
- history_t * history;
-
- void add_price(const datetime_t& date, const amount_t& price);
- bool remove_price(const datetime_t& date);
- amount_t value(const datetime_t& moment = datetime_t::now);
-
- class updater_t {
- public:
- virtual ~updater_t() {}
- virtual void operator()(commodity_base_t& commodity,
- const datetime_t& moment,
- const datetime_t& date,
- const datetime_t& last,
- amount_t& price) = 0;
- };
- friend class updater_t;
-
- static updater_t * updater;
-};
-
-typedef std::map<const std::string, commodity_t *> commodities_map;
-typedef std::pair<const std::string, commodity_t *> commodities_pair;
-
-class commodity_t
-{
- friend class annotated_commodity_t;
-
- public:
- // This map remembers all commodities that have been defined.
-
- static commodities_map commodities;
- static bool commodities_sorted;
- static commodity_t * null_commodity;
- static commodity_t * default_commodity;
-
- static commodity_t * create(const std::string& symbol);
- static commodity_t * find(const std::string& name);
- static commodity_t * find_or_create(const std::string& symbol);
-
- static bool needs_quotes(const std::string& symbol);
-
- static void make_alias(const std::string& symbol,
- commodity_t * commodity);
-
- // These are specific to each commodity reference
-
- typedef unsigned long ident_t;
-
- ident_t ident;
- commodity_base_t * base;
- std::string qualified_symbol;
- bool annotated;
-
- public:
- explicit commodity_t() : base(NULL), annotated(false) {}
- virtual ~commodity_t() {}
-
- operator bool() const {
- return this != null_commodity;
- }
- virtual bool operator==(const commodity_t& comm) const {
- if (comm.annotated)
- return comm == *this;
- return base == comm.base;
- }
- bool operator!=(const commodity_t& comm) const {
- return ! (*this == comm);
- }
-
- std::string base_symbol() const {
- return base->symbol;
- }
- std::string symbol() const {
- return qualified_symbol;
- }
-
- void write(std::ostream& out) const {
- out << symbol();
- }
-
- std::string name() const {
- return base->name;
- }
- void set_name(const std::string& arg) {
- base->name = arg;
- }
-
- std::string note() const {
- return base->note;
- }
- void set_note(const std::string& arg) {
- base->note = arg;
- }
-
- unsigned char precision() const {
- return base->precision;
- }
- void set_precision(unsigned char arg) {
- base->precision = arg;
- }
-
- unsigned char flags() const {
- return base->flags;
- }
- void set_flags(unsigned char arg) {
- base->flags = arg;
- }
- void add_flags(unsigned char arg) {
- base->flags |= arg;
- }
- void drop_flags(unsigned char arg) {
- base->flags &= ~arg;
- }
-
- amount_t * smaller() const {
- return base->smaller;
- }
- void set_smaller(const amount_t& arg) {
- if (base->smaller)
- delete base->smaller;
- base->smaller = new amount_t(arg);
- }
-
- amount_t * larger() const {
- return base->larger;
- }
- void set_larger(const amount_t& arg) {
- if (base->larger)
- delete base->larger;
- base->larger = new amount_t(arg);
- }
-
- commodity_base_t::history_t * history() const {
- return base->history;
- }
-
- void add_price(const datetime_t& date, const amount_t& price) {
- return base->add_price(date, price);
- }
- bool remove_price(const datetime_t& date) {
- return base->remove_price(date);
- }
- amount_t value(const datetime_t& moment = datetime_t::now) const {
- return base->value(moment);
- }
-
- bool valid() const;
-};
-
-class annotated_commodity_t : public commodity_t
-{
- public:
- const commodity_t * ptr;
-
- amount_t price;
- datetime_t date;
- std::string tag;
-
- explicit annotated_commodity_t() {
- annotated = true;
- }
-
- virtual bool operator==(const commodity_t& comm) const;
-
- void write_annotations(std::ostream& out) const {
- annotated_commodity_t::write_annotations(out, price, date, tag);
- }
-
- static void write_annotations(std::ostream& out,
- const amount_t& price,
- const datetime_t& date,
- const std::string& tag);
-
- private:
- static commodity_t * create(const commodity_t& comm,
- const amount_t& price,
- const datetime_t& date,
- const std::string& tag,
- const std::string& mapping_key);
-
- static commodity_t * find_or_create(const commodity_t& comm,
- const amount_t& price,
- const datetime_t& date,
- const std::string& tag);
-
- friend class amount_t;
-};
-
-inline std::ostream& operator<<(std::ostream& out,
- const commodity_t& comm) {
- out << comm.symbol();
- return out;
-}
-
-inline amount_t amount_t::round() const {
- return round(commodity().precision());
-}
-
-inline commodity_t& amount_t::commodity() const {
- if (! commodity_)
- return *commodity_t::null_commodity;
- else
- return *commodity_;
-}
-
-class amount_error : public error {
- public:
- amount_error(const std::string& reason) throw() : error(reason) {}
- virtual ~amount_error() throw() {}
-};
-
-struct compare_amount_commodities {
- bool operator()(const amount_t * left, const amount_t * right) const;
-};
-
-} // namespace ledger
-
-#endif // _AMOUNT_H
diff --git a/amounts.cc b/amounts.cc
deleted file mode 100644
index 6bb9ddbc..00000000
--- a/amounts.cc
+++ /dev/null
@@ -1,14 +0,0 @@
-#include <boost/python.hpp>
-
-using namespace boost::python;
-
-void export_amount();
-void export_balance();
-void export_value();
-
-BOOST_PYTHON_MODULE(amounts)
-{
- export_amount();
- export_balance();
- export_value();
-}
diff --git a/balance.cc b/balance.cc
deleted file mode 100644
index 9e516736..00000000
--- a/balance.cc
+++ /dev/null
@@ -1,529 +0,0 @@
-#include "balance.h"
-#include "util.h"
-
-#include <deque>
-#include <algorithm>
-
-namespace ledger {
-
-amount_t balance_t::amount(const commodity_t& commodity) const
-{
- if (! commodity) {
- if (amounts.size() == 1) {
- amounts_map::const_iterator i = amounts.begin();
- return (*i).second;
- }
- else if (amounts.size() > 1) {
- // Try stripping annotations before giving an error.
- balance_t temp(strip_annotations());
- if (temp.amounts.size() == 1)
- return temp.amount(commodity);
-
- std::ostringstream errmsg;
- errmsg << "Requested amount of a balance with multiple commodities: "
- << temp;
- throw new amount_error(errmsg.str());
- }
- }
- else if (amounts.size() > 0) {
- amounts_map::const_iterator i = amounts.find(&commodity);
- if (i != amounts.end())
- return (*i).second;
- }
- return amount_t();
-}
-
-balance_t balance_t::value(const datetime_t& moment) const
-{
- balance_t temp;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- temp += (*i).second.value(moment);
-
- return temp;
-}
-
-balance_t balance_t::price() const
-{
- balance_t temp;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- temp += (*i).second.price();
-
- return temp;
-}
-
-datetime_t balance_t::date() const
-{
- datetime_t temp;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++) {
- datetime_t date = (*i).second.date();
- if (! temp && date)
- temp = date;
- else if (temp != date)
- return datetime_t();
- }
-
- return temp;
-}
-
-balance_t balance_t::strip_annotations(const bool keep_price,
- const bool keep_date,
- const bool keep_tag) const
-{
- balance_t temp;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- temp += (*i).second.strip_annotations(keep_price, keep_date, keep_tag);
-
- return temp;
-}
-
-void balance_t::write(std::ostream& out,
- const int first_width,
- const int latter_width) const
-{
- bool first = true;
- int lwidth = latter_width;
-
- if (lwidth == -1)
- lwidth = first_width;
-
- if (commodity_t::commodities_sorted) {
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++) {
- int width;
- if (! first) {
- out << std::endl;
- width = lwidth;
- } else {
- first = false;
- width = first_width;
- }
-
- out.width(width);
- out.fill(' ');
- out << std::right << (*i).second;
- }
- } else {
- typedef std::deque<const amount_t *> amounts_deque;
- amounts_deque sorted;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second)
- sorted.push_back(&(*i).second);
-
- std::stable_sort(sorted.begin(), sorted.end(), compare_amount_commodities());
-
- for (amounts_deque::const_iterator i = sorted.begin();
- i != sorted.end();
- i++) {
- int width;
- if (! first) {
- out << std::endl;
- width = lwidth;
- } else {
- first = false;
- width = first_width;
- }
-
- out.width(width);
- out.fill(' ');
- out << std::right << **i;
- }
- }
-
- if (first) {
- out.width(first_width);
- out.fill(' ');
- out << std::right << "0";
- }
-}
-
-balance_t& balance_t::operator*=(const balance_t& bal)
-{
- if (realzero() || bal.realzero()) {
- return *this = 0L;
- }
- else if (bal.amounts.size() == 1) {
- return *this *= (*bal.amounts.begin()).second;
- }
- else if (amounts.size() == 1) {
- return *this = bal * *this;
- }
- else {
- // Since we would fail with an error at this point otherwise, try
- // stripping annotations to see if we can come up with a
- // reasonable result. The user will not notice any annotations
- // missing (since they are viewing a stripped report anyway), only
- // that some of their value expression may not see any pricing or
- // date data because of this operation.
-
- balance_t temp(bal.strip_annotations());
- if (temp.amounts.size() == 1)
- return *this *= temp;
- temp = strip_annotations();
- if (temp.amounts.size() == 1)
- return *this = bal * temp;
-
- std::ostringstream errmsg;
- errmsg << "Cannot multiply two balances: " << temp << " * " << bal;
- throw new amount_error(errmsg.str());
- }
-}
-
-balance_t& balance_t::operator*=(const amount_t& amt)
-{
- if (realzero() || amt.realzero()) {
- return *this = 0L;
- }
- else if (! amt.commodity()) {
- // Multiplying by the null commodity causes all amounts to be
- // increased by the same factor.
- for (amounts_map::iterator i = amounts.begin();
- i != amounts.end();
- i++)
- (*i).second *= amt;
- }
- else if (amounts.size() == 1) {
- *this = (*amounts.begin()).second * amt;
- }
- else {
- amounts_map::iterator i = amounts.find(&amt.commodity());
- if (i != amounts.end()) {
- (*i).second *= amt;
- } else {
- // Try stripping annotations before giving an error.
- balance_t temp(strip_annotations());
- if (temp.amounts.size() == 1) {
- return *this = (*temp.amounts.begin()).second * amt;
- } else {
- i = temp.amounts.find(&amt.commodity());
- if (i != temp.amounts.end())
- return *this = temp * amt;
- }
-
- std::ostringstream errmsg;
- errmsg << "Attempt to multiply balance by a commodity"
- << " not found in that balance: "
- << temp << " * " << amt;
- throw new amount_error(errmsg.str());
- }
- }
- return *this;
-}
-
-balance_t& balance_t::operator/=(const balance_t& bal)
-{
- if (bal.realzero()) {
- std::ostringstream errmsg;
- errmsg << "Attempt to divide by zero: " << *this << " / " << bal;
- throw new amount_error(errmsg.str());
- }
- else if (realzero()) {
- return *this = 0L;
- }
- else if (bal.amounts.size() == 1) {
- return *this /= (*bal.amounts.begin()).second;
- }
- else if (*this == bal) {
- return *this = 1L;
- }
- else {
- // Try stripping annotations before giving an error.
- balance_t temp(bal.strip_annotations());
- if (temp.amounts.size() == 1)
- return *this /= temp;
-
- std::ostringstream errmsg;
- errmsg << "Cannot divide between two balances: " << temp << " / " << bal;
- throw new amount_error(errmsg.str());
- }
-}
-
-balance_t& balance_t::operator/=(const amount_t& amt)
-{
- if (amt.realzero()) {
- std::ostringstream errmsg;
- errmsg << "Attempt to divide by zero: " << *this << " / " << amt;
- throw new amount_error(errmsg.str());
- }
- else if (realzero()) {
- return *this = 0L;
- }
- else if (! amt.commodity()) {
- // Dividing by the null commodity causes all amounts to be
- // decreased by the same factor.
- for (amounts_map::iterator i = amounts.begin();
- i != amounts.end();
- i++)
- (*i).second /= amt;
- }
- else if (amounts.size() == 1 &&
- (*amounts.begin()).first == &amt.commodity()) {
- (*amounts.begin()).second /= amt;
- }
- else {
- amounts_map::iterator i = amounts.find(&amt.commodity());
- if (i != amounts.end()) {
- (*i).second /= amt;
- } else {
- // Try stripping annotations before giving an error.
- balance_t temp(strip_annotations());
- if (temp.amounts.size() == 1 &&
- (*temp.amounts.begin()).first == &amt.commodity())
- return *this = temp / amt;
-
- std::ostringstream errmsg;
- errmsg << "Attempt to divide balance by a commodity"
- << " not found in that balance: "
- << temp << " * " << amt;
- throw new amount_error(errmsg.str());
- }
- }
- return *this;
-}
-
-balance_t::operator amount_t() const
-{
- if (amounts.size() == 1) {
- return (*amounts.begin()).second;
- }
- else if (amounts.size() == 0) {
- return amount_t();
- }
- else {
- // Try stripping annotations before giving an error.
- balance_t temp(strip_annotations());
- if (temp.amounts.size() == 1)
- return (*temp.amounts.begin()).second;
-
- std::ostringstream errmsg;
- errmsg << "Cannot convert a balance with "
- << "multiple commodities to an amount: " << temp;
- throw new amount_error(errmsg.str());
- }
-}
-
-} // namespace ledger
-
-#ifdef USE_BOOST_PYTHON
-
-#include <boost/python.hpp>
-
-using namespace boost::python;
-using namespace ledger;
-
-unsigned int balance_len(balance_t& bal)
-{
- return bal.amounts.size();
-}
-
-amount_t balance_getitem(balance_t& bal, int i)
-{
- std::size_t len = bal.amounts.size();
-
- if (abs(i) >= len) {
- PyErr_SetString(PyExc_IndexError, "Index out of range");
- throw_error_already_set();
- }
-
- int x = i < 0 ? len + i : i;
- amounts_map::iterator elem = bal.amounts.begin();
- while (--x >= 0)
- elem++;
-
- return (*elem).second;
-}
-
-unsigned int balance_pair_len(balance_pair_t& bal_pair)
-{
- return balance_len(bal_pair.quantity);
-}
-
-amount_t balance_pair_getitem(balance_pair_t& bal_pair, int i)
-{
- return balance_getitem(bal_pair.quantity, i);
-}
-
-void export_balance()
-{
- class_< balance_t > ("Balance")
- .def(init<balance_t>())
- .def(init<amount_t>())
- .def(init<long>())
- .def(init<unsigned long>())
- .def(init<double>())
-
- .def(self += self)
- .def(self += other<amount_t>())
- .def(self += long())
- .def(self + self)
- .def(self + other<amount_t>())
- .def(self + long())
- .def(self -= self)
- .def(self -= other<amount_t>())
- .def(self -= long())
- .def(self - self)
- .def(self - other<amount_t>())
- .def(self - long())
- .def(self *= self)
- .def(self *= other<amount_t>())
- .def(self *= long())
- .def(self * self)
- .def(self * other<amount_t>())
- .def(self * long())
- .def(self /= self)
- .def(self /= other<amount_t>())
- .def(self /= long())
- .def(self / self)
- .def(self / other<amount_t>())
- .def(self / long())
- .def(- self)
-
- .def(self < self)
- .def(self < other<amount_t>())
- .def(self < long())
- .def(self <= self)
- .def(self <= other<amount_t>())
- .def(self <= long())
- .def(self > self)
- .def(self > other<amount_t>())
- .def(self > long())
- .def(self >= self)
- .def(self >= other<amount_t>())
- .def(self >= long())
- .def(self == self)
- .def(self == other<amount_t>())
- .def(self == long())
- .def(self != self)
- .def(self != other<amount_t>())
- .def(self != long())
- .def(! self)
-
- .def(abs(self))
- .def(self_ns::str(self))
-
- .def("__len__", balance_len)
- .def("__getitem__", balance_getitem)
-
- .def("valid", &balance_t::valid)
-
- .def("realzero", &balance_t::realzero)
- .def("amount", &balance_t::amount)
- .def("value", &balance_t::value)
- .def("price", &balance_t::price)
- .def("date", &balance_t::date)
- .def("strip_annotations", &balance_t::strip_annotations)
- .def("write", &balance_t::write)
- .def("round", &balance_t::round)
- .def("negate", &balance_t::negate)
- .def("negated", &balance_t::negated)
- ;
-
- class_< balance_pair_t > ("BalancePair")
- .def(init<balance_pair_t>())
- .def(init<balance_t>())
- .def(init<amount_t>())
- .def(init<long>())
- .def(init<unsigned long>())
- .def(init<double>())
-
- .def(self += self)
- .def(self += other<balance_t>())
- .def(self += other<amount_t>())
- .def(self += long())
- .def(self + self)
- .def(self + other<balance_t>())
- .def(self + other<amount_t>())
- .def(self + long())
- .def(self -= self)
- .def(self -= other<balance_t>())
- .def(self -= other<amount_t>())
- .def(self -= long())
- .def(self - self)
- .def(self - other<balance_t>())
- .def(self - other<amount_t>())
- .def(self - long())
- .def(self *= self)
- .def(self *= other<balance_t>())
- .def(self *= other<amount_t>())
- .def(self *= long())
- .def(self * self)
- .def(self * other<balance_t>())
- .def(self * other<amount_t>())
- .def(self * long())
- .def(self /= self)
- .def(self /= other<balance_t>())
- .def(self /= other<amount_t>())
- .def(self /= long())
- .def(self / self)
- .def(self / other<balance_t>())
- .def(self / other<amount_t>())
- .def(self / long())
- .def(- self)
-
- .def(self < self)
- .def(self < other<balance_t>())
- .def(self < other<amount_t>())
- .def(self < long())
- .def(self <= self)
- .def(self <= other<balance_t>())
- .def(self <= other<amount_t>())
- .def(self <= long())
- .def(self > self)
- .def(self > other<balance_t>())
- .def(self > other<amount_t>())
- .def(self > long())
- .def(self >= self)
- .def(self >= other<balance_t>())
- .def(self >= other<amount_t>())
- .def(self >= long())
- .def(self == self)
- .def(self == other<balance_t>())
- .def(self == other<amount_t>())
- .def(self == long())
- .def(self != self)
- .def(self != other<balance_t>())
- .def(self != other<amount_t>())
- .def(self != long())
- .def(! self)
-
- .def(abs(self))
- .def(self_ns::str(self))
-
- .def("__len__", balance_pair_len)
- .def("__getitem__", balance_pair_getitem)
-
- .def("valid", &balance_pair_t::valid)
-
- .def("realzero", &balance_pair_t::realzero)
- .def("amount", &balance_pair_t::amount)
- .def("value", &balance_pair_t::value)
- .def("price", &balance_pair_t::price)
- .def("date", &balance_pair_t::date)
- .def("strip_annotations", &balance_pair_t::strip_annotations)
- .def("write", &balance_pair_t::write)
- .def("round", &balance_pair_t::round)
- .def("negate", &balance_pair_t::negate)
- .def("negated", &balance_pair_t::negated)
-
- .add_property("cost",
- make_getter(&balance_pair_t::cost,
- return_value_policy<reference_existing_object>()))
- ;
-}
-
-#endif // USE_BOOST_PYTHON
diff --git a/balance.h b/balance.h
deleted file mode 100644
index 24b1f2dc..00000000
--- a/balance.h
+++ /dev/null
@@ -1,961 +0,0 @@
-#ifndef _BALANCE_H
-#define _BALANCE_H
-
-#include "amount.h"
-
-#include <map>
-#include <iostream>
-
-namespace ledger {
-
-typedef std::map<const commodity_t *, amount_t> amounts_map;
-typedef std::pair<const commodity_t *, amount_t> amounts_pair;
-
-class balance_t
-{
- public:
- amounts_map amounts;
-
- bool valid() const {
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if (! (*i).second.valid())
- return false;
- return true;
- }
-
- // constructors
- balance_t() {}
- balance_t(const balance_t& bal) {
- for (amounts_map::const_iterator i = bal.amounts.begin();
- i != bal.amounts.end();
- i++)
- *this += (*i).second;
- }
- balance_t(const amount_t& amt) {
- if (! amt.realzero())
- amounts.insert(amounts_pair(&amt.commodity(), amt));
- }
- template <typename T>
- balance_t(T value) {
- amount_t amt(value);
- if (! amt.realzero())
- amounts.insert(amounts_pair(&amt.commodity(), amt));
- }
-
- // assignment operator
- balance_t& operator=(const balance_t& bal) {
- if (this != &bal) {
- amounts.clear();
- for (amounts_map::const_iterator i = bal.amounts.begin();
- i != bal.amounts.end();
- i++)
- *this += (*i).second;
- }
- return *this;
- }
- balance_t& operator=(const amount_t& amt) {
- amounts.clear();
- *this += amt;
- return *this;
- }
- template <typename T>
- balance_t& operator=(T value) {
- amounts.clear();
- *this += value;
- return *this;
- }
-
- // in-place arithmetic
- balance_t& operator+=(const balance_t& bal) {
- for (amounts_map::const_iterator i = bal.amounts.begin();
- i != bal.amounts.end();
- i++)
- *this += (*i).second;
- return *this;
- }
- balance_t& operator+=(const amount_t& amt) {
- amounts_map::iterator i = amounts.find(&amt.commodity());
- if (i != amounts.end())
- (*i).second += amt;
- else if (! amt.realzero())
- amounts.insert(amounts_pair(&amt.commodity(), amt));
- return *this;
- }
- template <typename T>
- balance_t& operator+=(T val) {
- return *this += amount_t(val);
- }
- balance_t& operator-=(const balance_t& bal) {
- for (amounts_map::const_iterator i = bal.amounts.begin();
- i != bal.amounts.end();
- i++)
- *this -= (*i).second;
- return *this;
- }
- balance_t& operator-=(const amount_t& amt) {
- amounts_map::iterator i = amounts.find(&amt.commodity());
- if (i != amounts.end()) {
- (*i).second -= amt;
- if ((*i).second.realzero())
- amounts.erase(i);
- }
- else if (! amt.realzero()) {
- amounts.insert(amounts_pair(&amt.commodity(), - amt));
- }
- return *this;
- }
- template <typename T>
- balance_t& operator-=(T val) {
- return *this -= amount_t(val);
- }
-
- // simple arithmetic
- balance_t operator+(const balance_t& bal) const {
- balance_t temp = *this;
- temp += bal;
- return temp;
- }
- balance_t operator+(const amount_t& amt) const {
- balance_t temp = *this;
- temp += amt;
- return temp;
- }
- template <typename T>
- balance_t operator+(T val) const {
- balance_t temp = *this;
- temp += val;
- return temp;
- }
- balance_t operator-(const balance_t& bal) const {
- balance_t temp = *this;
- temp -= bal;
- return temp;
- }
- balance_t operator-(const amount_t& amt) const {
- balance_t temp = *this;
- temp -= amt;
- return temp;
- }
- template <typename T>
- balance_t operator-(T val) const {
- balance_t temp = *this;
- temp -= val;
- return temp;
- }
-
- // multiplication and divide
- balance_t& operator*=(const balance_t& bal);
- balance_t& operator*=(const amount_t& amt);
- template <typename T>
- balance_t& operator*=(T val) {
- return *this *= amount_t(val);
- }
-
- balance_t& operator/=(const balance_t& bal);
- balance_t& operator/=(const amount_t& amt);
- template <typename T>
- balance_t& operator/=(T val) {
- return *this /= amount_t(val);
- }
-
- // multiplication and divide
- balance_t operator*(const balance_t& bal) const {
- balance_t temp = *this;
- temp *= bal;
- return temp;
- }
- balance_t operator*(const amount_t& amt) const {
- balance_t temp = *this;
- temp *= amt;
- return temp;
- }
- template <typename T>
- balance_t operator*(T val) const {
- balance_t temp = *this;
- temp *= val;
- return temp;
- }
- balance_t operator/(const balance_t& bal) const {
- balance_t temp = *this;
- temp /= bal;
- return temp;
- }
- balance_t operator/(const amount_t& amt) const {
- balance_t temp = *this;
- temp /= amt;
- return temp;
- }
- template <typename T>
- balance_t operator/(T val) const {
- balance_t temp = *this;
- temp /= val;
- return temp;
- }
-
- // comparison
- bool operator<(const balance_t& bal) const {
- for (amounts_map::const_iterator i = bal.amounts.begin();
- i != bal.amounts.end();
- i++)
- if (! (amount(*(*i).first) < (*i).second))
- return false;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if (! ((*i).second < bal.amount(*(*i).first)))
- return false;
-
- if (bal.amounts.size() == 0 && amounts.size() == 0)
- return false;
-
- return true;
- }
- bool operator<(const amount_t& amt) const {
- if (amt.commodity())
- return amount(amt.commodity()) < amt;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second < amt)
- return true;
- return false;
- }
- template <typename T>
- bool operator<(T val) const {
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second < val)
- return true;
- return false;
- }
-
- bool operator<=(const balance_t& bal) const {
- for (amounts_map::const_iterator i = bal.amounts.begin();
- i != bal.amounts.end();
- i++)
- if (! (amount(*(*i).first) <= (*i).second))
- return false;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if (! ((*i).second <= bal.amount(*(*i).first)))
- return false;
-
- return true;
- }
- bool operator<=(const amount_t& amt) const {
- if (amt.commodity())
- return amount(amt.commodity()) <= amt;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second <= amt)
- return true;
- return false;
- }
- template <typename T>
- bool operator<=(T val) const {
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second <= val)
- return true;
- return false;
- }
-
- bool operator>(const balance_t& bal) const {
- for (amounts_map::const_iterator i = bal.amounts.begin();
- i != bal.amounts.end();
- i++)
- if (! (amount(*(*i).first) > (*i).second))
- return false;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if (! ((*i).second > bal.amount(*(*i).first)))
- return false;
-
- if (bal.amounts.size() == 0 && amounts.size() == 0)
- return false;
-
- return true;
- }
- bool operator>(const amount_t& amt) const {
- if (amt.commodity())
- return amount(amt.commodity()) > amt;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second > amt)
- return true;
- return false;
- }
- template <typename T>
- bool operator>(T val) const {
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second > val)
- return true;
- return false;
- }
-
- bool operator>=(const balance_t& bal) const {
- for (amounts_map::const_iterator i = bal.amounts.begin();
- i != bal.amounts.end();
- i++)
- if (! (amount(*(*i).first) >= (*i).second))
- return false;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if (! ((*i).second >= bal.amount(*(*i).first)))
- return false;
-
- return true;
- }
- bool operator>=(const amount_t& amt) const {
- if (amt.commodity())
- return amount(amt.commodity()) >= amt;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second >= amt)
- return true;
- return false;
- }
- template <typename T>
- bool operator>=(T val) const {
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second >= val)
- return true;
- return false;
- }
-
- bool operator==(const balance_t& bal) const {
- amounts_map::const_iterator i, j;
- for (i = amounts.begin(), j = bal.amounts.begin();
- i != amounts.end() && j != bal.amounts.end();
- i++, j++) {
- if (! ((*i).first == (*j).first &&
- (*i).second == (*j).second))
- return false;
- }
- return i == amounts.end() && j == bal.amounts.end();
- }
- bool operator==(const amount_t& amt) const {
- if (amt.commodity())
- return amounts.size() == 1 && (*amounts.begin()).second == amt;
-
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second == amt)
- return true;
- return false;
- }
- template <typename T>
- bool operator==(T val) const {
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second == val)
- return true;
- return false;
- }
-
- bool operator!=(const balance_t& bal) const {
- return ! (*this == bal);
- }
- bool operator!=(const amount_t& amt) const {
- return ! (*this == amt);
- }
- template <typename T>
- bool operator!=(T val) const {
- return ! (*this == val);
- }
-
- // unary negation
- void negate() {
- for (amounts_map::iterator i = amounts.begin();
- i != amounts.end();
- i++)
- (*i).second.negate();
- }
- balance_t negated() const {
- balance_t temp = *this;
- temp.negate();
- return temp;
- }
- balance_t operator-() const {
- return negated();
- }
-
- // conversion operators
- operator amount_t() const;
- operator bool() const {
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second)
- return true;
- return false;
- }
-
- bool realzero() const {
- if (amounts.size() == 0)
- return true;
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if (! (*i).second.realzero())
- return false;
- return true;
- }
-
- amount_t amount(const commodity_t& commodity =
- *commodity_t::null_commodity) const;
- balance_t value(const datetime_t& moment = datetime_t::now) const;
- balance_t price() const;
- datetime_t date() const;
-
- balance_t
- strip_annotations(const bool keep_price = amount_t::keep_price,
- const bool keep_date = amount_t::keep_date,
- const bool keep_tag = amount_t::keep_tag) const;
-
- void write(std::ostream& out, const int first_width,
- const int latter_width = -1) const;
-
- void abs() {
- for (amounts_map::iterator i = amounts.begin();
- i != amounts.end();
- i++)
- (*i).second.abs();
- }
-
- void reduce() {
- for (amounts_map::iterator i = amounts.begin();
- i != amounts.end();
- i++)
- (*i).second.reduce();
- }
-
- balance_t reduced() const {
- balance_t temp(*this);
- temp.reduce();
- return temp;
- }
-
- void round() {
- for (amounts_map::iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second.commodity())
- (*i).second = (*i).second.round();
- }
-
- balance_t unround() const {
- balance_t temp;
- for (amounts_map::const_iterator i = amounts.begin();
- i != amounts.end();
- i++)
- if ((*i).second.commodity())
- temp += (*i).second.unround();
- return temp;
- }
-};
-
-inline balance_t abs(const balance_t& bal) {
- balance_t temp = bal;
- temp.abs();
- return temp;
-}
-
-inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) {
- bal.write(out, 12);
- return out;
-}
-
-class balance_pair_t
-{
- public:
- balance_t quantity;
- balance_t * cost;
-
- // constructors
- balance_pair_t() : cost(NULL) {}
- balance_pair_t(const balance_pair_t& bal_pair)
- : quantity(bal_pair.quantity), cost(NULL) {
- if (bal_pair.cost)
- cost = new balance_t(*bal_pair.cost);
- }
- balance_pair_t(const balance_t& _quantity)
- : quantity(_quantity), cost(NULL) {}
- balance_pair_t(const amount_t& _quantity)
- : quantity(_quantity), cost(NULL) {}
- template <typename T>
- balance_pair_t(T value) : quantity(value), cost(NULL) {}
-
- // destructor
- ~balance_pair_t() {
- if (cost) delete cost;
- }
-
- // assignment operator
- balance_pair_t& operator=(const balance_pair_t& bal_pair) {
- if (this != &bal_pair) {
- if (cost) {
- delete cost;
- cost = NULL;
- }
- quantity = bal_pair.quantity;
- if (bal_pair.cost)
- cost = new balance_t(*bal_pair.cost);
- }
- return *this;
- }
- balance_pair_t& operator=(const balance_t& bal) {
- if (cost) {
- delete cost;
- cost = NULL;
- }
- quantity = bal;
- return *this;
- }
- balance_pair_t& operator=(const amount_t& amt) {
- if (cost) {
- delete cost;
- cost = NULL;
- }
- quantity = amt;
- return *this;
- }
- template <typename T>
- balance_pair_t& operator=(T value) {
- if (cost) {
- delete cost;
- cost = NULL;
- }
- quantity = value;
- return *this;
- }
-
- // in-place arithmetic
- balance_pair_t& operator+=(const balance_pair_t& bal_pair) {
- if (bal_pair.cost && ! cost)
- cost = new balance_t(quantity);
- quantity += bal_pair.quantity;
- if (cost)
- *cost += bal_pair.cost ? *bal_pair.cost : bal_pair.quantity;
- return *this;
- }
- balance_pair_t& operator+=(const balance_t& bal) {
- quantity += bal;
- if (cost)
- *cost += bal;
- return *this;
- }
- balance_pair_t& operator+=(const amount_t& amt) {
- quantity += amt;
- if (cost)
- *cost += amt;
- return *this;
- }
- template <typename T>
- balance_pair_t& operator+=(T val) {
- return *this += amount_t(val);
- }
-
- balance_pair_t& operator-=(const balance_pair_t& bal_pair) {
- if (bal_pair.cost && ! cost)
- cost = new balance_t(quantity);
- quantity -= bal_pair.quantity;
- if (cost)
- *cost -= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity;
- return *this;
- }
- balance_pair_t& operator-=(const balance_t& bal) {
- quantity -= bal;
- if (cost)
- *cost -= bal;
- return *this;
- }
- balance_pair_t& operator-=(const amount_t& amt) {
- quantity -= amt;
- if (cost)
- *cost -= amt;
- return *this;
- }
- template <typename T>
- balance_pair_t& operator-=(T val) {
- return *this -= amount_t(val);
- }
-
- // simple arithmetic
- balance_pair_t operator+(const balance_pair_t& bal_pair) const {
- balance_pair_t temp = *this;
- temp += bal_pair;
- return temp;
- }
- balance_pair_t operator+(const balance_t& bal) const {
- balance_pair_t temp = *this;
- temp += bal;
- return temp;
- }
- balance_pair_t operator+(const amount_t& amt) const {
- balance_pair_t temp = *this;
- temp += amt;
- return temp;
- }
- template <typename T>
- balance_pair_t operator+(T val) const {
- balance_pair_t temp = *this;
- temp += val;
- return temp;
- }
-
- balance_pair_t operator-(const balance_pair_t& bal_pair) const {
- balance_pair_t temp = *this;
- temp -= bal_pair;
- return temp;
- }
- balance_pair_t operator-(const balance_t& bal) const {
- balance_pair_t temp = *this;
- temp -= bal;
- return temp;
- }
- balance_pair_t operator-(const amount_t& amt) const {
- balance_pair_t temp = *this;
- temp -= amt;
- return temp;
- }
- template <typename T>
- balance_pair_t operator-(T val) const {
- balance_pair_t temp = *this;
- temp -= val;
- return temp;
- }
-
- // multiplication and division
- balance_pair_t& operator*=(const balance_pair_t& bal_pair) {
- if (bal_pair.cost && ! cost)
- cost = new balance_t(quantity);
- quantity *= bal_pair.quantity;
- if (cost)
- *cost *= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity;
- return *this;
- }
- balance_pair_t& operator*=(const balance_t& bal) {
- quantity *= bal;
- if (cost)
- *cost *= bal;
- return *this;
- }
- balance_pair_t& operator*=(const amount_t& amt) {
- quantity *= amt;
- if (cost)
- *cost *= amt;
- return *this;
- }
- template <typename T>
- balance_pair_t& operator*=(T val) {
- return *this *= amount_t(val);
- }
-
- balance_pair_t& operator/=(const balance_pair_t& bal_pair) {
- if (bal_pair.cost && ! cost)
- cost = new balance_t(quantity);
- quantity /= bal_pair.quantity;
- if (cost)
- *cost /= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity;
- return *this;
- }
- balance_pair_t& operator/=(const balance_t& bal) {
- quantity /= bal;
- if (cost)
- *cost /= bal;
- return *this;
- }
- balance_pair_t& operator/=(const amount_t& amt) {
- quantity /= amt;
- if (cost)
- *cost /= amt;
- return *this;
- }
- template <typename T>
- balance_pair_t& operator/=(T val) {
- return *this /= amount_t(val);
- }
-
- balance_pair_t operator*(const balance_pair_t& bal_pair) const {
- balance_pair_t temp = *this;
- temp *= bal_pair;
- return temp;
- }
- balance_pair_t operator*(const balance_t& bal) const {
- balance_pair_t temp = *this;
- temp *= bal;
- return temp;
- }
- balance_pair_t operator*(const amount_t& amt) const {
- balance_pair_t temp = *this;
- temp *= amt;
- return temp;
- }
- template <typename T>
- balance_pair_t operator*(T val) const {
- balance_pair_t temp = *this;
- temp *= val;
- return temp;
- }
-
- balance_pair_t operator/(const balance_pair_t& bal_pair) const {
- balance_pair_t temp = *this;
- temp /= bal_pair;
- return temp;
- }
- balance_pair_t operator/(const balance_t& bal) const {
- balance_pair_t temp = *this;
- temp /= bal;
- return temp;
- }
- balance_pair_t operator/(const amount_t& amt) const {
- balance_pair_t temp = *this;
- temp /= amt;
- return temp;
- }
- template <typename T>
- balance_pair_t operator/(T val) const {
- balance_pair_t temp = *this;
- temp /= val;
- return temp;
- }
-
- // comparison
- bool operator<(const balance_pair_t& bal_pair) const {
- return quantity < bal_pair.quantity;
- }
- bool operator<(const balance_t& bal) const {
- return quantity < bal;
- }
- bool operator<(const amount_t& amt) const {
- return quantity < amt;
- }
- template <typename T>
- bool operator<(T val) const {
- return quantity < val;
- }
-
- bool operator<=(const balance_pair_t& bal_pair) const {
- return quantity <= bal_pair.quantity;
- }
- bool operator<=(const balance_t& bal) const {
- return quantity <= bal;
- }
- bool operator<=(const amount_t& amt) const {
- return quantity <= amt;
- }
- template <typename T>
- bool operator<=(T val) const {
- return quantity <= val;
- }
-
- bool operator>(const balance_pair_t& bal_pair) const {
- return quantity > bal_pair.quantity;
- }
- bool operator>(const balance_t& bal) const {
- return quantity > bal;
- }
- bool operator>(const amount_t& amt) const {
- return quantity > amt;
- }
- template <typename T>
- bool operator>(T val) const {
- return quantity > val;
- }
-
- bool operator>=(const balance_pair_t& bal_pair) const {
- return quantity >= bal_pair.quantity;
- }
- bool operator>=(const balance_t& bal) const {
- return quantity >= bal;
- }
- bool operator>=(const amount_t& amt) const {
- return quantity >= amt;
- }
- template <typename T>
- bool operator>=(T val) const {
- return quantity >= val;
- }
-
- bool operator==(const balance_pair_t& bal_pair) const {
- return quantity == bal_pair.quantity;
- }
- bool operator==(const balance_t& bal) const {
- return quantity == bal;
- }
- bool operator==(const amount_t& amt) const {
- return quantity == amt;
- }
- template <typename T>
- bool operator==(T val) const {
- return quantity == val;
- }
-
- bool operator!=(const balance_pair_t& bal_pair) const {
- return ! (*this == bal_pair);
- }
- bool operator!=(const balance_t& bal) const {
- return ! (*this == bal);
- }
- bool operator!=(const amount_t& amt) const {
- return ! (*this == amt);
- }
- template <typename T>
- bool operator!=(T val) const {
- return ! (*this == val);
- }
-
- // unary negation
- void negate() {
- quantity.negate();
- if (cost) cost->negate();
- }
- balance_pair_t negated() const {
- balance_pair_t temp = *this;
- temp.negate();
- return temp;
- }
- balance_pair_t operator-() const {
- return negated();
- }
-
- // test for non-zero (use ! for zero)
- operator balance_t() const {
- return quantity;
- }
- operator amount_t() const {
- return quantity;
- }
- operator bool() const {
- return quantity;
- }
-
- bool realzero() const {
- return ((! cost || cost->realzero()) && quantity.realzero());
- }
-
- void abs() {
- quantity.abs();
- if (cost) cost->abs();
- }
-
- amount_t amount(const commodity_t& commodity =
- *commodity_t::null_commodity) const {
- return quantity.amount(commodity);
- }
- balance_t value(const datetime_t& moment = datetime_t::now) const {
- return quantity.value(moment);
- }
- balance_t price() const {
- return quantity.price();
- }
- datetime_t date() const {
- return quantity.date();
- }
-
- balance_t
- strip_annotations(const bool keep_price = amount_t::keep_price,
- const bool keep_date = amount_t::keep_date,
- const bool keep_tag = amount_t::keep_tag) const {
- return quantity.strip_annotations(keep_price, keep_date, keep_tag);
- }
-
- void write(std::ostream& out, const int first_width,
- const int latter_width = -1) const {
- quantity.write(out, first_width, latter_width);
- }
-
- balance_pair_t& add(const amount_t& amount,
- const amount_t * a_cost = NULL) {
- if (a_cost && ! cost)
- cost = new balance_t(quantity);
- quantity += amount;
- if (cost)
- *cost += a_cost ? *a_cost : amount;
- return *this;
- }
-
- bool valid() {
- return quantity.valid() && (! cost || cost->valid());
- }
-
- void reduce() {
- quantity.reduce();
- if (cost) cost->reduce();
- }
-
- balance_pair_t reduced() const {
- balance_pair_t temp(*this);
- temp.reduce();
- return temp;
- }
-
- void round() {
- quantity.round();
- if (cost) cost->round();
- }
-
- balance_pair_t unround() {
- balance_pair_t temp(quantity.unround());
- if (cost)
- temp.cost = new balance_t(cost->unround());
- return temp;
- }
-
- friend inline bool operator<(const balance_t& bal, const balance_pair_t& bal_pair) {
- return bal_pair >= bal;
- }
- friend inline bool operator<=(const balance_t& bal, const balance_pair_t& bal_pair) {
- return bal_pair > bal;
- }
- friend inline bool operator>(const balance_t& bal, const balance_pair_t& bal_pair) {
- return bal_pair <= bal;
- }
- friend inline bool operator>=(const balance_t& bal, const balance_pair_t& bal_pair) {
- return bal_pair < bal;
- }
- friend inline bool operator==(const balance_t& bal, const balance_pair_t& bal_pair) {
- return bal_pair == bal;
- }
-};
-
-inline balance_pair_t abs(const balance_pair_t& bal_pair) {
- balance_pair_t temp;
- temp.abs();
- return temp;
-}
-
-inline std::ostream& operator<<(std::ostream& out,
- const balance_pair_t& bal_pair) {
- bal_pair.quantity.write(out, 12);
- return out;
-}
-
-} // namespace ledger
-
-#endif // _BALANCE_H
diff --git a/binary.cc b/binary.cc
deleted file mode 100644
index 295fc1bc..00000000
--- a/binary.cc
+++ /dev/null
@@ -1,1338 +0,0 @@
-#include "journal.h"
-#include "valexpr.h"
-#include "binary.h"
-
-#include <fstream>
-#include <sys/stat.h>
-
-#define TIMELOG_SUPPORT 1
-
-namespace ledger {
-
-static unsigned long binary_magic_number = 0xFFEED765;
-#ifdef DEBUG_ENABLED
-static unsigned long format_version = 0x0002060d;
-#else
-static unsigned long format_version = 0x0002060c;
-#endif
-
-static account_t ** accounts;
-static account_t ** accounts_next;
-static unsigned int account_index;
-
-static commodity_base_t ** base_commodities;
-static commodity_base_t ** base_commodities_next;
-static unsigned int base_commodity_index;
-
-static commodity_t ** commodities;
-static commodity_t ** commodities_next;
-static unsigned int commodity_index;
-
-extern char * bigints;
-extern char * bigints_next;
-extern unsigned int bigints_index;
-extern unsigned int bigints_count;
-
-template <typename T>
-inline void read_binary_number_nocheck(std::istream& in, T& num) {
- in.read((char *)&num, sizeof(num));
-}
-
-template <typename T>
-inline T read_binary_number_nocheck(std::istream& in) {
- T num;
- read_binary_number_nocheck(in, num);
- return num;
-}
-
-template <typename T>
-inline void read_binary_number_nocheck(char *& data, T& num) {
- num = *((T *) data);
- data += sizeof(T);
-}
-
-template <typename T>
-inline T read_binary_number_nocheck(char *& data) {
- T num;
- read_binary_number_nocheck(data, num);
- return num;
-}
-
-#if DEBUG_LEVEL >= ALPHA
-static void assert_failed() {
- assert(0);
-}
-#define read_binary_guard(in, id) \
- if (read_binary_number_nocheck<unsigned short>(in) != id) \
- assert_failed();
-#else
-#define read_binary_guard(in, id)
-#endif
-
-template <typename T>
-inline void read_binary_number(std::istream& in, T& num) {
- read_binary_guard(in, 0x2003);
- in.read((char *)&num, sizeof(num));
- read_binary_guard(in, 0x2004);
-}
-
-inline void read_binary_bool(std::istream& in, bool& num) {
- read_binary_guard(in, 0x2005);
- unsigned char val;
- in.read((char *)&val, sizeof(val));
- num = val == 1;
- read_binary_guard(in, 0x2006);
-}
-
-template <typename T>
-inline void read_binary_long(std::istream& in, T& num) {
- read_binary_guard(in, 0x2001);
-
- unsigned char len;
- read_binary_number_nocheck(in, len);
-
- num = 0;
- unsigned char temp;
- if (len > 3) {
- read_binary_number_nocheck(in, temp);
- num |= ((unsigned long)temp) << 24;
- }
- if (len > 2) {
- read_binary_number_nocheck(in, temp);
- num |= ((unsigned long)temp) << 16;
- }
- if (len > 1) {
- read_binary_number_nocheck(in, temp);
- num |= ((unsigned long)temp) << 8;
- }
-
- read_binary_number_nocheck(in, temp);
- num |= ((unsigned long)temp);
-
- read_binary_guard(in, 0x2002);
-}
-
-template <typename T>
-inline T read_binary_number(std::istream& in) {
- T num;
- read_binary_number(in, num);
- return num;
-}
-
-inline bool read_binary_bool(std::istream& in) {
- bool num;
- read_binary_bool(in, num);
- return num;
-}
-
-template <typename T>
-inline T read_binary_long(std::istream& in) {
- T num;
- read_binary_long(in, num);
- return num;
-}
-
-inline void read_binary_string(std::istream& in, std::string& str)
-{
- read_binary_guard(in, 0x3001);
-
- unsigned char len;
- read_binary_number_nocheck(in, len);
- if (len == 0xff) {
- unsigned short slen;
- read_binary_number_nocheck(in, slen);
- char * buf = new char[slen + 1];
- in.read(buf, slen);
- buf[slen] = '\0';
- str = buf;
- delete[] buf;
- }
- else if (len) {
- char buf[256];
- in.read(buf, len);
- buf[len] = '\0';
- str = buf;
- } else {
- str = "";
- }
-
- read_binary_guard(in, 0x3002);
-}
-
-inline std::string read_binary_string(std::istream& in) {
- std::string temp;
- read_binary_string(in, temp);
- return temp;
-}
-
-template <typename T>
-inline void read_binary_number(char *& data, T& num) {
- read_binary_guard(data, 0x2003);
- num = *((T *) data);
- data += sizeof(T);
- read_binary_guard(data, 0x2004);
-}
-
-inline void read_binary_bool(char *& data, bool& num) {
- read_binary_guard(data, 0x2005);
- unsigned char val = *((unsigned char *) data);
- data += sizeof(unsigned char);
- num = val == 1;
- read_binary_guard(data, 0x2006);
-}
-
-template <typename T>
-inline void read_binary_long(char *& data, T& num) {
- read_binary_guard(data, 0x2001);
-
- unsigned char len;
- read_binary_number_nocheck(data, len);
-
- num = 0;
- unsigned char temp;
- if (len > 3) {
- read_binary_number_nocheck(data, temp);
- num |= ((unsigned long)temp) << 24;
- }
- if (len > 2) {
- read_binary_number_nocheck(data, temp);
- num |= ((unsigned long)temp) << 16;
- }
- if (len > 1) {
- read_binary_number_nocheck(data, temp);
- num |= ((unsigned long)temp) << 8;
- }
-
- read_binary_number_nocheck(data, temp);
- num |= ((unsigned long)temp);
-
- read_binary_guard(data, 0x2002);
-}
-
-template <typename T>
-inline T read_binary_number(char *& data) {
- T num;
- read_binary_number(data, num);
- return num;
-}
-
-inline bool read_binary_bool(char *& data) {
- bool num;
- read_binary_bool(data, num);
- return num;
-}
-
-template <typename T>
-inline T read_binary_long(char *& data) {
- T num;
- read_binary_long(data, num);
- return num;
-}
-
-inline void read_binary_string(char *& data, std::string& str)
-{
- read_binary_guard(data, 0x3001);
-
- unsigned char len;
- read_binary_number_nocheck(data, len);
- if (len == 0xff) {
- unsigned short slen;
- read_binary_number_nocheck(data, slen);
- str = std::string(data, slen);
- data += slen;
- }
- else if (len) {
- str = std::string(data, len);
- data += len;
- }
- else {
- str = "";
- }
-
- read_binary_guard(data, 0x3002);
-}
-
-inline std::string read_binary_string(char *& data)
-{
- std::string temp;
- read_binary_string(data, temp);
- return temp;
-}
-
-inline void read_binary_string(char *& data, std::string * str)
-{
- read_binary_guard(data, 0x3001);
-
- unsigned char len;
- read_binary_number_nocheck(data, len);
- if (len == 0xff) {
- unsigned short slen;
- read_binary_number_nocheck(data, slen);
- new(str) std::string(data, slen);
- data += slen;
- }
- else if (len) {
- new(str) std::string(data, len);
- data += len;
- }
- else {
- new(str) std::string("");
- }
-
- read_binary_guard(data, 0x3002);
-}
-
-inline void read_binary_amount(char *& data, amount_t& amt)
-{
- commodity_t::ident_t ident;
- read_binary_long(data, ident);
- if (ident == 0xffffffff)
- amt.commodity_ = NULL;
- else if (ident == 0)
- amt.commodity_ = commodity_t::null_commodity;
- else
- amt.commodity_ = commodities[ident - 1];
-
- amt.read_quantity(data);
-}
-
-inline void read_binary_value(char *& data, value_t& val)
-{
- val.type = static_cast<value_t::type_t>(read_binary_long<int>(data));
-
- switch (val.type) {
- case value_t::BOOLEAN:
- read_binary_bool(data, *((bool *) val.data));
- break;
- case value_t::INTEGER:
- read_binary_long(data, *((long *) val.data));
- break;
- case value_t::DATETIME:
- read_binary_number(data, *((datetime_t *) val.data));
- break;
- case value_t::AMOUNT:
- read_binary_amount(data, *((amount_t *) val.data));
- break;
-
- case value_t::BALANCE:
- case value_t::BALANCE_PAIR:
- assert(0);
- break;
- }
-}
-
-inline void read_binary_mask(char *& data, mask_t *& mask)
-{
- bool exclude;
- read_binary_number(data, exclude);
- std::string pattern;
- read_binary_string(data, pattern);
-
- mask = new mask_t(pattern);
- mask->exclude = exclude;
-}
-
-inline void read_binary_value_expr(char *& data, value_expr_t *& expr)
-{
- if (! read_binary_bool(data)) {
- expr = NULL;
- return;
- }
-
- value_expr_t::kind_t kind;
- read_binary_number(data, kind);
-
- expr = new value_expr_t(kind);
-
- 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::O_ARG:
- case value_expr_t::INDEX:
- read_binary_long(data, expr->arg_index);
- break;
- case value_expr_t::CONSTANT:
- expr->value = new value_t;
- read_binary_value(data, *expr->value);
- 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_bool(data))
- 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;
- }
-}
-
-
-inline void read_binary_transaction(char *& data, transaction_t * xact)
-{
- read_binary_number(data, xact->_date);
- read_binary_number(data, xact->_date_eff);
- xact->account = accounts[read_binary_long<account_t::ident_t>(data) - 1];
-
- unsigned char flag = read_binary_number<unsigned char>(data);
- if (flag == 0) {
- read_binary_amount(data, xact->amount);
- }
- else if (flag == 1) {
- read_binary_amount(data, xact->amount);
- read_binary_string(data, xact->amount_expr.expr);
- }
- else {
- value_expr_t * ptr = NULL;
- read_binary_value_expr(data, ptr);
- assert(ptr);
- xact->amount_expr.reset(ptr);
- read_binary_string(data, xact->amount_expr.expr);
- }
-
- if (read_binary_bool(data)) {
- xact->cost = new amount_t;
- read_binary_amount(data, *xact->cost);
- read_binary_string(data, xact->cost_expr);
- } else {
- xact->cost = NULL;
- }
-
- read_binary_number(data, xact->state);
- read_binary_number(data, xact->flags);
- xact->flags |= TRANSACTION_BULK_ALLOC;
- read_binary_string(data, &xact->note);
-
- 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);
-
- xact->data = NULL;
-
- if (xact->amount_expr)
- compute_amount(xact->amount_expr, xact->amount, xact);
-}
-
-inline void read_binary_entry_base(char *& data, entry_base_t * entry,
- transaction_t *& xact_pool, bool& finalize)
-{
- 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);
-
- bool ignore_calculated = read_binary_bool(data);
-
- for (unsigned long i = 0, count = read_binary_long<unsigned long>(data);
- i < count;
- i++) {
- new(xact_pool) transaction_t;
- read_binary_transaction(data, xact_pool);
- if (ignore_calculated && xact_pool->flags & TRANSACTION_CALCULATED)
- finalize = true;
- entry->add_transaction(xact_pool++);
- }
-}
-
-inline void read_binary_entry(char *& data, entry_t * entry,
- transaction_t *& xact_pool, bool& finalize)
-{
- read_binary_entry_base(data, entry, xact_pool, finalize);
- read_binary_number(data, entry->_date);
- read_binary_number(data, entry->_date_eff);
- read_binary_string(data, &entry->code);
- read_binary_string(data, &entry->payee);
-}
-
-inline void read_binary_auto_entry(char *& data, auto_entry_t * entry,
- transaction_t *& xact_pool)
-{
- bool ignore;
- read_binary_entry_base(data, entry, xact_pool, ignore);
- 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,
- transaction_t *& xact_pool, bool& finalize)
-{
- read_binary_entry_base(data, entry, xact_pool, finalize);
- read_binary_string(data, &entry->period_string);
- std::istringstream stream(entry->period_string);
- entry->period.parse(stream);
-}
-
-inline commodity_base_t * read_binary_commodity_base(char *& data)
-{
- commodity_base_t * commodity = new commodity_base_t;
- *base_commodities_next++ = commodity;
-
- read_binary_string(data, commodity->symbol);
- read_binary_string(data, commodity->name);
- read_binary_string(data, commodity->note);
- read_binary_number(data, commodity->precision);
- read_binary_number(data, commodity->flags);
-
- return commodity;
-}
-
-inline void read_binary_commodity_base_extra(char *& data,
- commodity_t::ident_t ident)
-{
- commodity_base_t * commodity = base_commodities[ident];
-
- bool read_history = false;
- for (unsigned long i = 0, count = read_binary_long<unsigned long>(data);
- i < count;
- i++) {
- datetime_t when;
- read_binary_number(data, when);
- amount_t amt;
- read_binary_amount(data, amt);
-
- // 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_base_t::history_t;
- commodity->history->prices.insert(history_pair(when, amt));
-
- read_history = true;
- }
- if (read_history)
- read_binary_number(data, commodity->history->last_lookup);
-
- if (read_binary_bool(data)) {
- amount_t amt;
- read_binary_amount(data, amt);
- commodity->smaller = new amount_t(amt);
- }
-
- if (read_binary_bool(data)) {
- amount_t amt;
- read_binary_amount(data, amt);
- commodity->larger = new amount_t(amt);
- }
-}
-
-inline commodity_t * read_binary_commodity(char *& data)
-{
- commodity_t * commodity = new commodity_t;
- *commodities_next++ = commodity;
-
- commodity->base =
- base_commodities[read_binary_long<commodity_base_t::ident_t>(data) - 1];
-
- read_binary_string(data, commodity->qualified_symbol);
- commodity->annotated = false;
-
- return commodity;
-}
-
-inline commodity_t * read_binary_commodity_annotated(char *& data)
-{
- annotated_commodity_t * commodity = new annotated_commodity_t;
- *commodities_next++ = commodity;
-
- commodity->base =
- base_commodities[read_binary_long<commodity_base_t::ident_t>(data) - 1];
-
- read_binary_string(data, commodity->qualified_symbol);
- commodity->annotated = true;
-
- commodity->ptr =
- commodities[read_binary_long<commodity_t::ident_t>(data) - 1];
-
- // This read-and-then-assign causes a new amount to be allocated
- // which does not live within the bulk allocation pool, since that
- // pool will be deleted *before* the commodities are destroyed.
- amount_t amt;
- read_binary_amount(data, amt);
- commodity->price = amt;
-
- read_binary_number(data, commodity->date);
- read_binary_string(data, commodity->tag);
-
- return commodity;
-}
-
-inline
-account_t * read_binary_account(char *& data, journal_t * journal,
- account_t * master = NULL)
-{
- account_t * acct = new account_t(NULL);
- *accounts_next++ = acct;
-
- acct->journal = journal;
-
- account_t::ident_t id;
- read_binary_long(data, id); // parent id
- if (id == 0xffffffff)
- acct->parent = NULL;
- else
- acct->parent = accounts[id - 1];
-
- read_binary_string(data, acct->name);
- read_binary_string(data, acct->note);
- read_binary_number(data, acct->depth);
-
- // If all of the subaccounts will be added to a different master
- // account, throw away what we've learned about the recorded
- // journal's own master account.
-
- if (master && acct != master) {
- delete acct;
- acct = master;
- }
-
- for (account_t::ident_t i = 0,
- count = read_binary_long<account_t::ident_t>(data);
- i < count;
- i++) {
- account_t * child = read_binary_account(data, journal);
- child->parent = acct;
- assert(acct != child);
- acct->add_account(child);
- }
-
- return acct;
-}
-
-unsigned int read_binary_journal(std::istream& in,
- const std::string& file,
- journal_t * journal,
- account_t * master)
-{
- account_index =
- base_commodity_index =
- commodity_index = 0;
-
- // Read in the files that participated in this journal, so that they
- // can be checked for changes on reading.
-
- if (! file.empty()) {
- for (unsigned short i = 0,
- count = read_binary_number<unsigned short>(in);
- i < count;
- i++) {
- std::string path = read_binary_string(in);
- std::time_t old_mtime;
- read_binary_number(in, old_mtime);
- struct stat info;
- stat(path.c_str(), &info);
- if (std::difftime(info.st_mtime, old_mtime) > 0)
- return 0;
-
- journal->sources.push_back(path);
- }
-
- // Make sure that the cache uses the same price database,
- // otherwise it means that LEDGER_PRICE_DB has been changed, and
- // we should ignore this cache file.
- if (read_binary_string(in) != journal->price_db)
- return 0;
- }
-
- // Read all of the data in at once, so that we're just dealing with
- // a big data buffer.
-
- unsigned long data_size = read_binary_number<unsigned long>(in);
-
- char * data_pool = new char[data_size];
- char * data = data_pool;
- in.read(data, data_size);
-
- // Read in the accounts
-
- account_t::ident_t a_count = read_binary_long<account_t::ident_t>(data);
- accounts = accounts_next = new account_t *[a_count];
-
- assert(journal->master);
- delete journal->master;
- journal->master = read_binary_account(data, journal, master);
-
- if (read_binary_bool(data))
- journal->basket = accounts[read_binary_long<account_t::ident_t>(data) - 1];
-
- // Allocate the memory needed for the entries and transactions in
- // one large block, which is then chopped up and custom constructed
- // as necessary.
-
- unsigned long count = read_binary_long<unsigned long>(data);
- unsigned long auto_count = read_binary_long<unsigned long>(data);
- unsigned long period_count = read_binary_long<unsigned long>(data);
- unsigned long xact_count = read_binary_number<unsigned long>(data);
- unsigned long bigint_count = read_binary_number<unsigned long>(data);
-
- std::size_t pool_size = (sizeof(entry_t) * count +
- sizeof(transaction_t) * xact_count +
- sizeof_bigint_t() * bigint_count);
-
- char * item_pool = new char[pool_size];
-
- journal->item_pool = item_pool;
- journal->item_pool_end = item_pool + pool_size;
-
- entry_t * entry_pool = (entry_t *) item_pool;
- transaction_t * xact_pool = (transaction_t *) (item_pool +
- sizeof(entry_t) * count);
- bigints_index = 0;
- bigints = bigints_next = (item_pool + sizeof(entry_t) * count +
- sizeof(transaction_t) * xact_count);
-
- // Read in the base commodities and then derived commodities
-
- commodity_base_t::ident_t bc_count =
- read_binary_long<commodity_base_t::ident_t>(data);
- base_commodities = base_commodities_next = new commodity_base_t *[bc_count];
-
- for (commodity_base_t::ident_t i = 0; i < bc_count; i++) {
- commodity_base_t * commodity = read_binary_commodity_base(data);
-
- std::pair<base_commodities_map::iterator, bool> result =
- commodity_base_t::commodities.insert
- (base_commodities_pair(commodity->symbol, commodity));
- if (! result.second) {
- base_commodities_map::iterator c =
- commodity_base_t::commodities.find(commodity->symbol);
-
- // It's possible the user might have used a commodity in a value
- // expression passed to an option, we'll just override the
- // flags, but keep the commodity pointer intact.
- if (c == commodity_base_t::commodities.end())
- throw new error(std::string("Failed to read base commodity from cache: ") +
- commodity->symbol);
-
- (*c).second->name = commodity->name;
- (*c).second->note = commodity->note;
- (*c).second->precision = commodity->precision;
- (*c).second->flags = commodity->flags;
- if ((*c).second->smaller)
- delete (*c).second->smaller;
- (*c).second->smaller = commodity->smaller;
- if ((*c).second->larger)
- delete (*c).second->larger;
- (*c).second->larger = commodity->larger;
-
- *(base_commodities_next - 1) = (*c).second;
- delete commodity;
- }
- }
-
- commodity_t::ident_t c_count = read_binary_long<commodity_t::ident_t>(data);
- commodities = commodities_next = new commodity_t *[c_count];
-
- for (commodity_t::ident_t i = 0; i < c_count; i++) {
- commodity_t * commodity;
- std::string mapping_key;
-
- if (! read_binary_bool(data)) {
- commodity = read_binary_commodity(data);
- mapping_key = commodity->base->symbol;
- } else {
- read_binary_string(data, mapping_key);
- commodity = read_binary_commodity_annotated(data);
- }
-
- std::pair<commodities_map::iterator, bool> result =
- commodity_t::commodities.insert(commodities_pair
- (mapping_key, commodity));
- if (! result.second) {
- commodities_map::iterator c =
- commodity_t::commodities.find(mapping_key);
- if (c == commodity_t::commodities.end())
- throw new error(std::string("Failed to read commodity from cache: ") +
- commodity->symbol());
-
- *(commodities_next - 1) = (*c).second;
- delete commodity;
- }
- }
-
- for (commodity_base_t::ident_t i = 0; i < bc_count; i++)
- read_binary_commodity_base_extra(data, i);
-
- commodity_t::ident_t ident;
- read_binary_long(data, ident);
- if (ident == 0xffffffff || ident == 0)
- commodity_t::default_commodity = NULL;
- else
- commodity_t::default_commodity = commodities[ident - 1];
-
- // Read in the entries and transactions
-
- for (unsigned long i = 0; i < count; i++) {
- new(entry_pool) entry_t;
- bool finalize = false;
- read_binary_entry(data, entry_pool, xact_pool, finalize);
- entry_pool->journal = journal;
- if (finalize && ! entry_pool->finalize())
- continue;
- journal->entries.push_back(entry_pool++);
- }
-
- for (unsigned long i = 0; i < auto_count; i++) {
- auto_entry_t * auto_entry = new auto_entry_t;
- read_binary_auto_entry(data, auto_entry, xact_pool);
- auto_entry->journal = journal;
- journal->auto_entries.push_back(auto_entry);
- }
-
- for (unsigned long i = 0; i < period_count; i++) {
- period_entry_t * period_entry = new period_entry_t;
- bool finalize = false;
- read_binary_period_entry(data, period_entry, xact_pool, finalize);
- period_entry->journal = journal;
- if (finalize && ! period_entry->finalize())
- continue;
- journal->period_entries.push_back(period_entry);
- }
-
- // Clean up and return the number of entries read
-
- delete[] accounts;
- delete[] commodities;
- delete[] data_pool;
-
- VALIDATE(journal->valid());
-
- return count;
-}
-
-bool binary_parser_t::test(std::istream& in) const
-{
- if (read_binary_number_nocheck<unsigned long>(in) == binary_magic_number &&
- read_binary_number_nocheck<unsigned long>(in) == format_version)
- return true;
-
- in.clear();
- in.seekg(0, std::ios::beg);
- return false;
-}
-
-unsigned int binary_parser_t::parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master,
- const std::string * original_file)
-{
- return read_binary_journal(in, original_file ? *original_file : "",
- journal, master);
-}
-
-template <typename T>
-inline void write_binary_number_nocheck(std::ostream& out, T num) {
- out.write((char *)&num, sizeof(num));
-}
-
-#if DEBUG_LEVEL >= ALPHA
-#define write_binary_guard(out, id) \
- write_binary_number_nocheck<unsigned short>(out, id)
-#else
-#define write_binary_guard(in, id)
-#endif
-
-template <typename T>
-inline void write_binary_number(std::ostream& out, T num) {
- write_binary_guard(out, 0x2003);
- out.write((char *)&num, sizeof(num));
- write_binary_guard(out, 0x2004);
-}
-
-inline void write_binary_bool(std::ostream& out, bool num) {
- write_binary_guard(out, 0x2005);
- unsigned char val = num ? 1 : 0;
- out.write((char *)&val, sizeof(val));
- write_binary_guard(out, 0x2006);
-}
-
-template <typename T>
-inline void write_binary_long(std::ostream& out, T num) {
- write_binary_guard(out, 0x2001);
-
- unsigned char len = 4;
- if (((unsigned long)num) < 0x00000100UL)
- len = 1;
- else if (((unsigned long)num) < 0x00010000UL)
- len = 2;
- else if (((unsigned long)num) < 0x01000000UL)
- len = 3;
- write_binary_number_nocheck<unsigned char>(out, len);
-
- unsigned char temp;
- if (len > 3) {
- temp = (((unsigned long)num) & 0xFF000000UL) >> 24;
- write_binary_number_nocheck(out, temp);
- }
- if (len > 2) {
- temp = (((unsigned long)num) & 0x00FF0000UL) >> 16;
- write_binary_number_nocheck(out, temp);
- }
- if (len > 1) {
- temp = (((unsigned long)num) & 0x0000FF00UL) >> 8;
- write_binary_number_nocheck(out, temp);
- }
-
- temp = (((unsigned long)num) & 0x000000FFUL);
- write_binary_number_nocheck(out, temp);
-
- write_binary_guard(out, 0x2002);
-}
-
-inline void write_binary_string(std::ostream& out, const std::string& str)
-{
- write_binary_guard(out, 0x3001);
-
- unsigned long len = str.length();
- if (len > 255) {
- assert(len < 65536);
- write_binary_number_nocheck<unsigned char>(out, 0xff);
- write_binary_number_nocheck<unsigned short>(out, len);
- } else {
- write_binary_number_nocheck<unsigned char>(out, len);
- }
-
- if (len)
- out.write(str.c_str(), len);
-
- write_binary_guard(out, 0x3002);
-}
-
-void write_binary_amount(std::ostream& out, const amount_t& amt)
-{
- if (amt.commodity_)
- write_binary_long(out, amt.commodity_->ident);
- else
- write_binary_long<commodity_t::ident_t>(out, 0xffffffff);
-
- amt.write_quantity(out);
-}
-
-void write_binary_value(std::ostream& out, const value_t& val)
-{
- write_binary_long(out, (int)val.type);
-
- switch (val.type) {
- case value_t::BOOLEAN:
- write_binary_bool(out, *((bool *) val.data));
- break;
- case value_t::INTEGER:
- write_binary_long(out, *((long *) val.data));
- break;
- case value_t::DATETIME:
- write_binary_number(out, *((datetime_t *) val.data));
- break;
- case value_t::AMOUNT:
- write_binary_amount(out, *((amount_t *) val.data));
- break;
-
- case value_t::BALANCE:
- case value_t::BALANCE_PAIR:
- throw new error("Cannot write a balance to the binary cache");
- }
-}
-
-void write_binary_mask(std::ostream& out, mask_t * mask)
-{
- write_binary_number(out, mask->exclude);
- write_binary_string(out, mask->pattern);
-}
-
-void write_binary_value_expr(std::ostream& out, const value_expr_t * expr)
-{
- if (! expr) {
- write_binary_bool(out, false);
- return;
- }
- write_binary_bool(out, true);
- write_binary_number(out, expr->kind);
-
- if (expr->kind > value_expr_t::TERMINALS)
- write_binary_value_expr(out, expr->left);
-
- switch (expr->kind) {
- case value_expr_t::O_ARG:
- case value_expr_t::INDEX:
- write_binary_long(out, expr->arg_index);
- break;
- case value_expr_t::CONSTANT:
- write_binary_value(out, *expr->value);
- 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_bool(out, true);
- write_binary_mask(out, expr->mask);
- } else {
- write_binary_bool(out, false);
- }
- break;
-
- default:
- if (expr->kind > value_expr_t::TERMINALS)
- write_binary_value_expr(out, expr->right);
- break;
- }
-
-}
-
-void write_binary_transaction(std::ostream& out, transaction_t * xact,
- bool ignore_calculated)
-{
- write_binary_number(out, xact->_date);
- write_binary_number(out, xact->_date_eff);
- write_binary_long(out, xact->account->ident);
-
- if (ignore_calculated && xact->flags & TRANSACTION_CALCULATED) {
- write_binary_number<unsigned char>(out, 0);
- write_binary_amount(out, amount_t());
- }
- else if (xact->amount_expr) {
- write_binary_number<unsigned char>(out, 2);
- write_binary_value_expr(out, xact->amount_expr.get());
- write_binary_string(out, xact->amount_expr.expr);
- }
- else if (! xact->amount_expr.expr.empty()) {
- write_binary_number<unsigned char>(out, 1);
- write_binary_amount(out, xact->amount);
- write_binary_string(out, xact->amount_expr.expr);
- }
- else {
- write_binary_number<unsigned char>(out, 0);
- write_binary_amount(out, xact->amount);
- }
-
- if (xact->cost &&
- (! (ignore_calculated && xact->flags & TRANSACTION_CALCULATED))) {
- write_binary_bool(out, true);
- write_binary_amount(out, *xact->cost);
- write_binary_string(out, xact->cost_expr);
- } else {
- write_binary_bool(out, false);
- }
-
- write_binary_number(out, xact->state);
- write_binary_number(out, xact->flags);
- write_binary_string(out, xact->note);
-
- 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);
-}
-
-void write_binary_entry_base(std::ostream& out, entry_base_t * entry)
-{
- 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);
-
- bool ignore_calculated = false;
- for (transactions_list::const_iterator i = entry->transactions.begin();
- i != entry->transactions.end();
- i++)
- if ((*i)->amount_expr) {
- ignore_calculated = true;
- break;
- }
-
- write_binary_bool(out, ignore_calculated);
-
- write_binary_long(out, entry->transactions.size());
- for (transactions_list::const_iterator i = entry->transactions.begin();
- i != entry->transactions.end();
- i++)
- write_binary_transaction(out, *i, ignore_calculated);
-}
-
-void write_binary_entry(std::ostream& out, entry_t * entry)
-{
- write_binary_entry_base(out, entry);
- write_binary_number(out, entry->_date);
- write_binary_number(out, entry->_date_eff);
- write_binary_string(out, entry->code);
- write_binary_string(out, entry->payee);
-}
-
-void write_binary_auto_entry(std::ostream& out, auto_entry_t * entry)
-{
- write_binary_entry_base(out, entry);
- write_binary_value_expr(out, entry->predicate->predicate);
-}
-
-void write_binary_period_entry(std::ostream& out, period_entry_t * entry)
-{
- write_binary_entry_base(out, entry);
- write_binary_string(out, entry->period_string);
-}
-
-void write_binary_commodity_base(std::ostream& out, commodity_base_t * commodity)
-{
- commodity->ident = ++base_commodity_index;
-
- write_binary_string(out, commodity->symbol);
- write_binary_string(out, commodity->name);
- write_binary_string(out, commodity->note);
- write_binary_number(out, commodity->precision);
- write_binary_number(out, commodity->flags);
-}
-
-void write_binary_commodity_base_extra(std::ostream& out,
- commodity_base_t * commodity)
-{
- if (commodity->history && commodity->history->bogus_time)
- commodity->remove_price(commodity->history->bogus_time);
-
- 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();
- i++) {
- write_binary_number(out, (*i).first);
- write_binary_amount(out, (*i).second);
- }
- write_binary_number(out, commodity->history->last_lookup);
- }
-
- if (commodity->smaller) {
- write_binary_bool(out, true);
- write_binary_amount(out, *commodity->smaller);
- } else {
- write_binary_bool(out, false);
- }
-
- if (commodity->larger) {
- write_binary_bool(out, true);
- write_binary_amount(out, *commodity->larger);
- } else {
- write_binary_bool(out, false);
- }
-}
-
-void write_binary_commodity(std::ostream& out, commodity_t * commodity)
-{
- commodity->ident = ++commodity_index;
-
- write_binary_long(out, commodity->base->ident);
- write_binary_string(out, commodity->qualified_symbol);
-}
-
-void write_binary_commodity_annotated(std::ostream& out,
- commodity_t * commodity)
-{
- commodity->ident = ++commodity_index;
-
- write_binary_long(out, commodity->base->ident);
- write_binary_string(out, commodity->qualified_symbol);
-
- annotated_commodity_t * ann_comm =
- static_cast<annotated_commodity_t *>(commodity);
-
- write_binary_long(out, ann_comm->base->ident);
- write_binary_amount(out, ann_comm->price);
- write_binary_number(out, ann_comm->date);
- write_binary_string(out, ann_comm->tag);
-}
-
-static inline account_t::ident_t count_accounts(account_t * account)
-{
- account_t::ident_t count = 1;
-
- for (accounts_map::iterator i = account->accounts.begin();
- i != account->accounts.end();
- i++)
- count += count_accounts((*i).second);
-
- return count;
-}
-
-void write_binary_account(std::ostream& out, account_t * account)
-{
- account->ident = ++account_index;
-
- if (account->parent)
- write_binary_long(out, account->parent->ident);
- else
- write_binary_long<account_t::ident_t>(out, 0xffffffff);
-
- write_binary_string(out, account->name);
- write_binary_string(out, account->note);
- write_binary_number(out, account->depth);
-
- write_binary_long<account_t::ident_t>(out, account->accounts.size());
- for (accounts_map::iterator i = account->accounts.begin();
- i != account->accounts.end();
- i++)
- write_binary_account(out, (*i).second);
-}
-
-void write_binary_journal(std::ostream& out, journal_t * journal)
-{
- account_index =
- base_commodity_index =
- commodity_index = 0;
-
- write_binary_number_nocheck(out, binary_magic_number);
- write_binary_number_nocheck(out, format_version);
-
- // Write out the files that participated in this journal, so that
- // they can be checked for changes on reading.
-
- if (journal->sources.empty()) {
- write_binary_number<unsigned short>(out, 0);
- } else {
- write_binary_number<unsigned short>(out, journal->sources.size());
- for (strings_list::const_iterator i = journal->sources.begin();
- i != journal->sources.end();
- i++) {
- write_binary_string(out, *i);
- struct stat info;
- stat((*i).c_str(), &info);
- write_binary_number(out, std::time_t(info.st_mtime));
- }
-
- // Write out the price database that relates to this data file, so
- // that if it ever changes the cache can be invalidated.
- write_binary_string(out, journal->price_db);
- }
-
- ostream_pos_type data_val = out.tellp();
- write_binary_number<unsigned long>(out, 0);
-
- // Write out the accounts
-
- write_binary_long<account_t::ident_t>(out, count_accounts(journal->master));
- write_binary_account(out, journal->master);
-
- if (journal->basket) {
- write_binary_bool(out, true);
- write_binary_long(out, journal->basket->ident);
- } else {
- write_binary_bool(out, false);
- }
-
- // Write out the number of entries, transactions, and amounts
-
- write_binary_long<unsigned long>(out, journal->entries.size());
- write_binary_long<unsigned long>(out, journal->auto_entries.size());
- write_binary_long<unsigned long>(out, journal->period_entries.size());
-
- ostream_pos_type xacts_val = out.tellp();
- write_binary_number<unsigned long>(out, 0);
-
- ostream_pos_type bigints_val = out.tellp();
- write_binary_number<unsigned long>(out, 0);
-
- bigints_count = 0;
-
- // Write out the commodities
-
- write_binary_long<commodity_t::ident_t>
- (out, commodity_base_t::commodities.size());
-
- for (base_commodities_map::const_iterator i =
- commodity_base_t::commodities.begin();
- i != commodity_base_t::commodities.end();
- i++)
- write_binary_commodity_base(out, (*i).second);
-
- write_binary_long<commodity_t::ident_t>
- (out, commodity_t::commodities.size());
-
- for (commodities_map::const_iterator i = commodity_t::commodities.begin();
- i != commodity_t::commodities.end();
- i++) {
- if (! (*i).second->annotated) {
- write_binary_bool(out, false);
- write_binary_commodity(out, (*i).second);
- }
- }
-
- for (commodities_map::const_iterator i = commodity_t::commodities.begin();
- i != commodity_t::commodities.end();
- i++) {
- if ((*i).second->annotated) {
- write_binary_bool(out, true);
- write_binary_string(out, (*i).first); // the mapping key
- write_binary_commodity_annotated(out, (*i).second);
- }
- }
-
- // Write out the history and smaller/larger convertible links after
- // both the base and the main commodities have been written, since
- // the amounts in both will refer to the mains.
-
- for (base_commodities_map::const_iterator i =
- commodity_base_t::commodities.begin();
- i != commodity_base_t::commodities.end();
- i++)
- write_binary_commodity_base_extra(out, (*i).second);
-
- if (commodity_t::default_commodity)
- write_binary_long(out, commodity_t::default_commodity->ident);
- else
- write_binary_long<commodity_t::ident_t>(out, 0xffffffff);
-
- // Write out the entries and transactions
-
- unsigned long xact_count = 0;
-
- for (entries_list::const_iterator i = journal->entries.begin();
- i != journal->entries.end();
- i++) {
- write_binary_entry(out, *i);
- xact_count += (*i)->transactions.size();
- }
-
- for (auto_entries_list::const_iterator i = journal->auto_entries.begin();
- i != journal->auto_entries.end();
- i++) {
- write_binary_auto_entry(out, *i);
- xact_count += (*i)->transactions.size();
- }
-
- for (period_entries_list::const_iterator i = journal->period_entries.begin();
- i != journal->period_entries.end();
- i++) {
- write_binary_period_entry(out, *i);
- xact_count += (*i)->transactions.size();
- }
-
- // Back-patch the count for amounts
-
- unsigned long data_size = (((unsigned long) out.tellp()) -
- ((unsigned long) data_val) -
- sizeof(unsigned long));
- out.seekp(data_val);
- write_binary_number<unsigned long>(out, data_size);
- out.seekp(xacts_val);
- write_binary_number<unsigned long>(out, xact_count);
- out.seekp(bigints_val);
- write_binary_number<unsigned long>(out, bigints_count);
-}
-
-} // namespace ledger
diff --git a/binary.h b/binary.h
deleted file mode 100644
index ca3254c9..00000000
--- a/binary.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef _BINARY_H
-#define _BINARY_H
-
-#include "journal.h"
-#include "parser.h"
-
-namespace ledger {
-
-class binary_parser_t : public parser_t
-{
- public:
- virtual bool test(std::istream& in) const;
-
- virtual unsigned int parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master = NULL,
- const std::string * original_file = NULL);
-};
-
-void write_binary_journal(std::ostream& out,
- journal_t * journal);
-
-} // namespace ledger
-
-#endif // _BINARY_H
diff --git a/config.cc b/config.cc
deleted file mode 100644
index 847feff7..00000000
--- a/config.cc
+++ /dev/null
@@ -1,132 +0,0 @@
-#include "config.h"
-#include "acconf.h"
-#include "option.h"
-#include "datetime.h"
-#include "quotes.h"
-#include "valexpr.h"
-#include "walk.h"
-
-#include <fstream>
-#include <cstdlib>
-#ifdef WIN32
-#include <io.h>
-#else
-#include <unistd.h>
-#endif
-
-#ifdef HAVE_REALPATH
-extern "C" char *realpath(const char *, char resolved_path[]);
-#endif
-
-#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM)
-#include <pwd.h>
-#endif
-
-namespace ledger {
-
-std::string expand_path(const std::string& path)
-{
- if (path.length() == 0 || path[0] != '~')
- return path;
-
- const char * pfx = NULL;
- std::string::size_type pos = path.find_first_of('/');
-
- if (path.length() == 1 || pos == 1) {
- pfx = std::getenv("HOME");
-#ifdef HAVE_GETPWUID
- if (! pfx) {
- // Punt. We're trying to expand ~/, but HOME isn't set
- struct passwd * pw = getpwuid(getuid());
- if (pw)
- pfx = pw->pw_dir;
- }
-#endif
- }
-#ifdef HAVE_GETPWNAM
- else {
- std::string user(path, 1, pos == std::string::npos ?
- std::string::npos : pos - 1);
- struct passwd * pw = getpwnam(user.c_str());
- if (pw)
- pfx = pw->pw_dir;
- }
-#endif
-
- // if we failed to find an expansion, return the path unchanged.
-
- if (! pfx)
- return path;
-
- std::string result(pfx);
-
- if (pos == std::string::npos)
- return result;
-
- if (result.length() == 0 || result[result.length() - 1] != '/')
- result += '/';
-
- result += path.substr(pos + 1);
-
- return result;
-}
-
-std::string resolve_path(const std::string& path)
-{
- if (path[0] == '~')
- return expand_path(path);
- return path;
-}
-
-config_t::config_t()
-{
- balance_format = "%20T %2_%-a\n";
- register_format = ("%D %-.20P %-.22A %12.67t %!12.80T\n%/"
- "%32|%-.22A %12.67t %!12.80T\n");
- wide_register_format = ("%D %-.35P %-.38A %22.108t %!22.132T\n%/"
- "%48|%-.38A %22.108t %!22.132T\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";
- equity_format = "\n%D %Y%C%P\n%/ %-34W %12t\n";
- prices_format = "%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n";
- pricesdb_format = "P %[%Y/%m/%d %H:%M:%S] %A %t\n";
-
- pricing_leeway = 24 * 3600;
-
- download_quotes = false;
- use_cache = false;
- cache_dirty = false;
- debug_mode = false;
- verbose_mode = false;
- trace_mode = false;
-}
-
-//////////////////////////////////////////////////////////////////////
-
-void trace(const std::string& cat, const std::string& str)
-{
- char buf[32];
- std::strftime(buf, 31, "%H:%M:%S", datetime_t::now.localtime());
- std::cerr << buf << " " << cat << ": " << str << std::endl;
-}
-
-void trace_push(const std::string& cat, const std::string& str,
- timing_t& timer)
-{
- timer.start();
- trace(cat, str);
-}
-
-void trace_pop(const std::string& cat, const std::string& str,
- timing_t& timer)
-{
- timer.stop();
- std::ostringstream out;
- out << str << ": " << (double(timer.cumulative) / double(CLOCKS_PER_SEC)) << "s";
- trace(cat, out.str());
-}
-
-} // namespace ledger
diff --git a/config.h b/config.h
deleted file mode 100644
index 3bb8ed0d..00000000
--- a/config.h
+++ /dev/null
@@ -1,79 +0,0 @@
-#ifndef _CONFIG_H
-#define _CONFIG_H
-
-#include "ledger.h"
-#include "timing.h"
-
-#include <iostream>
-#include <memory>
-#include <list>
-
-namespace ledger {
-
-class config_t
-{
- public:
- std::string init_file;
- std::string data_file;
- std::string cache_file;
- std::string price_db;
-
- std::string balance_format;
- std::string register_format;
- std::string wide_register_format;
- std::string plot_amount_format;
- std::string plot_total_format;
- std::string print_format;
- std::string write_hdr_format;
- std::string write_xact_format;
- std::string equity_format;
- std::string prices_format;
- std::string pricesdb_format;
-
- std::string date_input_format;
-
- std::string account;
- std::string pager;
-
- unsigned long pricing_leeway;
-
- bool download_quotes;
- bool use_cache;
- bool cache_dirty;
- bool debug_mode;
- bool verbose_mode;
- bool trace_mode;
-
- config_t();
-};
-
-//////////////////////////////////////////////////////////////////////
-
-std::string resolve_path(const std::string& path);
-
-//////////////////////////////////////////////////////////////////////
-
-void trace(const std::string& cat, const std::string& str);
-void trace_push(const std::string& cat, const std::string& str,
- timing_t& timer);
-void trace_pop(const std::string& cat, const std::string& str,
- timing_t& timer);
-
-#define TRACE(cat, msg) if (config.trace_mode) trace(#cat, msg)
-#define TRACE_(cat, msg) if (trace_mode) trace(#cat, msg)
-
-#define TRACE_PUSH(cat, msg) \
- timing_t timer_ ## cat(#cat); \
- if (config.trace_mode) trace_push(#cat, msg, timer_ ## cat)
-#define TRACE_PUSH_(cat, msg) \
- timing_t timer_ ## cat(#cat); \
- if (trace_mode) trace_push(#cat, msg, timer_ ## cat)
-
-#define TRACE_POP(cat, msg) \
- if (config.trace_mode) trace_pop(#cat, msg, timer_ ## cat)
-#define TRACE_POP_(cat, msg) \
- if (trace_mode) trace_pop(#cat, msg, timer_ ## cat)
-
-} // namespace ledger
-
-#endif // _CONFIG_H
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 00000000..4ad5dca7
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,418 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ(2.61)
+
+m4_include([version.m4])
+
+AC_INIT([ledger],[VERSION_NUMBER],[johnw@newartisans.com])
+AC_CONFIG_SRCDIR([src/main.cc])
+AC_CONFIG_HEADER([acconf.h])
+AM_INIT_AUTOMAKE([dist-bzip2 foreign])
+
+# Checks for programs.
+AC_PROG_CXX
+AC_PROG_MAKE_SET
+AC_PROG_LIBTOOL
+AM_PROG_LIBTOOL
+
+AC_CHECK_PROG([VALGRIND_TEST], [valgrind], [true], [false])
+AM_CONDITIONAL(HAVE_VALGRIND, test x$VALGRIND_TEST = xtrue)
+
+# Checks for emacs lisp path
+AM_PATH_LISPDIR
+
+# Check for options
+AC_ARG_ENABLE(debug,
+ [ --enable-debug Turn on debugging],
+ [case "${enableval}" in
+ yes) debug=true ;;
+ no) debug=false ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;;
+ esac],[debug=false])
+
+AM_CONDITIONAL(DEBUG, test x$debug = xtrue)
+
+AC_ARG_ENABLE(pch,
+ [ --enable-pch Use GCC 4.x pre-compiled headers],
+ [case "${enableval}" in
+ yes) pch=true ;;
+ no) pch=false ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-pch) ;;
+ esac],[pch=false])
+
+AM_CONDITIONAL(USE_PCH, test x$pch = xtrue)
+
+AC_ARG_WITH(boost-suffix,
+ [ --with-boost-suffix=X Append X to the Boost library names],
+ [BOOST_SUFFIX="${withval}"],
+ [BOOST_SUFFIX=""])
+
+AC_SUBST([BOOST_SUFFIX], $BOOST_SUFFIX)
+
+# check if UNIX pipes are available
+AC_CACHE_CHECK(
+ [if pipes can be used],
+ [pipes_avail_cv_],
+ [AC_LANG_PUSH(C++)
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <sys/types.h>
+ #include <sys/wait.h>
+ #include <unistd.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <stdio.h>]],
+ [[int status, pfd[2];
+ status = pipe(pfd);
+ status = fork();
+ if (status < 0) {
+ ;
+ } else if (status == 0) {
+ char *arg0;
+
+ status = dup2(pfd[0], STDIN_FILENO);
+
+ close(pfd[1]);
+ close(pfd[0]);
+
+ execlp("", arg0, (char *)0);
+ perror("execl");
+ exit(1);
+ } else {
+ close(pfd[0]);
+ }]])],
+ [pipes_avail_cv_=true],
+ [pipes_avail_cv_=false])
+ AC_LANG_POP])
+
+if [test x$pipes_avail_cv_ = xtrue ]; then
+ AC_DEFINE([HAVE_UNIX_PIPES], [1], [Whether UNIX pipes are available])
+fi
+
+# check for gmp
+AC_CACHE_CHECK(
+ [if libgmp is available],
+ [libgmp_avail_cv_],
+ [libgmp_save_libs=$LIBS
+ LIBS="-lgmp $LIBS"
+ AC_LANG_PUSH(C++)
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <gmp.h>]], [[mpz_t bar;
+ mpz_init(bar);
+ mpz_clear(bar);]])],[libgmp_avail_cv_=true],[libgmp_avail_cv_=false])
+ AC_LANG_POP
+ LIBS=$libgmp_save_libs])
+
+if [test x$libgmp_avail_cv_ = xtrue ]; then
+ LIBS="-lgmp $LIBS"
+else
+ AC_MSG_FAILURE("Could not find gmp library (set CPPFLAGS and LDFLAGS?)")
+fi
+
+# check for expat or xmlparse
+AC_ARG_ENABLE(xml,
+ [ --enable-xml Turn on support for XML parsing],
+ [case "${enableval}" in
+ yes) xml=true ;;
+ no) xml=false ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-xml) ;;
+ esac],[xml=true])
+AM_CONDITIONAL(USE_XML, test x$xml = xtrue)
+
+if [test x$xml = xtrue ]; then
+ AC_CACHE_CHECK(
+ [if libexpat is available],
+ [libexpat_avail_cv_],
+ [libexpat_save_libs=$LIBS
+ LIBS="-lexpat $LIBS"
+ AC_LANG_PUSH(C++)
+ AC_TRY_LINK(
+ [#include <stdio.h>
+ extern "C" {
+ #include <expat.h> // expat XML parser
+ }],
+ [XML_Parser parser = XML_ParserCreate(NULL);
+ return parser != NULL;],
+ [libexpat_avail_cv_=true],
+ [libexpat_avail_cv_=false])
+ AC_LANG_POP
+ LIBS=$libexpat_save_libs])
+
+ if [test x$libexpat_avail_cv_ = xtrue ]; then
+ AM_CONDITIONAL(HAVE_EXPAT, true)
+ LIBS="-lexpat $LIBS"
+ else
+ AM_CONDITIONAL(HAVE_EXPAT, false)
+ fi
+else
+ AM_CONDITIONAL(HAVE_EXPAT, false)
+fi
+
+if [test x$xml = xtrue ]; then
+ if [test x$libexpat_avail_cv_ = xfalse ]; then
+ AC_CACHE_CHECK(
+ [if libxmlparse is available],
+ [libxmlparse_avail_cv_],
+ [libxmlparse_save_libs=$LIBS
+ LIBS="-lxmlparse -lxmltok $LIBS"
+ AC_LANG_PUSH(C++)
+ AC_TRY_LINK(
+ [#include <stdio.h>
+ extern "C" {
+ #include <xmlparse.h> // expat XML parser
+ }],
+ [XML_Parser parser = XML_ParserCreate(NULL);
+ return parser != NULL;],
+ [libxmlparse_avail_cv_=true],
+ [libxmlparse_avail_cv_=false])
+ AC_LANG_POP
+ LIBS=$libxmlparse_save_libs])
+
+ if [test x$libxmlparse_avail_cv_ = xtrue ]; then
+ AM_CONDITIONAL(HAVE_XMLPARSE, true)
+ LIBS="-lxmlparse -lxmltok $LIBS"
+ else
+ AM_CONDITIONAL(HAVE_XMLPARSE, false)
+ fi
+ else
+ AM_CONDITIONAL(HAVE_XMLPARSE, false)
+ fi
+else
+ AM_CONDITIONAL(HAVE_XMLPARSE, false)
+fi
+
+# check for boost_regex
+AC_CACHE_CHECK(
+ [if boost_regex is available],
+ [boost_regex_avail_cv_],
+ [boost_regex_save_libs=$LIBS
+ LIBS="-lboost_regex$BOOST_SUFFIX $LIBS"
+ AC_LANG_PUSH(C++)
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <boost/regex.hpp>]],
+ [[boost::regex foo_regexp("Hello, world!");]])],
+ [boost_regex_avail_cv_=true],
+ [boost_regex_avail_cv_=false])
+ AC_LANG_POP
+ LIBS=$boost_regex_save_libs])
+
+if [test x$boost_regex_avail_cv_ = xtrue ]; then
+ LIBS="-lboost_regex$BOOST_SUFFIX $LIBS"
+else
+ AC_MSG_FAILURE("Could not find boost_regex library (set CPPFLAGS and LDFLAGS?)")
+fi
+
+# check for boost_date_time
+AC_CACHE_CHECK(
+ [if boost_date_time is available],
+ [boost_date_time_cpplib_avail_cv_],
+ [boost_date_time_save_libs=$LIBS
+ LIBS="-lboost_date_time$BOOST_SUFFIX $LIBS"
+ AC_LANG_PUSH(C++)
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <boost/date_time/posix_time/posix_time.hpp>
+ #include <boost/date_time/gregorian/gregorian.hpp>
+ #include <boost/date_time/local_time_adjustor.hpp>
+ #include <boost/date_time/time_duration.hpp>
+
+ using namespace boost::posix_time;
+ using namespace boost::date_time;
+
+ #include <ctime>
+
+ inline ptime time_to_system_local(const ptime& when) {
+ struct std::tm tm_gmt = to_tm(when);
+ return from_time_t(mktime(&tm_gmt));
+ }]],
+ [[ptime t10 = ptime(boost::gregorian::from_string("2007-01-15"),
+ ptime::time_duration_type());
+
+ ptime t12 = time_to_system_local(t10);
+
+ return t10 != t12;]])],
+ [boost_date_time_cpplib_avail_cv_=true],
+ [boost_date_time_cpplib_avail_cv_=false])
+ AC_LANG_POP
+ LIBS=$boost_date_time_save_libs])
+
+if [test x$boost_date_time_cpplib_avail_cv_ = xtrue ]; then
+ LIBS="-lboost_date_time$BOOST_SUFFIX $LIBS"
+else
+ AC_MSG_FAILURE("Could not find boost_date_time library (set CPPFLAGS and LDFLAGS?)")
+fi
+
+# check for boost_filesystem
+AC_CACHE_CHECK(
+ [if boost_filesystem is available],
+ [boost_filesystem_cpplib_avail_cv_],
+ [boost_filesystem_save_libs=$LIBS
+ LIBS="-lboost_filesystem$BOOST_SUFFIX -lboost_system$BOOST_SUFFIX $LIBS"
+ AC_LANG_PUSH(C++)
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <boost/filesystem/path.hpp>]],
+ [[boost::filesystem::path this_path("Hello");]])],
+ [boost_filesystem_cpplib_avail_cv_=true],
+ [boost_filesystem_cpplib_avail_cv_=false])
+ AC_LANG_POP
+ LIBS=$boost_filesystem_save_libs])
+
+if [test x$boost_filesystem_cpplib_avail_cv_ = xtrue ]; then
+ LIBS="-lboost_filesystem$BOOST_SUFFIX -lboost_system$BOOST_SUFFIX $LIBS"
+else
+ AC_MSG_FAILURE("Could not find boost_filesystem library (set CPPFLAGS and LDFLAGS?)")
+fi
+
+## check for boost_signals
+#AC_CACHE_CHECK(
+# [if boost_signals is available],
+# [boost_signals_cpplib_avail_cv_],
+# [boost_signals_save_libs=$LIBS
+# LIBS="-lboost_signals$BOOST_SUFFIX $LIBS"
+# AC_LANG_PUSH(C++)
+# AC_LINK_IFELSE(
+# [AC_LANG_PROGRAM(
+# [[#include <boost/signal.hpp>]],
+# [[boost::signal<void (void)> this_signal;]])],
+# [boost_signals_cpplib_avail_cv_=true],
+# [boost_signals_cpplib_avail_cv_=false])
+# AC_LANG_POP
+# LIBS=$boost_signals_save_libs])
+#
+#if [test x$boost_signals_cpplib_avail_cv_ = xtrue ]; then
+# LIBS="-lboost_signals$BOOST_SUFFIX $LIBS"
+#else
+# AC_MSG_FAILURE("Could not find boost_signals library (set CPPFLAGS and LDFLAGS?)")
+#fi
+
+# check for libofx
+AC_ARG_ENABLE(ofx,
+ [ --enable-ofx Turn on support for OFX/OCF parsing],
+ [case "${enableval}" in
+ yes) ofx=true ;;
+ no) ofx=false ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-ofx) ;;
+ esac],[ofx=true])
+
+AM_CONDITIONAL(USE_OFX, test x$ofx = xtrue)
+
+if [test x$ofx = xtrue ]; then
+ AC_CACHE_CHECK(
+ [if libofx is available],
+ [libofx_avail_cv_],
+ [libofx_save_libs=$LIBS
+ LIBS="-lofx $LIBS"
+ AC_LANG_PUSH(C++)
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <libofx.h>]],
+ [[LibofxContextPtr libofx_context = libofx_get_new_context();]])],
+ [libofx_avail_cv_=true],
+ [libofx_avail_cv_=false])
+ AC_LANG_POP
+ LIBS=$libofx_save_libs])
+
+ if [test x$libofx_avail_cv_ = xtrue ]; then
+ AM_CONDITIONAL(HAVE_LIBOFX, true)
+ LIBS="-lofx $LIBS"
+ else
+ AM_CONDITIONAL(HAVE_LIBOFX, false)
+ fi
+else
+ AM_CONDITIONAL(HAVE_LIBOFX, false)
+fi
+
+# check for Python
+AC_ARG_ENABLE(python,
+ [ --enable-python Build the amounts library as a Python module],
+ [case "${enableval}" in
+ yes) python=true ;;
+ no) python=false ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-python) ;;
+ esac],[python=false])
+
+AM_CONDITIONAL(USE_PYTHON, test x$python = xtrue)
+
+if [test x$python = xtrue ]; then
+ AM_PATH_PYTHON(2.2,, :)
+ if [test "$PYTHON" != :]; then
+ AC_CACHE_CHECK(
+ [if boost_python is available],
+ [boost_python_cpplib_avail_cv_],
+ [boost_python_save_libs=$LIBS
+ LIBS="-lboost_python$BOOST_SUFFIX -lpython$PYTHON_VERSION $LIBS"
+ AC_LANG_PUSH(C++)
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <boost/python.hpp>
+ using namespace boost::python;
+ class foo {};
+ BOOST_PYTHON_MODULE(samp) {
+ class_< foo > ("foo") ;
+ }]],
+ [[return 0]])],
+ [boost_python_cpplib_avail_cv_=true],
+ [boost_python_cpplib_avail_cv_=false])
+ AC_LANG_POP
+ LIBS=$boost_python_save_libs])
+
+ if [test x$boost_python_cpplib_avail_cv_ = xtrue ]; then
+ AM_CONDITIONAL(HAVE_BOOST_PYTHON, true)
+ LIBS="-lboost_python$BOOST_SUFFIX -lpython$PYTHON_VERSION $LIBS"
+ else
+ AM_CONDITIONAL(HAVE_BOOST_PYTHON, false)
+ fi
+ else
+ AM_CONDITIONAL(HAVE_BOOST_PYTHON, false)
+ fi
+else
+ AM_CONDITIONAL(HAVE_BOOST_PYTHON, false)
+fi
+
+# check for CppUnit
+AC_CACHE_CHECK(
+ [if cppunit is available],
+ [cppunit_avail_cv_],
+ [cppunit_save_libs=$LIBS
+ LIBS="-lcppunit $LIBS"
+ AC_LANG_PUSH(C++)
+ AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <cppunit/CompilerOutputter.h>
+ #include <cppunit/TestResult.h>
+ #include <cppunit/TestResultCollector.h>
+ #include <cppunit/TestRunner.h>
+ #include <cppunit/TextTestProgressListener.h>
+ #include <cppunit/BriefTestProgressListener.h>
+ #include <cppunit/XmlOutputter.h>
+ #include <cppunit/extensions/TestFactoryRegistry.h>]],
+ [[CPPUNIT_NS::TestResult controller;
+ CPPUNIT_NS::TestResultCollector result;]])],
+ [cppunit_avail_cv_=true],
+ [cppunit_avail_cv_=false])
+ AC_LANG_POP
+ LIBS=$cppunit_save_libs])
+
+if [test x$cppunit_avail_cv_ = xtrue ]; then
+ AM_CONDITIONAL(HAVE_CPPUNIT, true)
+else
+ AM_CONDITIONAL(HAVE_CPPUNIT, false)
+fi
+
+# Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS([sys/stat.h langinfo.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_HEADER_STDBOOL
+AC_TYPE_SIZE_T
+AC_STRUCT_TM
+
+# Checks for library functions.
+AC_HEADER_STDC
+AC_CHECK_FUNCS([access mktime realpath getpwuid getpwnam nl_langinfo])
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/configure.in b/configure.in
deleted file mode 100644
index 866334f0..00000000
--- a/configure.in
+++ /dev/null
@@ -1,280 +0,0 @@
-# -*- Autoconf -*-
-# Process this file with autoconf to produce a configure script.
-
-AC_PREREQ(2.59)
-AC_INIT(ledger, 2.6.1, johnw@newartisans.com)
-AM_INIT_AUTOMAKE(ledger, 2.6.1)
-AC_CONFIG_SRCDIR([main.cc])
-AC_CONFIG_HEADER([acconf.h])
-
-# Checks for programs.
-AC_PROG_CXX
-AC_PROG_MAKE_SET
-AC_PROG_LIBTOOL
-AM_PROG_LIBTOOL
-
-# Checks for emacs lisp path
-AM_PATH_LISPDIR
-
-# check if UNIX pipes are available
-AC_CACHE_CHECK(
- [if pipes can be used],
- [pipes_avail_cv_],
- [AC_LANG_PUSH(C++)
- AC_TRY_LINK(
- [#include <sys/types.h>
- #include <sys/wait.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <string.h>
- #include <stdio.h>],
- [int status, pfd[2];
- status = pipe(pfd);
- status = fork();
- if (status < 0) {
- ;
- } else if (status == 0) {
- char *arg0;
-
- status = dup2(pfd[0], STDIN_FILENO);
-
- close(pfd[1]);
- close(pfd[0]);
-
- execlp("", arg0, (char *)0);
- perror("execl");
- exit(1);
- } else {
- close(pfd[0]);
- }],
- [pipes_avail_cv_=true],
- [pipes_avail_cv_=false])
- AC_LANG_POP])
-
-if [test x$pipes_avail_cv_ = xtrue ]; then
- AC_DEFINE([HAVE_UNIX_PIPES], [1], [Whether UNIX pipes are available])
-fi
-
-# check for gmp
-AC_CACHE_CHECK(
- [if libgmp is available],
- [libgmp_avail_cv_],
- [libgmp_save_libs=$LIBS
- LIBS="-lgmp $LIBS"
- AC_LANG_PUSH(C++)
- AC_TRY_LINK(
- [#include <gmp.h>],
- [mpz_t bar;
- mpz_init(bar);
- mpz_clear(bar);],
- [libgmp_avail_cv_=true],
- [libgmp_avail_cv_=false])
- AC_LANG_POP
- LIBS=$libgmp_save_libs])
-
-if [test x$libgmp_avail_cv_ = xtrue ]; then
- AM_CONDITIONAL(HAVE_GMP, true)
- LIBS="-lgmp $LIBS"
-else
- AC_MSG_FAILURE("Could not find gmp library (set CPPFLAGS and LDFLAGS?)")
-fi
-
-# check for pcre
-AC_CACHE_CHECK(
- [if libpcre is available],
- [libpcre_avail_cv_],
- [libpcre_save_libs=$LIBS
- LIBS="-lpcre $LIBS"
- AC_LANG_PUSH(C++)
- AC_TRY_LINK(
- [#include <pcre.h>],
- [pcre_free((pcre *)NULL);],
- [libpcre_avail_cv_=true],
- [libpcre_avail_cv_=false])
- AC_LANG_POP
- LIBS=$libpcre_save_libs])
-
-if [test x$libpcre_avail_cv_ = xtrue ]; then
- AM_CONDITIONAL(HAVE_PCRE, true)
- LIBS="-lpcre $LIBS"
-else
- AC_MSG_FAILURE("Could not find pcre library (set CPPFLAGS and LDFLAGS?)")
-fi
-
-# check for expat or xmlparse
-AC_ARG_ENABLE(xml,
- [ --enable-xml Turn on support for XML parsing],
- [case "${enableval}" in
- yes) xml=true ;;
- no) xml=false ;;
- *) AC_MSG_ERROR(bad value ${enableval} for --enable-xml) ;;
- esac],[xml=true])
-AM_CONDITIONAL(USE_XML, test x$xml = xtrue)
-
-if [test x$xml = xtrue ]; then
- AC_CACHE_CHECK(
- [if libexpat is available],
- [libexpat_avail_cv_],
- [libexpat_save_libs=$LIBS
- LIBS="-lexpat $LIBS"
- AC_LANG_PUSH(C++)
- AC_TRY_LINK(
- [#include <stdio.h>
- extern "C" {
- #include <expat.h> // expat XML parser
- }],
- [XML_Parser parser = XML_ParserCreate(NULL);
- return parser != NULL;],
- [libexpat_avail_cv_=true],
- [libexpat_avail_cv_=false])
- AC_LANG_POP
- LIBS=$libexpat_save_libs])
-
- if [test x$libexpat_avail_cv_ = xtrue ]; then
- AM_CONDITIONAL(HAVE_EXPAT, true)
- LIBS="-lexpat $LIBS"
- else
- AM_CONDITIONAL(HAVE_EXPAT, false)
- fi
-else
- AM_CONDITIONAL(HAVE_EXPAT, false)
-fi
-
-if [test x$xml = xtrue ]; then
- if [test x$libexpat_avail_cv_ = xfalse ]; then
- AC_CACHE_CHECK(
- [if libxmlparse is available],
- [libxmlparse_avail_cv_],
- [libxmlparse_save_libs=$LIBS
- LIBS="-lxmlparse -lxmltok $LIBS"
- AC_LANG_PUSH(C++)
- AC_TRY_LINK(
- [#include <stdio.h>
- extern "C" {
- #include <xmlparse.h> // expat XML parser
- }],
- [XML_Parser parser = XML_ParserCreate(NULL);
- return parser != NULL;],
- [libxmlparse_avail_cv_=true],
- [libxmlparse_avail_cv_=false])
- AC_LANG_POP
- LIBS=$libxmlparse_save_libs])
-
- if [test x$libxmlparse_avail_cv_ = xtrue ]; then
- AM_CONDITIONAL(HAVE_XMLPARSE, true)
- LIBS="-lxmlparse -lxmltok $LIBS"
- else
- AM_CONDITIONAL(HAVE_XMLPARSE, false)
- fi
- else
- AM_CONDITIONAL(HAVE_XMLPARSE, false)
- fi
-else
- AM_CONDITIONAL(HAVE_XMLPARSE, false)
-fi
-
-# check for libofx
-AC_ARG_ENABLE(ofx,
- [ --enable-ofx Turn on support for OFX/OCF parsing],
- [case "${enableval}" in
- yes) ofx=true ;;
- no) ofx=false ;;
- *) AC_MSG_ERROR(bad value ${enableval} for --enable-ofx) ;;
- esac],[ofx=true])
-AM_CONDITIONAL(USE_OFX, test x$ofx = xtrue)
-
-if [test x$ofx = xtrue ]; then
- AC_CACHE_CHECK(
- [if libofx is available],
- [libofx_avail_cv_],
- [libofx_save_libs=$LIBS
- LIBS="-lofx $LIBS"
- AC_LANG_PUSH(C++)
- AC_TRY_LINK(
- [#include <libofx.h>],
- [ LibofxContextPtr libofx_context = libofx_get_new_context();],
- [libofx_avail_cv_=true],
- [libofx_avail_cv_=false])
- AC_LANG_POP
- LIBS=$libofx_save_libs])
-
- if [test x$libofx_avail_cv_ = xtrue ]; then
- AM_CONDITIONAL(HAVE_LIBOFX, true)
- LIBS="-lofx $LIBS"
- else
- AM_CONDITIONAL(HAVE_LIBOFX, false)
- fi
-else
- AM_CONDITIONAL(HAVE_LIBOFX, false)
-fi
-
-# check for Python
-AC_ARG_ENABLE(python,
- [ --enable-python Build the amounts library as a Python module],
- [case "${enableval}" in
- yes) python=true ;;
- no) python=false ;;
- *) AC_MSG_ERROR(bad value ${enableval} for --enable-python) ;;
- esac],[python=false])
-AM_CONDITIONAL(USE_PYTHON, test x$python = xtrue)
-
-if [test x$python = xtrue ]; then
- AM_PATH_PYTHON(2.2,, :)
- if [test "$PYTHON" != :]; then
- AC_CACHE_CHECK(
- [if boost_python is available],
- [boost_python_cpplib_avail_cv_],
- [boost_python_save_libs=$LIBS
- LIBS="-lboost_python -lpython$PYTHON_VERSION $LIBS"
- AC_LANG_PUSH(C++)
- AC_TRY_LINK(
- [#include <boost/python.hpp>
- using namespace boost::python;
- class foo {};
- BOOST_PYTHON_MODULE(samp) {
- class_< foo > ("foo") ;
- }],
- [return 0],
- [boost_python_cpplib_avail_cv_=true],
- [boost_python_cpplib_avail_cv_=false])
- AC_LANG_POP
- LIBS=$boost_python_save_libs])
- if [test x$boost_python_cpplib_avail_cv_ = xtrue ]; then
- AM_CONDITIONAL(HAVE_BOOST_PYTHON, true)
- LIBS="-lboost_python -lpython$PYTHON_VERSION $LIBS"
- else
- AM_CONDITIONAL(HAVE_BOOST_PYTHON, false)
- fi
- else
- AM_CONDITIONAL(HAVE_BOOST_PYTHON, false)
- fi
-else
- AM_CONDITIONAL(HAVE_BOOST_PYTHON, false)
-fi
-
-# Check for options
-AC_ARG_ENABLE(debug,
- [ --enable-debug Turn on debugging],
- [case "${enableval}" in
- yes) debug=true ;;
- no) debug=false ;;
- *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;;
- esac],[debug=false])
-AM_CONDITIONAL(DEBUG, test x$debug = xtrue)
-
-# Checks for header files.
-AC_STDC_HEADERS
-AC_HAVE_HEADERS(sys/stat.h)
-
-# Checks for typedefs, structures, and compiler characteristics.
-AC_HEADER_STDBOOL
-AC_TYPE_SIZE_T
-AC_STRUCT_TM
-
-# Checks for library functions.
-#AC_FUNC_ERROR_AT_LINE
-AC_HEADER_STDC
-AC_CHECK_FUNCS([access mktime realpath stat strftime strptime getpwuid getpwnam])
-
-AC_CONFIG_FILES([Makefile])
-AC_OUTPUT
diff --git a/contrib/CSVReader.cs b/contrib/CSVReader.cs
new file mode 100644
index 00000000..a22eab06
--- /dev/null
+++ b/contrib/CSVReader.cs
@@ -0,0 +1,165 @@
+// This code is in the public domain. I can't remember where I found it on the Web, but it
+// didn't come with any license.
+
+using System;
+using System.Collections;
+using System.IO;
+using System.Text;
+
+namespace CSVReader {
+
+ /// <summary>
+ /// A data-reader style interface for reading CSV files.
+ /// </summary>
+ public class CSVReader : IDisposable {
+
+ #region Private variables
+
+ private Stream stream;
+ private StreamReader reader;
+
+ #endregion
+
+ /// <summary>
+ /// Create a new reader for the given stream.
+ /// </summary>
+ /// <param name="s">The stream to read the CSV from.</param>
+ public CSVReader(Stream s) : this(s, null) { }
+
+ /// <summary>
+ /// Create a new reader for the given stream and encoding.
+ /// </summary>
+ /// <param name="s">The stream to read the CSV from.</param>
+ /// <param name="enc">The encoding used.</param>
+ public CSVReader(Stream s, Encoding enc) {
+
+ this.stream = s;
+ if (!s.CanRead) {
+ throw new CSVReaderException("Could not read the given CSV stream!");
+ }
+ reader = (enc != null) ? new StreamReader(s, enc) : new StreamReader(s);
+ }
+
+ /// <summary>
+ /// Creates a new reader for the given text file path.
+ /// </summary>
+ /// <param name="filename">The name of the file to be read.</param>
+ public CSVReader(string filename) : this(filename, null) { }
+
+ /// <summary>
+ /// Creates a new reader for the given text file path and encoding.
+ /// </summary>
+ /// <param name="filename">The name of the file to be read.</param>
+ /// <param name="enc">The encoding used.</param>
+ public CSVReader(string filename, Encoding enc)
+ : this(new FileStream(filename, FileMode.Open), enc) { }
+
+ /// <summary>
+ /// Returns the fields for the next row of CSV data (or null if at eof)
+ /// </summary>
+ /// <returns>A string array of fields or null if at the end of file.</returns>
+ public string[] GetCSVLine() {
+
+ string data = reader.ReadLine();
+ if (data == null) return null;
+ if (data.Length == 0) return new string[0];
+
+ ArrayList result = new ArrayList();
+
+ ParseCSVFields(result, data);
+
+ return (string[])result.ToArray(typeof(string));
+ }
+
+ // Parses the CSV fields and pushes the fields into the result arraylist
+ private void ParseCSVFields(ArrayList result, string data) {
+
+ int pos = -1;
+ while (pos < data.Length)
+ result.Add(ParseCSVField(data, ref pos));
+ }
+
+ // Parses the field at the given position of the data, modified pos to match
+ // the first unparsed position and returns the parsed field
+ private string ParseCSVField(string data, ref int startSeparatorPosition) {
+
+ if (startSeparatorPosition == data.Length-1) {
+ startSeparatorPosition++;
+ // The last field is empty
+ return "";
+ }
+
+ int fromPos = startSeparatorPosition + 1;
+
+ // Determine if this is a quoted field
+ if (data[fromPos] == '"') {
+ // If we're at the end of the string, let's consider this a field that
+ // only contains the quote
+ if (fromPos == data.Length-1) {
+ fromPos++;
+ return "\"";
+ }
+
+ // Otherwise, return a string of appropriate length with double quotes collapsed
+ // Note that FSQ returns data.Length if no single quote was found
+ int nextSingleQuote = FindSingleQuote(data, fromPos+1);
+ startSeparatorPosition = nextSingleQuote+1;
+ return data.Substring(fromPos+1, nextSingleQuote-fromPos-1).Replace("\"\"", "\"");
+ }
+
+ // The field ends in the next comma or EOL
+ int nextComma = data.IndexOf(',', fromPos);
+ if (nextComma == -1) {
+ startSeparatorPosition = data.Length;
+ return data.Substring(fromPos);
+ }
+ else {
+ startSeparatorPosition = nextComma;
+ return data.Substring(fromPos, nextComma-fromPos);
+ }
+ }
+
+ // Returns the index of the next single quote mark in the string
+ // (starting from startFrom)
+ private int FindSingleQuote(string data, int startFrom) {
+
+ int i = startFrom-1;
+ while (++i < data.Length)
+ if (data[i] == '"') {
+ // If this is a double quote, bypass the chars
+ if (i < data.Length-1 && data[i+1] == '"') {
+ i++;
+ continue;
+ }
+ else
+ return i;
+ }
+ // If no quote found, return the end value of i (data.Length)
+ return i;
+ }
+
+ /// <summary>
+ /// Disposes the CSVReader. The underlying stream is closed.
+ /// </summary>
+ public void Dispose() {
+ // Closing the reader closes the underlying stream, too
+ if (reader != null) reader.Close();
+ else if (stream != null)
+ stream.Close(); // In case we failed before the reader was constructed
+ GC.SuppressFinalize(this);
+ }
+ }
+
+
+ /// <summary>
+ /// Exception class for CSVReader exceptions.
+ /// </summary>
+ public class CSVReaderException : ApplicationException {
+
+ /// <summary>
+ /// Constructs a new exception object with the given message.
+ /// </summary>
+ /// <param name="message">The exception message.</param>
+ public CSVReaderException(string message) : base(message) { }
+ }
+}
diff --git a/contrib/Makefile b/contrib/Makefile
new file mode 100644
index 00000000..6e4d367a
--- /dev/null
+++ b/contrib/Makefile
@@ -0,0 +1,4 @@
+all: ParseCcStmt.exe
+
+ParseCcStmt.exe: ParseCcStmt.cs CSVReader.cs
+ gmcs -out:ParseCcStmt.exe ParseCcStmt.cs CSVReader.cs
diff --git a/contrib/ParseCcStmt.cs b/contrib/ParseCcStmt.cs
new file mode 100644
index 00000000..f6b2f20b
--- /dev/null
+++ b/contrib/ParseCcStmt.cs
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+
+using CSVReader;
+
+/**
+ * @file ParseCcStmt.cs
+ *
+ * @brief Provides a .NET way to turn a CSV report into Ledger entries.
+ *
+ * I use this code for converting the statements from my own credit card
+ * issuer. I realize it's strange for this to be in C#, but I wrote it
+ * during a phase of C# contracting. The code is solid enough now --
+ * and the Mono project is portable enough -- that I haven't seen the
+ * need to rewrite it into another language like Python.
+ */
+
+namespace JohnWiegley
+{
+ public class Transaction
+ {
+ public DateTime Date;
+ public DateTime PostedDate;
+ public string Code;
+ public string Payee;
+ public Decimal Amount;
+ }
+
+ public interface IStatementConverter
+ {
+ List<Transaction> ConvertRecords(Stream s);
+ }
+
+ public class ConvertGoldMasterCardStatement : IStatementConverter
+ {
+ public List<Transaction> ConvertRecords(Stream s)
+ {
+ List<Transaction> xacts = new List<Transaction>();
+
+ using (CSVReader.CSVReader csv = new CSVReader.CSVReader(s)) {
+ string[] fields;
+ while ((fields = csv.GetCSVLine()) != null) {
+ if (fields[0] == "TRANSACTION DATE")
+ continue;
+
+ Transaction xact = new Transaction();
+
+ xact.Date = DateTime.ParseExact(fields[0], "mm/dd/yy", null);
+ xact.PostedDate = DateTime.ParseExact(fields[1], "mm/dd/yy", null);
+ xact.Payee = fields[2].Trim();
+ xact.Code = fields[3].Trim();
+ xact.Amount = Convert.ToDecimal(fields[4].Trim());
+
+ if (xact.Code.Length == 0)
+ xact.Code = null;
+
+ xacts.Add(xact);
+ }
+ }
+ return xacts;
+ }
+ }
+
+ public class ConvertMastercardStatement : IStatementConverter
+ {
+ public List<Transaction> ConvertRecords(Stream s)
+ {
+ List<Transaction> xacts = new List<Transaction>();
+
+ using (CSVReader.CSVReader csv = new CSVReader.CSVReader(s)) {
+ string[] fields;
+ while ((fields = csv.GetCSVLine()) != null) {
+ Transaction xact = new Transaction();
+
+ xact.Date = DateTime.ParseExact(fields[0], "m/dd/yyyy", null);
+ xact.Payee = fields[2].Trim();
+ xact.Code = fields[3].Trim();
+ xact.Amount = - Convert.ToDecimal(fields[4].Trim());
+
+ if (xact.Code.Length == 0)
+ xact.Code = null;
+
+ xacts.Add(xact);
+ }
+ }
+ return xacts;
+ }
+ }
+
+ public class PrintTransactions
+ {
+ public string DefaultAccount(Transaction xact) {
+ if (Regex.IsMatch(xact.Payee, "IGA"))
+ return "Expenses:Food";
+ return "Expenses:Food";
+ }
+
+ public void Print(string AccountName, string PayAccountName,
+ List<Transaction> xacts)
+ {
+ foreach (Transaction xact in xacts) {
+ if (xact.Amount < 0) {
+ Console.WriteLine("{0} * {1}{2}", xact.Date.ToString("yyyy/mm/dd"),
+ xact.Code != null ? "(" + xact.Code + ") " : "",
+ xact.Payee);
+ Console.WriteLine(" {0,-36}{1,12}", AccountName,
+ "$" + (- xact.Amount).ToString());
+ Console.WriteLine(" {0}", PayAccountName);
+ } else {
+ Console.WriteLine("{0} {1}{2}", xact.Date.ToString("yyyy/mm/dd"),
+ xact.Code != null ? "(" + xact.Code + ") " : "",
+ xact.Payee);
+ Console.WriteLine(" {0,-36}{1,12}", DefaultAccount(xact),
+ "$" + xact.Amount.ToString());
+ Console.WriteLine(" * {0}", AccountName);
+ }
+ Console.WriteLine();
+ }
+ }
+ }
+
+ public class ParseCcStmt
+ {
+ public static int Main(string[] args)
+ {
+ StreamReader reader = new StreamReader(args[0]);
+ string firstLine = reader.ReadLine();
+
+ string CardAccount = args[1];
+ string BankAccount = args[2];
+
+ IStatementConverter converter;
+
+ if (firstLine.StartsWith("TRANSACTION DATE")) {
+ converter = new ConvertGoldMasterCardStatement();
+ } else {
+ converter = new ConvertMastercardStatement();
+ }
+
+ reader = new StreamReader(args[0]);
+ List<Transaction> xacts = converter.ConvertRecords(reader.BaseStream);
+
+ PrintTransactions printer = new PrintTransactions();
+ printer.Print(CardAccount, BankAccount, xacts);
+
+ return 0;
+ }
+ }
+}
diff --git a/ledger.vim b/contrib/ledger.vim
index df63feb8..df63feb8 100644
--- a/ledger.vim
+++ b/contrib/ledger.vim
diff --git a/scripts/README b/contrib/scripts/README
index 6108afbf..6108afbf 100644
--- a/scripts/README
+++ b/contrib/scripts/README
diff --git a/scripts/bal b/contrib/scripts/bal
index 423e3e41..423e3e41 100755
--- a/scripts/bal
+++ b/contrib/scripts/bal
diff --git a/scripts/bal-huquq b/contrib/scripts/bal-huquq
index fad2854a..fad2854a 100755
--- a/scripts/bal-huquq
+++ b/contrib/scripts/bal-huquq
diff --git a/scripts/entry b/contrib/scripts/entry
index cc030d8e..cc030d8e 100755
--- a/scripts/entry
+++ b/contrib/scripts/entry
diff --git a/scripts/getquote b/contrib/scripts/getquote
index bed561d6..bed561d6 100755
--- a/scripts/getquote
+++ b/contrib/scripts/getquote
diff --git a/scripts/ledger-du b/contrib/scripts/ledger-du
index f5d7dd7d..f5d7dd7d 100755
--- a/scripts/ledger-du
+++ b/contrib/scripts/ledger-du
diff --git a/scripts/report b/contrib/scripts/report
index 24418cdc..24418cdc 100755
--- a/scripts/report
+++ b/contrib/scripts/report
diff --git a/scripts/tc b/contrib/scripts/tc
index c24be99a..c24be99a 100755
--- a/scripts/tc
+++ b/contrib/scripts/tc
diff --git a/scripts/ti b/contrib/scripts/ti
index a7214e65..a7214e65 100755
--- a/scripts/ti
+++ b/contrib/scripts/ti
diff --git a/scripts/to b/contrib/scripts/to
index 3198db3c..3198db3c 100755
--- a/scripts/to
+++ b/contrib/scripts/to
diff --git a/scripts/trend b/contrib/scripts/trend
index 3c189c0b..3c189c0b 100755
--- a/scripts/trend
+++ b/contrib/scripts/trend
diff --git a/csv.cc b/csv.cc
deleted file mode 100644
index 4a8c1157..00000000
--- a/csv.cc
+++ /dev/null
@@ -1,105 +0,0 @@
-#include "csv.h"
-
-namespace ledger {
-
-namespace {
- inline void write_escaped_string(std::ostream& out, const std::string& xact)
- {
- out << "\"";
- for (std::string::const_iterator i = xact.begin(); i != xact.end(); i++)
- if (*i == '"') {
- out << "\\";
- out << "\"";
- } else {
- out << *i;
- }
- out << "\"";
- }
-}
-
-void format_csv_transactions::operator()(transaction_t& xact)
-{
- if (! transaction_has_xdata(xact) ||
- ! (transaction_xdata_(xact).dflags & TRANSACTION_DISPLAYED)) {
-
- {
- format_t fmt("%D");
- std::ostringstream str;
- fmt.format(str, details_t(xact));
- write_escaped_string(out, str.str());
- }
- out << ',';
-
- {
- format_t fmt("%P");
- std::ostringstream str;
- fmt.format(str, details_t(xact));
- write_escaped_string(out, str.str());
- }
- out << ',';
-
- {
- format_t fmt("%A");
- std::ostringstream str;
- fmt.format(str, details_t(xact));
- write_escaped_string(out, str.str());
- }
- out << ',';
-
- {
- format_t fmt("%t");
- std::ostringstream str;
- fmt.format(str, details_t(xact));
- write_escaped_string(out, str.str());
- }
- out << ',';
-
- {
- format_t fmt("%T");
- std::ostringstream str;
- fmt.format(str, details_t(xact));
- write_escaped_string(out, str.str());
- }
- out << ',';
-
- switch (xact.state) {
- case transaction_t::CLEARED:
- write_escaped_string(out, "*");
- break;
- case transaction_t::PENDING:
- write_escaped_string(out, "!");
- break;
- default: {
- transaction_t::state_t state;
- if (xact.entry->get_state(&state))
- switch (state) {
- case transaction_t::CLEARED:
- write_escaped_string(out, "*");
- break;
- case transaction_t::PENDING:
- write_escaped_string(out, "!");
- break;
- default:
- write_escaped_string(out, "");
- break;
- }
- }
- }
- out << ',';
-
- write_escaped_string(out, xact.entry->code);
- out << ',';
-
- {
- format_t fmt("%N");
- std::ostringstream str;
- fmt.format(str, details_t(xact));
- write_escaped_string(out, str.str());
- }
- out << '\n';
-
- transaction_xdata(xact).dflags |= TRANSACTION_DISPLAYED;
- }
-}
-
-} // namespace ledger
diff --git a/csv.h b/csv.h
deleted file mode 100644
index 3b370631..00000000
--- a/csv.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef _CSV_H
-#define _CSV_H
-
-#include "journal.h"
-#include "format.h"
-
-namespace ledger {
-
-class format_csv_transactions : public item_handler<transaction_t>
-{
- protected:
- std::ostream& out;
-
- public:
- format_csv_transactions(std::ostream& _out) : out(_out) {}
- virtual void flush() {
- out.flush();
- }
- virtual void operator()(transaction_t& xact);
-};
-
-} // namespace ledger
-
-#endif // _REPORT_H
diff --git a/datetime.cc b/datetime.cc
deleted file mode 100644
index 2b9ddddf..00000000
--- a/datetime.cc
+++ /dev/null
@@ -1,372 +0,0 @@
-#if defined(__GNUG__) && __GNUG__ < 3
-#define _XOPEN_SOURCE
-#endif
-
-#include "debug.h"
-#include "datetime.h"
-
-#include <ctime>
-#include <cctype>
-#include <climits>
-#include <cstdlib>
-
-date_t date_t::now(std::time(NULL));
-int date_t::current_year = date_t::now.year();
-std::string date_t::input_format;
-std::string date_t::output_format = "%Y/%m/%d";
-
-const char * date_t::formats[] = {
- "%y/%m/%d",
- "%Y/%m/%d",
- "%m/%d",
- "%y.%m.%d",
- "%Y.%m.%d",
- "%m.%d",
- "%y-%m-%d",
- "%Y-%m-%d",
- "%m-%d",
- "%a",
- "%A",
- "%b",
- "%B",
- "%Y",
- NULL
-};
-
-datetime_t datetime_t::now(std::time(NULL));
-
-namespace {
- static std::time_t base = -1;
- static int base_year = -1;
-
- static const int month_days[12] = {
- 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
- };
-
- bool parse_date_mask(const char * date_str, struct std::tm * result);
- bool parse_date(const char * date_str, std::time_t * result,
- const int year = -1);
- bool quick_parse_date(const char * date_str, std::time_t * result);
-}
-
-date_t::date_t(const std::string& _when)
-{
- if (! quick_parse_date(_when.c_str(), &when))
- throw new date_error
- (std::string("Invalid date string: ") + _when);
-}
-
-datetime_t::datetime_t(const std::string& _when)
-{
- if (const char * p = std::strchr(_when.c_str(), ' ')) {
- date_t date(std::string(_when, 0, p - _when.c_str()));
-
- struct std::tm moment = *std::localtime(&date.when);
- if (! strptime(++p, "%H:%M:%S", &moment))
- throw new datetime_error
- (std::string("Invalid date/time string: ") + _when);
-
- when = std::mktime(&moment);
- } else {
- when = date_t(_when).when;
- }
-}
-
-datetime_t interval_t::first(const datetime_t& moment) const
-{
- datetime_t quant(begin);
-
- if (moment && moment > quant) {
- // Find an efficient starting point for the upcoming while loop.
- // We want a date early enough that the range will be correct, but
- // late enough that we don't spend hundreds of thousands of loops
- // skipping through time.
-
- struct std::tm * desc = std::localtime(&moment.when);
-
- if (years)
- desc->tm_mon = 0;
- desc->tm_mday = 1;
-
- desc->tm_hour = 0;
- desc->tm_min = 0;
- desc->tm_sec = 0;
- desc->tm_isdst = -1;
-
- quant = std::mktime(desc);
-
- datetime_t temp;
- while (moment >= (temp = increment(quant))) {
- if (quant == temp)
- break;
- quant = temp;
- }
- }
-
- return quant;
-}
-
-datetime_t interval_t::increment(const datetime_t& moment) const
-{
- struct std::tm * desc = std::localtime(&moment.when);
-
- if (years)
- desc->tm_year += years;
- if (months)
- desc->tm_mon += months;
- if (days)
- desc->tm_mday += days;
-
- desc->tm_hour += hours;
- desc->tm_min += minutes;
- desc->tm_sec += seconds;
-
- desc->tm_isdst = -1;
-
- return std::mktime(desc);
-}
-
-namespace {
- void parse_inclusion_specifier(const std::string& word,
- datetime_t * begin, datetime_t * end)
- {
- struct std::tm when;
-
- if (! parse_date_mask(word.c_str(), &when))
- throw new datetime_error(std::string("Could not parse date mask: ") + word);
-
- when.tm_hour = 0;
- when.tm_min = 0;
- when.tm_sec = 0;
- when.tm_isdst = -1;
-
- bool saw_year = true;
- bool saw_mon = true;
- bool saw_day = true;
-
- if (when.tm_year == -1) {
- when.tm_year = date_t::current_year - 1900;
- saw_year = false;
- }
- if (when.tm_mon == -1) {
- when.tm_mon = 0;
- saw_mon = false;
- } else {
- saw_year = false; // don't increment by year if month used
- }
- if (when.tm_mday == -1) {
- when.tm_mday = 1;
- saw_day = false;
- } else {
- saw_mon = false; // don't increment by month if day used
- saw_year = false; // don't increment by year if day used
- }
-
- if (begin) {
- *begin = std::mktime(&when);
- assert(int(*begin) != -1);
- if (end) {
- *end = interval_t(saw_day ? 1 : 0, saw_mon ? 1 : 0,
- saw_year ? 1 : 0).increment(*begin);
- assert(int(*end) != -1);
- }
- }
- else if (end) {
- *end = std::mktime(&when);
- assert(int(*end) != -1);
- }
- }
-
- inline void read_lower_word(std::istream& in, std::string& word) {
- in >> word;
- for (int i = 0, l = word.length(); i < l; i++)
- word[i] = std::tolower(word[i]);
- }
-
- void parse_date_words(std::istream& in, std::string& word,
- datetime_t * begin, datetime_t * end)
- {
- std::string type;
-
- bool mon_spec = false;
- char buf[32];
-
- if (word == "this" || word == "last" || word == "next") {
- type = word;
- if (! in.eof())
- read_lower_word(in, word);
- else
- word = "month";
- } else {
- type = "this";
- }
-
- if (word == "month") {
- std::strftime(buf, 31, "%B", datetime_t::now.localtime());
- word = buf;
- mon_spec = true;
- }
- else if (word == "year") {
- std::strftime(buf, 31, "%Y", datetime_t::now.localtime());
- word = buf;
- }
-
- parse_inclusion_specifier(word, begin, end);
-
- if (type == "last") {
- if (mon_spec) {
- if (begin)
- *begin = interval_t(0, -1, 0).increment(*begin);
- if (end)
- *end = interval_t(0, -1, 0).increment(*end);
- } else {
- if (begin)
- *begin = interval_t(0, 0, -1).increment(*begin);
- if (end)
- *end = interval_t(0, 0, -1).increment(*end);
- }
- }
- else if (type == "next") {
- if (mon_spec) {
- if (begin)
- *begin = interval_t(0, 1, 0).increment(*begin);
- if (end)
- *end = interval_t(0, 1, 0).increment(*end);
- } else {
- if (begin)
- *begin = interval_t(0, 0, 1).increment(*begin);
- if (end)
- *end = interval_t(0, 0, 1).increment(*end);
- }
- }
- }
-}
-
-void interval_t::parse(std::istream& in)
-{
- std::string word;
-
- while (! in.eof()) {
- read_lower_word(in, word);
- if (word == "every") {
- read_lower_word(in, word);
- if (std::isdigit(word[0])) {
- int quantity = std::atol(word.c_str());
- read_lower_word(in, word);
- if (word == "days")
- days = quantity;
- else if (word == "weeks")
- days = 7 * quantity;
- else if (word == "months")
- months = quantity;
- else if (word == "quarters")
- months = 3 * quantity;
- else if (word == "years")
- years = quantity;
- else if (word == "hours")
- hours = quantity;
- else if (word == "minutes")
- minutes = quantity;
- else if (word == "seconds")
- seconds = quantity;
- }
- else if (word == "day")
- days = 1;
- else if (word == "week")
- days = 7;
- else if (word == "month")
- months = 1;
- else if (word == "quarter")
- months = 3;
- else if (word == "year")
- years = 1;
- else if (word == "hour")
- hours = 1;
- else if (word == "minute")
- minutes = 1;
- else if (word == "second")
- seconds = 1;
- }
- else if (word == "daily")
- days = 1;
- else if (word == "weekly")
- days = 7;
- else if (word == "biweekly")
- days = 14;
- else if (word == "monthly")
- months = 1;
- else if (word == "bimonthly")
- months = 2;
- else if (word == "quarterly")
- months = 3;
- else if (word == "yearly")
- years = 1;
- else if (word == "hourly")
- hours = 1;
- else if (word == "this" || word == "last" || word == "next") {
- parse_date_words(in, word, &begin, &end);
- }
- else if (word == "in") {
- read_lower_word(in, word);
- parse_date_words(in, word, &begin, &end);
- }
- else if (word == "from" || word == "since") {
- read_lower_word(in, word);
- parse_date_words(in, word, &begin, NULL);
- }
- else if (word == "to" || word == "until") {
- read_lower_word(in, word);
- parse_date_words(in, word, NULL, &end);
- }
- else {
- parse_inclusion_specifier(word, &begin, &end);
- }
- }
-}
-
-namespace {
- bool parse_date_mask(const char * date_str, struct std::tm * result)
- {
- if (! date_t::input_format.empty()) {
- std::memset(result, -1, sizeof(struct std::tm));
- if (strptime(date_str, date_t::input_format.c_str(), result))
- return true;
- }
- for (const char ** f = date_t::formats; *f; f++) {
- std::memset(result, -1, sizeof(struct std::tm));
- if (strptime(date_str, *f, result))
- return true;
- }
- return false;
- }
-
- bool parse_date(const char * date_str, std::time_t * result, const int year)
- {
- struct std::tm when;
-
- if (! parse_date_mask(date_str, &when))
- return false;
-
- when.tm_hour = 0;
- when.tm_min = 0;
- when.tm_sec = 0;
-
- if (when.tm_year == -1)
- when.tm_year = ((year == -1) ? date_t::current_year : year) - 1900;
-
- if (when.tm_mon == -1)
- when.tm_mon = 0;
-
- if (when.tm_mday == -1)
- when.tm_mday = 1;
-
- *result = std::mktime(&when);
-
- return true;
- }
-
- bool quick_parse_date(const char * date_str, std::time_t * result)
- {
- return parse_date(date_str, result, date_t::current_year);
- }
-}
diff --git a/datetime.h b/datetime.h
deleted file mode 100644
index c057c76b..00000000
--- a/datetime.h
+++ /dev/null
@@ -1,310 +0,0 @@
-#ifndef _DATETIME_H
-#define _DATETIME_H
-
-#include <ctime>
-#include <sstream>
-
-#include "error.h"
-
-class date_error : public error {
- public:
- date_error(const std::string& reason) throw() : error(reason) {}
- virtual ~date_error() throw() {}
-};
-
-struct interval_t;
-class datetime_t;
-
-class date_t
-{
- date_t(const datetime_t& _when);
-
- public:
- static date_t now;
- static const char * formats[];
- static int current_year;
- static std::string input_format;
- static std::string output_format;
-
- std::time_t when;
-
- date_t() : when(0) {}
- date_t(const date_t& _when) : when(_when.when) {}
-
- date_t(const std::time_t _when) : when(_when) {
-#if 0
- struct std::tm * moment = std::localtime(&_when);
- moment->tm_hour = 0;
- moment->tm_min = 0;
- moment->tm_sec = 0;
- when = std::mktime(moment);
-#endif
- }
- date_t(const interval_t& period);
- date_t(const std::string& _when);
-
- virtual ~date_t() {}
-
- date_t& operator=(const date_t& _when) {
- when = _when.when;
- return *this;
- }
- date_t& operator=(const std::time_t _when) {
- return *this = date_t(_when);
- }
- date_t& operator=(const datetime_t& _when) {
- return *this = date_t(_when);
- }
- date_t& operator=(const interval_t& period) {
- return *this = date_t(period);
- }
- date_t& operator=(const std::string& _when) {
- return *this = date_t(_when);
- }
-
- date_t& operator+=(const interval_t& period);
-
- long operator-=(const date_t& date) {
- return (when - date.when) / 86400;
- }
-
- virtual date_t& operator+=(const long days) {
- // jww (2006-03-26): This is not accurate enough when DST is in effect!
- assert(0);
- when += days * 86400;
- return *this;
- }
- virtual date_t& operator-=(const long days) {
- assert(0);
- when -= days * 86400;
- return *this;
- }
-
-#define DEF_DATE_OP(OP) \
- bool operator OP(const date_t& other) const { \
- return when OP other.when; \
- }
-
- DEF_DATE_OP(<)
- DEF_DATE_OP(<=)
- DEF_DATE_OP(>)
- DEF_DATE_OP(>=)
- DEF_DATE_OP(==)
- DEF_DATE_OP(!=)
-
- operator bool() const {
- return when != 0;
- }
- operator std::string() const {
- return to_string();
- }
-
- std::string to_string(const std::string& format = output_format) const {
- char buf[64];
- std::strftime(buf, 63, format.c_str(), localtime());
- return buf;
- }
-
- int year() const {
- return localtime()->tm_year + 1900;
- }
- int month() const {
- return localtime()->tm_mon + 1;
- }
- int day() const {
- return localtime()->tm_mday;
- }
- int wday() const {
- return localtime()->tm_wday;
- }
-
- std::tm * localtime() const {
- return std::localtime(&when);
- }
-
- void write(std::ostream& out,
- const std::string& format = output_format) const {
- out << to_string(format);
- }
-
- friend class datetime_t;
- friend struct interval_t;
-};
-
-inline long operator-(const date_t& left, const date_t& right) {
- date_t temp(left);
- temp -= right;
- return (long)temp;
-}
-
-inline date_t operator+(const date_t& left, const long days) {
- date_t temp(left);
- temp += days;
- return temp;
-}
-
-inline date_t operator-(const date_t& left, const long days) {
- date_t temp(left);
- temp -= days;
- return temp;
-}
-
-inline std::ostream& operator<<(std::ostream& out, const date_t& moment) {
- moment.write(out);
- return out;
-}
-
-class datetime_error : public error {
- public:
- datetime_error(const std::string& reason) throw() : error(reason) {}
- virtual ~datetime_error() throw() {}
-};
-
-class datetime_t : public date_t
-{
- public:
- static datetime_t now;
-
- datetime_t() : date_t() {}
- datetime_t(const datetime_t& _when) : date_t(_when.when) {}
- datetime_t(const date_t& _when) : date_t(_when) {}
-
- datetime_t(const std::time_t _when) : date_t(_when) {}
- datetime_t(const std::string& _when);
-
- datetime_t& operator=(const datetime_t& _when) {
- when = _when.when;
- return *this;
- }
- datetime_t& operator=(const date_t& _when) {
- when = _when.when;
- return *this;
- }
- datetime_t& operator=(const std::time_t _when) {
- return *this = datetime_t(_when);
- }
- datetime_t& operator=(const std::string& _when) {
- return *this = datetime_t(_when);
- }
-
- long operator-=(const datetime_t& date) {
- return when - date.when;
- }
-
- virtual datetime_t& operator+=(const long secs) {
- when += secs;
- return *this;
- }
- virtual datetime_t& operator-=(const long secs) {
- when -= secs;
- return *this;
- }
-
-#define DEF_DATETIME_OP(OP) \
- bool operator OP(const datetime_t& other) const { \
- return when OP other.when; \
- }
-
- DEF_DATETIME_OP(<)
- DEF_DATETIME_OP(<=)
- DEF_DATETIME_OP(>)
- DEF_DATETIME_OP(>=)
- DEF_DATETIME_OP(==)
- DEF_DATETIME_OP(!=)
-
- int hour() const {
- return localtime()->tm_hour;
- }
- int min() const {
- return localtime()->tm_min;
- }
- int sec() const {
- return localtime()->tm_sec;
- }
-
- friend inline long operator-(const datetime_t& left, const datetime_t& right) {
- std::time_t left_time = left.when;
- std::time_t right_time = right.when;
- return long(left_time) - long(right_time);
- }
-};
-
-inline datetime_t operator+(const datetime_t& left, const long seconds) {
- datetime_t temp(left);
- temp += seconds;
- return temp;
-}
-
-inline datetime_t operator-(const datetime_t& left, const long seconds) {
- datetime_t temp(left);
- temp -= seconds;
- return temp;
-}
-
-inline std::ostream& operator<<(std::ostream& out,
- const datetime_t& moment) {
- char buf[64];
- std::strftime(buf, 63, (date_t::output_format + " %H:%M:%S").c_str(),
- moment.localtime());
- out << buf;
- return out;
-}
-
-struct interval_t
-{
- int years;
- int months;
- int days;
- int hours;
- int minutes;
- int seconds;
-
- datetime_t begin;
- datetime_t end;
-
- interval_t(int _days = 0, int _months = 0, int _years = 0,
- const date_t& _begin = date_t(),
- const date_t& _end = date_t())
- : years(_years), months(_months), days(_days),
- hours(0), minutes(0), seconds(0),
- begin(_begin), end(_end) {}
-
- interval_t(const std::string& desc)
- : years(0), months(0), days(0),
- hours(0), minutes(0), seconds(0) {
- std::istringstream stream(desc);
- parse(stream);
- }
-
- operator bool() const {
- return (years > 0 || months > 0 || days > 0 ||
- hours > 0 || minutes > 0 || seconds > 0);
- }
-
- void start(const datetime_t& moment) {
- begin = first(moment);
- }
- datetime_t first(const datetime_t& moment = datetime_t()) const;
- datetime_t increment(const datetime_t&) const;
-
- void parse(std::istream& in);
-};
-
-inline date_t::date_t(const interval_t& period) {
- when = period.first().when;
-}
-
-inline date_t& date_t::operator+=(const interval_t& period) {
- return *this = period.increment(*this);
-}
-
-inline date_t::date_t(const datetime_t& _when) {
- assert(0);
- struct std::tm * moment = _when.localtime();
- moment->tm_hour = 0;
- moment->tm_min = 0;
- moment->tm_sec = 0;
- when = std::mktime(moment);
-}
-
-#endif // _DATETIME_H
diff --git a/debug.cc b/debug.cc
deleted file mode 100644
index b3b140bc..00000000
--- a/debug.cc
+++ /dev/null
@@ -1,125 +0,0 @@
-#include "debug.h"
-
-#ifdef DEBUG_ENABLED
-
-#include <map>
-#include <fstream>
-
-#include <unistd.h> // for the `write' method
-
-int offset = 0;
-
-std::map<void *, int> ptrs;
-
-#define PRINT_INC(x) { \
- char buf[128]; \
- std::sprintf(buf, "%d: %p: %s", ++offset, ptr, x); \
- write(1, buf, std::strlen(buf)); \
-}
-
-#define PRINT_DEC(x) { \
- char buf[128]; \
- std::sprintf(buf, "%d: %p: %s", --offset, ptr, x); \
- write(1, buf, std::strlen(buf)); \
-}
-
-void * operator new(std::size_t size) throw (std::bad_alloc) {
- void * ptr = std::malloc(size);
- if (DEBUG("debug.alloc")) {
- PRINT_INC("void * operator new(std::size_t size) throw (std::bad_alloc)\n");
- }
- return ptr;
-}
-void * operator new[](std::size_t size) throw (std::bad_alloc) {
- void * ptr = std::malloc(size);
- if (DEBUG("debug.alloc")) {
- PRINT_INC("void * operator new[](std::size_t) throw (std::bad_alloc)\n");
- }
- return ptr;
-}
-void * operator new(std::size_t size, const std::nothrow_t&) throw() {
- void * ptr = std::malloc(size);
- if (DEBUG("debug.alloc")) {
- PRINT_INC("void * operator new(std::size_t size, const std::nothrow_t&) throw()\n");
- }
- return ptr;
-}
-void * operator new[](std::size_t size, const std::nothrow_t&) throw() {
- void * ptr = std::malloc(size);
- if (DEBUG("debug.alloc")) {
- PRINT_INC("void * operator new[](std::size_t size, const std::nothrow_t&) throw()\n");
- }
- return ptr;
-}
-void operator delete(void * ptr) throw() {
- if (DEBUG("debug.alloc")) {
- PRINT_DEC("void operator delete(void * ptr) throw()\n");
- }
- std::free(ptr);
-}
-void operator delete[](void * ptr) throw() {
- if (DEBUG("debug.alloc")) {
- PRINT_DEC("void operator delete[](void * ptr) throw()\n");
- }
- std::free(ptr);
-}
-void operator delete(void * ptr, const std::nothrow_t&) throw() {
- if (DEBUG("debug.alloc")) {
- PRINT_DEC("void operator delete(void * ptr, const std::nothrow_t&) throw()\n");
- }
- std::free(ptr);
-}
-void operator delete[](void * ptr, const std::nothrow_t&) throw() {
- if (DEBUG("debug.alloc")) {
- PRINT_DEC("void operator delete[](void * ptr, const std::nothrow_t&) throw()\n");
- }
- std::free(ptr);
-}
-
-std::ostream * _debug_stream = &std::cerr;
-bool _free_debug_stream = false;
-
-bool _debug_active(const char * const cls) {
- if (char * debug = std::getenv("DEBUG_CLASS")) {
- static const char * error;
- static int erroffset;
- static int ovec[30];
- static pcre * class_regexp = pcre_compile(debug, PCRE_CASELESS,
- &error, &erroffset, NULL);
- return pcre_exec(class_regexp, NULL, cls, std::strlen(cls),
- 0, 0, ovec, 30) >= 0;
- }
- return false;
-}
-
-static struct init_streams {
- init_streams() {
- // If debugging is enabled and DEBUG_FILE is set, all debugging
- // output goes to that file.
- if (const char * p = std::getenv("DEBUG_FILE")) {
- _debug_stream = new std::ofstream(p);
- _free_debug_stream = true;
- }
- }
- ~init_streams() {
- if (_free_debug_stream && _debug_stream) {
- delete _debug_stream;
- _debug_stream = NULL;
- }
- }
-} _debug_init;
-
-#endif // DEBUG_ENABLED
-
-#if DEBUG_LEVEL >= BETA
-
-#include <string>
-
-void debug_assert(const std::string& reason,
- const std::string& file,
- unsigned long line)
-{
- throw new fatal_assert(reason, new file_context(file, line));
-}
-
-#endif
diff --git a/debug.h b/debug.h
deleted file mode 100644
index 81083ad3..00000000
--- a/debug.h
+++ /dev/null
@@ -1,146 +0,0 @@
-#ifndef _DEBUG_H
-#define _DEBUG_H
-
-#define DEVELOPER 4
-#define ALPHA 3
-#define BETA 2
-#define RELEASE 1
-#define NO_SEATBELT 0
-
-#ifndef DEBUG_LEVEL
-#define DEBUG_LEVEL NO_SEATBELT
-#endif
-
-#if DEBUG_LEVEL >= RELEASE
-#include "error.h"
-
-#ifdef assert
-#undef assert
-#endif
-#if DEBUG_LEVEL >= BETA
-void debug_assert(const std::string& reason,
- const std::string& file,
- unsigned long line);
-#define assert(x) \
- if (! (x)) \
- debug_assert(#x, __FILE__, __LINE__)
-#else
-#define assert(x) \
- if (! (x)) \
- throw new fatal_assert(#x, new file_context(__FILE__, __LINE__))
-#endif
-#else
-#ifdef assert
-#undef assert
-#endif
-#define assert(x)
-#endif
-
-//////////////////////////////////////////////////////////////////////
-//
-// General debugging facilities
-//
-// - In developer level, all checking and debugging facilities are
-// active.
-//
-// - Alpha level does not include performance degrading
-// VALIDATE calls.
-//
-// - Beta level is like Alpha, but does not include debugging
-// facilities.
-//
-// - Release level does not include CONFIRM checks, but does include
-// assert calls.
-//
-// - Running with no seatbelt disables all checking except for normal
-// syntax and semantic error checking.
-
-#if DEBUG_LEVEL >= ALPHA
-
-#include <pcre.h>
-#include <cstring>
-#include <new>
-#include <iostream>
-#include <cstdlib>
-
-#include "datetime.h"
-
-#define DEBUG_ENABLED
-
-extern std::ostream * _debug_stream;
-extern bool _free_debug_stream;
-
-bool _debug_active(const char * const cls);
-
-#define DEBUG_CLASS(cls) static const char * const _debug_cls = (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; \
- }
-#define DEBUG_PRINT_(x) DEBUG_PRINT(_debug_cls, x)
-
-#define DEBUG_PRINT_TIME(cls, x) { \
- DEBUG_PRINT(cls, #x << " is " << x); \
-}
-
-#define DEBUG_PRINT_TIME_(x) DEBUG_PRINT_TIME(_debug_cls, x)
-
-#define CONFIRM(x) assert(x)
-
-#if DEBUG_LEVEL == DEVELOPER
-#define VALIDATE(x) assert(x)
-#else
-#define VALIDATE(x)
-#endif
-
-void * operator new(std::size_t) throw (std::bad_alloc);
-void * operator new[](std::size_t) throw (std::bad_alloc);
-void operator delete(void*) throw();
-void operator delete[](void*) throw();
-void * operator new(std::size_t, const std::nothrow_t&) throw();
-void * operator new[](std::size_t, const std::nothrow_t&) throw();
-void operator delete(void*, const std::nothrow_t&) throw();
-void operator delete[](void*, const std::nothrow_t&) throw();
-
-#else // DEBUG_LEVEL
-
-#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)
-#define DEBUG_PRINT_TIME_(x)
-
-#define VALIDATE(x)
-
-#if DEBUG_LEVEL == NO_SEATBELT
-
-#ifdef assert
-#undef assert
-#endif
-#define assert(x)
-#define CONFIRM(x)
-
-#elif DEBUG_LEVEL >= RELEASE
-
-#define CONFIRM(x)
-
-#elif DEBUG_LEVEL >= BETA
-
-#define CONFIRM(x) assert(x)
-
-#endif
-
-#endif // DEBUG_LEVEL
-
-#endif // _DEBUG_H
diff --git a/derive.cc b/derive.cc
deleted file mode 100644
index 1b74578f..00000000
--- a/derive.cc
+++ /dev/null
@@ -1,202 +0,0 @@
-#include "derive.h"
-#include "datetime.h"
-#include "error.h"
-#include "mask.h"
-#include "walk.h"
-
-#include <memory>
-
-namespace ledger {
-
-entry_t * derive_new_entry(journal_t& journal,
- strings_list::iterator i,
- strings_list::iterator end)
-{
- std::auto_ptr<entry_t> added(new entry_t);
-
- entry_t * matching = NULL;
-
- added->_date = *i++;
- if (i == end)
- throw new error("Too few arguments to 'entry'");
-
- mask_t regexp(*i++);
-
- entries_list::reverse_iterator j;
- for (j = journal.entries.rbegin();
- j != journal.entries.rend();
- j++)
- if (regexp.match((*j)->payee)) {
- matching = *j;
- break;
- }
-
- added->payee = matching ? matching->payee : regexp.pattern;
-
- 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 {
- transaction_t * xact = new transaction_t(acct, amount_t(*i++));
- added->add_transaction(xact);
-
- if (! xact->amount.commodity()) {
- // If the amount has no commodity, we can determine it given
- // the account by creating a final for the account and then
- // checking if it contains only a single commodity. An
- // account to which only dollars are applied would imply that
- // dollars are wanted now too.
-
- std::auto_ptr<item_handler<transaction_t> > formatter;
- formatter.reset(new set_account_value);
- walk_entries(journal.entries, *formatter.get());
- formatter->flush();
-
- sum_accounts(*journal.master);
-
- value_t total = account_xdata(*acct).total;
- if (total.type == value_t::AMOUNT)
- xact->amount.set_commodity(((amount_t *) total.data)->commodity());
- }
- }
-
- acct = NULL;
-
- if (i != end) {
- if (! acct)
- acct = journal.find_account_re(*i);
- if (! acct)
- acct = journal.find_account(*i);
- }
-
- if (! acct) {
- 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 k = matching->transactions.begin();
- k != matching->transactions.end();
- k++)
- added->add_transaction(new transaction_t(**k));
- }
- else if ((*i)[0] == '-' || std::isdigit((*i)[0])) {
- transaction_t * m_xact, * xact, * first;
- m_xact = matching->transactions.front();
-
- first = xact = new transaction_t(m_xact->account, amount_t(*i++));
- added->add_transaction(xact);
-
- if (! xact->amount.commodity())
- xact->amount.set_commodity(m_xact->amount.commodity());
-
- m_xact = matching->transactions.back();
-
- xact = new transaction_t(m_xact->account, - first->amount);
- added->add_transaction(xact);
-
- if (i != end) {
- account_t * acct = journal.find_account_re(*i);
- if (! acct)
- acct = journal.find_account(*i);
- assert(acct);
- added->transactions.back()->account = acct;
- }
- }
- else {
- account_t * draw_acct = NULL;
-
- while (i != end) {
- std::string& re_pat(*i++);
- account_t * acct = NULL;
- amount_t * amt = NULL;
-
- mask_t acct_regex(re_pat);
-
- 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:
- transaction_t * xact;
- if (i == end) {
- if (amt)
- xact = new transaction_t(acct, *amt);
- else
- xact = new transaction_t(acct);
- } else {
- amount_t amount(*i++);
-
- strings_list::iterator x = i;
- if (i != end && ++x == end) {
- draw_acct = journal.find_account_re(*i);
- if (! draw_acct)
- draw_acct = journal.find_account(*i);
- i++;
- }
-
- if (! acct)
- acct = journal.find_account_re(re_pat);
- if (! acct)
- acct = journal.find_account(re_pat);
-
- xact = new transaction_t(acct, amount);
- 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);
- }
- }
- added->add_transaction(xact);
- }
-
- if (! draw_acct) {
- assert(matching->transactions.back()->account);
- draw_acct = matching->transactions.back()->account;
- }
- if (draw_acct)
- added->add_transaction(new transaction_t(draw_acct));
- }
-
- done:
- if (! run_hooks(journal.entry_finalize_hooks, *added, false) ||
- ! added->finalize() ||
- ! run_hooks(journal.entry_finalize_hooks, *added, true))
- throw new error("Failed to finalize derived entry (check commodities)");
-
- return added.release();
-}
-
-} // namespace ledger
diff --git a/derive.h b/derive.h
deleted file mode 100644
index 0df7f231..00000000
--- a/derive.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#ifndef _DERIVE_H
-#define _DERIVE_H
-
-#include "journal.h"
-
-namespace ledger {
-
-entry_t * derive_new_entry(journal_t& journal,
- strings_list::iterator begin,
- strings_list::iterator end);
-
-} // namespace ledger
-
-#endif // _DERIVE_H
diff --git a/doc/Doxyfile b/doc/Doxyfile
new file mode 100644
index 00000000..b69be81e
--- /dev/null
+++ b/doc/Doxyfile
@@ -0,0 +1,1406 @@
+# Doxyfile 1.5.5
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = Ledger
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = 3.0
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = %builddir%/doc
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
+# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
+# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish,
+# and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH = /Applications/
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member
+# documentation.
+
+DETAILS_AT_TOP = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = YES
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = YES
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = %srcdir%/src %srcdir%/test/unit
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS = *.c \
+ *.cc \
+ *.cxx \
+ *.cpp \
+ *.c++ \
+ *.d \
+ *.java \
+ *.ii \
+ *.ixx \
+ *.ipp \
+ *.i++ \
+ *.inl \
+ *.h \
+ *.hh \
+ *.hxx \
+ *.hpp \
+ *.h++ \
+ *.idl \
+ *.odl \
+ *.cs \
+ *.php \
+ *.php3 \
+ *.inc \
+ *.m \
+ *.mm \
+ *.dox \
+ *.py \
+ *.C \
+ *.CC \
+ *.C++ \
+ *.II \
+ *.I++ \
+ *.H \
+ *.HH \
+ *.H++ \
+ *.CS \
+ *.PHP \
+ *.PHP3 \
+ *.M \
+ *.MM \
+ *.PY
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output. If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default)
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default)
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code. Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = YES
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = usletter
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = YES
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = YES
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = YES
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = YES
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 1000
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is enabled by default, which results in a transparent
+# background. Warning: Depending on the platform used, enabling this option
+# may lead to badly anti-aliased labels on the edges of a graph (i.e. they
+# become hard to read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO
diff --git a/doc/INSTALL b/doc/INSTALL
new file mode 100644
index 00000000..5458714e
--- /dev/null
+++ b/doc/INSTALL
@@ -0,0 +1,234 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
+2006 Free Software Foundation, Inc.
+
+This file is free documentation; the Free Software Foundation gives
+unlimited permission to copy, distribute and modify it.
+
+Basic Installation
+==================
+
+Briefly, the shell commands `./configure; make; make install' should
+configure, build, and install this package. The following
+more-detailed instructions are generic; see the `README' file for
+instructions specific to this package.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+ It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring. Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+ The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'. You need `configure.ac' if
+you want to change it or regenerate `configure' using a newer version
+of `autoconf'.
+
+The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system.
+
+ Running `configure' might take a while. While running, it prints
+ some messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Optionally, type `make check' to run any self-tests that come with
+ the package.
+
+ 4. Type `make install' to install the programs and any data files and
+ documentation.
+
+ 5. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'. To also remove the
+ files that `configure' created (so you can compile the package for
+ a different kind of computer), type `make distclean'. There is
+ also a `make maintainer-clean' target, but that is intended mainly
+ for the package's developers. If you use it, you may have to get
+ all sorts of other programs in order to regenerate files that came
+ with the distribution.
+
+Compilers and Options
+=====================
+
+Some systems require unusual options for compilation or linking that the
+`configure' script does not know about. Run `./configure --help' for
+details on some of the pertinent environment variables.
+
+ You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment. Here
+is an example:
+
+ ./configure CC=c99 CFLAGS=-g LIBS=-lposix
+
+ *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you can use GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+ With a non-GNU `make', it is safer to compile the package for one
+architecture at a time in the source code directory. After you have
+installed the package for one architecture, use `make distclean' before
+reconfiguring for another architecture.
+
+Installation Names
+==================
+
+By default, `make install' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc. You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX'.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+ In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files. Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+There may be some features `configure' cannot figure out automatically,
+but needs to determine by the type of machine the package will run on.
+Usually, assuming the package is built to be run on the _same_
+architectures, `configure' can figure that out, but if it prints a
+message saying it cannot guess the machine type, give it the
+`--build=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+ CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+ OS KERNEL-OS
+
+ See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+ If you are _building_ compiler tools for cross-compiling, you should
+use the option `--target=TYPE' to select the type of system they will
+produce code for.
+
+ If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+If you want to set default values for `configure' scripts to share, you
+can create a site shell script called `config.site' that gives default
+values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+Variables not defined in a site shell script can be set in the
+environment passed to `configure'. However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost. In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'. For example:
+
+ ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for `CONFIG_SHELL' due to
+an Autoconf bug. Until the bug is fixed you can use this workaround:
+
+ CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+`configure' Invocation
+======================
+
+`configure' recognizes the following options to control how it operates.
+
+`--help'
+`-h'
+ Print a summary of the options to `configure', and exit.
+
+`--version'
+`-V'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`--cache-file=FILE'
+ Enable the cache: use and save the results of the tests in FILE,
+ traditionally `config.cache'. FILE defaults to `/dev/null' to
+ disable caching.
+
+`--config-cache'
+`-C'
+ Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made. To
+ suppress all normal output, redirect it to `/dev/null' (any error
+ messages will still be shown).
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`configure' also accepts some other, not widely useful, options. Run
+`configure --help' for more details.
+
diff --git a/LICENSE b/doc/LICENSE
index f4b7f221..f4b7f221 100644
--- a/LICENSE
+++ b/doc/LICENSE
diff --git a/NEWS b/doc/NEWS
index cd95ed87..cd95ed87 100644
--- a/NEWS
+++ b/doc/NEWS
diff --git a/README b/doc/README
index 8a2406d9..8a2406d9 100644
--- a/README
+++ b/doc/README
diff --git a/ledger.texi b/doc/ledger.texi
index 4b995e78..4b995e78 100644
--- a/ledger.texi
+++ b/doc/ledger.texi
diff --git a/sample.dat b/doc/sample.dat
index 4a271d6f..4a271d6f 100644
--- a/sample.dat
+++ b/doc/sample.dat
diff --git a/emacs.cc b/emacs.cc
deleted file mode 100644
index ed0674a2..00000000
--- a/emacs.cc
+++ /dev/null
@@ -1,80 +0,0 @@
-#include "emacs.h"
-
-namespace ledger {
-
-void format_emacs_transactions::write_entry(entry_t& entry)
-{
- int idx = entry.src_idx;
- for (strings_list::iterator i = entry.journal->sources.begin();
- i != entry.journal->sources.end();
- i++)
- if (! idx--) {
- out << "\"" << *i << "\" ";
- break;
- }
-
- out << (((unsigned long)entry.beg_line) + 1) << " ";
-
- std::time_t date = entry.date().when;
- out << "(" << (date / 65536) << " " << (date % 65536) << " 0) ";
-
- if (entry.code.empty())
- out << "nil ";
- else
- out << "\"" << entry.code << "\" ";
-
- if (entry.payee.empty())
- out << "nil";
- else
- out << "\"" << entry.payee << "\"";
-
- out << "\n";
-}
-
-void format_emacs_transactions::operator()(transaction_t& xact)
-{
- if (! transaction_has_xdata(xact) ||
- ! (transaction_xdata_(xact).dflags & TRANSACTION_DISPLAYED)) {
- if (! last_entry) {
- out << "((";
- write_entry(*xact.entry);
- }
- else if (xact.entry != last_entry) {
- out << ")\n (";
- write_entry(*xact.entry);
- }
- else {
- out << "\n";
- }
-
- out << " (" << (((unsigned long)xact.beg_line) + 1) << " ";
- out << "\"" << xact_account(xact)->fullname() << "\" \""
- << xact.amount << "\"";
-
- switch (xact.state) {
- case transaction_t::CLEARED:
- out << " t";
- break;
- case transaction_t::PENDING:
- out << " pending";
- break;
- default:
- out << " nil";
- break;
- }
-
- if (xact.cost)
- out << " \"" << *xact.cost << "\"";
- else if (! xact.note.empty())
- out << " nil";
- if (! xact.note.empty())
- out << " \"" << xact.note << "\"";
- out << ")";
-
- last_entry = xact.entry;
-
- transaction_xdata(xact).dflags |= TRANSACTION_DISPLAYED;
- }
-}
-
-} // namespace ledger
diff --git a/emacs.h b/emacs.h
deleted file mode 100644
index ea58bad8..00000000
--- a/emacs.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef _EMACS_H
-#define _EMACS_H
-
-#include "journal.h"
-#include "format.h"
-
-namespace ledger {
-
-class format_emacs_transactions : public item_handler<transaction_t>
-{
- protected:
- std::ostream& out;
- entry_t * last_entry;
-
- public:
- format_emacs_transactions(std::ostream& _out)
- : out(_out), last_entry(NULL) {}
-
- virtual void write_entry(entry_t& entry);
- virtual void flush() {
- if (last_entry)
- out << "))\n";
- out.flush();
- }
- virtual void operator()(transaction_t& xact);
-};
-
-} // namespace ledger
-
-#endif // _REPORT_H
diff --git a/error.h b/error.h
deleted file mode 100644
index 7f77db0f..00000000
--- a/error.h
+++ /dev/null
@@ -1,125 +0,0 @@
-#ifndef _ERROR_H
-#define _ERROR_H
-
-#include <exception>
-#include <string>
-#include <cstring>
-#include <sstream>
-#include <list>
-
-class error_context
-{
- public:
- std::string desc;
-
- error_context(const std::string& _desc) throw() : desc(_desc) {}
- virtual ~error_context() throw() {}
- virtual void describe(std::ostream& out) const throw() {
- if (! desc.empty())
- out << desc << std::endl;
- }
-};
-
-class file_context : public error_context
-{
- protected:
- std::string file;
- unsigned long line;
- public:
- file_context(const std::string& _file, unsigned long _line,
- const std::string& desc = "") throw()
- : error_context(desc), file(_file), line(_line) {}
- virtual ~file_context() throw() {}
-
- virtual void describe(std::ostream& out) const throw() {
- if (! desc.empty())
- out << desc << " ";
-
- out << "\"" << file << "\", line " << line << ": ";
- }
-};
-
-class line_context : public error_context {
- public:
- std::string line;
- long pos;
-
- line_context(const std::string& _line, long _pos,
- const std::string& desc = "") throw()
- : error_context(desc), line(_line), pos(_pos) {}
- virtual ~line_context() throw() {}
-
- virtual void describe(std::ostream& out) const throw() {
- if (! desc.empty())
- out << desc << std::endl;
-
- out << " " << line << std::endl << " ";
- long idx = pos < 0 ? line.length() - 1 : pos;
- for (int i = 0; i < idx; i++)
- out << " ";
- out << "^" << std::endl;
- }
-};
-
-//////////////////////////////////////////////////////////////////////
-
-class str_exception : public std::exception {
- protected:
- std::string reason;
- public:
- std::list<error_context *> context;
-
- str_exception(const std::string& _reason,
- error_context * ctxt = NULL) throw()
- : reason(_reason) {
- if (ctxt)
- context.push_back(ctxt);
- }
-
- virtual ~str_exception() throw() {
- for (std::list<error_context *>::iterator i = context.begin();
- i != context.end();
- i++)
- delete *i;
- }
-
- virtual void reveal_context(std::ostream& out,
- const std::string& kind) const throw() {
- for (std::list<error_context *>::const_reverse_iterator i =
- context.rbegin();
- i != context.rend();
- i++) {
- std::list<error_context *>::const_reverse_iterator x = i;
- if (++x == context.rend())
- out << kind << ": ";
- (*i)->describe(out);
- }
- }
-
- virtual const char* what() const throw() {
- return reason.c_str();
- }
-};
-
-class error : public str_exception {
- public:
- error(const std::string& reason, error_context * ctxt = NULL) throw()
- : str_exception(reason, ctxt) {}
- virtual ~error() throw() {}
-};
-
-class fatal : public str_exception {
- public:
- fatal(const std::string& reason, error_context * ctxt = NULL) throw()
- : str_exception(reason, ctxt) {}
- virtual ~fatal() throw() {}
-};
-
-class fatal_assert : public fatal {
- public:
- fatal_assert(const std::string& reason, error_context * ctxt = NULL) throw()
- : fatal(std::string("assertion failed '") + reason + "'", ctxt) {}
- virtual ~fatal_assert() throw() {}
-};
-
-#endif // _ERROR_H
diff --git a/format.cc b/format.cc
deleted file mode 100644
index cb890926..00000000
--- a/format.cc
+++ /dev/null
@@ -1,985 +0,0 @@
-#include "format.h"
-#include "error.h"
-#include "util.h"
-
-#include <cstdlib>
-
-namespace ledger {
-
-format_t::elision_style_t format_t::elision_style = ABBREVIATE;
-int format_t::abbrev_length = 2;
-
-bool format_t::ansi_codes = false;
-bool format_t::ansi_invert = false;
-
-std::string format_t::truncate(const std::string& str, unsigned int width,
- const bool is_account)
-{
- const int len = str.length();
- if (len <= width)
- return str;
-
- assert(width < 4095);
-
- char buf[4096];
-
- switch (elision_style) {
- case TRUNCATE_LEADING:
- // This method truncates at the beginning.
- std::strncpy(buf, str.c_str() + (len - width), width);
- buf[0] = '.';
- buf[1] = '.';
- break;
-
- case TRUNCATE_MIDDLE:
- // This method truncates in the middle.
- std::strncpy(buf, str.c_str(), width / 2);
- std::strncpy(buf + width / 2,
- str.c_str() + (len - (width / 2 + width % 2)),
- width / 2 + width % 2);
- buf[width / 2 - 1] = '.';
- buf[width / 2] = '.';
- break;
-
- case ABBREVIATE:
- if (is_account) {
- std::list<std::string> parts;
- std::string::size_type beg = 0;
- for (std::string::size_type pos = str.find(':');
- pos != std::string::npos;
- beg = pos + 1, pos = str.find(':', beg))
- parts.push_back(std::string(str, beg, pos - beg));
- parts.push_back(std::string(str, beg));
-
- std::string result;
- int newlen = len;
- for (std::list<std::string>::iterator i = parts.begin();
- i != parts.end();
- i++) {
- // Don't contract the last element
- std::list<std::string>::iterator x = i;
- if (++x == parts.end()) {
- result += *i;
- break;
- }
-
- if (newlen > width) {
- result += std::string(*i, 0, abbrev_length);
- result += ":";
- newlen -= (*i).length() - abbrev_length;
- } else {
- result += *i;
- result += ":";
- }
- }
-
- if (newlen > width) {
- // Even abbreviated its too big to show the last account, so
- // abbreviate all but the last and truncate at the beginning.
- std::strncpy(buf, result.c_str() + (result.length() - width), width);
- buf[0] = '.';
- buf[1] = '.';
- } else {
- std::strcpy(buf, result.c_str());
- }
- break;
- }
- // fall through...
-
- case TRUNCATE_TRAILING:
- // This method truncates at the end (the default).
- std::strncpy(buf, str.c_str(), width - 2);
- buf[width - 2] = '.';
- buf[width - 1] = '.';
- break;
- }
- buf[width] = '\0';
-
- return buf;
-}
-
-std::string partial_account_name(const account_t& account)
-{
- std::string name;
-
- for (const account_t * acct = &account;
- acct && acct->parent;
- acct = acct->parent) {
- if (account_has_xdata(*acct) &&
- account_xdata_(*acct).dflags & ACCOUNT_DISPLAYED)
- break;
-
- if (name.empty())
- name = acct->name;
- else
- name = acct->name + ":" + name;
- }
-
- return name;
-}
-
-element_t * format_t::parse_elements(const std::string& fmt)
-{
- std::auto_ptr<element_t> result;
-
- element_t * current = NULL;
-
- char buf[1024];
- char * q = buf;
-
- for (const char * p = fmt.c_str(); *p; p++) {
- if (*p != '%' && *p != '\\') {
- *q++ = *p;
- continue;
- }
-
- if (! result.get()) {
- result.reset(new element_t);
- current = result.get();
- } else {
- current->next = new element_t;
- current = current->next;
- }
-
- if (q != buf) {
- current->type = element_t::STRING;
- current->chars = std::string(buf, q);
- q = buf;
-
- current->next = new element_t;
- current = current->next;
- }
-
- if (*p == '\\') {
- p++;
- current->type = element_t::STRING;
- switch (*p) {
- case 'b': current->chars = "\b"; break;
- case 'f': current->chars = "\f"; break;
- case 'n': current->chars = "\n"; break;
- case 'r': current->chars = "\r"; break;
- case 't': current->chars = "\t"; break;
- case 'v': current->chars = "\v"; break;
- }
- continue;
- }
-
- ++p;
- while (*p == '!' || *p == '-') {
- switch (*p) {
- case '-':
- current->flags |= ELEMENT_ALIGN_LEFT;
- break;
- case '!':
- current->flags |= ELEMENT_HIGHLIGHT;
- break;
- }
- ++p;
- }
-
- int num = 0;
- while (*p && std::isdigit(*p)) {
- num *= 10;
- num += *p++ - '0';
- }
- current->min_width = num;
-
- if (*p == '.') {
- ++p;
- num = 0;
- while (*p && std::isdigit(*p)) {
- num *= 10;
- num += *p++ - '0';
- }
- current->max_width = num;
- if (current->min_width == 0)
- current->min_width = current->max_width;
- }
-
- switch (*p) {
- case '%':
- current->type = element_t::STRING;
- current->chars = "%";
- break;
-
- case '(': {
- ++p;
- const char * b = p;
- int depth = 1;
- while (*p) {
- if (*p == ')' && --depth == 0)
- break;
- else if (*p == '(')
- ++depth;
- p++;
- }
- if (*p != ')')
- throw new format_error("Missing ')'");
-
- current->type = element_t::VALUE_EXPR;
-
- assert(! current->val_expr);
- current->val_expr = std::string(b, p);
- break;
- }
-
- case '[': {
- ++p;
- const char * b = p;
- int depth = 1;
- while (*p) {
- if (*p == ']' && --depth == 0)
- break;
- else if (*p == '[')
- ++depth;
- p++;
- }
- if (*p != ']')
- throw new format_error("Missing ']'");
-
- current->type = element_t::DATE_STRING;
- current->chars = std::string(b, p);
- break;
- }
-
- case 'x':
- switch (*++p) {
- case 'B': current->type = element_t::XACT_BEG_POS; break;
- case 'b': current->type = element_t::XACT_BEG_LINE; break;
- case 'E': current->type = element_t::XACT_END_POS; break;
- case 'e': current->type = element_t::XACT_END_LINE; break;
- case '\0':
- goto END;
- }
- break;
-
- case 'd':
- current->type = element_t::COMPLETE_DATE_STRING;
- current->chars = datetime_t::output_format;
- break;
- case 'D':
- current->type = element_t::DATE_STRING;
- current->chars = datetime_t::output_format;
- break;
-
- case 'S': current->type = element_t::SOURCE; break;
- case 'B': current->type = element_t::ENTRY_BEG_POS; break;
- case 'b': current->type = element_t::ENTRY_BEG_LINE; break;
- case 'E': current->type = element_t::ENTRY_END_POS; break;
- case 'e': current->type = element_t::ENTRY_END_LINE; break;
- case 'X': current->type = element_t::CLEARED; break;
- case 'Y': current->type = element_t::ENTRY_CLEARED; break;
- case 'C': current->type = element_t::CODE; break;
- case 'P': current->type = element_t::PAYEE; break;
- case 'W': current->type = element_t::OPT_ACCOUNT; break;
- case 'a': current->type = element_t::ACCOUNT_NAME; break;
- case 'A': current->type = element_t::ACCOUNT_FULLNAME; break;
- case 't': current->type = element_t::AMOUNT; break;
- case 'o': current->type = element_t::OPT_AMOUNT; break;
- case 'T': current->type = element_t::TOTAL; break;
- case 'N': current->type = element_t::NOTE; break;
- case 'n': current->type = element_t::OPT_NOTE; break;
- case '|': current->type = element_t::SPACER; break;
- case '_': current->type = element_t::DEPTH_SPACER; break;
- }
- }
-
- END:
- if (q != buf) {
- if (! result.get()) {
- result.reset(new element_t);
- current = result.get();
- } else {
- current->next = new element_t;
- current = current->next;
- }
- current->type = element_t::STRING;
- current->chars = std::string(buf, q);
- }
-
- return result.release();
-}
-
-namespace {
- inline void mark_red(std::ostream& out, const element_t * elem) {
- out.setf(std::ios::left);
- out.width(0);
- out << "\e[31m";
-
- if (elem->flags & ELEMENT_ALIGN_LEFT)
- out << std::left;
- else
- out << std::right;
-
- if (elem->min_width > 0)
- out.width(elem->min_width);
- }
-
- inline void mark_plain(std::ostream& out) {
- out << "\e[0m";
- }
-}
-
-void format_t::format(std::ostream& out_str, const details_t& details) const
-{
- for (const element_t * elem = elements; elem; elem = elem->next) {
- std::ostringstream out;
- std::string name;
- bool ignore_max_width = false;
-
- if (elem->flags & ELEMENT_ALIGN_LEFT)
- out << std::left;
- else
- out << std::right;
-
- if (elem->min_width > 0)
- out.width(elem->min_width);
-
- switch (elem->type) {
- case element_t::STRING:
- out << elem->chars;
- break;
-
- case element_t::AMOUNT:
- case element_t::TOTAL:
- case element_t::VALUE_EXPR: {
- value_expr calc;
- switch (elem->type) {
- case element_t::AMOUNT: calc = amount_expr; break;
- case element_t::TOTAL: calc = total_expr; break;
- case element_t::VALUE_EXPR: calc = elem->val_expr; break;
- default:
- assert(0);
- break;
- }
- if (! calc)
- break;
-
- value_t value;
- balance_t * bal = NULL;
-
- calc->compute(value, details);
-
- if (! amount_t::keep_price ||
- ! amount_t::keep_date ||
- ! amount_t::keep_tag) {
- switch (value.type) {
- case value_t::AMOUNT:
- case value_t::BALANCE:
- case value_t::BALANCE_PAIR:
- value = value.strip_annotations();
- break;
- default:
- break;
- }
- }
-
- bool highlighted = false;
-
- switch (value.type) {
- case value_t::BOOLEAN:
- out << (*((bool *) value.data) ? "true" : "false");
- break;
-
- case value_t::INTEGER:
- if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) {
- if (ansi_invert) {
- if (*((long *) value.data) > 0) {
- mark_red(out, elem);
- highlighted = true;
- }
- } else {
- if (*((long *) value.data) < 0) {
- mark_red(out, elem);
- highlighted = true;
- }
- }
- }
- out << *((long *) value.data);
- break;
-
- case value_t::DATETIME:
- out << *((datetime_t *) value.data);
- break;
-
- case value_t::AMOUNT:
- if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) {
- if (ansi_invert) {
- if (*((amount_t *) value.data) > 0) {
- mark_red(out, elem);
- highlighted = true;
- }
- } else {
- if (*((amount_t *) value.data) < 0) {
- mark_red(out, elem);
- highlighted = true;
- }
- }
- }
- out << *((amount_t *) value.data);
- break;
-
- case value_t::BALANCE:
- bal = (balance_t *) value.data;
- // fall through...
-
- case value_t::BALANCE_PAIR:
- if (! bal)
- bal = &((balance_pair_t *) value.data)->quantity;
-
- if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) {
- if (ansi_invert) {
- if (*bal > 0) {
- mark_red(out, elem);
- highlighted = true;
- }
- } else {
- if (*bal < 0) {
- mark_red(out, elem);
- highlighted = true;
- }
- }
- }
- bal->write(out, elem->min_width,
- (elem->max_width > 0 ?
- elem->max_width : elem->min_width));
-
- ignore_max_width = true;
- break;
- default:
- assert(0);
- break;
- }
-
- if (highlighted)
- mark_plain(out);
- break;
- }
-
- case element_t::OPT_AMOUNT:
- if (details.xact) {
- std::string disp;
- bool use_disp = false;
-
- if (details.xact->cost && details.xact->amount) {
- std::ostringstream stream;
- if (! details.xact->amount_expr.expr.empty())
- stream << details.xact->amount_expr.expr;
- else
- stream << details.xact->amount.strip_annotations();
-
- if (! details.xact->cost_expr.empty())
- stream << details.xact->cost_expr;
- else
- stream << " @ " << amount_t(*details.xact->cost /
- details.xact->amount).unround();
- disp = stream.str();
- use_disp = true;
- }
- else if (details.entry) {
- unsigned int xacts_count = 0;
- transaction_t * first = NULL;
- transaction_t * last = NULL;
-
- for (transactions_list::const_iterator i
- = details.entry->transactions.begin();
- i != details.entry->transactions.end();
- i++)
- if (transaction_has_xdata(**i) &&
- transaction_xdata_(**i).dflags & TRANSACTION_TO_DISPLAY) {
- xacts_count++;
- if (! first)
- first = *i;
- last = *i;
- }
-
- use_disp = (xacts_count == 2 && details.xact == last &&
- first->amount == - last->amount);
- }
-
- if (! use_disp) {
- if (! details.xact->amount_expr.expr.empty())
- out << details.xact->amount_expr.expr;
- else
- out << details.xact->amount.strip_annotations();
- } else {
- out << disp;
- }
- }
- break;
-
- case element_t::SOURCE:
- if (details.entry && details.entry->journal) {
- int idx = details.entry->src_idx;
- for (strings_list::iterator i = details.entry->journal->sources.begin();
- i != details.entry->journal->sources.end();
- i++)
- if (! idx--) {
- out << *i;
- break;
- }
- }
- break;
-
- case element_t::ENTRY_BEG_POS:
- if (details.entry)
- out << (unsigned long)details.entry->beg_pos;
- break;
-
- case element_t::ENTRY_BEG_LINE:
- if (details.entry)
- out << details.entry->beg_line;
- break;
-
- case element_t::ENTRY_END_POS:
- if (details.entry)
- out << (unsigned long)details.entry->end_pos;
- break;
-
- case element_t::ENTRY_END_LINE:
- if (details.entry)
- out << details.entry->end_line;
- break;
-
- case element_t::XACT_BEG_POS:
- if (details.xact)
- out << (unsigned long)details.xact->beg_pos;
- break;
-
- case element_t::XACT_BEG_LINE:
- if (details.xact)
- out << details.xact->beg_line;
- break;
-
- case element_t::XACT_END_POS:
- if (details.xact)
- out << (unsigned long)details.xact->end_pos;
- break;
-
- case element_t::XACT_END_LINE:
- if (details.xact)
- out << details.xact->end_line;
- break;
-
- case element_t::DATE_STRING: {
- datetime_t date;
- if (details.xact)
- date = details.xact->date();
- else if (details.entry)
- date = details.entry->date();
-
- char buf[256];
- std::strftime(buf, 255, elem->chars.c_str(), date.localtime());
- out << (elem->max_width == 0 ? buf : truncate(buf, elem->max_width));
- break;
- }
-
- case element_t::COMPLETE_DATE_STRING: {
- datetime_t actual_date;
- datetime_t effective_date;
- if (details.xact) {
- actual_date = details.xact->actual_date();
- effective_date = details.xact->effective_date();
- }
- else if (details.entry) {
- actual_date = details.entry->actual_date();
- effective_date = details.entry->effective_date();
- }
-
- char abuf[256];
- std::strftime(abuf, 255, elem->chars.c_str(), actual_date.localtime());
-
- if (effective_date && effective_date != actual_date) {
- char buf[512];
- char ebuf[256];
- std::strftime(ebuf, 255, elem->chars.c_str(),
- effective_date.localtime());
-
- std::strcpy(buf, abuf);
- std::strcat(buf, "=");
- std::strcat(buf, ebuf);
-
- out << (elem->max_width == 0 ? buf : truncate(buf, elem->max_width));
- } else {
- out << (elem->max_width == 0 ? abuf : truncate(abuf, elem->max_width));
- }
- break;
- }
-
- case element_t::CLEARED:
- if (details.xact) {
- switch (details.xact->state) {
- case transaction_t::CLEARED:
- out << "* ";
- break;
- case transaction_t::PENDING:
- out << "! ";
- break;
- }
- }
- break;
-
- case element_t::ENTRY_CLEARED:
- if (details.entry) {
- transaction_t::state_t state;
- if (details.entry->get_state(&state))
- switch (state) {
- case transaction_t::CLEARED:
- out << "* ";
- break;
- case transaction_t::PENDING:
- out << "! ";
- break;
- }
- }
- break;
-
- case element_t::CODE: {
- std::string temp;
- if (details.entry && ! details.entry->code.empty()) {
- temp += "(";
- temp += details.entry->code;
- temp += ") ";
- }
- out << temp;
- break;
- }
-
- case element_t::PAYEE:
- if (details.entry)
- out << (elem->max_width == 0 ?
- details.entry->payee : truncate(details.entry->payee,
- elem->max_width));
- break;
-
- case element_t::OPT_NOTE:
- if (details.xact && ! details.xact->note.empty())
- out << " ; ";
- // fall through...
-
- case element_t::NOTE:
- if (details.xact)
- out << (elem->max_width == 0 ?
- details.xact->note : truncate(details.xact->note,
- elem->max_width));
- break;
-
- case element_t::OPT_ACCOUNT:
- if (details.entry && details.xact) {
- transaction_t::state_t state;
- if (! details.entry->get_state(&state))
- switch (details.xact->state) {
- case transaction_t::CLEARED:
- name = "* ";
- break;
- case transaction_t::PENDING:
- name = "! ";
- break;
- }
- }
- // fall through...
-
- case element_t::ACCOUNT_NAME:
- case element_t::ACCOUNT_FULLNAME:
- if (details.account) {
- name += (elem->type == element_t::ACCOUNT_FULLNAME ?
- details.account->fullname() :
- partial_account_name(*details.account));
-
- if (details.xact && details.xact->flags & TRANSACTION_VIRTUAL) {
- if (elem->max_width > 2)
- name = truncate(name, elem->max_width - 2, true);
-
- if (details.xact->flags & TRANSACTION_BALANCE)
- name = "[" + name + "]";
- else
- name = "(" + name + ")";
- }
- else if (elem->max_width > 0)
- name = truncate(name, elem->max_width, true);
-
- out << name;
- } else {
- out << " ";
- }
- break;
-
- case element_t::SPACER:
- out << " ";
- break;
-
- case element_t::DEPTH_SPACER:
- for (const account_t * acct = details.account;
- acct;
- acct = acct->parent)
- if (account_has_xdata(*acct) &&
- account_xdata_(*acct).dflags & ACCOUNT_DISPLAYED) {
- if (elem->min_width > 0 || elem->max_width > 0)
- out.width(elem->min_width > elem->max_width ?
- elem->min_width : elem->max_width);
- out << " ";
- }
- break;
-
- default:
- assert(0);
- break;
- }
-
- std::string temp = out.str();
- if (! ignore_max_width &&
- elem->max_width > 0 && elem->max_width < temp.length())
- temp.erase(elem->max_width);
- out_str << temp;
- }
-}
-
-format_transactions::format_transactions(std::ostream& _output_stream,
- const std::string& format)
- : output_stream(_output_stream), last_entry(NULL), last_xact(NULL)
-{
- const char * f = format.c_str();
- if (const char * p = std::strstr(f, "%/")) {
- first_line_format.reset(std::string(f, 0, p - f));
- next_lines_format.reset(std::string(p + 2));
- } else {
- first_line_format.reset(format);
- next_lines_format.reset(format);
- }
-}
-
-void format_transactions::operator()(transaction_t& xact)
-{
- if (! transaction_has_xdata(xact) ||
- ! (transaction_xdata_(xact).dflags & TRANSACTION_DISPLAYED)) {
- if (last_entry != xact.entry) {
- first_line_format.format(output_stream, details_t(xact));
- last_entry = xact.entry;
- }
- else if (last_xact && last_xact->date() != xact.date()) {
- first_line_format.format(output_stream, details_t(xact));
- }
- else {
- next_lines_format.format(output_stream, details_t(xact));
- }
-
- transaction_xdata(xact).dflags |= TRANSACTION_DISPLAYED;
- last_xact = &xact;
- }
-}
-
-void format_entries::format_last_entry()
-{
- bool first = true;
- for (transactions_list::const_iterator i = last_entry->transactions.begin();
- i != last_entry->transactions.end();
- i++) {
- if (transaction_has_xdata(**i) &&
- transaction_xdata_(**i).dflags & TRANSACTION_TO_DISPLAY) {
- if (first) {
- first_line_format.format(output_stream, details_t(**i));
- first = false;
- } else {
- next_lines_format.format(output_stream, details_t(**i));
- }
- transaction_xdata_(**i).dflags |= TRANSACTION_DISPLAYED;
- }
- }
-}
-
-void format_entries::operator()(transaction_t& xact)
-{
- transaction_xdata(xact).dflags |= TRANSACTION_TO_DISPLAY;
-
- if (last_entry && xact.entry != last_entry)
- format_last_entry();
-
- last_entry = xact.entry;
-}
-
-void print_entry(std::ostream& out, const entry_base_t& entry_base,
- const std::string& prefix)
-{
- std::string print_format;
-
- if (const entry_t * entry = dynamic_cast<const entry_t *>(&entry_base)) {
- print_format = (prefix + "%D %X%C%P\n" +
- prefix + " %-34A %12o\n%/" +
- prefix + " %-34A %12o\n");
- }
- else if (const auto_entry_t * entry =
- dynamic_cast<const auto_entry_t *>(&entry_base)) {
- out << "= " << entry->predicate_string << '\n';
- print_format = prefix + " %-34A %12o\n";
- }
- else if (const period_entry_t * entry =
- dynamic_cast<const period_entry_t *>(&entry_base)) {
- out << "~ " << entry->period_string << '\n';
- print_format = prefix + " %-34A %12o\n";
- }
- else {
- assert(0);
- }
-
- format_entries formatter(out, print_format);
- walk_transactions(const_cast<transactions_list&>(entry_base.transactions),
- formatter);
- formatter.flush();
-
- clear_transaction_xdata cleaner;
- walk_transactions(const_cast<transactions_list&>(entry_base.transactions),
- cleaner);
-}
-
-bool disp_subaccounts_p(const account_t& account,
- const item_predicate<account_t>& disp_pred,
- const account_t *& to_show)
-{
- bool display = false;
- unsigned int counted = 0;
- bool matches = disp_pred(account);
- value_t acct_total;
- bool computed = false;
- value_t result;
-
- to_show = NULL;
-
- for (accounts_map::const_iterator i = account.accounts.begin();
- i != account.accounts.end();
- i++) {
- if (! disp_pred(*(*i).second))
- continue;
-
- compute_total(result, details_t(*(*i).second));
- if (! computed) {
- compute_total(acct_total, details_t(account));
- computed = true;
- }
-
- if ((result != acct_total) || counted > 0) {
- display = matches;
- break;
- }
- to_show = (*i).second;
- counted++;
- }
-
- return display;
-}
-
-bool display_account(const account_t& account,
- const item_predicate<account_t>& disp_pred)
-{
- // Never display an account that has already been displayed.
- if (account_has_xdata(account) &&
- account_xdata_(account).dflags & ACCOUNT_DISPLAYED)
- return false;
-
- // At this point, one of two possibilities exists: the account is a
- // leaf which matches the predicate restrictions; or it is a parent
- // and two or more children must be subtotaled; or it is a parent
- // and its child has been hidden by the predicate. So first,
- // determine if it is a parent that must be displayed regardless of
- // the predicate.
-
- const account_t * account_to_show = NULL;
- if (disp_subaccounts_p(account, disp_pred, account_to_show))
- return true;
-
- return ! account_to_show && disp_pred(account);
-}
-
-void format_account::operator()(account_t& account)
-{
- if (display_account(account, disp_pred)) {
- if (! account.parent) {
- account_xdata(account).dflags |= ACCOUNT_TO_DISPLAY;
- } else {
- format.format(output_stream, details_t(account));
- account_xdata(account).dflags |= ACCOUNT_DISPLAYED;
- }
- }
-}
-
-format_equity::format_equity(std::ostream& _output_stream,
- const std::string& _format,
- const std::string& display_predicate)
- : output_stream(_output_stream), disp_pred(display_predicate)
-{
- const char * f = _format.c_str();
- if (const char * p = std::strstr(f, "%/")) {
- first_line_format.reset(std::string(f, 0, p - f));
- next_lines_format.reset(std::string(p + 2));
- } else {
- first_line_format.reset(_format);
- next_lines_format.reset(_format);
- }
-
- entry_t header_entry;
- header_entry.payee = "Opening Balances";
- header_entry._date = datetime_t::now;
- first_line_format.format(output_stream, details_t(header_entry));
-}
-
-void format_equity::flush()
-{
- account_xdata_t xdata;
- xdata.value = total;
- xdata.value.negate();
- account_t summary(NULL, "Equity:Opening Balances");
- summary.data = &xdata;
-
- if (total.type >= value_t::BALANCE) {
- balance_t * bal;
- if (total.type == value_t::BALANCE)
- bal = (balance_t *) total.data;
- else if (total.type == value_t::BALANCE_PAIR)
- bal = &((balance_pair_t *) total.data)->quantity;
- else
- assert(0);
-
- for (amounts_map::const_iterator i = bal->amounts.begin();
- i != bal->amounts.end();
- i++) {
- xdata.value = (*i).second;
- xdata.value.negate();
- next_lines_format.format(output_stream, details_t(summary));
- }
- } else {
- next_lines_format.format(output_stream, details_t(summary));
- }
- output_stream.flush();
-}
-
-void format_equity::operator()(account_t& account)
-{
- if (display_account(account, disp_pred)) {
- if (account_has_xdata(account)) {
- value_t val = account_xdata_(account).value;
-
- if (val.type >= value_t::BALANCE) {
- balance_t * bal;
- if (val.type == value_t::BALANCE)
- bal = (balance_t *) val.data;
- else if (val.type == value_t::BALANCE_PAIR)
- bal = &((balance_pair_t *) val.data)->quantity;
- else
- assert(0);
-
- for (amounts_map::const_iterator i = bal->amounts.begin();
- i != bal->amounts.end();
- i++) {
- account_xdata_(account).value = (*i).second;
- next_lines_format.format(output_stream, details_t(account));
- }
- account_xdata_(account).value = val;
- } else {
- next_lines_format.format(output_stream, details_t(account));
- }
- total += val;
- }
- account_xdata(account).dflags |= ACCOUNT_DISPLAYED;
- }
-}
-
-} // namespace ledger
diff --git a/format.h b/format.h
deleted file mode 100644
index 778ec53f..00000000
--- a/format.h
+++ /dev/null
@@ -1,218 +0,0 @@
-#ifndef _FORMAT_H
-#define _FORMAT_H
-
-#include "journal.h"
-#include "valexpr.h"
-#include "walk.h"
-
-namespace ledger {
-
-std::string truncated(const std::string& str, unsigned int width,
- const int style = 2);
-
-std::string partial_account_name(const account_t& account,
- const unsigned int start_depth);
-
-#define ELEMENT_ALIGN_LEFT 0x01
-#define ELEMENT_HIGHLIGHT 0x02
-
-struct element_t
-{
- enum kind_t {
- STRING,
- VALUE_EXPR,
- SOURCE,
- ENTRY_BEG_POS,
- ENTRY_BEG_LINE,
- ENTRY_END_POS,
- ENTRY_END_LINE,
- XACT_BEG_POS,
- XACT_BEG_LINE,
- XACT_END_POS,
- XACT_END_LINE,
- DATE_STRING,
- COMPLETE_DATE_STRING,
- CLEARED,
- ENTRY_CLEARED,
- CODE,
- PAYEE,
- OPT_ACCOUNT,
- ACCOUNT_NAME,
- ACCOUNT_FULLNAME,
- AMOUNT,
- OPT_AMOUNT,
- TOTAL,
- NOTE,
- OPT_NOTE,
- SPACER,
- DEPTH_SPACER
- };
-
- kind_t type;
- unsigned char flags;
- std::string chars;
- unsigned char min_width;
- unsigned char max_width;
- value_expr val_expr;
-
- struct element_t * next;
-
- element_t() : type(STRING), flags(false),
- min_width(0), max_width(0), next(NULL) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor element_t");
- }
-
- ~element_t() {
- DEBUG_PRINT("ledger.memory.dtors", "dtor element_t");
- if (next) delete next; // recursive, but not too deep
- }
-};
-
-struct format_t
-{
- std::string format_string;
- element_t * elements;
-
- enum elision_style_t {
- TRUNCATE_TRAILING,
- TRUNCATE_MIDDLE,
- TRUNCATE_LEADING,
- ABBREVIATE
- };
-
- static elision_style_t elision_style;
- static int abbrev_length;
-
- static bool ansi_codes;
- static bool ansi_invert;
-
- format_t() : elements(NULL) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor format_t");
- }
- format_t(const std::string& _format) : elements(NULL) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor format_t");
- reset(_format);
- }
- ~format_t() {
- DEBUG_PRINT("ledger.memory.dtors", "dtor format_t");
- if (elements) delete elements;
- }
-
- void reset(const std::string& _format) {
- if (elements)
- delete elements;
- elements = parse_elements(_format);
- format_string = _format;
- }
-
- static element_t * parse_elements(const std::string& fmt);
-
- static std::string truncate(const std::string& str, unsigned int width,
- const bool is_account = false);
-
- void format(std::ostream& out, const details_t& details) const;
-};
-
-class format_transactions : public item_handler<transaction_t>
-{
- protected:
- std::ostream& output_stream;
- format_t first_line_format;
- format_t next_lines_format;
- entry_t * last_entry;
- transaction_t * last_xact;
-
- public:
- format_transactions(std::ostream& _output_stream,
- const std::string& format);
-
- virtual void flush() {
- output_stream.flush();
- }
- virtual void operator()(transaction_t& xact);
-};
-
-class format_entries : public format_transactions
-{
- public:
- format_entries(std::ostream& output_stream, const std::string& format)
- : format_transactions(output_stream, format) {}
-
- virtual void format_last_entry();
-
- virtual void flush() {
- if (last_entry) {
- format_last_entry();
- last_entry = NULL;
- }
- format_transactions::flush();
- }
- virtual void operator()(transaction_t& xact);
-};
-
-void print_entry(std::ostream& out, const entry_base_t& entry,
- const std::string& prefix = "");
-
-bool disp_subaccounts_p(const account_t& account,
- const item_predicate<account_t>& disp_pred,
- const account_t *& to_show);
-
-inline bool disp_subaccounts_p(const account_t& account) {
- const account_t * temp;
- return disp_subaccounts_p(account, item_predicate<account_t>(NULL), temp);
-}
-
-bool display_account(const account_t& account,
- const item_predicate<account_t>& disp_pred);
-
-class format_account : public item_handler<account_t>
-{
- std::ostream& output_stream;
-
- item_predicate<account_t> disp_pred;
-
- public:
- format_t format;
-
- format_account(std::ostream& _output_stream,
- const std::string& _format,
- const std::string& display_predicate = NULL)
- : output_stream(_output_stream), disp_pred(display_predicate),
- format(_format) {}
-
- virtual void flush() {
- output_stream.flush();
- }
-
- virtual void operator()(account_t& account);
-};
-
-class format_equity : public item_handler<account_t>
-{
- std::ostream& output_stream;
- format_t first_line_format;
- format_t next_lines_format;
-
- item_predicate<account_t> disp_pred;
-
- mutable value_t total;
-
- public:
- format_equity(std::ostream& _output_stream,
- const std::string& _format,
- const std::string& display_predicate);
-
- virtual void flush();
- virtual void operator()(account_t& account);
-};
-
-class format_error : public error {
- public:
- format_error(const std::string& reason, error_context * ctxt = NULL) throw()
- : error(reason, ctxt) {}
- virtual ~format_error() throw() {}
-};
-
-} // namespace ledger
-
-#endif // _FORMAT_H
diff --git a/gnucash.h b/gnucash.h
deleted file mode 100644
index 6945e55f..00000000
--- a/gnucash.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef _GNUCASH_H
-#define _GNUCASH_H
-
-#include "parser.h"
-
-namespace ledger {
-
-class gnucash_parser_t : public parser_t
-{
- public:
- virtual bool test(std::istream& in) const;
-
- virtual unsigned int parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master = NULL,
- const std::string * original_file = NULL);
-};
-
-} // namespace ledger
-
-#endif // _GNUCASH_H
diff --git a/journal.cc b/journal.cc
deleted file mode 100644
index 2736ae1f..00000000
--- a/journal.cc
+++ /dev/null
@@ -1,674 +0,0 @@
-#include "journal.h"
-#include "datetime.h"
-#include "valexpr.h"
-#include "mask.h"
-#include "format.h"
-#include "acconf.h"
-
-#include <fstream>
-
-namespace ledger {
-
-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;
-}
-
-datetime_t transaction_t::actual_date() const
-{
- if (! _date && entry)
- return entry->actual_date();
- return _date;
-}
-
-datetime_t transaction_t::effective_date() const
-{
- if (! _date_eff && entry)
- return entry->effective_date();
- return _date_eff;
-}
-
-bool transaction_t::valid() const
-{
- if (! entry) {
- DEBUG_PRINT("ledger.validate", "transaction_t: ! entry");
- return false;
- }
-
- if (state != UNCLEARED && state != CLEARED && state != PENDING) {
- DEBUG_PRINT("ledger.validate", "transaction_t: state is bad");
- return false;
- }
-
- bool found = false;
- for (transactions_list::const_iterator i = entry->transactions.begin();
- i != entry->transactions.end();
- i++)
- if (*i == this) {
- found = true;
- break;
- }
- if (! found) {
- DEBUG_PRINT("ledger.validate", "transaction_t: ! found");
- return false;
- }
-
- if (! account) {
- DEBUG_PRINT("ledger.validate", "transaction_t: ! account");
- return false;
- }
-
- if (! amount.valid()) {
- DEBUG_PRINT("ledger.validate", "transaction_t: ! amount.valid()");
- return false;
- }
-
- if (cost && ! cost->valid()) {
- DEBUG_PRINT("ledger.validate", "transaction_t: cost && ! cost->valid()");
- return false;
- }
-
- if (flags & ~0x003f) {
- DEBUG_PRINT("ledger.validate", "transaction_t: flags are bad");
- return false;
- }
-
- return true;
-}
-
-void entry_base_t::add_transaction(transaction_t * xact)
-{
- transactions.push_back(xact);
-}
-
-bool entry_base_t::remove_transaction(transaction_t * xact)
-{
- transactions.remove(xact);
- return true;
-}
-
-bool entry_base_t::finalize()
-{
- // Scan through and compute the total balance for the entry. This
- // is used for auto-calculating the value of entries with no cost,
- // and the per-unit price of unpriced commodities.
-
- value_t balance;
-
- bool no_amounts = true;
- bool saw_null = false;
- for (transactions_list::const_iterator x = transactions.begin();
- x != transactions.end();
- x++)
- if (! ((*x)->flags & TRANSACTION_VIRTUAL) ||
- ((*x)->flags & TRANSACTION_BALANCE)) {
- amount_t * p = (*x)->cost ? (*x)->cost : &(*x)->amount;
- if (*p) {
- if (no_amounts) {
- balance = *p;
- no_amounts = false;
- } else {
- balance += *p;
- }
-
- if ((*x)->cost && (*x)->amount.commodity().annotated) {
- annotated_commodity_t&
- ann_comm(static_cast<annotated_commodity_t&>
- ((*x)->amount.commodity()));
- if (ann_comm.price)
- balance += ann_comm.price * (*x)->amount - *((*x)->cost);
- }
- } else {
- saw_null = true;
- }
- }
-
- // If it's a null entry, then let the user have their fun
- if (no_amounts)
- return true;
-
- // If there is only one transaction, balance against the basket
- // account if one has been set.
-
- if (journal && journal->basket && transactions.size() == 1) {
- assert(balance.type < value_t::BALANCE);
- transaction_t * nxact = new transaction_t(journal->basket);
- // The amount doesn't need to be set because the code below will
- // balance this transaction against the other.
- add_transaction(nxact);
- nxact->flags |= TRANSACTION_CALCULATED;
- }
-
- // If the first transaction of a two-transaction entry is of a
- // different commodity than the other, and it has no per-unit price,
- // determine its price by dividing the unit count into the value of
- // the balance. This is done for the last eligible commodity.
-
- if (! saw_null && balance && balance.type == value_t::BALANCE &&
- ((balance_t *) balance.data)->amounts.size() == 2) {
- transactions_list::const_iterator x = transactions.begin();
- commodity_t& this_comm = (*x)->amount.commodity();
-
- amounts_map::const_iterator this_bal =
- ((balance_t *) balance.data)->amounts.find(&this_comm);
- amounts_map::const_iterator other_bal =
- ((balance_t *) balance.data)->amounts.begin();
- if (this_bal == other_bal)
- other_bal++;
-
- if (this_bal != ((balance_t *) balance.data)->amounts.end()) {
- amount_t per_unit_cost =
- amount_t((*other_bal).second / (*this_bal).second).unround();
-
- for (; x != transactions.end(); x++) {
- if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL) ||
- ! (*x)->amount || (*x)->amount.commodity() != this_comm)
- continue;
-
- assert((*x)->amount);
- balance -= (*x)->amount;
-
- entry_t * entry = dynamic_cast<entry_t *>(this);
-
- if ((*x)->amount.commodity() &&
- ! (*x)->amount.commodity().annotated)
- (*x)->amount.annotate_commodity
- (abs(per_unit_cost),
- entry ? entry->actual_date() : datetime_t(),
- entry ? entry->code : "");
-
- (*x)->cost = new amount_t(- (per_unit_cost * (*x)->amount));
- balance += *(*x)->cost;
- }
- }
- }
-
- // Walk through each of the transactions, fixing up any that we
- // can, and performing any on-the-fly calculations.
-
- bool empty_allowed = true;
-
- for (transactions_list::const_iterator x = transactions.begin();
- x != transactions.end();
- x++) {
- if (! (*x)->amount.null() ||
- (((*x)->flags & TRANSACTION_VIRTUAL) &&
- ! ((*x)->flags & TRANSACTION_BALANCE)))
- continue;
-
- if (! empty_allowed)
- throw new error("Only one transaction with null amount allowed per entry");
- empty_allowed = false;
-
- // If one transaction gives no value at all, its value will become
- // the inverse of the value of the others. If multiple
- // commodities are involved, multiple transactions will be
- // generated to balance them all.
-
- balance_t * bal = NULL;
- switch (balance.type) {
- case value_t::BALANCE_PAIR:
- bal = &((balance_pair_t *) balance.data)->quantity;
- // fall through...
-
- case value_t::BALANCE:
- if (! bal)
- bal = (balance_t *) balance.data;
-
- if (bal->amounts.size() < 2) {
- balance.cast(value_t::AMOUNT);
- } else {
- bool first = true;
- for (amounts_map::const_iterator i = bal->amounts.begin();
- i != bal->amounts.end();
- i++) {
- amount_t amt = (*i).second;
- amt.negate();
-
- if (first) {
- (*x)->amount = amt;
- first = false;
- } else {
- transaction_t * nxact = new transaction_t((*x)->account);
- add_transaction(nxact);
- nxact->flags |= TRANSACTION_CALCULATED;
- nxact->amount = amt;
- }
-
- balance += amt;
- }
- break;
- }
- // fall through...
-
- case value_t::AMOUNT:
- (*x)->amount = *((amount_t *) balance.data);
- (*x)->amount.negate();
- (*x)->flags |= TRANSACTION_CALCULATED;
-
- balance += (*x)->amount;
- break;
-
- default:
- break;
- }
- }
-
- balance.round();
-
- if (balance) {
- error * err =
- new balance_error("Entry does not balance",
- new entry_context(*this, "While balancing entry:"));
- DEBUG_PRINT("ledger.journal.unbalanced_remainder",
- "balance = " << balance);
- err->context.push_front
- (new value_context(balance, "Unbalanced remainder is:"));
- throw err;
- }
-
- // Add the final calculated totals each to their related account
-
- if (dynamic_cast<entry_t *>(this)) {
- for (transactions_list::const_iterator x = transactions.begin();
- x != transactions.end();
- x++) {
- account_xdata_t& xdata(account_xdata(*(*x)->account));
- // jww (2008-08-09): For now, this feature only works for
- // non-specific commodities.
- xdata.value += (*x)->amount.strip_annotations();
- if ((*x)->account->fullname() == "Assets:Cash")
- DEBUG_PRINT("ledger.xact.assign",
- "account " << (*x)->account->fullname() << " balance = " << xdata.value);
- }
- }
-
- return true;
-}
-
-entry_t::entry_t(const entry_t& e)
- : entry_base_t(e), _date(e._date), _date_eff(e._date_eff),
- code(e.code), payee(e.payee)
-{
- DEBUG_PRINT("ledger.memory.ctors", "ctor entry_t");
-
- for (transactions_list::const_iterator i = transactions.begin();
- i != transactions.end();
- i++)
- (*i)->entry = this;
-}
-
-bool entry_t::get_state(transaction_t::state_t * state) const
-{
- bool first = true;
- bool hetero = false;
-
- for (transactions_list::const_iterator i = transactions.begin();
- i != transactions.end();
- i++) {
- if (first) {
- *state = (*i)->state;
- first = false;
- }
- else if (*state != (*i)->state) {
- hetero = true;
- break;
- }
- }
-
- return ! hetero;
-}
-
-void entry_t::add_transaction(transaction_t * xact)
-{
- xact->entry = this;
- entry_base_t::add_transaction(xact);
-}
-
-bool entry_t::valid() const
-{
- if (! _date || ! journal) {
- DEBUG_PRINT("ledger.validate", "entry_t: ! _date || ! journal");
- return false;
- }
-
- for (transactions_list::const_iterator i = transactions.begin();
- i != transactions.end();
- i++)
- if ((*i)->entry != this || ! (*i)->valid()) {
- DEBUG_PRINT("ledger.validate", "entry_t: transaction not valid");
- return false;
- }
-
- return true;
-}
-
-auto_entry_t::auto_entry_t(const std::string& _predicate)
- : predicate_string(_predicate)
-{
- DEBUG_PRINT("ledger.memory.ctors", "ctor auto_entry_t");
- predicate = new item_predicate<transaction_t>(predicate_string);
-}
-
-auto_entry_t::~auto_entry_t()
-{
- DEBUG_PRINT("ledger.memory.dtors", "dtor auto_entry_t");
- if (predicate)
- delete predicate;
-}
-
-void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
-{
- transactions_list initial_xacts(entry.transactions.begin(),
- entry.transactions.end());
-
- for (transactions_list::iterator i = initial_xacts.begin();
- i != initial_xacts.end();
- i++) {
- if ((*predicate)(**i)) {
- for (transactions_list::iterator t = transactions.begin();
- t != transactions.end();
- t++) {
- amount_t amt;
- if (! (*t)->amount.commodity()) {
- if (! post)
- continue;
- amt = (*i)->amount * (*t)->amount;
- } else {
- if (post)
- continue;
- amt = (*t)->amount;
- }
-
- account_t * account = (*t)->account;
- std::string fullname = account->fullname();
- assert(! fullname.empty());
- if (fullname == "$account" || fullname == "@account")
- account = (*i)->account;
-
- transaction_t * xact
- = new transaction_t(account, amt, (*t)->flags | TRANSACTION_AUTO);
-
- // Copy over details so that the resulting transaction is a mirror of
- // the automated entry's one.
- xact->state = (*t)->state;
- xact->_date = (*t)->_date;
- xact->_date_eff = (*t)->_date_eff;
- xact->note = (*t)->note;
- xact->beg_pos = (*t)->beg_pos;
- xact->beg_line = (*t)->beg_line;
- xact->end_pos = (*t)->end_pos;
- xact->end_line = (*t)->end_line;
-
- entry.add_transaction(xact);
- }
- }
- }
-}
-
-account_t::~account_t()
-{
- DEBUG_PRINT("ledger.memory.dtors", "dtor account_t " << this);
- //assert(! data);
-
- for (accounts_map::iterator i = accounts.begin();
- i != accounts.end();
- i++)
- delete (*i).second;
-}
-
-account_t * account_t::find_account(const std::string& name,
- const bool auto_create)
-{
- accounts_map::const_iterator i = accounts.find(name);
- if (i != accounts.end())
- return (*i).second;
-
- char buf[256];
-
- std::string::size_type sep = name.find(':');
- assert(sep < 256|| sep == std::string::npos);
-
- const char * first, * rest;
- if (sep == std::string::npos) {
- first = name.c_str();
- rest = NULL;
- } else {
- std::strncpy(buf, name.c_str(), sep);
- buf[sep] = '\0';
-
- first = buf;
- rest = name.c_str() + sep + 1;
- }
-
- account_t * account;
-
- i = accounts.find(first);
- if (i == accounts.end()) {
- if (! auto_create)
- return NULL;
-
- account = new account_t(this, first);
- account->journal = journal;
-
- std::pair<accounts_map::iterator, bool> result
- = accounts.insert(accounts_pair(first, account));
- assert(result.second);
- } else {
- account = (*i).second;
- }
-
- if (rest)
- account = account->find_account(rest, auto_create);
-
- return account;
-}
-
-static inline
-account_t * find_account_re_(account_t * account, const mask_t& regexp)
-{
- if (regexp.match(account->fullname()))
- return account;
-
- for (accounts_map::iterator i = account->accounts.begin();
- i != account->accounts.end();
- i++)
- if (account_t * a = find_account_re_((*i).second, regexp))
- return a;
-
- return NULL;
-}
-
-account_t * journal_t::find_account_re(const std::string& regexp)
-{
- return find_account_re_(master, mask_t(regexp));
-}
-
-std::string account_t::fullname() const
-{
- if (! _fullname.empty()) {
- return _fullname;
- } else {
- const account_t * first = this;
- std::string fullname = name;
-
- while (first->parent) {
- first = first->parent;
- if (! first->name.empty())
- fullname = first->name + ":" + fullname;
- }
-
- _fullname = fullname;
-
- return fullname;
- }
-}
-
-std::ostream& operator<<(std::ostream& out, const account_t& account)
-{
- out << account.fullname();
- return out;
-}
-
-bool account_t::valid() const
-{
- if (depth > 256 || ! journal) {
- DEBUG_PRINT("ledger.validate", "account_t: depth > 256 || ! journal");
- return false;
- }
-
- for (accounts_map::const_iterator i = accounts.begin();
- i != accounts.end();
- i++) {
- if (this == (*i).second) {
- DEBUG_PRINT("ledger.validate", "account_t: parent refers to itself!");
- return false;
- }
-
- if (! (*i).second->valid()) {
- DEBUG_PRINT("ledger.validate", "account_t: child not valid");
- return false;
- }
- }
-
- return true;
-}
-
-journal_t::~journal_t()
-{
- DEBUG_PRINT("ledger.memory.dtors", "dtor journal_t");
-
- assert(master);
- delete master;
-
- // Don't bother unhooking each entry's transactions from the
- // accounts they refer to, because all accounts are about to
- // be deleted.
- for (entries_list::iterator i = entries.begin();
- i != entries.end();
- i++)
- if (! item_pool ||
- ((char *) *i) < item_pool || ((char *) *i) >= item_pool_end)
- delete *i;
- else
- (*i)->~entry_t();
-
- for (auto_entries_list::iterator i = auto_entries.begin();
- i != auto_entries.end();
- i++)
- if (! item_pool ||
- ((char *) *i) < item_pool || ((char *) *i) >= item_pool_end)
- delete *i;
- else
- (*i)->~auto_entry_t();
-
- for (period_entries_list::iterator i = period_entries.begin();
- i != period_entries.end();
- i++)
- if (! item_pool ||
- ((char *) *i) < item_pool || ((char *) *i) >= item_pool_end)
- delete *i;
- else
- (*i)->~period_entry_t();
-
- if (item_pool)
- delete[] item_pool;
-}
-
-bool journal_t::add_entry(entry_t * entry)
-{
- entry->journal = this;
-
- if (! run_hooks(entry_finalize_hooks, *entry, false) ||
- ! entry->finalize() ||
- ! run_hooks(entry_finalize_hooks, *entry, true)) {
- entry->journal = NULL;
- return false;
- }
-
- entries.push_back(entry);
-
- for (transactions_list::const_iterator i = entry->transactions.begin();
- i != entry->transactions.end();
- i++)
- if ((*i)->cost && (*i)->amount)
- (*i)->amount.commodity().add_price(entry->date(),
- *(*i)->cost / (*i)->amount);
-
- return true;
-}
-
-bool journal_t::remove_entry(entry_t * entry)
-{
- bool found = false;
- entries_list::iterator i;
- for (i = entries.begin(); i != entries.end(); i++)
- if (*i == entry) {
- found = true;
- break;
- }
- if (! found)
- return false;
-
- entries.erase(i);
- entry->journal = NULL;
-
- return true;
-}
-
-bool journal_t::valid() const
-{
- if (! master->valid()) {
- DEBUG_PRINT("ledger.validate", "journal_t: master not valid");
- return false;
- }
-
- for (entries_list::const_iterator i = entries.begin();
- i != entries.end();
- i++)
- if (! (*i)->valid()) {
- DEBUG_PRINT("ledger.validate", "journal_t: entry not valid");
- return false;
- }
-
- for (commodities_map::const_iterator i = commodity_t::commodities.begin();
- i != commodity_t::commodities.end();
- i++)
- if (! (*i).second->valid()) {
- DEBUG_PRINT("ledger.validate", "journal_t: commodity not valid");
- return false;
- }
-
- return true;
-}
-
-void entry_context::describe(std::ostream& out) const throw()
-{
- if (! desc.empty())
- out << desc << std::endl;
-
- print_entry(out, entry, " ");
-}
-
-xact_context::xact_context(const ledger::transaction_t& _xact,
- const std::string& desc) throw()
- : xact(_xact), file_context("", 0, desc)
-{
- const ledger::strings_list& sources(xact.entry->journal->sources);
- int x = 0;
- for (ledger::strings_list::const_iterator i = sources.begin();
- i != sources.end();
- i++, x++)
- if (x == xact.entry->src_idx) {
- file = *i;
- break;
- }
- line = xact.beg_line;
-}
-
-} // namespace ledger
diff --git a/journal.h b/journal.h
deleted file mode 100644
index a459160c..00000000
--- a/journal.h
+++ /dev/null
@@ -1,467 +0,0 @@
-#ifndef _JOURNAL_H
-#define _JOURNAL_H
-
-#include <map>
-#include <list>
-#include <string>
-#include <iostream>
-
-#include "amount.h"
-#include "datetime.h"
-#include "value.h"
-#include "valexpr.h"
-#include "error.h"
-#include "debug.h"
-#include "util.h"
-
-namespace ledger {
-
-// These flags persist with the object
-#define TRANSACTION_NORMAL 0x0000
-#define TRANSACTION_VIRTUAL 0x0001
-#define TRANSACTION_BALANCE 0x0002
-#define TRANSACTION_AUTO 0x0004
-#define TRANSACTION_BULK_ALLOC 0x0008
-#define TRANSACTION_CALCULATED 0x0010
-
-class entry_t;
-class account_t;
-
-class transaction_t
-{
- public:
- enum state_t { UNCLEARED, CLEARED, PENDING };
-
- entry_t * entry;
- datetime_t _date;
- datetime_t _date_eff;
- account_t * account;
- amount_t amount;
- value_expr amount_expr;
- amount_t * cost;
- std::string cost_expr;
- state_t state;
- unsigned short flags;
- std::string note;
- istream_pos_type beg_pos;
- unsigned long beg_line;
- istream_pos_type end_pos;
- unsigned long end_line;
- mutable void * data;
-
- static bool use_effective_date;
-
- transaction_t(account_t * _account = NULL)
- : entry(NULL), account(_account), cost(NULL),
- state(UNCLEARED), flags(TRANSACTION_NORMAL),
- 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,
- const std::string& _note = "")
- : entry(NULL), account(_account), amount(_amount), cost(NULL),
- state(UNCLEARED), flags(_flags),
- note(_note), 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), account(xact.account), amount(xact.amount),
- cost(xact.cost ? new amount_t(*xact.cost) : NULL),
- state(xact.state), flags(xact.flags), note(xact.note),
- beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t");
- }
- ~transaction_t();
-
- datetime_t actual_date() const;
- datetime_t effective_date() const;
- datetime_t date() const {
- if (use_effective_date)
- return effective_date();
- else
- return actual_date();
- }
-
- bool operator==(const transaction_t& xact) {
- return this == &xact;
- }
- bool operator!=(const transaction_t& xact) {
- return ! (*this == xact);
- }
-
- bool valid() const;
-};
-
-class xact_context : public file_context {
- public:
- const transaction_t& xact;
-
- xact_context(const transaction_t& _xact,
- const std::string& desc = "") throw();
- virtual ~xact_context() throw() {}
-};
-
-class journal_t;
-
-typedef std::list<transaction_t *> transactions_list;
-
-class entry_base_t
-{
- public:
- journal_t * journal;
- std::string note;
- unsigned long src_idx;
- istream_pos_type beg_pos;
- unsigned long beg_line;
- istream_pos_type end_pos;
- unsigned long end_line;
- transactions_list transactions;
-
- entry_base_t() : journal(NULL),
- beg_pos(0), beg_line(0), end_pos(0), end_line(0)
- {
- DEBUG_PRINT("ledger.memory.ctors", "ctor entry_base_t");
- }
- entry_base_t(const entry_base_t& e) : journal(NULL),
- beg_pos(0), beg_line(0), end_pos(0), end_line(0)
- {
- DEBUG_PRINT("ledger.memory.ctors", "ctor entry_base_t");
- for (transactions_list::const_iterator i = e.transactions.begin();
- i != e.transactions.end();
- i++)
- transactions.push_back(new transaction_t(**i));
- }
- virtual ~entry_base_t() {
- DEBUG_PRINT("ledger.memory.dtors", "dtor entry_base_t");
- for (transactions_list::iterator i = transactions.begin();
- i != transactions.end();
- i++)
- if (! ((*i)->flags & TRANSACTION_BULK_ALLOC))
- delete *i;
- else
- (*i)->~transaction_t();
- }
-
- bool operator==(const entry_base_t& entry) {
- return this == &entry;
- }
- bool operator!=(const entry_base_t& entry) {
- return ! (*this == entry);
- }
-
- virtual void add_transaction(transaction_t * xact);
- virtual bool remove_transaction(transaction_t * xact);
-
- virtual bool finalize();
- virtual bool valid() const = 0;
-};
-
-class entry_t : public entry_base_t
-{
- public:
- datetime_t _date;
- datetime_t _date_eff;
- std::string code;
- std::string payee;
-
- entry_t() {
- DEBUG_PRINT("ledger.memory.ctors", "ctor entry_t");
- }
- entry_t(const entry_t& e);
-
- virtual ~entry_t() {
- DEBUG_PRINT("ledger.memory.dtors", "dtor entry_t");
- }
-
- datetime_t actual_date() const {
- return _date;
- }
- datetime_t effective_date() const {
- if (! _date_eff)
- return _date;
- return _date_eff;
- }
- datetime_t date() const {
- if (transaction_t::use_effective_date)
- return effective_date();
- else
- return actual_date();
- }
-
- virtual void add_transaction(transaction_t * xact);
-
- virtual bool valid() const;
-
- bool get_state(transaction_t::state_t * state) const;
-};
-
-struct entry_finalizer_t {
- virtual ~entry_finalizer_t() {}
- virtual bool operator()(entry_t& entry, bool post) = 0;
-};
-
-class entry_context : public error_context {
- public:
- const entry_base_t& entry;
-
- entry_context(const entry_base_t& _entry,
- const std::string& desc = "") throw()
- : error_context(desc), entry(_entry) {}
- virtual ~entry_context() throw() {}
-
- virtual void describe(std::ostream& out) const throw();
-};
-
-class balance_error : public error {
- public:
- balance_error(const std::string& reason, error_context * ctxt = NULL) throw()
- : error(reason, ctxt) {}
- virtual ~balance_error() throw() {}
-};
-
-
-template <typename T>
-class item_predicate;
-
-class auto_entry_t : public entry_base_t
-{
-public:
- item_predicate<transaction_t> * predicate;
- std::string predicate_string;
-
- auto_entry_t() : predicate(NULL) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor auto_entry_t");
- }
- auto_entry_t(const std::string& _predicate);
-
- virtual ~auto_entry_t();
-
- virtual void extend_entry(entry_base_t& entry, bool post);
- virtual bool valid() const {
- return true;
- }
-};
-
-class journal_t;
-struct auto_entry_finalizer_t : public entry_finalizer_t {
- journal_t * journal;
- auto_entry_finalizer_t(journal_t * _journal) : journal(_journal) {}
- virtual bool operator()(entry_t& entry, bool post);
-};
-
-
-class period_entry_t : public entry_base_t
-{
- public:
- interval_t period;
- std::string period_string;
-
- period_entry_t() {
- DEBUG_PRINT("ledger.memory.ctors", "ctor period_entry_t");
- }
- period_entry_t(const std::string& _period)
- : period(_period), period_string(_period) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor period_entry_t");
- }
- period_entry_t(const period_entry_t& e)
- : entry_base_t(e), period(e.period), period_string(e.period_string) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor period_entry_t");
- }
-
- virtual ~period_entry_t() {
- DEBUG_PRINT("ledger.memory.dtors", "dtor period_entry_t");
- }
-
- virtual bool valid() const {
- return period;
- }
-};
-
-
-typedef std::map<const std::string, account_t *> accounts_map;
-typedef std::pair<const std::string, account_t *> accounts_pair;
-
-class account_t
-{
- public:
- typedef unsigned long ident_t;
-
- journal_t * journal;
- account_t * parent;
- std::string name;
- std::string note;
- unsigned short depth;
- accounts_map accounts;
-
- mutable void * data;
- mutable ident_t ident;
- mutable std::string _fullname;
-
- account_t(account_t * _parent = NULL,
- const std::string& _name = "",
- const std::string& _note = "")
- : parent(_parent), name(_name), note(_note),
- depth(parent ? parent->depth + 1 : 0), data(NULL), ident(0) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor account_t " << this);
- }
- ~account_t();
-
- bool operator==(const account_t& account) {
- return this == &account;
- }
- bool operator!=(const account_t& account) {
- return ! (*this == account);
- }
-
- std::string fullname() const;
-
- void add_account(account_t * acct) {
- accounts.insert(accounts_pair(acct->name, acct));
- acct->journal = journal;
- }
- bool remove_account(account_t * acct) {
- accounts_map::size_type n = accounts.erase(acct->name);
- acct->journal = NULL;
- return n > 0;
- }
-
- account_t * find_account(const std::string& name, bool auto_create = true);
-
- operator std::string() const {
- return fullname();
- }
-
- bool valid() const;
-
- friend class journal_t;
-};
-
-std::ostream& operator<<(std::ostream& out, const account_t& account);
-
-
-struct func_finalizer_t : public entry_finalizer_t {
- typedef bool (*func_t)(entry_t& entry, bool post);
- func_t func;
- func_finalizer_t(func_t _func) : func(_func) {}
- func_finalizer_t(const func_finalizer_t& other) : func(other.func) {}
- virtual bool operator()(entry_t& entry, bool post) {
- return func(entry, post);
- }
-};
-
-template <typename T>
-void add_hook(std::list<T>& list, T obj, const bool prepend = false) {
- if (prepend)
- list.push_front(obj);
- else
- list.push_back(obj);
-}
-
-template <typename T>
-void remove_hook(std::list<T>& list, T obj) {
- list.remove(obj);
-}
-
-template <typename T, typename Data>
-bool run_hooks(std::list<T>& list, Data& item, bool post) {
- for (typename std::list<T>::const_iterator i = list.begin();
- i != list.end();
- i++)
- if (! (*(*i))(item, post))
- return false;
- return true;
-}
-
-
-typedef std::list<entry_t *> entries_list;
-typedef std::list<auto_entry_t *> auto_entries_list;
-typedef std::list<period_entry_t *> period_entries_list;
-typedef std::list<std::string> strings_list;
-
-class journal_t
-{
- public:
- account_t * master;
- account_t * basket;
- entries_list entries;
- strings_list sources;
- std::string price_db;
- char * item_pool;
- char * item_pool_end;
-
- auto_entries_list auto_entries;
- period_entries_list period_entries;
- mutable accounts_map accounts_cache;
-
- std::list<entry_finalizer_t *> entry_finalize_hooks;
-
- journal_t() : basket(NULL) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor journal_t");
- master = new account_t(NULL, "");
- master->journal = this;
- item_pool = item_pool_end = NULL;
- }
- ~journal_t();
-
- bool operator==(const journal_t& journal) {
- return this == &journal;
- }
- bool operator!=(const journal_t& journal) {
- return ! (*this == journal);
- }
-
- void add_account(account_t * acct) {
- master->add_account(acct);
- acct->journal = this;
- }
- bool remove_account(account_t * acct) {
- return master->remove_account(acct);
- acct->journal = NULL;
- }
-
- account_t * find_account(const std::string& name, bool auto_create = true) {
- accounts_map::iterator c = accounts_cache.find(name);
- if (c != accounts_cache.end())
- return (*c).second;
-
- account_t * account = master->find_account(name, auto_create);
- accounts_cache.insert(accounts_pair(name, account));
- account->journal = this;
- return account;
- }
- account_t * find_account_re(const std::string& regexp);
-
- bool add_entry(entry_t * entry);
- bool remove_entry(entry_t * entry);
-
- void add_entry_finalizer(entry_finalizer_t * finalizer) {
- add_hook<entry_finalizer_t *>(entry_finalize_hooks, finalizer);
- }
- void remove_entry_finalizer(entry_finalizer_t * finalizer) {
- remove_hook<entry_finalizer_t *>(entry_finalize_hooks, finalizer);
- }
-
- bool valid() const;
-};
-
-inline void extend_entry_base(journal_t * journal, entry_base_t& entry,
- bool post) {
- for (auto_entries_list::iterator i = journal->auto_entries.begin();
- i != journal->auto_entries.end();
- i++)
- (*i)->extend_entry(entry, post);
-}
-
-inline bool auto_entry_finalizer_t::operator()(entry_t& entry, bool post) {
- extend_entry_base(journal, entry, post);
- return true;
-}
-
-extern const std::string version;
-
-} // namespace ledger
-
-#endif // _JOURNAL_H
diff --git a/ledger.h b/ledger.h
deleted file mode 100644
index a59d562d..00000000
--- a/ledger.h
+++ /dev/null
@@ -1,51 +0,0 @@
-#ifndef _LEDGER_H
-#define _LEDGER_H
-
-//////////////////////////////////////////////////////////////////////
-//
-// Ledger Accounting Tool
-//
-// A command-line tool for general double-entry accounting.
-//
-// Copyright (c) 2003,2004 John Wiegley <johnw@newartisans.com>
-//
-
-#include <amount.h>
-#include <balance.h>
-#include <value.h>
-
-#include <journal.h>
-
-#include <datetime.h>
-#include <format.h>
-#include <emacs.h>
-#include <csv.h>
-#include <quotes.h>
-#include <valexpr.h>
-#include <walk.h>
-#include <derive.h>
-#include <reconcile.h>
-#include <error.h>
-#include <option.h>
-
-#include <parser.h>
-#include <textual.h>
-#include <binary.h>
-#include <xml.h>
-#include <gnucash.h>
-#include <qif.h>
-#include <ofx.h>
-
-namespace ledger {
- extern parser_t * binary_parser_ptr;
- extern parser_t * xml_parser_ptr;
- extern parser_t * gnucash_parser_ptr;
- extern parser_t * ofx_parser_ptr;
- extern parser_t * qif_parser_ptr;
- extern parser_t * textual_parser_ptr;
-}
-
-#include <config.h>
-#include <report.h>
-
-#endif // _LEDGER_H
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644
index 00000000..39c85c9b
--- /dev/null
+++ b/lib/Makefile
@@ -0,0 +1,42 @@
+# Uncomment these if you are on OS X and want to build universal libraries.
+# This is only important if you intend to produce a Ledger binary for
+# installation.
+
+#ARCH_CFLAGS = -g -arch i386 -arch ppc -isysroot /Developer/SDKs/MacOSX10.5.sdk
+#ARCH_LDFLAGS = -g -arch i386 -arch ppc -Wl,-syslibroot,/Developer/SDKs/MacOSX10.5.sdk
+
+STOW_ROOT = /usr/local/stow
+
+all: boost cppunit # gdtoa
+
+boost:
+ (cd boost; \
+ bjam release --prefix=$(STOW_ROOT)/boost \
+ --build-dir=/tmp/boost --toolset=darwin \
+ architecture=combined install && \
+ bjam debug --prefix=$(STOW_ROOT)/boost \
+ --build-dir=/tmp/boost --toolset=darwin \
+ architecture=combined define=_GLIBCXX_DEBUG=1 install)
+
+cppunit:
+ (cd cppunit; \
+ configure CFLAGS="$(ARCH_CFLAGS)" \
+ LDFLAGS="$(ARCH_LDFLAGS)" \
+ --prefix=$(STOW_ROOT)/cppunit \
+ --disable-dependency-tracking && \
+ make install)
+ (cd cppunit; \
+ configure CPPFLAGS="-D_GLIBCXX_DEBUG=1" \
+ CFLAGS="-g $(ARCH_CFLAGS)" \
+ LDFLAGS="-g $(ARCH_LDFLAGS)" \
+ --prefix=$(STOW_ROOT)/cppunit-debug \
+ --disable-dependency-tracking && \
+ make install)
+
+gdtoa:
+ (cd gdtoa; \
+ configure CFLAGS="$(ARCH_CFLAGS)" \
+ LDFLAGS="$(ARCH_LDFLAGS)" \
+ --prefix=$(STOW_ROOT)/gdtoa \
+ --disable-dependency-tracking && \
+ make install)
diff --git a/lib/boost b/lib/boost
new file mode 160000
+Subproject 39d1e20cb03d22465f8d8884f744d254dfbfa0c
diff --git a/lib/cppunit b/lib/cppunit
new file mode 160000
+Subproject 1f41e59a4dae47720d0aacb422548b45be0daa1
diff --git a/lib/gdtoa b/lib/gdtoa
new file mode 160000
+Subproject b2f6cab9da2e603003912eea91e7e5dcc51d887
diff --git a/lib/libofx b/lib/libofx
new file mode 160000
+Subproject 699016bfd00e2be7f13120f314a4f976eeacadd
diff --git a/ledger.el b/lisp/ledger.el
index abdefdfb..2ce949d9 100644
--- a/ledger.el
+++ b/lisp/ledger.el
@@ -4,7 +4,7 @@
;; Emacs Lisp Archive Entry
;; Filename: ledger.el
-;; Version: 2.6.1
+;; Version: 2.7
;; Date: Fri 18-Jul-2008
;; Keywords: data
;; Author: John Wiegley (johnw AT gnu DOT org)
diff --git a/timeclock.el b/lisp/timeclock.el
index 03159e94..03159e94 100644
--- a/timeclock.el
+++ b/lisp/timeclock.el
diff --git a/main.cc b/main.cc
deleted file mode 100644
index 8be24935..00000000
--- a/main.cc
+++ /dev/null
@@ -1,494 +0,0 @@
-#include <iostream>
-#include <fstream>
-#include <sstream>
-#include <algorithm>
-#include <exception>
-#include <iterator>
-#include <string>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-
-#include "acconf.h"
-
-#ifdef HAVE_UNIX_PIPES
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include "fdstream.hpp"
-#endif
-
-#include "ledger.h"
-
-using namespace ledger;
-
-int parse_and_report(config_t& config, std::auto_ptr<journal_t>& journal,
- report_t& report, int argc, char * argv[], char * envp[])
-{
- // Configure the terminus for value expressions
-
- ledger::terminus = datetime_t::now;
-
- // Parse command-line arguments, and those set in the environment
-
- std::list<std::string> args;
- process_arguments(ledger::config_options, argc - 1, argv + 1, false, args);
-
- if (args.empty()) {
- option_help(std::cerr);
- return 1;
- }
- strings_list::iterator arg = args.begin();
-
- if (config.cache_file == "<none>")
- config.use_cache = false;
- else
- config.use_cache = config.data_file.empty() && config.price_db.empty();
- DEBUG_PRINT("ledger.config.cache", "1. use_cache = " << config.use_cache);
-
- TRACE(main, "Processing options and environment variables");
-
- process_environment(ledger::config_options,
- const_cast<const char **>(envp), "LEDGER_");
-
-#if 1
- // These are here for backwards compatability, but are deprecated.
-
- if (const char * p = std::getenv("LEDGER"))
- process_option(ledger::config_options, "file", p);
- if (const char * p = std::getenv("LEDGER_INIT"))
- process_option(ledger::config_options, "init-file", p);
- if (const char * p = std::getenv("PRICE_HIST"))
- process_option(ledger::config_options, "price-db", p);
- if (const char * p = std::getenv("PRICE_EXP"))
- process_option(ledger::config_options, "price-exp", p);
-#endif
-
- const char * p = std::getenv("HOME");
- std::string home = p ? p : "";
-
- if (config.init_file.empty())
- config.init_file = home + "/.ledgerrc";
- if (config.price_db.empty())
- config.price_db = home + "/.pricedb";
-
- if (config.cache_file.empty())
- config.cache_file = home + "/.ledger-cache";
-
- if (config.data_file == config.cache_file)
- config.use_cache = false;
- DEBUG_PRINT("ledger.config.cache", "2. use_cache = " << config.use_cache);
-
- TRACE(main, std::string("Initialization file is ") + config.init_file);
- TRACE(main, std::string("Price database is ") + config.price_db);
- TRACE(main, std::string("Binary cache is ") + config.cache_file);
- TRACE(main, std::string("Main journal is ") + config.data_file);
-
- TRACE(main, std::string("Based on option settings, binary cache ") +
- (config.use_cache ? "WILL " : "will NOT ") + "be used");
-
- // Read the command word, canonicalize it to its one letter form,
- // then configure the system based on the kind of report to be
- // generated
-
- std::string command = *arg++;
-
- if (command == "balance" || command == "bal" || command == "b")
- command = "b";
- else if (command == "register" || command == "reg" || command == "r")
- command = "r";
- else if (command == "print" || command == "p")
- command = "p";
- else if (command == "output")
- command = "w";
- else if (command == "dump")
- command = "W";
- else if (command == "emacs" || command == "lisp")
- command = "x";
- else if (command == "xml")
- command = "X";
- else if (command == "entry")
- command = "e";
- else if (command == "equity")
- command = "E";
- else if (command == "prices")
- command = "P";
- else if (command == "pricesdb")
- command = "D";
- else if (command == "csv")
- command = "c";
- else if (command == "parse") {
- value_expr expr(ledger::parse_value_expr(*arg));
-
- if (config.verbose_mode) {
- std::cout << "Value expression tree:" << std::endl;
- ledger::dump_value_expr(std::cout, expr.get());
- std::cout << std::endl;
- std::cout << "Value expression parsed was:" << std::endl;
- ledger::write_value_expr(std::cout, expr.get());
- std::cout << std::endl << std::endl;
- std::cout << "Result of computation: ";
- }
-
- value_t result = guarded_compute(expr.get());
- std::cout << result.strip_annotations() << std::endl;
-
- return 0;
- }
- else if (command == "expr") {
- // this gets done below...
- }
- else {
- throw new error(std::string("Unrecognized command '") + command + "'");
- }
-
- // Parse initialization files, ledger data, price database, etc.
-
- journal.reset(new journal_t);
-
- { TRACE_PUSH(parser, "Parsing journal file");
-
- if (parse_ledger_data(config, journal.get()) == 0)
- throw new error("Please specify ledger file using -f"
- " or LEDGER_FILE environment variable.");
-
- TRACE_POP(parser, "Finished parsing"); }
-
- // process the command word and its following arguments
-
- std::string first_arg;
- if (command == "w") {
- if (arg != args.end())
- first_arg = *arg++;
- }
- else if (command == "W") {
- if (report.output_file.empty())
- throw new error("The 'dump' command requires use of the --output option");
- }
-
- TRACE(options, std::string("Post-processing options ") +
- "for command \"" + command + "\"");
-
- report.process_options(command, arg, args.end());
-
- // If downloading is to be supported, configure the updater
-
- if (! commodity_base_t::updater && config.download_quotes)
- commodity_base_t::updater =
- new quotes_by_script(config.price_db, config.pricing_leeway,
- config.cache_dirty);
-
- std::auto_ptr<entry_t> new_entry;
- if (command == "e") {
- if (arg == args.end()) {
- std::cout << "\
-The entry command requires at least one argument, so Ledger can intelligently\n\
-create a new entry for you. The possible arguments are:\n\
- DATE PAYEE [ACCOUNT] [AMOUNT] [DRAW ACCOUNT]\n\n\
-Some things to note:\n\
- - The ACCOUNT is optional; if no account is given, the last account affected\n\
- by PAYEE is used. If no payee can be found, the generic account 'Expenses'\n\
- is used.\n\
- - The AMOUNT is optional; if not specified, the same amount is used as the\n\
- last time PAYEE was seen, or 0 if not applicable.\n\
- - The AMOUNT does not require a commodity; if none is given, the commodity\n\
- currently contained within ACCOUNT is used, or no commodity at all if\n\
- either: the ACCOUNT was not found, or it contains more than one commodity.\n\
- - Lastly, the DRAW ACCOUNT is optional; if not present, the last account\n\
- drawn from by PAYEE is used, or the 'basket' account (specified with\n\
- 'A ACCOUNT' in your Ledger file) if that does not apply, or the generic\n\
- account 'Equity' is used.\n\n\
-Here are a few examples, all of which may be equivalent depending on your\n\
-Ledger data:\n\
- ledger entry 3/25 chevron\n\
- ledger entry 3/25 chevron 20\n\
- ledger entry 3/25 chevron \\$20\n\
- ledger entry 3/25 chevron gas 20\n\
- ledger entry 3/25 chevron gas \\$20 checking\n\n\
-A final note: Ledger never modifies your data! You are responsible for\n\
-appending the output of this command to your Ledger file if you so choose."
- << std::endl;
- return 1;
- }
- new_entry.reset(derive_new_entry(*journal, arg, args.end()));
- if (! new_entry.get())
- return 1;
- }
-
- // Configure the output stream
-
-#ifdef HAVE_UNIX_PIPES
- int status, pfd[2]; // Pipe file descriptors
-#endif
- std::ostream * out = &std::cout;
-
- if (! report.output_file.empty()) {
- out = new std::ofstream(report.output_file.c_str());
- }
-#ifdef HAVE_UNIX_PIPES
- else if (! config.pager.empty()) {
- status = pipe(pfd);
- if (status == -1)
- throw new error("Failed to create pipe");
-
- status = fork();
- if (status < 0) {
- throw new error("Failed to fork child process");
- }
- else if (status == 0) { // child
- const char *arg0;
-
- // Duplicate pipe's reading end into stdin
- status = dup2(pfd[0], STDIN_FILENO);
- if (status == -1)
- perror("dup2");
-
- // Close unuseful file descriptors: the pipe's writing and
- // reading ends (the latter is not needed anymore, after the
- // duplication).
- close(pfd[1]);
- close(pfd[0]);
-
- // Find command name: its the substring starting right of the
- // rightmost '/' character in the pager pathname. See manpage
- // for strrchr.
- arg0 = std::strrchr(config.pager.c_str(), '/');
- if (arg0)
- arg0++;
- else
- arg0 = config.pager.c_str(); // No slashes in pager.
-
- execlp(config.pager.c_str(), arg0, (char *)0);
- perror("execl");
- exit(1);
- }
- else { // parent
- close(pfd[0]);
- out = new boost::fdostream(pfd[1]);
- }
- }
-#endif
-
- // Are we handling the parse or expr commands? Do so now.
-
- if (command == "expr") {
- value_expr expr(ledger::parse_value_expr(*arg));
-
- if (config.verbose_mode) {
- std::cout << "Value expression tree:" << std::endl;
- ledger::dump_value_expr(std::cout, expr.get());
- std::cout << std::endl;
- std::cout << "Value expression parsed was:" << std::endl;
- ledger::write_value_expr(std::cout, expr.get());
- std::cout << std::endl << std::endl;
- std::cout << "Result of computation: ";
- }
-
- value_t result = guarded_compute(expr.get());
- std::cout << result.strip_annotations() << std::endl;
-
- return 0;
- }
-
- // Compile the format strings
-
- const std::string * format;
-
- if (! report.format_string.empty())
- format = &report.format_string;
- else if (command == "b")
- format = &config.balance_format;
- else if (command == "r")
- format = &config.register_format;
- else if (command == "E")
- format = &config.equity_format;
- else if (command == "P")
- format = &config.prices_format;
- else if (command == "D")
- format = &config.pricesdb_format;
- else if (command == "w")
- format = &config.write_xact_format;
- else
- format = &config.print_format;
-
- // Walk the entries based on the report type and the options
-
- item_handler<transaction_t> * formatter;
- std::list<item_handler<transaction_t> *> formatter_ptrs;
-
- if (command == "b" || command == "E")
- formatter = new set_account_value;
- else if (command == "p" || command == "e")
- formatter = new format_entries(*out, *format);
- else if (command == "x")
- formatter = new format_emacs_transactions(*out);
- else if (command == "X")
- formatter = new format_xml_entries(*out, report.show_totals);
- else if (command == "c")
- formatter = new format_csv_transactions(*out);
- else
- formatter = new format_transactions(*out, *format);
-
- if (command == "w") {
- TRACE_PUSH(text_writer, "Writing journal file");
- write_textual_journal(*journal, first_arg, *formatter,
- config.write_hdr_format, *out);
- TRACE_POP(text_writer, "Finished writing");
- }
- else if (command == "W") {
- TRACE_PUSH(binary_writer, "Writing binary file");
- std::ofstream stream(report.output_file.c_str());
- write_binary_journal(stream, journal.get());
- TRACE_POP(binary_writer, "Finished writing");
- }
- else {
- TRACE_PUSH(main, "Walking journal entries");
-
- formatter = report.chain_xact_handlers(command, formatter, journal.get(),
- journal->master, formatter_ptrs);
- if (command == "e")
- walk_transactions(new_entry->transactions, *formatter);
- else if (command == "P" || command == "D")
- walk_commodities(commodity_t::commodities, *formatter);
- else
- walk_entries(journal->entries, *formatter);
-
- if (command != "P" && command != "D")
- formatter->flush();
-
- TRACE_POP(main, "Finished entry walk");
- }
-
- // For the balance and equity reports, output the sum totals.
-
- if (command == "b") {
- TRACE_PUSH(main, "Walking journal accounts");
-
- format_account acct_formatter(*out, *format, report.display_predicate);
- sum_accounts(*journal->master);
- walk_accounts(*journal->master, acct_formatter, report.sort_string);
- acct_formatter.flush();
-
- if (account_has_xdata(*journal->master)) {
- account_xdata_t& xdata = account_xdata(*journal->master);
- if (! report.show_collapsed && xdata.total) {
- *out << "--------------------\n";
- xdata.value = xdata.total;
- acct_formatter.format.format(*out, details_t(*journal->master));
- }
- }
- TRACE_POP(main, "Finished account walk");
- }
- else if (command == "E") {
- TRACE_PUSH(main, "Walking journal accounts");
-
- format_equity acct_formatter(*out, *format, report.display_predicate);
- sum_accounts(*journal->master);
- walk_accounts(*journal->master, acct_formatter, report.sort_string);
- acct_formatter.flush();
-
- TRACE_POP(main, "Finished account walk");
- }
-
-#if DEBUG_LEVEL >= BETA
- { TRACE_PUSH(cleanup, "Cleaning up allocated memory");
-
- clear_transaction_xdata xact_cleaner;
- walk_entries(journal->entries, xact_cleaner);
-
- clear_account_xdata acct_cleaner;
- walk_accounts(*journal->master, acct_cleaner);
-
- if (! report.output_file.empty())
- delete out;
-
- for (std::list<item_handler<transaction_t> *>::iterator i
- = formatter_ptrs.begin();
- i != formatter_ptrs.end();
- i++)
- delete *i;
-
- TRACE_POP(cleanup, "Finished cleaning"); }
-#endif
-
- // Write out the binary cache, if need be
-
- if (config.use_cache && config.cache_dirty &&
- ! config.cache_file.empty()) {
- TRACE_PUSH(binary_cache, "Writing journal file");
-
- std::ofstream stream(config.cache_file.c_str());
- write_binary_journal(stream, journal.get());
-
- TRACE_POP(binary_cache, "Finished writing");
- }
-
-#ifdef HAVE_UNIX_PIPES
- if (! config.pager.empty()) {
- delete out;
- close(pfd[1]);
-
- // Wait for child to finish
- wait(&status);
- if (status & 0xffff != 0)
- throw new error("Something went wrong in the pager");
- }
-#endif
-
- return 0;
-}
-
-int main(int argc, char * argv[], char * envp[])
-{
- // This variable must be defined here so that any memory it holds is still
- // available should the subsequent exception handlers catch an inner
- // exception and need to report something on the invalid state of the
- // journal (such as an unbalanced entry).
- std::auto_ptr<journal_t> journal;
-
- try {
-#if DEBUG_LEVEL < BETA
- ledger::do_cleanup = false;
-#endif
- config_t config;
- report_t report;
- ledger::config = &config;
- ledger::report = &report;
- TRACE_PUSH(main, "Ledger starting");
- int status = parse_and_report(config, journal, report, argc, argv, envp);
- TRACE_POP(main, "Ledger done");
- return status;
- }
- catch (error * err) {
- std::cout.flush();
- // Push a null here since there's no file context
- if (err->context.empty() ||
- ! dynamic_cast<xact_context *>(err->context.front()))
- err->context.push_front(new error_context(""));
- err->reveal_context(std::cerr, "Error");
- std::cerr << err->what() << std::endl;
- delete err;
- return 1;
- }
- catch (fatal * err) {
- std::cout.flush();
- // Push a null here since there's no file context
- if (err->context.empty() ||
- ! dynamic_cast<xact_context *>(err->context.front()))
- err->context.push_front(new error_context(""));
- err->reveal_context(std::cerr, "Fatal");
- std::cerr << err->what() << std::endl;
- delete err;
- return 1;
- }
- catch (const std::exception& err) {
- std::cout.flush();
- std::cerr << "Error: " << err.what() << std::endl;
- return 1;
- }
- catch (int status) {
- return status;
- }
-}
-
-// main.cc ends here.
diff --git a/mask.cc b/mask.cc
deleted file mode 100644
index 972b24ce..00000000
--- a/mask.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-#include "mask.h"
-#include "debug.h"
-#include "util.h"
-
-#include <cstdlib>
-
-#include <pcre.h>
-
-mask_t::mask_t(const std::string& pat) : exclude(false)
-{
- const char * p = pat.c_str();
- if (*p == '-') {
- exclude = true;
- p++;
- while (std::isspace(*p))
- p++;
- }
- else if (*p == '+') {
- p++;
- while (std::isspace(*p))
- p++;
- }
- pattern = p;
-
- const char *error;
- int erroffset;
- regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
- &error, &erroffset, NULL);
- if (! regexp)
- throw new mask_error(std::string("Failed to compile regexp '") +
- pattern + "'");
-}
-
-mask_t::mask_t(const mask_t& m) : exclude(m.exclude), pattern(m.pattern)
-{
- const char *error;
- int erroffset;
- regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
- &error, &erroffset, NULL);
- assert(regexp);
-}
-
-mask_t::~mask_t() {
- pcre_free((pcre *)regexp);
-}
-
-bool mask_t::match(const std::string& str) const
-{
- static int ovec[30];
- int result = pcre_exec((pcre *)regexp, NULL,
- str.c_str(), str.length(), 0, 0, ovec, 30);
- return result >= 0 && ! exclude;
-}
diff --git a/mask.h b/mask.h
deleted file mode 100644
index 6d4790a3..00000000
--- a/mask.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef _MASK_H
-#define _MASK_H
-
-#include <string>
-#include <exception>
-
-#include "error.h"
-
-class mask_t
-{
- public:
- bool exclude;
- std::string pattern;
- void * regexp;
-
- explicit mask_t(const std::string& pattern);
- mask_t(const mask_t&);
- ~mask_t();
-
- bool match(const std::string& str) const;
-};
-
-class mask_error : public error {
- public:
- mask_error(const std::string& reason) throw() : error(reason) {}
- virtual ~mask_error() throw() {}
-};
-
-#endif // _MASK_H
diff --git a/ofx.cc b/ofx.cc
deleted file mode 100644
index 8b53b284..00000000
--- a/ofx.cc
+++ /dev/null
@@ -1,226 +0,0 @@
-#include "journal.h"
-#include "ofx.h"
-#include "format.h"
-#include "datetime.h"
-#include "error.h"
-#include "debug.h"
-#include "util.h"
-
-#include <libofx.h>
-
-namespace ledger {
-
-typedef std::map<const std::string, account_t *> accounts_map;
-typedef std::pair<const std::string, account_t *> accounts_pair;
-
-typedef std::map<const std::string, commodity_t *> commodities_map;
-typedef std::pair<const std::string, commodity_t *> commodities_pair;
-
-journal_t * curr_journal;
-accounts_map ofx_accounts;
-commodities_map ofx_account_currencies;
-commodities_map ofx_securities;
-account_t * master_account;
-
-int ofx_proc_statement_cb(struct OfxStatementData data, void * statement_data)
-{
-}
-
-int ofx_proc_account_cb(struct OfxAccountData data, void * account_data)
-{
- if (! data.account_id_valid)
- return -1;
-
- DEBUG_PRINT("ledger.ofx.parse", "account " << data.account_name);
- account_t * account = new account_t(master_account, data.account_name);
- curr_journal->add_account(account);
- ofx_accounts.insert(accounts_pair(data.account_id, account));
-
- if (data.currency_valid) {
- commodity_t * commodity = commodity_t::find_or_create(data.currency);
- commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED);
-
- commodities_map::iterator i = ofx_account_currencies.find(data.account_id);
- if (i == ofx_account_currencies.end())
- ofx_account_currencies.insert(commodities_pair(data.account_id,
- commodity));
- }
-
- return 0;
-}
-
-int ofx_proc_transaction_cb(struct OfxTransactionData data,
- void * transaction_data)
-{
- if (! data.account_id_valid || ! data.units_valid)
- return -1;
-
- accounts_map::iterator i = ofx_accounts.find(data.account_id);
- assert(i != ofx_accounts.end());
- account_t * account = (*i).second;
-
- entry_t * entry = new entry_t;
-
- entry->add_transaction(new transaction_t(account));
- transaction_t * xact = entry->transactions.back();
-
- // get the account's default currency
- commodities_map::iterator ac = ofx_account_currencies.find(data.account_id);
- assert(ac != ofx_account_currencies.end());
- commodity_t * default_commodity = (*ac).second;
-
- std::ostringstream stream;
- stream << - data.units;
-
- // jww (2005-02-09): what if the amount contains fees?
-
- if (data.unique_id_valid) {
- commodities_map::iterator s = ofx_securities.find(data.unique_id);
- assert(s != ofx_securities.end());
- xact->amount = stream.str() + " " + (*s).second->base_symbol();
- } else {
- xact->amount = stream.str() + " " + default_commodity->base_symbol();
- }
-
- if (data.unitprice_valid && data.unitprice != 1.0) {
- std::ostringstream cstream;
- stream << - data.unitprice;
- xact->cost = new amount_t(stream.str() + " " + default_commodity->base_symbol());
- }
-
- DEBUG_PRINT("ledger.ofx.parse", "xact " << xact->amount
- << " from " << *xact->account);
-
- if (data.date_initiated_valid)
- entry->_date = data.date_initiated;
- else if (data.date_posted_valid)
- entry->_date = data.date_posted;
-
- if (data.check_number_valid)
- entry->code = data.check_number;
- else if (data.reference_number_valid)
- entry->code = data.reference_number;
-
- if (data.name_valid)
- entry->payee = data.name;
-
- if (data.memo_valid)
- xact->note = data.memo;
-
- // jww (2005-02-09): check for fi_id_corrected? or is this handled
- // by the library?
-
- // Balance all entries into <Unknown>, since it is not specified.
- account = curr_journal->find_account("<Unknown>");
- entry->add_transaction(new transaction_t(account));
-
- if (! curr_journal->add_entry(entry)) {
- print_entry(std::cerr, *entry);
- // jww (2005-02-09): uncomment
- //have_error = "The above entry does not balance";
- delete entry;
- return -1;
- }
- return 0;
-}
-
-int ofx_proc_security_cb(struct OfxSecurityData data, void * security_data)
-{
- if (! data.unique_id_valid)
- return -1;
-
- std::string symbol;
- if (data.ticker_valid)
- symbol = data.ticker;
- else if (data.currency_valid)
- symbol = data.currency;
- else
- return -1;
-
- commodity_t * commodity = commodity_t::find_or_create(symbol);
- commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED);
-
- if (data.secname_valid)
- commodity->set_name(data.secname);
-
- if (data.memo_valid)
- commodity->set_note(data.memo);
-
- commodities_map::iterator i = ofx_securities.find(data.unique_id);
- if (i == ofx_securities.end()) {
- DEBUG_PRINT("ledger.ofx.parse", "security " << symbol);
- ofx_securities.insert(commodities_pair(data.unique_id, commodity));
- }
-
- // jww (2005-02-09): What is the commodity for data.unitprice?
- if (data.date_unitprice_valid && data.unitprice_valid) {
- DEBUG_PRINT("ledger.ofx.parse", " price " << data.unitprice);
- commodity->add_price(data.date_unitprice, amount_t(data.unitprice));
- }
-
- return 0;
-}
-
-int ofx_proc_status_cb(struct OfxStatusData data, void * status_data)
-{
-}
-
-bool ofx_parser_t::test(std::istream& in) const
-{
- char buf[80];
-
- in.getline(buf, 79);
- if (std::strncmp(buf, "OFXHEADER", 9) == 0) {
- in.clear();
- in.seekg(0, std::ios::beg);
- return true;
- }
- else if (std::strncmp(buf, "<?xml", 5) != 0) {
- in.clear();
- in.seekg(0, std::ios::beg);
- return false;
- }
-
- in.getline(buf, 79);
- if (std::strncmp(buf, "<?OFX", 5) != 0 &&
- std::strncmp(buf, "<?ofx", 5) != 0) {
- in.clear();
- in.seekg(0, std::ios::beg);
- return false;
- }
-
- in.clear();
- in.seekg(0, std::ios::beg);
- return true;
-}
-
-unsigned int ofx_parser_t::parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master,
- const std::string * original_file)
-{
- if (! original_file)
- return 0;
-
- curr_journal = journal;
- master_account = master ? master : journal->master;
-
- LibofxContextPtr libofx_context = libofx_get_new_context();
-
- ofx_set_statement_cb (libofx_context, ofx_proc_statement_cb, 0);
- ofx_set_account_cb (libofx_context, ofx_proc_account_cb, 0);
- ofx_set_transaction_cb(libofx_context, ofx_proc_transaction_cb, 0);
- ofx_set_security_cb (libofx_context, ofx_proc_security_cb, 0);
- ofx_set_status_cb (libofx_context, ofx_proc_status_cb, 0);
-
- // The processing is done by way of callbacks, which are all defined
- // above.
- libofx_proc_file(libofx_context, original_file->c_str(), AUTODETECT);
-
- libofx_free_context(libofx_context);
-
- return 1; // jww (2005-02-09): count;
-}
-
-} // namespace ledger
diff --git a/ofx.h b/ofx.h
deleted file mode 100644
index 232daecb..00000000
--- a/ofx.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef _OFX_H
-#define _OFX_H
-
-#include "parser.h"
-
-namespace ledger {
-
-class ofx_parser_t : public parser_t
-{
- public:
- virtual bool test(std::istream& in) const;
-
- virtual unsigned int parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master = NULL,
- const std::string * original_file = NULL);
-};
-
-} // namespace ledger
-
-#endif // _OFX_H
diff --git a/option.cc b/option.cc
deleted file mode 100644
index 624a8f0b..00000000
--- a/option.cc
+++ /dev/null
@@ -1,1065 +0,0 @@
-#include "option.h"
-#include "config.h"
-#include "report.h"
-#include "debug.h"
-#include "error.h"
-
-#include <iostream>
-#include <cstdarg>
-#include <cstdlib>
-#include <unistd.h>
-
-#include "util.h"
-
-namespace {
- inline void process_option(option_t * opt, const char * arg = NULL) {
- if (! opt->handled) {
- try {
- opt->handler(arg);
- }
- catch (error * err) {
- err->context.push_back
- (new error_context
- (std::string("While parsing option '--") + opt->long_opt +
- "'" + (opt->short_opt != '\0' ?
- (std::string(" (-") + opt->short_opt + "):") : ":")));
- throw err;
- }
- 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(option_t * options, const std::string& name,
- const char * arg)
-{
- option_t * opt = search_options(options, name.c_str());
- if (opt) {
- process_option(opt, arg);
- return true;
- }
- return false;
-}
-
-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; *i; i++) {
- if ((*i)[0] != '-') {
- if (anywhere) {
- args.push_back(*i);
- continue;
- } else {
- for (; *i; i++)
- args.push_back(*i);
- break;
- }
- }
-
- // --long-option or -s
- again:
- if ((*i)[1] == '-') {
- if ((*i)[2] == '\0')
- break;
-
- char * name = *i + 2;
- char * value = NULL;
- if (char * p = std::strchr(name, '=')) {
- *p++ = '\0';
- value = p;
- }
-
- option_t * opt = search_options(options, name);
- if (! opt)
- throw new option_error(std::string("illegal option --") + name);
-
- if (opt->wants_arg && value == NULL) {
- value = *++i;
- if (value == NULL)
- throw new option_error(std::string("missing option argument for --") +
- name);
- }
- process_option(opt, value);
- }
- else if ((*i)[1] == '\0') {
- throw new option_error(std::string("illegal option -"));
- }
- else {
- std::list<option_t *> opt_queue;
-
- int x = 1;
- for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) {
- option_t * opt = search_options(options, c);
- if (! opt)
- throw new option_error(std::string("illegal option -") + c);
- opt_queue.push_back(opt);
- }
-
- for (std::list<option_t *>::iterator o = opt_queue.begin();
- o != opt_queue.end(); o++) {
- char * value = NULL;
- if ((*o)->wants_arg) {
- value = *++i;
- if (value == NULL)
- throw new option_error(std::string("missing option argument for -") +
- (*o)->short_opt);
- }
- process_option(*o, value);
- }
- }
-
- next:
- ;
- }
-}
-
-void process_environment(option_t * options, const char ** envp,
- const std::string& tag)
-{
- const char * tag_p = tag.c_str();
- unsigned int tag_len = tag.length();
-
- for (const char ** p = envp; *p; p++)
- if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) {
- char buf[128];
- char * r = buf;
- const char * q;
- for (q = *p + tag_len;
- *q && *q != '=' && r - buf < 128;
- q++)
- if (*q == '_')
- *r++ = '-';
- else
- *r++ = std::tolower(*q);
- *r = '\0';
-
- if (*q == '=') {
- try {
- process_option(options, buf, q + 1);
- }
- catch (error * err) {
- err->context.pop_back();
- err->context.push_back
- (new error_context
- (std::string("While parsing environment variable option '") +
- *p + "':"));
- throw err;
- }
- }
- }
-}
-
-//////////////////////////////////////////////////////////////////////
-
-namespace ledger {
-
-config_t * config = NULL;
-report_t * report = NULL;
-
-static void show_version(std::ostream& out)
-{
- out << "Ledger " << ledger::version << ", the command-line accounting tool";
- out << "\n\nCopyright (c) 2003-2008, John Wiegley. All rights reserved.\n\n\
-This program is made available under the terms of the BSD Public License.\n\
-See LICENSE file included with the distribution for details and disclaimer.\n";
- out << "\n(modules: gmp, pcre";
-#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
- out << ", xml";
-#endif
-#ifdef HAVE_LIBOFX
- out << ", ofx";
-#endif
- out << ")\n";
-}
-
-void option_full_help(std::ostream& out)
-{
- out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\
-Basic options:\n\
- -H, --full-help display this help text\n\
- -h, --help display summarized help text\n\
- -v, --version show version information\n\
- -f, --file FILE read ledger data from FILE\n\
- -o, --output FILE write output to FILE\n\
- -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\
- --cache FILE use FILE as a binary cache when --file is not used\n\
- --no-cache don't use a cache, even if it would be appropriate\n\
- -a, --account NAME use NAME for the default account (useful with QIF)\n\n\
-Report filtering:\n\
- -c, --current show only current and past entries (not future)\n\
- -b, --begin DATE set report begin date\n\
- -e, --end DATE set report end date\n\
- -p, --period STR report using the given period\n\
- --period-sort EXPR sort each report period's entries by EXPR\n\
- -C, --cleared consider only cleared transactions\n\
- -U, --uncleared consider only uncleared transactions\n\
- -R, --real consider only real (non-virtual) transactions\n\
- -L, --actual consider only actual (non-automated) transactions\n\
- -r, --related calculate report using related transactions\n\
- --budget generate budget entries based on periodic entries\n\
- --add-budget show all transactions plus the budget\n\
- --unbudgeted show only unbudgeted transactions\n\
- --forecast EXPR generate forecast entries while EXPR is true\n\
- -l, --limit EXPR calculate only transactions matching EXPR\n\
- -t, --amount EXPR use EXPR to calculate the displayed amount\n\
- -T, --total EXPR use EXPR to calculate the displayed total\n\n\
-Output customization:\n\
- -n, --collapse register: collapse entries; balance: no grand total\n\
- -s, --subtotal balance: show sub-accounts; other: show subtotals\n\
- -P, --by-payee show summarized totals by payee\n\
- -x, --comm-as-payee set commodity name as the payee, for reporting\n\
- -E, --empty balance: show accounts with zero balance\n\
- -W, --weekly show weekly sub-totals\n\
- -M, --monthly show monthly sub-totals\n\
- -Y, --yearly show yearly sub-totals\n\
- --dow show a days-of-the-week report\n\
- -S, --sort EXPR sort report according to the value expression EXPR\n\
- -w, --wide for the default register report, use 132 columns\n\
- --head COUNT show only the first COUNT entries (negative inverts)\n\
- --tail COUNT show only the last COUNT entries (negative inverts)\n\
- --pager PAGER send all output through the given PAGER program\n\
- -A, --average report average transaction amount\n\
- -D, --deviation report deviation from the average\n\
- -%, --percentage report balance totals as a percentile of the parent\n\
- --totals in the \"xml\" report, include running total\n\
- -j, --amount-data print only raw amount data (useful for scripting)\n\
- -J, --total-data print only raw total data\n\
- -d, --display EXPR display only transactions matching EXPR\n\
- -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\
- -F, --format STR use STR as the format; for each report type, use:\n\
- --balance-format --register-format --print-format\n\
- --plot-amount-format --plot-total-format --equity-format\n\
- --prices-format --wide-register-format\n\n\
-Commodity reporting:\n\
- --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\
- -L, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\
- -Q, --download download price information when needed\n\
- -O, --quantity report commodity totals (this is the default)\n\
- -B, --basis report cost basis of commodities\n\
- -V, --market report last known market value\n\
- -g, --performance report gain/loss for each displayed transaction\n\
- -G, --gain report net gain/loss\n\n\
-Commands:\n\
- balance [REGEXP]... show balance totals for matching accounts\n\
- register [REGEXP]... show register of matching transactions\n\
- print [REGEXP]... print all matching entries\n\
- xml [REGEXP]... print matching entries in XML format\n\
- equity [REGEXP]... output equity entries for matching accounts\n\
- prices [REGEXP]... display price history for matching commodities\n\
- entry DATE PAYEE AMT output a derived entry, based on the arguments\n";
-}
-
-void option_help(std::ostream& out)
-{
- out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\
-Use -H to see all the help text on one page, or:\n\
- --help-calc calculation options\n\
- --help-disp display options\n\
- --help-comm commodity options\n\n\
-Basic options:\n\
- -h, --help display this help text\n\
- -v, --version show version information\n\
- -f, --file FILE read ledger data from FILE\n\
- -o, --output FILE write output to FILE\n\
- -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\
- --cache FILE use FILE as a binary cache when --file is not used\n\
- --no-cache don't use a cache, even if it would be appropriate\n\
- -a, --account NAME use NAME for the default account (useful with QIF)\n\n\
-Commands:\n\
- balance [REGEXP]... show balance totals for matching accounts\n\
- register [REGEXP]... show register of matching transactions\n\
- print [REGEXP]... print all matching entries\n\
- xml [REGEXP]... print matching entries in XML format\n\
- equity [REGEXP]... output equity entries for matching accounts\n\
- prices [REGEXP]... display price history for matching commodities\n\
- entry DATE PAYEE AMT output a derived entry, based on the arguments\n";
-}
-
-void option_calc_help(std::ostream& out)
-{
- out << "Options to control how a report is calculated:\n\
- -c, --current show only current and past entries (not future)\n\
- -b, --begin DATE set report begin date\n\
- -e, --end DATE set report end date\n\
- -p, --period STR report using the given period\n\
- --period-sort EXPR sort each report period's entries by EXPR\n\
- -C, --cleared consider only cleared transactions\n\
- -U, --uncleared consider only uncleared transactions\n\
- -R, --real consider only real (non-virtual) transactions\n\
- -L, --actual consider only actual (non-automated) transactions\n\
- -r, --related calculate report using related transactions\n\
- --budget generate budget entries based on periodic entries\n\
- --add-budget show all transactions plus the budget\n\
- --unbudgeted show only unbudgeted transactions\n\
- --forecast EXPR generate forecast entries while EXPR is true\n\
- -l, --limit EXPR calculate only transactions matching EXPR\n\
- -t, --amount EXPR use EXPR to calculate the displayed amount\n\
- -T, --total EXPR use EXPR to calculate the displayed total\n";
-}
-
-void option_disp_help(std::ostream& out)
-{
- out << "Output to control how report results are displayed:\n\
- -n, --collapse register: collapse entries; balance: no grand total\n\
- -s, --subtotal balance: show sub-accounts; other: show subtotals\n\
- -P, --by-payee show summarized totals by payee\n\
- -x, --comm-as-payee set commodity name as the payee, for reporting\n\
- -E, --empty balance: show accounts with zero balance\n\
- -W, --weekly show weekly sub-totals\n\
- -M, --monthly show monthly sub-totals\n\
- -Y, --yearly show yearly sub-totals\n\
- --dow show a days-of-the-week report\n\
- -S, --sort EXPR sort report according to the value expression EXPR\n\
- -w, --wide for the default register report, use 132 columns\n\
- --head COUNT show only the first COUNT entries (negative inverts)\n\
- --tail COUNT show only the last COUNT entries (negative inverts)\n\
- --pager PAGER send all output through the given PAGER program\n\
- -A, --average report average transaction amount\n\
- -D, --deviation report deviation from the average\n\
- -%, --percentage report balance totals as a percentile of the parent\n\
- --totals in the \"xml\" report, include running total\n\
- -j, --amount-data print only raw amount data (useful for scripting)\n\
- -J, --total-data print only raw total data\n\
- -d, --display EXPR display only transactions matching EXPR\n\
- -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\
- -F, --format STR use STR as the format; for each report type, use:\n\
- --balance-format --register-format --print-format\n\
- --plot-amount-format --plot-total-format --equity-format\n\
- --prices-format --wide-register-format\n";
-}
-
-void option_comm_help(std::ostream& out)
-{
- out << "Options to control how commodity values are determined:\n\
- --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\
- -Z, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\
- -Q, --download download price information when needed\n\
- -O, --quantity report commodity totals (this is the default)\n\
- -B, --basis report cost basis of commodities\n\
- -V, --market report last known market value\n\
- -g, --performance report gain/loss for each displayed transaction\n\
- -G, --gain report net gain/loss\n";
-}
-
-//////////////////////////////////////////////////////////////////////
-//
-// Basic options
-
-OPT_BEGIN(full_help, "H") {
- option_full_help(std::cout);
- throw 0;
-} OPT_END(full_help);
-
-OPT_BEGIN(help, "h") {
- option_help(std::cout);
- throw 0;
-} OPT_END(help);
-
-OPT_BEGIN(help_calc, "") {
- option_calc_help(std::cout);
- throw 0;
-} OPT_END(help_calc);
-
-OPT_BEGIN(help_disp, "") {
- option_disp_help(std::cout);
- throw 0;
-} OPT_END(help_disp);
-
-OPT_BEGIN(help_comm, "") {
- option_comm_help(std::cout);
- throw 0;
-} OPT_END(help_comm);
-
-OPT_BEGIN(version, "v") {
- show_version(std::cout);
- throw 0;
-} OPT_END(version);
-
-OPT_BEGIN(init_file, "i:") {
- std::string path = resolve_path(optarg);
- if (access(path.c_str(), R_OK) != -1)
- config->init_file = path;
- else
- throw new error(std::string("The init file '") + path +
- "' does not exist or is not readable");
-} OPT_END(init_file);
-
-OPT_BEGIN(file, "f:") {
- if (std::string(optarg) == "-") {
- config->data_file = optarg;
- } else {
- std::string path = resolve_path(optarg);
- if (access(path.c_str(), R_OK) != -1)
- config->data_file = path;
- else
- throw new error(std::string("The ledger file '") + path +
- "' does not exist or is not readable");
- }
-} OPT_END(file);
-
-OPT_BEGIN(cache, ":") {
- config->cache_file = resolve_path(optarg);
-} OPT_END(cache);
-
-OPT_BEGIN(no_cache, "") {
- config->cache_file = "<none>";
-} OPT_END(no_cache);
-
-OPT_BEGIN(output, "o:") {
- if (std::string(optarg) != "-") {
- std::string path = resolve_path(optarg);
- report->output_file = path;
- }
-} OPT_END(output);
-
-OPT_BEGIN(account, "a:") {
- config->account = optarg;
-} OPT_END(account);
-
-OPT_BEGIN(debug, ":") {
- config->debug_mode = true;
- ::setenv("DEBUG_CLASS", optarg, 1);
-} OPT_END(debug);
-
-OPT_BEGIN(verbose, "") {
- config->verbose_mode = true;
-} OPT_END(verbose);
-
-OPT_BEGIN(trace, "") {
- config->trace_mode = true;
-} OPT_END(trace);
-
-//////////////////////////////////////////////////////////////////////
-//
-// Report filtering
-
-OPT_BEGIN(effective, "") {
- transaction_t::use_effective_date = true;
-} OPT_END(effective);
-
-OPT_BEGIN(begin, "b:") {
- char buf[128];
- interval_t interval(optarg);
- if (! interval.begin)
- throw new error(std::string("Could not determine beginning of period '") +
- optarg + "'");
-
- if (! report->predicate.empty())
- report->predicate += "&";
- report->predicate += "d>=[";
- report->predicate += interval.begin.to_string();
- report->predicate += "]";
-} OPT_END(begin);
-
-OPT_BEGIN(end, "e:") {
- char buf[128];
- interval_t interval(optarg);
- if (! interval.begin)
- throw new error(std::string("Could not determine end of period '") +
- optarg + "'");
-
- if (! report->predicate.empty())
- report->predicate += "&";
- report->predicate += "d<[";
- report->predicate += interval.begin.to_string();
- report->predicate += "]";
-
- terminus = interval.begin;
-} OPT_END(end);
-
-OPT_BEGIN(current, "c") {
- if (! report->predicate.empty())
- report->predicate += "&";
- report->predicate += "d<=m";
-} OPT_END(current);
-
-OPT_BEGIN(cleared, "C") {
- if (! report->predicate.empty())
- report->predicate += "&";
- report->predicate += "X";
-} OPT_END(cleared);
-
-OPT_BEGIN(uncleared, "U") {
- if (! report->predicate.empty())
- report->predicate += "&";
- report->predicate += "!X";
-} OPT_END(uncleared);
-
-OPT_BEGIN(real, "R") {
- if (! report->predicate.empty())
- report->predicate += "&";
- report->predicate += "R";
-} OPT_END(real);
-
-OPT_BEGIN(actual, "L") {
- if (! report->predicate.empty())
- report->predicate += "&";
- report->predicate += "L";
-} OPT_END(actual);
-
-OPT_BEGIN(lots, "") {
- report->keep_price =
- report->keep_date =
- report->keep_tag = true;
-} OPT_END(lots);
-
-OPT_BEGIN(lot_prices, "") {
- report->keep_price = true;
-} OPT_END(lots_prices);
-
-OPT_BEGIN(lot_dates, "") {
- report->keep_date = true;
-} OPT_END(lots_dates);
-
-OPT_BEGIN(lot_tags, "") {
- report->keep_tag = true;
-} OPT_END(lots_tags);
-
-//////////////////////////////////////////////////////////////////////
-//
-// Output customization
-
-OPT_BEGIN(format, "F:") {
- report->format_string = optarg;
-} OPT_END(format);
-
-OPT_BEGIN(date_format, "y:") {
- report->date_output_format = optarg;
-} OPT_END(date_format);
-
-OPT_BEGIN(input_date_format, ":") {
- config->date_input_format = optarg;
-} OPT_END(input_date_format);
-
-OPT_BEGIN(balance_format, ":") {
- config->balance_format = optarg;
-} OPT_END(balance_format);
-
-OPT_BEGIN(register_format, ":") {
- config->register_format = optarg;
-} OPT_END(register_format);
-
-OPT_BEGIN(wide_register_format, ":") {
- config->wide_register_format = optarg;
-} OPT_END(wide_register_format);
-
-OPT_BEGIN(plot_amount_format, ":") {
- config->plot_amount_format = optarg;
-} OPT_END(plot_amount_format);
-
-OPT_BEGIN(plot_total_format, ":") {
- config->plot_total_format = optarg;
-} OPT_END(plot_total_format);
-
-OPT_BEGIN(print_format, ":") {
- config->print_format = optarg;
-} OPT_END(print_format);
-
-OPT_BEGIN(write_hdr_format, ":") {
- config->write_hdr_format = optarg;
-} OPT_END(write_hdr_format);
-
-OPT_BEGIN(write_xact_format, ":") {
- config->write_xact_format = optarg;
-} OPT_END(write_xact_format);
-
-OPT_BEGIN(equity_format, ":") {
- config->equity_format = optarg;
-} OPT_END(equity_format);
-
-OPT_BEGIN(prices_format, ":") {
- config->prices_format = optarg;
-} OPT_END(prices_format);
-
-OPT_BEGIN(wide, "w") {
- config->register_format = config->wide_register_format;
-} OPT_END(wide);
-
-OPT_BEGIN(head, ":") {
- report->head_entries = std::atoi(optarg);
-} OPT_END(head);
-
-OPT_BEGIN(tail, ":") {
- report->tail_entries = std::atoi(optarg);
-} OPT_END(tail);
-
-OPT_BEGIN(pager, ":") {
- config->pager = optarg;
-} OPT_END(pager);
-
-OPT_BEGIN(truncate, ":") {
- std::string style(optarg);
- if (style == "leading")
- format_t::elision_style = format_t::TRUNCATE_LEADING;
- else if (style == "middle")
- format_t::elision_style = format_t::TRUNCATE_MIDDLE;
- else if (style == "trailing")
- format_t::elision_style = format_t::TRUNCATE_TRAILING;
- else if (style == "abbrev")
- format_t::elision_style = format_t::ABBREVIATE;
-} OPT_END(truncate);
-
-OPT_BEGIN(abbrev_len, ":") {
- format_t::abbrev_length = std::atoi(optarg);
-} OPT_END(abbrev_len);
-
-OPT_BEGIN(empty, "E") {
- report->show_empty = true;
-} OPT_END(empty);
-
-OPT_BEGIN(collapse, "n") {
- report->show_collapsed = true;
-} OPT_END(collapse);
-
-OPT_BEGIN(subtotal, "s") {
- report->show_subtotal = true;
-} OPT_END(subtotal);
-
-OPT_BEGIN(totals, "") {
- report->show_totals = true;
-} OPT_END(totals);
-
-OPT_BEGIN(sort, "S:") {
- report->sort_string = optarg;
-} OPT_END(sort);
-
-OPT_BEGIN(sort_entries, "") {
- report->sort_string = optarg;
- report->entry_sort = true;
-} OPT_END(sort_entries);
-
-OPT_BEGIN(sort_all, "") {
- report->sort_string = optarg;
- report->entry_sort = false;
- report->sort_all = true;
-} OPT_END(sort_all);
-
-OPT_BEGIN(period_sort, ":") {
- report->sort_string = optarg;
- report->entry_sort = true;
-} OPT_END(period_sort);
-
-OPT_BEGIN(related, "r") {
- report->show_related = true;
-} OPT_END(related);
-
-OPT_BEGIN(descend, "") {
- std::string arg(optarg);
- std::string::size_type beg = 0;
- report->descend_expr = "";
- for (std::string::size_type pos = arg.find(';');
- pos != std::string::npos;
- beg = pos + 1, pos = arg.find(';', beg))
- report->descend_expr += (std::string("t=={") +
- std::string(arg, beg, pos - beg) + "};");
- report->descend_expr += (std::string("t=={") +
- std::string(arg, beg) + "}");
-} OPT_END(descend);
-
-OPT_BEGIN(descend_if, "") {
- report->descend_expr = optarg;
-} OPT_END(descend_if);
-
-OPT_BEGIN(period, "p:") {
- if (report->report_period.empty()) {
- report->report_period = optarg;
- } else {
- report->report_period += " ";
- report->report_period += optarg;
- }
-
- // If the period gives a beginning and/or ending date, make sure to
- // modify the calculation predicate (via the --begin and --end
- // options) to take this into account.
-
- interval_t interval(report->report_period);
-
- if (interval.begin) {
- if (! report->predicate.empty())
- report->predicate += "&";
- report->predicate += "d>=[";
- report->predicate += interval.begin.to_string();
- report->predicate += "]";
- }
-
- if (interval.end) {
- if (! report->predicate.empty())
- report->predicate += "&";
- report->predicate += "d<[";
- report->predicate += interval.end.to_string();
- report->predicate += "]";
-
- terminus = interval.end;
- }
-} OPT_END(period);
-
-OPT_BEGIN(daily, "") {
- if (report->report_period.empty())
- report->report_period = "daily";
- else
- report->report_period = std::string("daily ") + report->report_period;
-} OPT_END(daily);
-
-OPT_BEGIN(weekly, "W") {
- if (report->report_period.empty())
- report->report_period = "weekly";
- else
- report->report_period = std::string("weekly ") + report->report_period;
-} OPT_END(weekly);
-
-OPT_BEGIN(monthly, "M") {
- if (report->report_period.empty())
- report->report_period = "monthly";
- else
- report->report_period = std::string("monthly ") + report->report_period;
-} OPT_END(monthly);
-
-OPT_BEGIN(quarterly, "") {
- if (report->report_period.empty())
- report->report_period = "quarterly";
- else
- report->report_period = std::string("quarterly ") + report->report_period;
-} OPT_END(quarterly);
-
-OPT_BEGIN(yearly, "Y") {
- if (report->report_period.empty())
- report->report_period = "yearly";
- else
- report->report_period = std::string("yearly ") + report->report_period;
-} OPT_END(yearly);
-
-OPT_BEGIN(dow, "") {
- report->days_of_the_week = true;
-} OPT_END(dow);
-
-OPT_BEGIN(by_payee, "P") {
- report->by_payee = true;
-} OPT_END(by_payee);
-
-OPT_BEGIN(comm_as_payee, "x") {
- report->comm_as_payee = true;
-} OPT_END(comm_as_payee);
-
-OPT_BEGIN(code_as_payee, "") {
- report->code_as_payee = true;
-} OPT_END(code_as_payee);
-
-OPT_BEGIN(budget, "") {
- report->budget_flags = BUDGET_BUDGETED;
-} OPT_END(budget);
-
-OPT_BEGIN(add_budget, "") {
- report->budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED;
-} OPT_END(add_budget);
-
-OPT_BEGIN(unbudgeted, "") {
- report->budget_flags = BUDGET_UNBUDGETED;
-} OPT_END(unbudgeted);
-
-OPT_BEGIN(forecast, ":") {
- report->forecast_limit = optarg;
-} OPT_END(forecast);
-
-OPT_BEGIN(reconcile, ":") {
- report->reconcile_balance = optarg;
-} OPT_END(reconcile);
-
-OPT_BEGIN(reconcile_date, ":") {
- report->reconcile_date = optarg;
-} OPT_END(reconcile_date);
-
-OPT_BEGIN(limit, "l:") {
- if (! report->predicate.empty())
- report->predicate += "&";
- report->predicate += "(";
- report->predicate += optarg;
- report->predicate += ")";
-} OPT_END(limit);
-
-OPT_BEGIN(only, ":") {
- if (! report->secondary_predicate.empty())
- report->secondary_predicate += "&";
- report->secondary_predicate += "(";
- report->secondary_predicate += optarg;
- report->secondary_predicate += ")";
-} OPT_END(only);
-
-OPT_BEGIN(display, "d:") {
- if (! report->display_predicate.empty())
- report->display_predicate += "&";
- report->display_predicate += "(";
- report->display_predicate += optarg;
- report->display_predicate += ")";
-} OPT_END(display);
-
-OPT_BEGIN(amount, "t:") {
- ledger::amount_expr = optarg;
-} OPT_END(amount);
-
-OPT_BEGIN(total, "T:") {
- ledger::total_expr = optarg;
-} OPT_END(total);
-
-OPT_BEGIN(amount_data, "j") {
- report->format_string = config->plot_amount_format;
-} OPT_END(amount_data);
-
-OPT_BEGIN(total_data, "J") {
- report->format_string = config->plot_total_format;
-} OPT_END(total_data);
-
-OPT_BEGIN(ansi, "") {
- format_t::ansi_codes = true;
- format_t::ansi_invert = false;
-} OPT_END(ansi);
-
-OPT_BEGIN(ansi_invert, "") {
- format_t::ansi_codes =
- format_t::ansi_invert = true;
-} OPT_END(ansi);
-
-//////////////////////////////////////////////////////////////////////
-//
-// Commodity reporting
-
-OPT_BEGIN(base, ":") {
- amount_t::keep_base = true;
-} OPT_END(base);
-
-OPT_BEGIN(price_db, ":") {
- config->price_db = optarg;
-} OPT_END(price_db);
-
-OPT_BEGIN(price_exp, "Z:") {
- config->pricing_leeway = std::atol(optarg) * 60;
-} OPT_END(price_exp);
-
-OPT_BEGIN(download, "Q") {
- config->download_quotes = true;
-} OPT_END(download);
-
-OPT_BEGIN(quantity, "O") {
- ledger::amount_expr = "@a";
- ledger::total_expr = "@O";
-} OPT_END(quantity);
-
-OPT_BEGIN(basis, "B") {
- ledger::amount_expr = "@b";
- ledger::total_expr = "@B";
-} OPT_END(basis);
-
-OPT_BEGIN(price, "I") {
- ledger::amount_expr = "@i";
- ledger::total_expr = "@I";
-} OPT_END(price);
-
-OPT_BEGIN(market, "V") {
- report->show_revalued = true;
-
- ledger::amount_expr = "@v";
- ledger::total_expr = "@V";
-} OPT_END(market);
-
-namespace {
- void parse_price_setting(const char * optarg)
- {
- char * equals = std::strchr(optarg, '=');
- if (! equals)
- return;
-
- while (std::isspace(*optarg))
- optarg++;
- while (equals > optarg && std::isspace(*(equals - 1)))
- equals--;
-
- std::string symbol(optarg, 0, equals - optarg);
- amount_t price(equals + 1);
-
- if (commodity_t * commodity = commodity_t::find_or_create(symbol)) {
- commodity->add_price(datetime_t::now, price);
- commodity->history()->bogus_time = datetime_t::now;
- }
- }
-}
-
-OPT_BEGIN(set_price, ":") {
- std::string arg(optarg);
- std::string::size_type beg = 0;
- for (std::string::size_type pos = arg.find(';');
- pos != std::string::npos;
- beg = pos + 1, pos = arg.find(';', beg))
- parse_price_setting(std::string(arg, beg, pos - beg).c_str());
- parse_price_setting(std::string(arg, beg).c_str());
-} OPT_END(set_price);
-
-OPT_BEGIN(performance, "g") {
- ledger::amount_expr = "@P(@a,@m)-@b";
- ledger::total_expr = "@P(@O,@m)-@B";
-} OPT_END(performance);
-
-OPT_BEGIN(gain, "G") {
- report->show_revalued =
- report->show_revalued_only = true;
-
- ledger::amount_expr = "@a";
- ledger::total_expr = "@G";
-} OPT_END(gain);
-
-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;
-}
-
-OPT_BEGIN(average, "A") {
- ledger::total_expr = expand_value_expr("@A(#)", ledger::total_expr.expr);
-} OPT_END(average);
-
-OPT_BEGIN(deviation, "D") {
- ledger::total_expr = expand_value_expr("@t-@A(#)", ledger::total_expr.expr);
-} OPT_END(deviation);
-
-OPT_BEGIN(percentage, "%") {
- ledger::total_expr = expand_value_expr("^#&{100.0%}*(#/^#)",
- ledger::total_expr.expr);
-} OPT_END(percentage);
-
-//////////////////////////////////////////////////////////////////////
-
-option_t config_options[CONFIG_OPTIONS_SIZE] = {
- { "abbrev-len", '\0', true, opt_abbrev_len, false },
- { "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 },
- { "ansi", '\0', false, opt_ansi, false },
- { "ansi-invert", '\0', false, opt_ansi_invert, false },
- { "average", 'A', false, opt_average, false },
- { "balance-format", '\0', true, opt_balance_format, false },
- { "base", '\0', false, opt_base, 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 },
- { "code-as-payee", '\0', false, opt_code_as_payee, false },
- { "collapse", 'n', false, opt_collapse, false },
- { "comm-as-payee", 'x', false, opt_comm_as_payee, false },
- { "cost", '\0', false, opt_basis, false },
- { "current", 'c', false, opt_current, false },
- { "daily", '\0', false, opt_daily, false },
- { "date-format", 'y', true, opt_date_format, false },
- { "debug", '\0', true, opt_debug, false },
- { "descend", '\0', true, opt_descend, false },
- { "descend-if", '\0', true, opt_descend_if, 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 },
- { "lot-dates", '\0', false, opt_lot_dates, false },
- { "lot-prices", '\0', false, opt_lot_prices, false },
- { "lot-tags", '\0', false, opt_lot_tags, 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 },
- { "only", '\0', true, opt_only, 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 },
- { "quarterly", '\0', false, opt_quarterly, 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 },
- { "set-price", '\0', true, opt_set_price, false },
- { "sort", 'S', true, opt_sort, false },
- { "sort-all", '\0', true, opt_sort_all, false },
- { "sort-entries", '\0', true, opt_sort_entries, 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 },
- { "trace", '\0', false, opt_trace, false },
- { "truncate", '\0', true, opt_truncate, false },
- { "unbudgeted", '\0', false, opt_unbudgeted, false },
- { "uncleared", 'U', false, opt_uncleared, false },
- { "verbose", '\0', false, opt_verbose, 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
diff --git a/option.h b/option.h
deleted file mode 100644
index 91838b99..00000000
--- a/option.h
+++ /dev/null
@@ -1,53 +0,0 @@
-#ifndef _OPTION_H
-#define _OPTION_H
-
-#include <list>
-#include <string>
-#include <exception>
-
-#include "error.h"
-
-typedef void (*handler_t)(const char * arg);
-
-struct option_t {
- const char * long_opt;
- char short_opt;
- bool wants_arg;
- handler_t handler;
- bool handled;
-};
-
-class option_error : public error {
- public:
- option_error(const std::string& reason) throw() : error(reason) {}
- virtual ~option_error() throw() {}
-};
-
-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, const char ** envp,
- const std::string& tag);
-
-namespace ledger {
-
-class config_t;
-class report_t;
-
-extern config_t * config;
-extern report_t * report;
-
-#define CONFIG_OPTIONS_SIZE 97
-extern option_t config_options[CONFIG_OPTIONS_SIZE];
-
-void option_help(std::ostream& out);
-
-#define OPT_BEGIN(tag, chars) \
- void opt_ ## tag(const char * optarg)
-
-#define OPT_END(tag)
-
-} // namespace ledger
-
-#endif // _OPTION_H
diff --git a/parser.cc b/parser.cc
deleted file mode 100644
index c96f2435..00000000
--- a/parser.cc
+++ /dev/null
@@ -1,197 +0,0 @@
-#include "parser.h"
-#include "journal.h"
-#include "config.h"
-
-#include <fstream>
-#ifdef WIN32
-#include <io.h>
-#else
-#include <unistd.h>
-#endif
-
-namespace ledger {
-
-typedef std::list<parser_t *> parsers_list;
-
-static parsers_list * parsers = NULL;
-
-void initialize_parser_support()
-{
- parsers = new parsers_list;
-}
-
-void shutdown_parser_support()
-{
- if (parsers) {
- delete parsers;
- parsers = NULL;
- }
-}
-
-bool register_parser(parser_t * parser)
-{
- parsers_list::iterator i;
- for (i = parsers->begin(); i != parsers->end(); i++)
- if (*i == parser)
- break;
- if (i != parsers->end())
- return false;
-
- parsers->push_back(parser);
-
- return true;
-}
-
-bool unregister_parser(parser_t * parser)
-{
- parsers_list::iterator i;
- for (i = parsers->begin(); i != parsers->end(); i++)
- if (*i == parser)
- break;
- if (i == parsers->end())
- return false;
-
- parsers->erase(i);
-
- return true;
-}
-
-unsigned int parse_journal(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master,
- const std::string * original_file)
-{
- if (! master)
- master = journal->master;
-
- for (parsers_list::iterator i = parsers->begin();
- i != parsers->end();
- i++)
- if ((*i)->test(in))
- return (*i)->parse(in, config, journal, master, original_file);
-
- return 0;
-}
-
-unsigned int parse_journal_file(const std::string& path,
- config_t& config,
- journal_t * journal,
- account_t * master,
- const std::string * original_file)
-{
- journal->sources.push_back(path);
-
- if (access(path.c_str(), R_OK) == -1)
- throw new error(std::string("Cannot read file '") + path + "'");
-
- if (! original_file)
- original_file = &path;
-
- std::ifstream stream(path.c_str());
- return parse_journal(stream, config, journal, master, original_file);
-}
-
-extern parser_t * binary_parser_ptr;
-extern parser_t * xml_parser_ptr;
-extern parser_t * textual_parser_ptr;
-
-unsigned int parse_ledger_data(config_t& config,
- journal_t * journal,
- parser_t * cache_parser,
- parser_t * xml_parser,
- parser_t * stdin_parser)
-{
- unsigned int entry_count = 0;
-
- if (! cache_parser)
- cache_parser = binary_parser_ptr;
- if (! xml_parser)
- xml_parser = xml_parser_ptr;
- if (! stdin_parser)
- stdin_parser = textual_parser_ptr;
-
- DEBUG_PRINT("ledger.config.cache",
- "3. use_cache = " << config.use_cache);
-
- if (! config.init_file.empty() &&
- access(config.init_file.c_str(), R_OK) != -1) {
- if (parse_journal_file(config.init_file, config, journal) ||
- journal->auto_entries.size() > 0 ||
- journal->period_entries.size() > 0)
- throw new error(std::string("Entries found in initialization file '") +
- config.init_file + "'");
-
- journal->sources.pop_front(); // remove init file
- }
-
- if (config.use_cache && ! config.cache_file.empty() &&
- ! config.data_file.empty()) {
- DEBUG_PRINT("ledger.config.cache",
- "using_cache " << config.cache_file);
- config.cache_dirty = true;
- if (access(config.cache_file.c_str(), R_OK) != -1) {
- std::ifstream stream(config.cache_file.c_str());
- if (cache_parser && cache_parser->test(stream)) {
- std::string price_db_orig = journal->price_db;
- journal->price_db = config.price_db;
- entry_count += cache_parser->parse(stream, config, journal,
- NULL, &config.data_file);
- if (entry_count > 0)
- config.cache_dirty = false;
- else
- journal->price_db = price_db_orig;
- }
- }
- }
-
- if (entry_count == 0 && ! config.data_file.empty()) {
- account_t * acct = NULL;
- if (! config.account.empty())
- acct = journal->find_account(config.account);
-
- journal->price_db = config.price_db;
- if (! journal->price_db.empty() &&
- access(journal->price_db.c_str(), R_OK) != -1) {
- if (parse_journal_file(journal->price_db, config, journal)) {
- throw new error("Entries not allowed in price history file");
- } else {
- DEBUG_PRINT("ledger.config.cache",
- "read price database " << journal->price_db);
- journal->sources.pop_back();
- }
- }
-
- DEBUG_PRINT("ledger.config.cache",
- "rejected cache, parsing " << config.data_file);
- if (config.data_file == "-") {
- config.use_cache = false;
- journal->sources.push_back("<stdin>");
-#if 0
- // jww (2006-03-23): Why doesn't XML work on stdin?
- if (xml_parser && std::cin.peek() == '<')
- entry_count += xml_parser->parse(std::cin, config, journal,
- acct);
- else if (stdin_parser)
-#endif
- entry_count += stdin_parser->parse(std::cin, config,
- journal, acct);
- }
- else if (access(config.data_file.c_str(), R_OK) != -1) {
- entry_count += parse_journal_file(config.data_file, config,
- journal, acct);
- if (! journal->price_db.empty())
- journal->sources.push_back(journal->price_db);
-
- // Clear out what was set during the textual parsing phase
- clear_account_xdata acct_cleaner;
- walk_accounts(*journal->master, acct_cleaner);
- }
- }
-
- VALIDATE(journal->valid());
-
- return entry_count;
-}
-
-} // namespace ledger
diff --git a/parser.h b/parser.h
deleted file mode 100644
index 6178d293..00000000
--- a/parser.h
+++ /dev/null
@@ -1,62 +0,0 @@
-#ifndef _PARSER_H
-#define _PARSER_H
-
-#include <iostream>
-#include <string>
-
-#include "error.h"
-
-namespace ledger {
-
-class account_t;
-class journal_t;
-class config_t;
-
-class parser_t
-{
- public:
- virtual ~parser_t() {}
-
- virtual bool test(std::istream& in) const = 0;
-
- virtual unsigned int parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master = NULL,
- const std::string * original_file = NULL) = 0;
-};
-
-bool register_parser(parser_t * parser);
-bool unregister_parser(parser_t * parser);
-
-unsigned int parse_journal(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master = NULL,
- const std::string * original_file = NULL);
-
-unsigned int parse_journal_file(const std::string& path,
- config_t& config,
- journal_t * journal,
- account_t * master = NULL,
- const std::string * original_file = NULL);
-
-unsigned int parse_ledger_data(config_t& config,
- journal_t * journal,
- parser_t * cache_parser = NULL,
- parser_t * xml_parser = NULL,
- parser_t * stdin_parser = NULL);
-
-void initialize_parser_support();
-void shutdown_parser_support();
-
-class parse_error : public error {
- public:
- parse_error(const std::string& reason, error_context * ctxt = NULL) throw()
- : error(reason, ctxt) {}
- virtual ~parse_error() throw() {}
-};
-
-} // namespace ledger
-
-#endif // _PARSER_H
diff --git a/python/amounts.cc b/python/amounts.cc
new file mode 100644
index 00000000..ad3cda9a
--- /dev/null
+++ b/python/amounts.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <boost/python.hpp>
+
+using namespace boost::python;
+
+void export_amount();
+void export_balance();
+void export_value();
+
+BOOST_PYTHON_MODULE(amounts)
+{
+ export_amount();
+ export_balance();
+ export_value();
+}
diff --git a/python/py_amount.cc b/python/py_amount.cc
new file mode 100644
index 00000000..bbd5fcb5
--- /dev/null
+++ b/python/py_amount.cc
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "amount.h"
+
+#include <boost/python/exception_translator.hpp>
+#include <boost/python/implicit.hpp>
+#include <boost/python/args.hpp>
+
+namespace ledger {
+
+using namespace boost::python;
+
+amount_t py_round_0(const amount_t& amount) {
+ return amount.round();
+}
+amount_t py_round_1(const amount_t& amount, amount_t::precision_t prec) {
+ return amount.round(prec);
+}
+
+#ifdef HAVE_GDTOA
+double py_to_double_0(amount_t& amount) {
+ return amount.to_double();
+}
+double py_to_double_1(amount_t& amount, bool no_check) {
+ return amount.to_double(no_check);
+}
+#endif
+
+long py_to_long_0(amount_t& amount) {
+ return amount.to_long();
+}
+long py_to_long_1(amount_t& amount, bool no_check) {
+ return amount.to_long(no_check);
+}
+
+boost::optional<amount_t> py_value_0(const amount_t& amount) {
+ return amount.value();
+}
+boost::optional<amount_t> py_value_1(const amount_t& amount,
+ const boost::optional<datetime_t>& moment) {
+ return amount.value(moment);
+}
+
+void py_parse_2(amount_t& amount, object in, unsigned char flags) {
+ if (PyFile_Check(in.ptr())) {
+ pyifstream instr(reinterpret_cast<PyFileObject *>(in.ptr()));
+ amount.parse(instr, flags);
+ } else {
+ PyErr_SetString(PyExc_IOError,
+ "Argument to amount.parse(file) is not a file object");
+ }
+}
+void py_parse_1(amount_t& amount, object in) {
+ py_parse_2(amount, in, 0);
+}
+
+void py_parse_str_1(amount_t& amount, const string& str) {
+ amount.parse(str);
+}
+void py_parse_str_2(amount_t& amount, const string& str, unsigned char flags) {
+ amount.parse(str, flags);
+}
+
+void py_read_1(amount_t& amount, object in) {
+ if (PyFile_Check(in.ptr())) {
+ pyifstream instr(reinterpret_cast<PyFileObject *>(in.ptr()));
+ amount.read(instr);
+ } else {
+ PyErr_SetString(PyExc_IOError,
+ "Argument to amount.parse(file) is not a file object");
+ }
+}
+void py_read_2(amount_t& amount, const std::string& str) {
+ const char * p = str.c_str();
+ amount.read(p);
+}
+
+#define EXC_TRANSLATOR(type) \
+ void exc_translate_ ## type(const type& err) { \
+ PyErr_SetString(PyExc_ArithmeticError, err.what()); \
+ }
+
+EXC_TRANSLATOR(amount_error)
+
+void export_amount()
+{
+ scope().attr("AMOUNT_PARSE_NO_MIGRATE") = AMOUNT_PARSE_NO_MIGRATE;
+ scope().attr("AMOUNT_PARSE_NO_REDUCE") = AMOUNT_PARSE_NO_REDUCE;
+
+ class_< amount_t > ("amount")
+#if 0
+ .def("initialize", &amount_t::initialize)
+ .staticmethod("initialize")
+ .def("shutdown", &amount_t::shutdown)
+ .staticmethod("shutdown")
+#endif
+
+ .add_static_property("current_pool",
+ make_getter(&amount_t::current_pool,
+ return_value_policy<reference_existing_object>()))
+
+ .add_static_property("keep_base", &amount_t::keep_base)
+
+ .add_static_property("keep_price", &amount_t::keep_price)
+ .add_static_property("keep_date", &amount_t::keep_date)
+ .add_static_property("keep_tag", &amount_t::keep_tag)
+
+ .add_static_property("stream_fullstrings",
+ make_getter(&amount_t::stream_fullstrings),
+ make_setter(&amount_t::stream_fullstrings))
+
+#ifdef HAVE_GDTOA
+ .def(init<double>())
+#endif
+ .def(init<long>())
+ .def(init<std::string>())
+
+ .def("exact", &amount_t::exact, args("value"),
+ "Construct an amount object whose display precision is always equal to its\n\
+internal precision.")
+ .staticmethod("exact")
+
+ .def(init<amount_t>())
+
+ .def("compare", &amount_t::compare)
+
+ .def(self == self)
+ .def(self == long())
+ .def(long() == self)
+#ifdef HAVE_GDTOA
+ .def(self == double())
+ .def(double() == self)
+#endif
+
+ .def(self != self)
+ .def(self != long())
+ .def(long() != self)
+#ifdef HAVE_GDTOA
+ .def(self != double())
+ .def(double() != self)
+#endif
+
+ .def(! self)
+
+ .def(self < self)
+ .def(self < long())
+ .def(long() < self)
+#ifdef HAVE_GDTOA
+ .def(self < double())
+ .def(double() < self)
+#endif
+
+ .def(self <= self)
+ .def(self <= long())
+ .def(long() <= self)
+#ifdef HAVE_GDTOA
+ .def(self <= double())
+ .def(double() <= self)
+#endif
+
+ .def(self > self)
+ .def(self > long())
+ .def(long() > self)
+#ifdef HAVE_GDTOA
+ .def(self > double())
+ .def(double() > self)
+#endif
+
+ .def(self >= self)
+ .def(self >= long())
+ .def(long() >= self)
+#ifdef HAVE_GDTOA
+ .def(self >= double())
+ .def(double() >= self)
+#endif
+
+ .def(self += self)
+ .def(self += long())
+#ifdef HAVE_GDTOA
+ .def(self += double())
+#endif
+
+ .def(self + self)
+ .def(self + long())
+ .def(long() + self)
+#ifdef HAVE_GDTOA
+ .def(self + double())
+ .def(double() + self)
+#endif
+
+ .def(self -= self)
+ .def(self -= long())
+#ifdef HAVE_GDTOA
+ .def(self -= double())
+#endif
+
+ .def(self - self)
+ .def(self - long())
+ .def(long() - self)
+#ifdef HAVE_GDTOA
+ .def(self - double())
+ .def(double() - self)
+#endif
+
+ .def(self *= self)
+ .def(self *= long())
+#ifdef HAVE_GDTOA
+ .def(self *= double())
+#endif
+
+ .def(self * self)
+ .def(self * long())
+ .def(long() * self)
+#ifdef HAVE_GDTOA
+ .def(self * double())
+ .def(double() * self)
+#endif
+
+ .def(self /= self)
+ .def(self /= long())
+#ifdef HAVE_GDTOA
+ .def(self /= double())
+#endif
+
+ .def(self / self)
+ .def(self / long())
+ .def(long() / self)
+#ifdef HAVE_GDTOA
+ .def(self / double())
+ .def(double() / self)
+#endif
+
+ .add_property("precision", &amount_t::precision)
+
+ .def("negate", &amount_t::negate)
+ .def("in_place_negate", &amount_t::in_place_negate,
+ return_value_policy<reference_existing_object>())
+ .def(- self)
+
+ .def("abs", &amount_t::abs)
+ .def("__abs__", &amount_t::abs)
+
+ .def("round", py_round_0)
+ .def("round", py_round_1)
+ .def("unround", &amount_t::unround)
+
+ .def("reduce", &amount_t::reduce)
+ .def("in_place_reduce", &amount_t::in_place_reduce,
+ return_value_policy<reference_existing_object>())
+
+ .def("unreduce", &amount_t::unreduce)
+ .def("in_place_unreduce", &amount_t::in_place_unreduce,
+ return_value_policy<reference_existing_object>())
+
+ .def("value", py_value_0)
+ .def("value", py_value_1)
+
+ .def("sign", &amount_t::sign)
+ .def("__nonzero__", &amount_t::is_nonzero)
+ .def("is_nonzero", &amount_t::is_nonzero)
+ .def("is_zero", &amount_t::is_zero)
+ .def("is_realzero", &amount_t::is_realzero)
+ .def("is_null", &amount_t::is_null)
+
+#ifdef HAVE_GDTOA
+ .def("to_double", py_to_double_0)
+ .def("to_double", py_to_double_1)
+ .def("__float__", py_to_double_0)
+#endif
+ .def("to_long", py_to_long_0)
+ .def("to_long", py_to_long_1)
+ .def("__int__", py_to_long_0)
+ .def("to_string", &amount_t::to_string)
+ .def("__str__", &amount_t::to_string)
+ .def("to_fullstring", &amount_t::to_fullstring)
+ .def("__repr__", &amount_t::to_fullstring)
+
+#ifdef HAVE_GDTOA
+ .def("fits_in_double", &amount_t::fits_in_double)
+#endif
+ .def("fits_in_long", &amount_t::fits_in_long)
+
+ .add_property("quantity_string", &amount_t::quantity_string)
+
+ .add_property("commodity",
+ make_function(&amount_t::commodity,
+ return_value_policy<reference_existing_object>()),
+ make_function(&amount_t::set_commodity,
+ with_custodian_and_ward<1, 2>()))
+
+ .def("has_commodity", &amount_t::has_commodity)
+ .def("clear_commodity", &amount_t::clear_commodity)
+ .add_property("number", &amount_t::number)
+
+ .def("annotate", &amount_t::annotate)
+ .def("is_annotated", &amount_t::is_annotated)
+#if 0
+ .add_property("annotation", &amount_t::annotation)
+#endif
+ .def("strip_annotations", &amount_t::strip_annotations)
+
+ .def("parse", py_parse_1)
+ .def("parse", py_parse_2)
+ .def("parse", py_parse_str_1)
+ .def("parse", py_parse_str_2)
+
+ .def("parse_conversion", &amount_t::parse_conversion)
+ .staticmethod("parse_conversion")
+
+ .def("read", py_read_1)
+ .def("read", py_read_2)
+ .def("write", &amount_t::write)
+
+ .def("valid", &amount_t::valid)
+ ;
+
+ register_optional_to_python<amount_t>();
+
+#ifdef HAVE_GDTOA
+ implicitly_convertible<double, amount_t>();
+#endif
+ implicitly_convertible<long, amount_t>();
+ implicitly_convertible<string, amount_t>();
+
+#define EXC_TRANSLATE(type) \
+ register_exception_translator<type>(&exc_translate_ ## type);
+
+ EXC_TRANSLATE(amount_error);
+}
+
+} // namespace ledger
diff --git a/python/py_commodity.cc b/python/py_commodity.cc
new file mode 100644
index 00000000..3cf7d30c
--- /dev/null
+++ b/python/py_commodity.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pyinterp.h"
+#include "pyutils.h"
+#include "amount.h"
+
+#include <boost/python/exception_translator.hpp>
+#include <boost/python/implicit.hpp>
+
+namespace ledger {
+
+using namespace boost::python;
+
+void export_commodity()
+{
+ scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS;
+ scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED;
+ scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED;
+ scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN;
+ scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS;
+ scope().attr("COMMODITY_STYLE_NOMARKET") = COMMODITY_STYLE_NOMARKET;
+ scope().attr("COMMODITY_STYLE_BUILTIN") = COMMODITY_STYLE_BUILTIN;
+
+ class_< commodity_t, bases<>,
+ commodity_t, boost::noncopyable > ("commodity", no_init)
+ .def(self == self)
+
+ .def("drop_flags", &commodity_t::drop_flags)
+
+ .add_property("precision", &commodity_t::precision)
+ ;
+}
+
+} // namespace ledger
diff --git a/python/py_times.cc b/python/py_times.cc
new file mode 100644
index 00000000..2d7ce662
--- /dev/null
+++ b/python/py_times.cc
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pyinterp.h"
+#include "pyutils.h"
+
+#include <boost/cast.hpp>
+#include <boost/python/module.hpp>
+#include <boost/python/def.hpp>
+#include <boost/python/to_python_converter.hpp>
+
+#include <Python.h>
+#include <datetime.h>
+
+// jww (2007-05-04): Convert time duration objects to PyDelta
+
+namespace ledger {
+
+using namespace boost::python;
+
+typedef boost::gregorian::date date;
+
+struct date_to_python
+{
+ static PyObject* convert(const date& dte)
+ {
+ PyDateTime_IMPORT;
+ return PyDate_FromDate(dte.year(), dte.month(), dte.day());
+ }
+};
+
+struct date_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ PyDateTime_IMPORT;
+ if (PyDate_Check(obj_ptr)) return obj_ptr;
+ return 0;
+ }
+
+ static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data)
+ {
+ PyDateTime_IMPORT;
+ int y = PyDateTime_GET_YEAR(obj_ptr);
+ int m = PyDateTime_GET_MONTH(obj_ptr);
+ int d = PyDateTime_GET_DAY(obj_ptr);
+ date* dte = new date(y,m,d);
+ data->convertible = (void*)dte;
+ }
+};
+
+typedef register_python_conversion<date, date_to_python, date_from_python>
+ date_python_conversion;
+
+
+struct datetime_to_python
+{
+ static PyObject* convert(const datetime_t& moment)
+ {
+ PyDateTime_IMPORT;
+ date dte = moment.date();
+ datetime_t::time_duration_type tod = moment.time_of_day();
+ return PyDateTime_FromDateAndTime(dte.year(), dte.month(), dte.day(),
+ tod.hours(), tod.minutes(), tod.seconds(),
+ tod.total_microseconds() % 1000000);
+ }
+};
+
+struct datetime_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ PyDateTime_IMPORT;
+ if(PyDateTime_Check(obj_ptr)) return obj_ptr;
+ return 0;
+ }
+
+ static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data)
+ {
+ PyDateTime_IMPORT;
+ int y = PyDateTime_GET_YEAR(obj_ptr);
+ int m = PyDateTime_GET_MONTH(obj_ptr);
+ int d = PyDateTime_GET_DAY(obj_ptr);
+ int h = PyDateTime_DATE_GET_HOUR(obj_ptr);
+ int min = PyDateTime_DATE_GET_MINUTE(obj_ptr);
+ int s = PyDateTime_DATE_GET_SECOND(obj_ptr);
+ datetime_t* moment = new datetime_t(date(y,m,d),
+ datetime_t::time_duration_type(h, min, s));
+ data->convertible = (void*)moment;
+ }
+};
+
+typedef register_python_conversion<datetime_t, datetime_to_python, datetime_from_python>
+ datetime_python_conversion;
+
+void export_times()
+{
+ date_python_conversion();
+ datetime_python_conversion();
+
+ register_optional_to_python<datetime_t>();
+}
+
+} // namespace ledger
diff --git a/python/py_utils.cc b/python/py_utils.cc
new file mode 100644
index 00000000..99936a8e
--- /dev/null
+++ b/python/py_utils.cc
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pyinterp.h"
+#include "pyutils.h"
+
+#include <boost/python/module.hpp>
+#include <boost/python/def.hpp>
+#include <boost/python/to_python_converter.hpp>
+
+namespace ledger {
+
+using namespace boost::python;
+
+struct bool_to_python
+{
+ static PyObject * convert(const bool truth)
+ {
+ if (truth)
+ Py_RETURN_TRUE;
+ else
+ Py_RETURN_FALSE;
+ }
+};
+
+struct bool_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ if (!PyBool_Check(obj_ptr)) return 0;
+ return obj_ptr;
+ }
+
+ static void construct(PyObject* obj_ptr,
+ converter::rvalue_from_python_stage1_data* data)
+ {
+ void* storage = ((converter::rvalue_from_python_storage<bool>*) data)->storage.bytes;
+ if (obj_ptr == Py_True)
+ new (storage) bool(true);
+ else
+ new (storage) bool(false);
+ data->convertible = storage;
+ }
+};
+
+typedef register_python_conversion<bool, bool_to_python, bool_from_python>
+ bool_python_conversion;
+
+
+struct string_to_python
+{
+ static PyObject* convert(const string& str)
+ {
+ return incref(object(*boost::polymorphic_downcast<const std::string *>(&str)).ptr());
+ }
+};
+
+struct string_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ if (!PyString_Check(obj_ptr)) return 0;
+ return obj_ptr;
+ }
+
+ static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data)
+ {
+ const char* value = PyString_AsString(obj_ptr);
+ if (value == 0) throw_error_already_set();
+ void* storage =
+ reinterpret_cast<converter::rvalue_from_python_storage<string> *>(data)->storage.bytes;
+ new (storage) string(value);
+ data->convertible = storage;
+ }
+};
+
+typedef register_python_conversion<string, string_to_python, string_from_python>
+ string_python_conversion;
+
+
+struct istream_to_python
+{
+ static PyObject* convert(const std::istream&)
+ {
+ return incref(boost::python::detail::none());
+ }
+};
+
+struct istream_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ if (!PyFile_Check(obj_ptr)) return 0;
+ return obj_ptr;
+ }
+
+ static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data)
+ {
+ void* storage =
+ reinterpret_cast<converter::rvalue_from_python_storage<pyifstream> *>(data)->storage.bytes;
+ new (storage) pyifstream(reinterpret_cast<PyFileObject *>(obj_ptr));
+ data->convertible = storage;
+ }
+};
+
+typedef register_python_conversion<std::istream, istream_to_python, istream_from_python>
+ istream_python_conversion;
+
+
+struct ostream_to_python
+{
+ static PyObject* convert(const std::ostream&)
+ {
+ return incref(boost::python::detail::none());
+ }
+};
+
+struct ostream_from_python
+{
+ static void* convertible(PyObject* obj_ptr)
+ {
+ if (!PyFile_Check(obj_ptr)) return 0;
+ return obj_ptr;
+ }
+
+ static void construct(PyObject* obj_ptr, converter::rvalue_from_python_stage1_data* data)
+ {
+ void* storage = reinterpret_cast<converter::rvalue_from_python_storage<pyofstream> *>(data)->storage.bytes;
+ new (storage) pyofstream(reinterpret_cast<PyFileObject *>(obj_ptr));
+ data->convertible = storage;
+ }
+};
+
+typedef register_python_conversion<std::ostream, ostream_to_python, ostream_from_python>
+ ostream_python_conversion;
+
+
+void export_utils()
+{
+ bool_python_conversion();
+ string_python_conversion();
+ istream_python_conversion();
+ ostream_python_conversion();
+}
+
+} // namespace ledger
diff --git a/python/pyfstream.h b/python/pyfstream.h
new file mode 100644
index 00000000..aaa5cbeb
--- /dev/null
+++ b/python/pyfstream.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PYFSTREAM_H
+#define _PYFSTREAM_H
+
+// pyofstream
+// - a stream that writes on a Python file object
+
+class pyoutbuf : public boost::noncopyable, public std::streambuf
+{
+ pyoutbuf();
+
+protected:
+ PyFileObject * fo; // Python file object
+
+public:
+ // constructor
+ pyoutbuf(PyFileObject * _fo) : fo(_fo) {
+ TRACE_CTOR(pyoutbuf, "PyFileObject *");
+ }
+ ~pyoutbuf() throw() {
+ TRACE_DTOR(pyoutbuf);
+ }
+
+protected:
+ // write one character
+ virtual int_type overflow (int_type c) {
+ if (c != EOF) {
+ char z[2];
+ z[0] = c;
+ z[1] = '\0';
+ if (PyFile_WriteString(z, reinterpret_cast<PyObject *>(fo)) < 0) {
+ return EOF;
+ }
+ }
+ return c;
+ }
+
+ // write multiple characters
+ virtual std::streamsize xsputn (const char* s, std::streamsize num) {
+ char * buf = new char[num + 1];
+ std::strncpy(buf, s, num);
+ buf[num] = '\0';
+ if (PyFile_WriteString(buf, reinterpret_cast<PyObject *>(fo)) < 0)
+ num = 0;
+ boost::checked_array_delete(buf);
+ return num;
+ }
+};
+
+class pyofstream : public boost::noncopyable, public std::ostream
+{
+ pyofstream();
+
+protected:
+ pyoutbuf buf;
+
+public:
+ pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) {
+ TRACE_CTOR(pyofstream, "PyFileObject *");
+ rdbuf(&buf);
+ }
+ ~pyofstream() throw() {
+ TRACE_DTOR(pyofstream);
+ }
+};
+
+// pyifstream
+// - a stream that reads on a file descriptor
+
+class pyinbuf : public boost::noncopyable, public std::streambuf
+{
+ pyinbuf();
+
+protected:
+ PyFileObject * fo; // Python file object
+
+protected:
+ /* data buffer:
+ * - at most, pbSize characters in putback area plus
+ * - at most, bufSize characters in ordinary read buffer
+ */
+ static const int pbSize = 4; // size of putback area
+ static const int bufSize = 1024; // size of the data buffer
+ char buffer[bufSize + pbSize]; // data buffer
+
+public:
+ /* constructor
+ * - initialize file descriptor
+ * - initialize empty data buffer
+ * - no putback area
+ * => force underflow()
+ */
+ pyinbuf (PyFileObject * _fo) : fo(_fo) {
+ TRACE_CTOR(pyinbuf, "PyFileObject *");
+
+ setg (buffer+pbSize, // beginning of putback area
+ buffer+pbSize, // read position
+ buffer+pbSize); // end position
+ }
+ ~pyinbuf() throw() {
+ TRACE_DTOR(pyinbuf);
+ }
+
+protected:
+ // insert new characters into the buffer
+ virtual int_type underflow () {
+#ifndef _MSC_VER
+ using std::memmove;
+#endif
+
+ // is read position before end of buffer?
+ if (gptr() < egptr()) {
+ return traits_type::to_int_type(*gptr());
+ }
+
+ /* process size of putback area
+ * - use number of characters read
+ * - but at most size of putback area
+ */
+ int numPutback;
+ numPutback = gptr() - eback();
+ if (numPutback > pbSize) {
+ numPutback = pbSize;
+ }
+
+ /* copy up to pbSize characters previously read into
+ * the putback area
+ */
+ memmove (buffer+(pbSize-numPutback), gptr()-numPutback,
+ numPutback);
+
+ // read at most bufSize new characters
+ int num;
+ PyObject *line = PyFile_GetLine(reinterpret_cast<PyObject *>(fo), bufSize);
+ if (! line || ! PyString_Check(line)) {
+ // ERROR or EOF
+ return EOF;
+ }
+
+ num = PyString_Size(line);
+ if (num == 0)
+ return EOF;
+
+ memmove (buffer+pbSize, PyString_AsString(line), num);
+
+ // reset buffer pointers
+ setg (buffer+(pbSize-numPutback), // beginning of putback area
+ buffer+pbSize, // read position
+ buffer+pbSize+num); // end of buffer
+
+ // return next character
+ return traits_type::to_int_type(*gptr());
+ }
+};
+
+class pyifstream : public boost::noncopyable, public std::istream
+{
+ pyifstream();
+
+protected:
+ pyinbuf buf;
+
+public:
+ pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) {
+ TRACE_CTOR(pyifstream, "PyFileObject *");
+ rdbuf(&buf);
+ }
+ ~pyifstream() throw() {
+ TRACE_DTOR(pyifstream);
+ }
+};
+
+#endif // _PYFSTREAM_H
diff --git a/python/pyinterp.cc b/python/pyinterp.cc
new file mode 100644
index 00000000..831852ad
--- /dev/null
+++ b/python/pyinterp.cc
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "pyinterp.h"
+
+#include <boost/python/module_init.hpp>
+
+namespace ledger {
+
+using namespace boost::python;
+
+void export_utils();
+void export_times();
+void export_amount();
+void export_commodity();
+#if 0
+void export_balance();
+void export_value();
+void export_journal();
+void export_parser();
+void export_option();
+void export_walk();
+void export_report();
+void export_format();
+void export_valexpr();
+#endif
+
+void initialize_for_python()
+{
+ export_utils();
+ export_times();
+ export_amount();
+ export_commodity();
+#if 0
+ export_balance();
+ export_value();
+ export_journal();
+ export_parser();
+ export_option();
+ export_walk();
+ export_format();
+ export_report();
+ export_valexpr();
+#endif
+}
+
+struct python_run
+{
+ object result;
+
+ python_run(python_interpreter_t * intepreter,
+ const string& str, int input_mode)
+ : result(handle<>(borrowed(PyRun_String(str.c_str(), input_mode,
+ intepreter->nspace.ptr(),
+ intepreter->nspace.ptr())))) {}
+ operator object() {
+ return result;
+ }
+};
+
+python_interpreter_t::python_interpreter_t()
+ : scope_t(), mmodule(borrowed(PyImport_AddModule("__main__"))),
+ nspace(handle<>(borrowed(PyModule_GetDict(mmodule.get()))))
+{
+ TRACE_CTOR(python_interpreter_t, "expr_t::scope_t&");
+
+ Py_Initialize();
+ boost::python::detail::init_module("ledger", &initialize_for_python);
+}
+
+object python_interpreter_t::import(const string& str)
+{
+ assert(Py_IsInitialized());
+
+ try {
+ PyObject * mod = PyImport_Import(PyString_FromString(str.c_str()));
+ if (! mod)
+ throw_(std::logic_error, "Failed to import Python module " << str);
+
+ object newmod(handle<>(borrowed(mod)));
+
+#if 1
+ // Import all top-level entries directly into the main namespace
+ dict m_nspace(handle<>(borrowed(PyModule_GetDict(mod))));
+ nspace.update(m_nspace);
+#else
+ nspace[string(PyModule_GetName(mod))] = newmod;
+#endif
+ return newmod;
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(std::logic_error, "Importing Python module " << str);
+ }
+ return object();
+}
+
+object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode)
+{
+ bool first = true;
+ string buffer;
+ buffer.reserve(4096);
+
+ while (! in.eof()) {
+ char buf[256];
+ in.getline(buf, 255);
+ if (buf[0] == '!')
+ break;
+ if (first)
+ first = false;
+ else
+ buffer += "\n";
+ buffer += buf;
+ }
+
+ try {
+ int input_mode;
+ switch (mode) {
+ case PY_EVAL_EXPR: input_mode = Py_eval_input; break;
+ case PY_EVAL_STMT: input_mode = Py_single_input; break;
+ case PY_EVAL_MULTI: input_mode = Py_file_input; break;
+ }
+ assert(Py_IsInitialized());
+ return python_run(this, buffer, input_mode);
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(std::logic_error, "Evaluating Python code");
+ }
+ return object();
+}
+
+object python_interpreter_t::eval(const string& str, py_eval_mode_t mode)
+{
+ try {
+ int input_mode;
+ switch (mode) {
+ case PY_EVAL_EXPR: input_mode = Py_eval_input; break;
+ case PY_EVAL_STMT: input_mode = Py_single_input; break;
+ case PY_EVAL_MULTI: input_mode = Py_file_input; break;
+ }
+ assert(Py_IsInitialized());
+ return python_run(this, str, input_mode);
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(std::logic_error, "Evaluating Python code");
+ }
+ return object();
+}
+
+value_t python_interpreter_t::functor_t::operator()(call_scope_t& args)
+{
+ try {
+ if (! PyCallable_Check(func.ptr())) {
+ return extract<value_t>(func.ptr());
+ } else {
+ if (args.size() > 0) {
+ list arglist;
+ if (args.value().is_sequence())
+ foreach (const value_t& value, args.value().as_sequence())
+ arglist.append(value);
+ else
+ arglist.append(args.value());
+
+ if (PyObject * val =
+ PyObject_CallObject(func.ptr(),
+ boost::python::tuple(arglist).ptr())) {
+ value_t result = extract<value_t>(val)();
+ Py_DECREF(val);
+ return result;
+ }
+ else if (PyObject * err = PyErr_Occurred()) {
+ PyErr_Print();
+ throw_(calc_error,
+ "While calling Python function '" /*<< name() <<*/ "': " << err);
+ } else {
+ assert(false);
+ }
+ } else {
+ return call<value_t>(func.ptr());
+ }
+ }
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(calc_error,
+ "While calling Python function '" /*<< name() <<*/ "'");
+ }
+ return NULL_VALUE;
+}
+
+value_t python_interpreter_t::lambda_t::operator()(call_scope_t& args)
+{
+ try {
+ assert(args.size() == 1);
+ value_t item = args[0];
+ return call<value_t>(func.ptr(), item);
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(calc_error, "While evaluating Python lambda expression");
+ }
+ return NULL_VALUE;
+}
+
+} // namespace ledger
diff --git a/python/pyinterp.h b/python/pyinterp.h
new file mode 100644
index 00000000..1deeb11b
--- /dev/null
+++ b/python/pyinterp.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PYINTERP_H
+#define _PYINTERP_H
+
+#include "scope.h"
+
+#include <boost/python.hpp>
+#include <Python.h>
+
+namespace ledger {
+
+class python_interpreter_t : public noncopyable, public scope_t
+{
+ boost::python::handle<> mmodule;
+
+ python_interpreter_t();
+
+public:
+ boost::python::dict nspace;
+
+ python_interpreter_t(scope_t& parent);
+
+ virtual ~python_interpreter_t() {
+ TRACE_DTOR(python_interpreter_t);
+
+ Py_Finalize();
+ }
+
+ boost::python::object import(const string& name);
+
+ enum py_eval_mode_t {
+ PY_EVAL_EXPR,
+ PY_EVAL_STMT,
+ PY_EVAL_MULTI
+ };
+
+ boost::python::object eval(std::istream& in,
+ py_eval_mode_t mode = PY_EVAL_EXPR);
+ boost::python::object eval(const string& str,
+ py_eval_mode_t mode = PY_EVAL_EXPR);
+ boost::python::object eval(const char * c_str,
+ py_eval_mode_t mode = PY_EVAL_EXPR) {
+ string str(c_str);
+ return eval(str, mode);
+ }
+
+ class functor_t {
+ functor_t();
+ protected:
+ boost::python::object func;
+ public:
+ functor_t(const string&, boost::python::object _func) : func(_func) {
+ TRACE_CTOR(functor_t, "const string&, boost::python::object");
+ }
+ functor_t(const functor_t& other) : func(other.func) {
+ TRACE_CTOR(functor_t, "copy");
+ }
+ virtual ~functor_t() throw() {
+ TRACE_DTOR(functor_t);
+ }
+ virtual value_t operator()(call_scope_t& args);
+ };
+
+ virtual expr_t::ptr_op_t lookup(const string& name) {
+ if (boost::python::object func = eval(name))
+ return WRAP_FUNCTOR(functor_t(name, func));
+ return expr_t::ptr_op_t();
+ }
+
+ class lambda_t : public functor_t {
+ lambda_t();
+ public:
+ lambda_t(boost::python::object code) : functor_t("<lambda>", code) {
+ TRACE_CTOR(functor_t, "boost::python::object");
+ }
+ lambda_t(const lambda_t& other) : functor_t(other) {
+ TRACE_CTOR(lambda_t, "copy");
+ }
+ virtual ~lambda_t() throw() {
+ TRACE_DTOR(lambda_t);
+ }
+ virtual value_t operator()(call_scope_t& args);
+ };
+};
+
+} // namespace ledger
+
+#endif // _PYINTERP_H
diff --git a/python/pyledger.cc b/python/pyledger.cc
new file mode 100644
index 00000000..76d7ab0c
--- /dev/null
+++ b/python/pyledger.cc
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <pyledger.h>
+
+using namespace boost::python;
+
+ledger::session_t python_session;
+
+namespace ledger {
+ extern void initialize_for_python();
+}
+
+BOOST_PYTHON_MODULE(ledger)
+{
+ ledger::set_session_context(&python_session);
+ ledger::initialize_for_python();
+}
diff --git a/python/pyledger.h b/python/pyledger.h
new file mode 100644
index 00000000..3d9aa14d
--- /dev/null
+++ b/python/pyledger.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PYLEDGER_H
+#define _PYLEDGER_H
+
+#include <ledger.h>
+#include <pyinterp.h>
+
+#endif // _PYLEDGER_H
diff --git a/python/pyutils.h b/python/pyutils.h
new file mode 100644
index 00000000..006f75ac
--- /dev/null
+++ b/python/pyutils.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PY_UTILS_H
+#define _PY_UTILS_H
+
+#include "pyfstream.h"
+
+template <typename T, typename TfromPy>
+struct object_from_python
+{
+ object_from_python() {
+ boost::python::converter::registry::push_back
+ (&TfromPy::convertible, &TfromPy::construct,
+ boost::python::type_id<T>());
+ }
+};
+
+template <typename T, typename TtoPy, typename TfromPy>
+struct register_python_conversion
+{
+ register_python_conversion() {
+ boost::python::to_python_converter<T, TtoPy>();
+ object_from_python<T, TfromPy>();
+ }
+};
+
+template <typename T>
+struct register_optional_to_python : public boost::noncopyable
+{
+ struct optional_to_python
+ {
+ static PyObject * convert(const boost::optional<T>& value)
+ {
+ return boost::python::incref
+ (value ? boost::python::to_python_value<T>()(*value) :
+ boost::python::detail::none());
+ }
+ };
+
+ struct optional_from_python
+ {
+ static void * convertible(PyObject * source)
+ {
+ using namespace boost::python::converter;
+
+ if (source == Py_None)
+ return source;
+
+ const registration& converters(registered<T>::converters);
+
+ if (implicit_rvalue_convertible_from_python(source, converters)) {
+ rvalue_from_python_stage1_data data =
+ rvalue_from_python_stage1(source, converters);
+ return rvalue_from_python_stage2(source, data, converters);
+ }
+ return NULL;
+ }
+
+ static void construct(PyObject * source,
+ boost::python::converter::rvalue_from_python_stage1_data * data)
+ {
+ using namespace boost::python::converter;
+
+ void * const storage =
+ reinterpret_cast<rvalue_from_python_storage<T> *>(data)->storage.bytes;
+
+ if (data->convertible == source) // == None
+ new (storage) boost::optional<T>(); // A Boost uninitialized value
+ else
+ new (storage) boost::optional<T>(*reinterpret_cast<T *>(data->convertible));
+
+ data->convertible = storage;
+ }
+ };
+
+ explicit register_optional_to_python() {
+ register_python_conversion<boost::optional<T>,
+ optional_to_python, optional_from_python>();
+ }
+};
+
+//boost::python::register_ptr_to_python< boost::shared_ptr<Base> >();
+
+#endif // _PY_UTILS_H
diff --git a/python/setup.py b/python/setup.py
new file mode 100755
index 00000000..26e74cf0
--- /dev/null
+++ b/python/setup.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+from distutils.core import setup, Extension
+
+import os
+
+defines = [('PYTHON_MODULE', 1)]
+libs = os.environ["PYLIBS"].split()
+
+setup(name = "Ledger",
+ version = "2.7",
+ description = "Ledger Accounting Library",
+ author = "John Wiegley",
+ author_email = "johnw@newartisans.com",
+ url = "http://www.newartisans.com/software/ledger.html",
+ ext_modules = [
+ Extension("ledger", ['python/pyledger.cc'],
+ define_macros = defines, libraries = libs)])
diff --git a/python/tuples.h b/python/tuples.h
new file mode 100644
index 00000000..d7695934
--- /dev/null
+++ b/python/tuples.h
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Copyright 2004-2007 Roman Yakovenko.
+// Distributed under the Boost Software License, Version 1.0. (See
+// accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef TUPLES_HPP_16_JAN_2007
+#define TUPLES_HPP_16_JAN_2007
+
+#include "boost/python.hpp"
+#include "boost/tuple/tuple.hpp"
+#include "boost/python/object.hpp" //len function
+#include <boost/mpl/int.hpp>
+#include <boost/mpl/next.hpp>
+
+/**
+ * Converts boost::tuples::tuple<...> to\from Python tuple
+ *
+ * The conversion is done "on-the-fly", you should only register the conversion
+ * with your tuple classes.
+ * For example:
+ *
+ * typedef boost::tuples::tuple< int, double, std::string > triplet;
+ * boost::python::register_tuple< triplet >();
+ *
+ * That's all. After this point conversion to\from next types will be handled
+ * by Boost.Python library:
+ *
+ * triplet
+ * triplet& ( return type only )
+ * const triplet
+ * const triplet&
+ *
+ * Implementation description.
+ * The conversion uses Boost.Python custom r-value converters. r-value converters
+ * is very powerful and undocumented feature of the library. The only documentation
+ * we have is http://boost.org/libs/python/doc/v2/faq.html#custom_string .
+ *
+ * The conversion consists from two parts: "to" and "from".
+ *
+ * "To" conversion
+ * The "to" part is pretty easy and well documented ( http://docs.python.org/api/api.html ).
+ * You should use Python C API to create an instance of a class and than you
+ * initialize the relevant members of the instance.
+ *
+ * "From" conversion
+ * Lets start from analyzing one of the use case Boost.Python library have to
+ * deal with:
+ *
+ * void do_smth( const triplet& arg ){...}
+ *
+ * In order to allow calling this function from Python, the library should keep
+ * parameter "arg" alive until the function returns. In other words, the library
+ * should provide instances life-time management. The provided interface is not
+ * ideal and could be improved. You have to implement two functions:
+ *
+ * void* convertible( PyObject* obj )
+ * Checks whether the "obj" could be converted to an instance of the desired
+ * class. If true, the function should return "obj", otherwise NULL
+ *
+ * void construct( PyObject* obj, converter::rvalue_from_python_stage1_data* data)
+ * Constructs the instance of the desired class. This function will be called
+ * if and only if "convertible" function returned true. The first argument
+ * is Python object, which was passed as parameter to "convertible" function.
+ * The second object is some kind of memory allocator for one object. Basically
+ * it keeps a memory chunk. You will use the memory for object allocation.
+ *
+ * For some unclear for me reason, the library implements "C style Inheritance"
+ * ( http://www.embedded.com/97/fe29712.htm ). So, in order to create new
+ * object in the storage you have to cast to the "right" class:
+ *
+ * typedef converter::rvalue_from_python_storage<your_type_t> storage_t;
+ * storage_t* the_storage = reinterpret_cast<storage_t*>( data );
+ * void* memory_chunk = the_storage->storage.bytes;
+ *
+ * "memory_chunk" points to the memory, where the instance will be allocated.
+ *
+ * In order to create object at specific location, you should use placement new
+ * operator:
+ *
+ * your_type_t* instance = new (memory_chunk) your_type_t();
+ *
+ * Now, you can continue to initialize the instance.
+ *
+ * instance->set_xyz = read xyz from obj
+ *
+ * If "your_type_t" constructor requires some arguments, "read" the Python
+ * object before you call the constructor:
+ *
+ * xyz_type xyz = read xyz from obj
+ * your_type_t* instance = new (memory_chunk) your_type_t(xyz);
+ *
+ * Hint:
+ * In most case you don't really need\have to work with C Python API. Let
+ * Boost.Python library to do some work for you!
+ *
+ **/
+
+namespace boost{ namespace python{
+
+namespace details{
+
+//Small helper function, introduced to allow short syntax for index incrementing
+template< int index>
+typename mpl::next< mpl::int_< index > >::type increment_index(){
+ typedef typename mpl::next< mpl::int_< index > >::type next_index_type;
+ return next_index_type();
+}
+
+}
+
+template< class TTuple >
+struct to_py_tuple{
+
+ typedef mpl::int_< tuples::length< TTuple >::value > length_type;
+
+ static PyObject* convert(const TTuple& c_tuple){
+ list values;
+ //add all c_tuple items to "values" list
+ convert_impl( c_tuple, values, mpl::int_< 0 >(), length_type() );
+ //create Python tuple from the list
+ return incref( python::tuple( values ).ptr() );
+ }
+
+private:
+
+ template< int index, int length >
+ static void
+ convert_impl( const TTuple &c_tuple, list& values, mpl::int_< index >, mpl::int_< length > ) {
+ values.append( c_tuple.template get< index >() );
+ convert_impl( c_tuple, values, details::increment_index<index>(), length_type() );
+ }
+
+ template< int length >
+ static void
+ convert_impl( const TTuple&, list& values, mpl::int_< length >, mpl::int_< length >)
+ {}
+
+};
+
+
+template< class TTuple>
+struct from_py_sequence{
+
+ typedef TTuple tuple_type;
+
+ typedef mpl::int_< tuples::length< TTuple >::value > length_type;
+
+ static void*
+ convertible(PyObject* py_obj){
+
+ if( !PySequence_Check( py_obj ) ){
+ return 0;
+ }
+
+ if( !PyObject_HasAttrString( py_obj, "__len__" ) ){
+ return 0;
+ }
+
+ python::object py_sequence( handle<>( borrowed( py_obj ) ) );
+
+ if( tuples::length< TTuple >::value != len( py_sequence ) ){
+ return 0;
+ }
+
+ if( convertible_impl( py_sequence, mpl::int_< 0 >(), length_type() ) ){
+ return py_obj;
+ }
+ else{
+ return 0;
+ }
+ }
+
+ static void
+ construct( PyObject* py_obj, converter::rvalue_from_python_stage1_data* data){
+ typedef converter::rvalue_from_python_storage<TTuple> storage_t;
+ storage_t* the_storage = reinterpret_cast<storage_t*>( data );
+ void* memory_chunk = the_storage->storage.bytes;
+ TTuple* c_tuple = new (memory_chunk) TTuple();
+ data->convertible = memory_chunk;
+
+ python::object py_sequence( handle<>( borrowed( py_obj ) ) );
+ construct_impl( py_sequence, *c_tuple, mpl::int_< 0 >(), length_type() );
+ }
+
+ static TTuple to_c_tuple( PyObject* py_obj ){
+ if( !convertible( py_obj ) ){
+ throw std::runtime_error( "Unable to construct boost::tuples::tuple from Python object!" );
+ }
+ TTuple c_tuple;
+ python::object py_sequence( handle<>( borrowed( py_obj ) ) );
+ construct_impl( py_sequence, c_tuple, mpl::int_< 0 >(), length_type() );
+ return c_tuple;
+ }
+
+private:
+
+ template< int index, int length >
+ static bool
+ convertible_impl( const python::object& py_sequence, mpl::int_< index >, mpl::int_< length > ){
+
+ typedef typename tuples::element< index, TTuple>::type element_type;
+
+ object element = py_sequence[index];
+ extract<element_type> type_checker( element );
+ if( !type_checker.check() ){
+ return false;
+ }
+ else{
+ return convertible_impl( py_sequence, details::increment_index<index>(), length_type() );
+ }
+ }
+
+ template< int length >
+ static bool
+ convertible_impl( const python::object& py_sequence, mpl::int_< length >, mpl::int_< length > ){
+ return true;
+ }
+
+ template< int index, int length >
+ static void
+ construct_impl( const python::object& py_sequence, TTuple& c_tuple, mpl::int_< index >, mpl::int_< length > ){
+
+ typedef typename tuples::element< index, TTuple>::type element_type;
+
+ object element = py_sequence[index];
+ c_tuple.template get< index >() = extract<element_type>( element );
+
+ construct_impl( py_sequence, c_tuple, details::increment_index<index>(), length_type() );
+ }
+
+ template< int length >
+ static void
+ construct_impl( const python::object& py_sequence, TTuple& c_tuple, mpl::int_< length >, mpl::int_< length > )
+ {}
+
+};
+
+template< class TTuple>
+void register_tuple(){
+
+ to_python_converter< TTuple, to_py_tuple<TTuple> >();
+
+ converter::registry::push_back( &from_py_sequence<TTuple>::convertible
+ , &from_py_sequence<TTuple>::construct
+ , type_id<TTuple>() );
+};
+
+} } //boost::python
+
+#endif//TUPLES_HPP_16_JAN_2007
diff --git a/qif.h b/qif.h
deleted file mode 100644
index d8c52576..00000000
--- a/qif.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef _QIF_H
-#define _QIF_H
-
-#include "parser.h"
-
-namespace ledger {
-
-class qif_parser_t : public parser_t
-{
- public:
- virtual bool test(std::istream& in) const;
-
- virtual unsigned int parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master = NULL,
- const std::string * original_file = NULL);
-};
-
-} // namespace ledger
-
-#endif // _QIF_H
diff --git a/quotes.cc b/quotes.cc
deleted file mode 100644
index a8fbfbc5..00000000
--- a/quotes.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-#include "quotes.h"
-#include "datetime.h"
-#include "error.h"
-#include "debug.h"
-
-#include <fstream>
-#include <cstdlib>
-#include <cstdio>
-
-namespace ledger {
-
-void quotes_by_script::operator()(commodity_base_t& commodity,
- const datetime_t& moment,
- const datetime_t& date,
- const datetime_t& last,
- amount_t& price)
-{
- DEBUG_CLASS("ledger.quotes.download");
-
- DEBUG_PRINT_("commodity: " << commodity.symbol);
- DEBUG_PRINT_TIME_(datetime_t::now);
- DEBUG_PRINT_TIME_(moment);
- DEBUG_PRINT_TIME_(date);
- DEBUG_PRINT_TIME_(last);
- if (commodity.history)
- DEBUG_PRINT_TIME_(commodity.history->last_lookup);
- DEBUG_PRINT_("pricing_leeway is " << pricing_leeway);
-
- if ((commodity.history &&
- (datetime_t::now - commodity.history->last_lookup) < pricing_leeway) ||
- (datetime_t::now - last) < pricing_leeway ||
- (price && moment > date && (moment - date) <= pricing_leeway))
- return;
-
- using namespace std;
-
- DEBUG_PRINT_("downloading quote for symbol " << commodity.symbol);
-
- char buf[256];
- buf[0] = '\0';
-
- bool success = true;
-
- if (FILE * fp = popen((string("getquote \"") +
- commodity.symbol + "\"").c_str(), "r")) {
- if (feof(fp) || ! fgets(buf, 255, fp))
- success = false;
- if (pclose(fp) != 0)
- success = false;
- } else {
- success = false;
- }
-
- if (success && buf[0]) {
- char * p = strchr(buf, '\n');
- if (p) *p = '\0';
-
- DEBUG_PRINT_("downloaded quote: " << buf);
-
- price.parse(buf);
- commodity.add_price(datetime_t::now, price);
-
- commodity.history->last_lookup = datetime_t::now;
- cache_dirty = true;
-
- if (price && ! price_db.empty()) {
-#if defined(__GNUG__) && __GNUG__ < 3
- ofstream database(price_db.c_str(), ios::out | ios::app);
-#else
- ofstream database(price_db.c_str(), ios_base::out | ios_base::app);
-#endif
- database << "P " << datetime_t::now.to_string("%Y/%m/%d %H:%M:%S")
- << " " << commodity.symbol << " " << price << endl;
- }
- } else {
- throw new error(std::string("Failed to download price for '") +
- commodity.symbol + "' (command: \"getquote " +
- commodity.symbol + "\")");
- }
-}
-
-} // namespace ledger
diff --git a/quotes.h b/quotes.h
deleted file mode 100644
index 12164b14..00000000
--- a/quotes.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef _QUOTES_H
-#define _QUOTES_H
-
-#include "amount.h"
-
-namespace ledger {
-
-class quotes_by_script : public commodity_base_t::updater_t
-{
- std::string price_db;
- unsigned long pricing_leeway;
- bool& cache_dirty;
-
- public:
- quotes_by_script(std::string _price_db,
- unsigned long _pricing_leeway,
- bool& _cache_dirty)
- : price_db(_price_db), pricing_leeway(_pricing_leeway),
- cache_dirty(_cache_dirty) {}
-
- virtual void operator()(commodity_base_t& commodity,
- const datetime_t& moment,
- const datetime_t& date,
- const datetime_t& last,
- amount_t& price);
-};
-
-} // namespace ledger
-
-#endif // _QUOTES_H
diff --git a/reconcile.cc b/reconcile.cc
deleted file mode 100644
index 5b6dba24..00000000
--- a/reconcile.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-#include "reconcile.h"
-#include "walk.h"
-
-namespace ledger {
-
-#define xact_next(x) ((transaction_t *)transaction_xdata(*x).ptr)
-#define xact_next_ptr(x) ((transaction_t **)&transaction_xdata(*x).ptr)
-
-static bool search_for_balance(amount_t& amount,
- transaction_t ** prev, transaction_t * next)
-{
- for (; next; next = xact_next(next)) {
- transaction_t * temp = *prev;
- *prev = next;
-
- amount -= next->amount;
- if (! amount ||
- search_for_balance(amount, xact_next_ptr(next), xact_next(next)))
- return true;
- amount += next->amount;
-
- *prev = temp;
- }
- return false;
-}
-
-void reconcile_transactions::push_to_handler(transaction_t * first)
-{
- for (; first; first = xact_next(first))
- item_handler<transaction_t>::operator()(*first);
-
- item_handler<transaction_t>::flush();
-}
-
-void reconcile_transactions::flush()
-{
- value_t cleared_balance;
- value_t pending_balance;
-
- transaction_t * first = NULL;
- transaction_t ** last_ptr = &first;
-
- bool found_pending = false;
- for (transactions_list::iterator x = xacts.begin();
- x != xacts.end();
- x++) {
- if (! cutoff || (*x)->date() < cutoff) {
- switch ((*x)->state) {
- case transaction_t::CLEARED:
- cleared_balance += (*x)->amount;
- break;
- case transaction_t::UNCLEARED:
- case transaction_t::PENDING:
- pending_balance += (*x)->amount;
- *last_ptr = *x;
- last_ptr = xact_next_ptr(*x);
- break;
- }
- }
- }
-
- if (cleared_balance.type >= value_t::BALANCE)
- throw new error("Cannot reconcile accounts with multiple commodities");
-
- cleared_balance.cast(value_t::AMOUNT);
- balance.cast(value_t::AMOUNT);
-
- commodity_t& cb_comm = ((amount_t *) cleared_balance.data)->commodity();
- commodity_t& b_comm = ((amount_t *) balance.data)->commodity();
-
- balance -= cleared_balance;
- if (balance.type >= value_t::BALANCE)
- throw new error(std::string("Reconcile balance is not of the same commodity ('") +
- b_comm.symbol() + "' != '" + cb_comm.symbol() + "')");
-
- // If the amount to reconcile is the same as the pending balance,
- // then assume an exact match and return the results right away.
- amount_t to_reconcile = *((amount_t *) balance.data);
- pending_balance.cast(value_t::AMOUNT);
- if (to_reconcile == *((amount_t *) pending_balance.data) ||
- search_for_balance(to_reconcile, &first, first)) {
- push_to_handler(first);
- } else {
- throw new error("Could not reconcile account!");
- }
-}
-
-} // namespace ledger
diff --git a/reconcile.h b/reconcile.h
deleted file mode 100644
index 7fd0d581..00000000
--- a/reconcile.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#ifndef _RECONCILE_H
-#define _RECONCILE_H
-
-#include "value.h"
-#include "walk.h"
-
-namespace ledger {
-
-class reconcile_transactions : public item_handler<transaction_t>
-{
- value_t balance;
- datetime_t cutoff;
-
- transactions_list xacts;
-
- public:
- reconcile_transactions(item_handler<transaction_t> * handler,
- const value_t& _balance,
- const datetime_t& _cutoff)
- : item_handler<transaction_t>(handler),
- balance(_balance), cutoff(_cutoff) {}
-
- void push_to_handler(transaction_t * first);
-
- virtual void flush();
- virtual void operator()(transaction_t& xact) {
- xacts.push_back(&xact);
- }
-};
-
-} // namespace ledger
-
-#endif // _RECONCILE_H
diff --git a/report.cc b/report.cc
deleted file mode 100644
index 90259440..00000000
--- a/report.cc
+++ /dev/null
@@ -1,413 +0,0 @@
-#include "report.h"
-
-namespace ledger {
-
-report_t::report_t()
-{
- ledger::amount_expr = "@a";
- ledger::total_expr = "@O";
-
- predicate = "";
- secondary_predicate = "";
- display_predicate = "";
- descend_expr = "";
-
- budget_flags = BUDGET_NO_BUDGET;
-
- head_entries = 0;
- tail_entries = 0;
-
- show_collapsed = false;
- show_subtotal = false;
- show_totals = false;
- show_related = false;
- show_all_related = false;
- show_inverted = false;
- show_empty = false;
- days_of_the_week = false;
- by_payee = false;
- comm_as_payee = false;
- code_as_payee = false;
- show_revalued = false;
- show_revalued_only = false;
- keep_price = false;
- keep_date = false;
- keep_tag = false;
- entry_sort = false;
- sort_all = false;
-}
-
-void
-report_t::regexps_to_predicate(const std::string& command,
- std::list<std::string>::const_iterator begin,
- std::list<std::string>::const_iterator end,
- const bool account_regexp,
- const bool add_account_short_masks,
- const bool logical_and)
-{
- std::string regexps[2];
-
- assert(begin != end);
-
- // Treat the remaining command-line arguments as regular
- // expressions, used for refining report results.
-
- for (std::list<std::string>::const_iterator i = begin;
- i != end;
- i++)
- if ((*i)[0] == '-') {
- if (! regexps[1].empty())
- regexps[1] += "|";
- regexps[1] += (*i).substr(1);
- }
- else if ((*i)[0] == '+') {
- if (! regexps[0].empty())
- regexps[0] += "|";
- regexps[0] += (*i).substr(1);
- }
- else {
- if (! regexps[0].empty())
- regexps[0] += "|";
- regexps[0] += *i;
- }
-
- for (int i = 0; i < 2; i++) {
- if (regexps[i].empty())
- continue;
-
- if (! predicate.empty())
- predicate += logical_and ? "&" : "|";
-
- int add_predicate = 0; // 1 adds /.../, 2 adds ///.../
- if (i == 1) {
- predicate += "!";
- }
- else if (add_account_short_masks) {
- if (regexps[i].find(':') != std::string::npos ||
- regexps[i].find('.') != std::string::npos ||
- regexps[i].find('*') != std::string::npos ||
- regexps[i].find('+') != std::string::npos ||
- regexps[i].find('[') != std::string::npos ||
- regexps[i].find('(') != std::string::npos) {
- show_subtotal = true;
- add_predicate = 1;
- } else {
- add_predicate = 2;
- }
- }
- else {
- add_predicate = 1;
- }
-
- if (i != 1 && command == "b" && account_regexp) {
- if (! show_related && ! show_all_related) {
- if (! display_predicate.empty())
- display_predicate += "&";
- if (! show_empty)
- display_predicate += "T&";
-
- if (add_predicate == 2)
- display_predicate += "//";
- display_predicate += "/(?:";
- display_predicate += regexps[i];
- display_predicate += ")/";
- }
- else if (! show_empty) {
- if (! display_predicate.empty())
- display_predicate += "&";
- display_predicate += "T";
- }
- }
-
- if (! account_regexp)
- predicate += "/";
- predicate += "/(?:";
- predicate += regexps[i];
- predicate += ")/";
- }
-}
-
-void report_t::process_options(const std::string& command,
- strings_list::iterator arg,
- strings_list::iterator args_end)
-{
- // Configure some other options depending on report type
-
- if (command == "p" || command == "e" || command == "w") {
- show_related =
- show_all_related = true;
- }
- else if (command == "E") {
- show_subtotal = true;
- }
- else if (show_related) {
- if (command == "r") {
- show_inverted = true;
- } else {
- show_subtotal = true;
- show_all_related = true;
- }
- }
-
- if (command != "b" && command != "r")
- amount_t::keep_base = true;
-
- // Process remaining command-line arguments
-
- if (command != "e") {
- // Treat the remaining command-line arguments as regular
- // expressions, used for refining report results.
-
- std::list<std::string>::iterator i = arg;
- for (; i != args_end; i++)
- if (*i == "--")
- break;
-
- if (i != arg)
- regexps_to_predicate(command, arg, i, true,
- (command == "b" && ! show_subtotal &&
- display_predicate.empty()));
- if (i != args_end && ++i != args_end)
- regexps_to_predicate(command, i, args_end);
- }
-
- // Setup the default value for the display predicate
-
- if (display_predicate.empty()) {
- if (command == "b") {
- if (! show_empty)
- display_predicate = "T";
- if (! show_subtotal) {
- if (! display_predicate.empty())
- display_predicate += "&";
- display_predicate += "l<=1";
- }
- }
- else if (command == "E") {
- display_predicate = "t";
- }
- else if (command == "r" && ! show_empty) {
- display_predicate = "a";
- }
- }
-
- DEBUG_PRINT("ledger.config.predicates", "Predicate: " << predicate);
- DEBUG_PRINT("ledger.config.predicates", "Display P: " << display_predicate);
-
- // Setup the values of %t and %T, used in format strings
-
- if (! amount_expr.empty())
- ledger::amount_expr = amount_expr;
- if (! total_expr.empty())
- ledger::total_expr = total_expr;
-
- // Now setup the various formatting strings
-
- if (! date_output_format.empty())
- date_t::output_format = date_output_format;
-
- amount_t::keep_price = keep_price;
- amount_t::keep_date = keep_date;
- amount_t::keep_tag = keep_tag;
-
- if (! report_period.empty() && ! sort_all)
- entry_sort = true;
-}
-
-item_handler<transaction_t> *
-report_t::chain_xact_handlers(const std::string& command,
- item_handler<transaction_t> * base_formatter,
- journal_t * journal,
- account_t * master,
- std::list<item_handler<transaction_t> *>& ptrs)
-{
- bool remember_components = false;
-
- item_handler<transaction_t> * formatter = NULL;
-
- ptrs.push_back(formatter = base_formatter);
-
- // format_transactions write each transaction received to the
- // output stream.
- if (! (command == "b" || command == "E")) {
- // truncate_entries cuts off a certain number of _entries_ from
- // being displayed. It does not affect calculation.
- if (head_entries || tail_entries)
- ptrs.push_back(formatter =
- new truncate_entries(formatter,
- head_entries, tail_entries));
-
- // filter_transactions will only pass through transactions
- // matching the `display_predicate'.
- if (! display_predicate.empty())
- ptrs.push_back(formatter =
- new filter_transactions(formatter,
- display_predicate));
-
- // calc_transactions computes the running total. When this
- // appears will determine, for example, whether filtered
- // transactions are included or excluded from the running total.
- ptrs.push_back(formatter = new calc_transactions(formatter));
-
- // component_transactions looks for reported transaction that
- // match the given `descend_expr', and then reports the
- // transactions which made up the total for that reported
- // transaction.
- if (! descend_expr.empty()) {
- std::list<std::string> descend_exprs;
-
- std::string::size_type beg = 0;
- for (std::string::size_type pos = descend_expr.find(';');
- pos != std::string::npos;
- beg = pos + 1, pos = descend_expr.find(';', beg))
- descend_exprs.push_back(std::string(descend_expr, beg, pos - beg));
- descend_exprs.push_back(std::string(descend_expr, beg));
-
- for (std::list<std::string>::reverse_iterator i =
- descend_exprs.rbegin();
- i != descend_exprs.rend();
- i++)
- ptrs.push_back(formatter =
- new component_transactions(formatter, *i));
-
- remember_components = true;
- }
-
- // reconcile_transactions will pass through only those
- // transactions which can be reconciled to a given balance
- // (calculated against the transactions which it receives).
- if (! reconcile_balance.empty()) {
- datetime_t cutoff = datetime_t::now;
- if (! reconcile_date.empty())
- cutoff = reconcile_date;
- ptrs.push_back(formatter =
- new reconcile_transactions
- (formatter, value_t(reconcile_balance), cutoff));
- }
-
- // filter_transactions will only pass through transactions
- // matching the `secondary_predicate'.
- if (! secondary_predicate.empty())
- ptrs.push_back(formatter =
- new filter_transactions(formatter,
- secondary_predicate));
-
- // sort_transactions will sort all the transactions it sees, based
- // on the `sort_order' value expression.
- if (! sort_string.empty()) {
- if (entry_sort)
- ptrs.push_back(formatter =
- new sort_entries(formatter, sort_string));
- else
- ptrs.push_back(formatter =
- new sort_transactions(formatter, sort_string));
- }
-
- // changed_value_transactions adds virtual transactions to the
- // list to account for changes in market value of commodities,
- // which otherwise would affect the running total unpredictably.
- if (show_revalued)
- ptrs.push_back(formatter =
- new changed_value_transactions(formatter,
- show_revalued_only));
-
- // collapse_transactions causes entries with multiple transactions
- // to appear as entries with a subtotaled transaction for each
- // commodity used.
- if (show_collapsed)
- ptrs.push_back(formatter = new collapse_transactions(formatter));
-
- // subtotal_transactions combines all the transactions it receives
- // into one subtotal entry, which has one transaction for each
- // commodity in each account.
- //
- // period_transactions is like subtotal_transactions, but it
- // subtotals according to time periods rather than totalling
- // everything.
- //
- // dow_transactions is like period_transactions, except that it
- // reports all the transactions that fall on each subsequent day
- // of the week.
- if (show_subtotal)
- ptrs.push_back(formatter =
- new subtotal_transactions(formatter, remember_components));
-
- if (days_of_the_week)
- ptrs.push_back(formatter =
- new dow_transactions(formatter, remember_components));
- else if (by_payee)
- ptrs.push_back(formatter =
- new by_payee_transactions(formatter, remember_components));
-
- // interval_transactions groups transactions together based on a
- // time period, such as weekly or monthly.
- if (! report_period.empty()) {
- ptrs.push_back(formatter =
- new interval_transactions(formatter, report_period,
- remember_components));
- ptrs.push_back(formatter = new sort_transactions(formatter, "d"));
- }
- }
-
- // invert_transactions inverts the value of the transactions it
- // receives.
- if (show_inverted)
- ptrs.push_back(formatter = new invert_transactions(formatter));
-
- // related_transactions will pass along all transactions related
- // to the transaction received. If `show_all_related' is true,
- // then all the entry's transactions are passed; meaning that if
- // one transaction of an entry is to be printed, all the
- // transaction for that entry will be printed.
- if (show_related)
- ptrs.push_back(formatter =
- new related_transactions(formatter,
- show_all_related));
-
- // This filter_transactions will only pass through transactions
- // matching the `predicate'.
- if (! predicate.empty())
- ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
-
- // budget_transactions takes a set of transactions from a data
- // file and uses them to generate "budget transactions" which
- // balance against the reported transactions.
- //
- // forecast_transactions is a lot like budget_transactions, except
- // that it adds entries only for the future, and does not balance
- // them against anything but the future balance.
-
- if (budget_flags) {
- budget_transactions * handler
- = new budget_transactions(formatter, budget_flags);
- handler->add_period_entries(journal->period_entries);
- ptrs.push_back(formatter = handler);
-
- // Apply this before the budget handler, so that only matching
- // transactions are calculated toward the budget. The use of
- // filter_transactions above will further clean the results so
- // that no automated transactions that don't match the filter get
- // reported.
- if (! predicate.empty())
- ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
- }
- else if (! forecast_limit.empty()) {
- forecast_transactions * handler
- = new forecast_transactions(formatter, forecast_limit);
- handler->add_period_entries(journal->period_entries);
- ptrs.push_back(formatter = handler);
-
- // See above, under budget_transactions.
- if (! predicate.empty())
- ptrs.push_back(formatter = new filter_transactions(formatter, predicate));
- }
-
- if (comm_as_payee)
- ptrs.push_back(formatter = new set_comm_as_payee(formatter));
- else if (code_as_payee)
- ptrs.push_back(formatter = new set_code_as_payee(formatter));
-
- return formatter;
-}
-
-} // namespace ledger
diff --git a/report.h b/report.h
deleted file mode 100644
index 377b9c57..00000000
--- a/report.h
+++ /dev/null
@@ -1,79 +0,0 @@
-#ifndef _REPORT_H
-#define _REPORT_H
-
-#include "ledger.h"
-#include "timing.h"
-
-#include <iostream>
-#include <memory>
-#include <list>
-
-namespace ledger {
-
-class report_t
-{
- public:
- std::string output_file;
- std::string predicate;
- std::string secondary_predicate;
- std::string display_predicate;
- std::string report_period;
- std::string report_period_sort;
- std::string format_string;
- std::string sort_string;
- std::string amount_expr;
- std::string total_expr;
- std::string descend_expr;
- std::string forecast_limit;
- std::string reconcile_balance;
- std::string reconcile_date;
- std::string date_output_format;
-
- unsigned long budget_flags;
-
- int head_entries;
- int tail_entries;
-
- bool show_collapsed;
- bool show_subtotal;
- bool show_totals;
- bool show_related;
- bool show_all_related;
- bool show_inverted;
- bool show_empty;
- bool days_of_the_week;
- bool by_payee;
- bool comm_as_payee;
- bool code_as_payee;
- bool show_revalued;
- bool show_revalued_only;
- bool keep_price;
- bool keep_date;
- bool keep_tag;
- bool entry_sort;
- bool sort_all;
-
- report_t();
-
- void regexps_to_predicate(const std::string& command,
- std::list<std::string>::const_iterator begin,
- std::list<std::string>::const_iterator end,
- const bool account_regexp = false,
- const bool add_account_short_masks = false,
- const bool logical_and = true);
-
- void process_options(const std::string& command,
- strings_list::iterator arg,
- strings_list::iterator args_end);
-
- item_handler<transaction_t> *
- chain_xact_handlers(const std::string& command,
- item_handler<transaction_t> * base_formatter,
- journal_t * journal,
- account_t * master,
- std::list<item_handler<transaction_t> *>& ptrs);
-};
-
-} // namespace ledger
-
-#endif // _REPORT_H
diff --git a/setup.py b/setup.py
deleted file mode 100755
index 0c78cb19..00000000
--- a/setup.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env python
-
-from distutils.core import setup, Extension
-
-import os
-
-libs = ["amounts", "boost_python", "gmp"]
-
-setup(name = "Amounts",
- version = "2.6.1",
- description = "Amounts and Commodities Library",
- author = "John Wiegley",
- author_email = "johnw@newartisans.com",
- url = "http://www.newartisans.com/johnw/",
- ext_modules = [
- Extension("amounts", ["amounts.cc"],
- define_macros = [('PYTHON_MODULE', 1)],
- libraries = libs)])
diff --git a/src/account.cc b/src/account.cc
new file mode 100644
index 00000000..d3b19ad0
--- /dev/null
+++ b/src/account.cc
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "account.h"
+
+namespace ledger {
+
+account_t::~account_t()
+{
+ TRACE_DTOR(account_t);
+
+ foreach (accounts_map::value_type& pair, accounts)
+ checked_delete(pair.second);
+}
+
+account_t * account_t::find_account(const string& name,
+ const bool auto_create)
+{
+ accounts_map::const_iterator i = accounts.find(name);
+ if (i != accounts.end())
+ return (*i).second;
+
+ char buf[256];
+
+ string::size_type sep = name.find(':');
+ assert(sep < 256|| sep == string::npos);
+
+ const char * first, * rest;
+ if (sep == string::npos) {
+ first = name.c_str();
+ rest = NULL;
+ } else {
+ std::strncpy(buf, name.c_str(), sep);
+ buf[sep] = '\0';
+
+ first = buf;
+ rest = name.c_str() + sep + 1;
+ }
+
+ account_t * account;
+
+ i = accounts.find(first);
+ if (i == accounts.end()) {
+ if (! auto_create)
+ return NULL;
+
+ account = new account_t(this, first);
+ std::pair<accounts_map::iterator, bool> result
+ = accounts.insert(accounts_map::value_type(first, account));
+ assert(result.second);
+ } else {
+ account = (*i).second;
+ }
+
+ if (rest)
+ account = account->find_account(rest, auto_create);
+
+ return account;
+}
+
+string account_t::fullname() const
+{
+ if (! _fullname.empty()) {
+ return _fullname;
+ } else {
+ const account_t * first = this;
+ string fullname = name;
+
+ while (first->parent) {
+ first = first->parent;
+ if (! first->name.empty())
+ fullname = first->name + ":" + fullname;
+ }
+
+ _fullname = fullname;
+
+ return fullname;
+ }
+}
+
+std::ostream& operator<<(std::ostream& out, const account_t& account)
+{
+ out << account.fullname();
+ return out;
+}
+
+namespace {
+ value_t get_total(account_t& account) {
+ assert(account.xdata_);
+ return account.xdata_->total;
+ }
+
+ template <value_t (*Func)(account_t&)>
+ value_t get_wrapper(call_scope_t& scope) {
+ return (*Func)(find_scope<account_t>(scope));
+ }
+}
+
+expr_t::ptr_op_t account_t::lookup(const string& name)
+{
+ switch (name[0]) {
+ case 'f':
+ if (name.find("fmt_") == 0) {
+ switch (name[4]) {
+ case 'T':
+ return WRAP_FUNCTOR(get_wrapper<&get_total>);
+ }
+ }
+ break;
+
+ case 't':
+ if (name == "total")
+ return WRAP_FUNCTOR(get_wrapper<&get_total>);
+ break;
+ }
+ return expr_t::ptr_op_t();
+}
+
+bool account_t::valid() const
+{
+ if (depth > 256) {
+ DEBUG("ledger.validate", "account_t: depth > 256");
+ return false;
+ }
+
+ foreach (const accounts_map::value_type& pair, accounts) {
+ if (this == pair.second) {
+ DEBUG("ledger.validate", "account_t: parent refers to itself!");
+ return false;
+ }
+
+ if (! pair.second->valid()) {
+ DEBUG("ledger.validate", "account_t: child not valid");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void account_t::calculate_sums()
+{
+ xdata_t& xd(xdata());
+
+ foreach (accounts_map::value_type& pair, accounts) {
+ (*pair.second).calculate_sums();
+
+ if (xd.total.is_null())
+ xd.total = (*pair.second).xdata().total;
+ else
+ xd.total += (*pair.second).xdata().total;
+
+ xd.total_count += ((*pair.second).xdata().total_count +
+ (*pair.second).xdata().count);
+ }
+
+ value_t result;
+#if 0
+ compute_amount(result, details_t(account));
+#endif
+
+ if (xd.total.is_null())
+ xd.total = result;
+ else
+ xd.total += result;
+
+ xd.total_count += xd.count;
+}
+
+} // namespace ledger
diff --git a/src/account.h b/src/account.h
new file mode 100644
index 00000000..7d35593c
--- /dev/null
+++ b/src/account.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ACCOUNT_H
+#define _ACCOUNT_H
+
+#include "utils.h"
+#include "scope.h"
+
+namespace ledger {
+
+class account_t;
+
+typedef std::map<const string, account_t *> accounts_map;
+
+class account_t : public scope_t
+{
+ public:
+ typedef unsigned long ident_t;
+
+ account_t * parent;
+ string name;
+ optional<string> note;
+ unsigned short depth;
+ accounts_map accounts;
+
+ mutable void * data;
+ mutable ident_t ident;
+ mutable string _fullname;
+
+ account_t(account_t * _parent = NULL,
+ const string& _name = "",
+ const optional<string>& _note = none)
+ : scope_t(), parent(_parent), name(_name), note(_note),
+ depth(parent ? parent->depth + 1 : 0), data(NULL), ident(0) {
+ TRACE_CTOR(account_t, "account_t *, const string&, const string&");
+ }
+ account_t(const account_t& other)
+ : scope_t(),
+ parent(other.parent),
+ name(other.name),
+ note(other.note),
+ depth(other.depth),
+ accounts(other.accounts),
+ data(NULL),
+ ident(0) {
+ TRACE_CTOR(account_t, "copy");
+ assert(other.data == NULL);
+ assert(other.ident == 0);
+ }
+ ~account_t();
+
+ operator string() const {
+ return fullname();
+ }
+ string fullname() const;
+
+ void add_account(account_t * acct) {
+ accounts.insert(accounts_map::value_type(acct->name, acct));
+ }
+ bool remove_account(account_t * acct) {
+ accounts_map::size_type n = accounts.erase(acct->name);
+ return n > 0;
+ }
+
+ account_t * find_account(const string& name, bool auto_create = true);
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+
+ bool valid() const;
+
+ friend class journal_t;
+
+ struct xdata_t : public supports_flags<>
+ {
+#define ACCOUNT_EXT_TO_DISPLAY 0x01
+#define ACCOUNT_EXT_DISPLAYED 0x02
+#define ACCOUNT_EXT_SORT_CALC 0x04
+#define ACCOUNT_EXT_HAS_NON_VIRTUALS 0x08
+#define ACCOUNT_EXT_HAS_UNB_VIRTUALS 0x10
+
+ value_t value;
+ value_t total;
+ value_t sort_value;
+ unsigned int count; // xacts counted toward amount
+ unsigned int total_count; // xacts counted toward total
+ unsigned int virtuals;
+ unsigned short dflags;
+
+ xdata_t()
+ : supports_flags<>(), count(0), total_count(0),
+ virtuals(0), dflags(0)
+ {
+ TRACE_CTOR(xdata_t, "");
+ }
+
+ ~xdata_t() throw() {
+ TRACE_DTOR(xdata_t);
+ }
+ };
+
+ // This variable holds optional "extended data" which is usually produced
+ // only during reporting, and only for the transaction set being reported.
+ // It's a memory-saving measure to delay allocation until the last possible
+ // moment.
+ mutable optional<xdata_t> xdata_;
+
+ bool has_xdata() const {
+ return xdata_;
+ }
+ void clear_xdata() {
+ xdata_ = none;
+ }
+ xdata_t& xdata() {
+ if (! xdata_)
+ xdata_ = xdata_t();
+ return *xdata_;
+ }
+
+ void calculate_sums();
+};
+
+std::ostream& operator<<(std::ostream& out, const account_t& account);
+
+} // namespace ledger
+
+#endif // _ACCOUNT_H
diff --git a/src/amount.cc b/src/amount.cc
new file mode 100644
index 00000000..502f1916
--- /dev/null
+++ b/src/amount.cc
@@ -0,0 +1,1433 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file amount.cc
+ * @author John Wiegley
+ * @date Thu Apr 26 15:19:46 2007
+ *
+ * @brief Types for handling commoditized math.
+ *
+ * This file defines member functions for amount_t, and also defines a
+ * helper class, bigint_t, which is used as a refcounted wrapper
+ * around libgmp's mpz_t type.
+ */
+
+#include "amount.h"
+#include "binary.h"
+
+namespace ledger {
+
+commodity_pool_t * amount_t::current_pool = NULL;
+
+bool amount_t::keep_base = false;
+
+bool amount_t::keep_price = false;
+bool amount_t::keep_date = false;
+bool amount_t::keep_tag = false;
+
+bool amount_t::stream_fullstrings = false;
+
+#ifndef THREADSAFE
+/**
+ * These global temporaries are pre-initialized for the sake of
+ * efficiency, and reused over and over again.
+ */
+static mpz_t temp;
+static mpz_t divisor;
+#endif
+
+struct amount_t::bigint_t : public supports_flags<>
+{
+#define BIGINT_BULK_ALLOC 0x01
+#define BIGINT_KEEP_PREC 0x02
+
+ mpz_t val;
+ precision_t prec;
+ uint_least16_t ref;
+ uint_fast32_t index;
+
+#define MPZ(bigint) ((bigint)->val)
+
+ bigint_t() : prec(0), ref(1), index(0) {
+ TRACE_CTOR(bigint_t, "");
+ mpz_init(val);
+ }
+ bigint_t(mpz_t _val) : prec(0), ref(1), index(0) {
+ TRACE_CTOR(bigint_t, "mpz_t");
+ mpz_init_set(val, _val);
+ }
+ bigint_t(const bigint_t& other)
+ : supports_flags<>(other.flags() & ~BIGINT_BULK_ALLOC),
+ prec(other.prec), ref(1), index(0) {
+ TRACE_CTOR(bigint_t, "copy");
+ mpz_init_set(val, other.val);
+ }
+ ~bigint_t() {
+ TRACE_DTOR(bigint_t);
+ assert(ref == 0);
+ mpz_clear(val);
+ }
+
+ bool valid() const {
+ if (prec > 128) {
+ DEBUG("ledger.validate", "amount_t::bigint_t: prec > 128");
+ return false;
+ }
+ if (ref > 16535) {
+ DEBUG("ledger.validate", "amount_t::bigint_t: ref > 16535");
+ return false;
+ }
+#if 0
+ // jww (2008-07-24): How does one check the validity of an mpz_t?
+ if (val[0]._mp_size < 0 || val[0]._mp_size > 100) {
+ DEBUG("ledger.validate", "amount_t::bigint_t: val._mp_size is bad");
+ return false;
+ }
+#endif
+ return true;
+ }
+};
+
+uint_fast32_t amount_t::sizeof_bigint_t()
+{
+ return sizeof(bigint_t);
+}
+
+void amount_t::initialize()
+{
+ mpz_init(temp);
+ mpz_init(divisor);
+
+ // jww (2007-05-02): Be very careful here!
+ if (! current_pool)
+ current_pool = new commodity_pool_t;
+
+ // Add time commodity conversions, so that timelog's may be parsed
+ // in terms of seconds, but reported as minutes or hours.
+ if (commodity_t * commodity = current_pool->create("s")) {
+ commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN);
+
+ parse_conversion("1.0m", "60s");
+ parse_conversion("1.0h", "60m");
+ } else {
+ assert(false);
+ }
+}
+
+void amount_t::shutdown()
+{
+ mpz_clear(temp);
+ mpz_clear(divisor);
+
+ // jww (2007-05-02): Be very careful here!
+ if (current_pool) {
+ checked_delete(current_pool);
+ current_pool = NULL;
+ }
+}
+
+void amount_t::_copy(const amount_t& amt)
+{
+ assert(amt.valid());
+
+ if (quantity != amt.quantity) {
+ if (quantity)
+ _release();
+
+ // Never maintain a pointer into a bulk allocation pool; such
+ // pointers are not guaranteed to remain.
+ if (amt.quantity->has_flags(BIGINT_BULK_ALLOC)) {
+ quantity = new bigint_t(*amt.quantity);
+ } else {
+ quantity = amt.quantity;
+ DEBUG("amounts.refs",
+ quantity << " ref++, now " << (quantity->ref + 1));
+ quantity->ref++;
+ }
+ }
+ commodity_ = amt.commodity_;
+
+ assert(valid());
+}
+
+void amount_t::_dup()
+{
+ assert(valid());
+
+ if (quantity->ref > 1) {
+ bigint_t * q = new bigint_t(*quantity);
+ _release();
+ quantity = q;
+ }
+
+ assert(valid());
+}
+
+void amount_t::_resize(precision_t prec)
+{
+ assert(prec < 256);
+
+ if (! quantity || prec == quantity->prec)
+ return;
+
+ _dup();
+
+ assert(prec > quantity->prec);
+ mpz_ui_pow_ui(divisor, 10, prec - quantity->prec);
+ mpz_mul(MPZ(quantity), MPZ(quantity), divisor);
+
+ quantity->prec = prec;
+
+ assert(valid());
+}
+
+void amount_t::_clear()
+{
+ if (quantity) {
+ _release();
+ quantity = NULL;
+ commodity_ = NULL;
+ } else {
+ assert(! commodity_);
+ }
+}
+
+void amount_t::_release()
+{
+ assert(valid());
+
+ DEBUG("amounts.refs", quantity << " ref--, now " << (quantity->ref - 1));
+
+ if (--quantity->ref == 0) {
+ if (quantity->has_flags(BIGINT_BULK_ALLOC))
+ quantity->~bigint_t();
+ else
+ checked_delete(quantity);
+ quantity = NULL;
+ commodity_ = NULL;
+ }
+
+ assert(valid());
+}
+
+
+#ifdef HAVE_GDTOA
+namespace {
+ amount_t::precision_t convert_double(mpz_t dest, double val)
+ {
+ int decpt, sign;
+ char * buf = dtoa(val, 0, 0, &decpt, &sign, NULL);
+ char * result;
+ int len = std::strlen(buf);
+
+ if (decpt <= len) {
+ decpt = len - decpt;
+ result = NULL;
+ } else {
+ // There were trailing zeros, which we have to put back on in
+ // order to convert this buffer into an integer.
+
+ int zeroes = decpt - len;
+ result = new char[len + zeroes + 1];
+
+ std::strcpy(result, buf);
+ int i;
+ for (i = 0; i < zeroes; i++)
+ result[len + i] = '0';
+ result[len + i] = '\0';
+
+ decpt = (len - decpt) + zeroes;
+ }
+
+ if (sign) {
+ char * newbuf = new char[std::strlen(result ? result : buf) + 2];
+ newbuf[0] = '-';
+ std::strcpy(&newbuf[1], result ? result : buf);
+ mpz_set_str(dest, newbuf, 10);
+ checked_array_delete(newbuf);
+ } else {
+ mpz_set_str(dest, result ? result : buf, 10);
+ }
+
+ if (result)
+ checked_array_delete(result);
+ freedtoa(buf);
+
+ return decpt;
+ }
+}
+
+amount_t::amount_t(const double val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const double");
+ quantity = new bigint_t;
+ quantity->prec = convert_double(MPZ(quantity), val);
+}
+#endif
+
+amount_t::amount_t(const unsigned long val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const unsigned long");
+ quantity = new bigint_t;
+ mpz_set_ui(MPZ(quantity), val);
+}
+
+amount_t::amount_t(const long val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const long");
+ quantity = new bigint_t;
+ mpz_set_si(MPZ(quantity), val);
+}
+
+
+amount_t& amount_t::operator=(const amount_t& amt)
+{
+ if (this != &amt) {
+ if (amt.quantity)
+ _copy(amt);
+ else if (quantity)
+ _clear();
+ }
+ return *this;
+}
+
+
+int amount_t::compare(const amount_t& amt) const
+{
+ assert(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, "Cannot compare an amount to an uninitialized amount");
+ else if (amt.quantity)
+ throw_(amount_error, "Cannot compare an uninitialized amount to an amount");
+ else
+ throw_(amount_error, "Cannot compare two uninitialized amounts");
+ }
+
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ "Cannot compare amounts with different commodities: " <<
+ commodity().symbol() << " and " << amt.commodity().symbol());
+
+ if (quantity->prec == amt.quantity->prec) {
+ return mpz_cmp(MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ amount_t t(*this);
+ t._resize(amt.quantity->prec);
+ return mpz_cmp(MPZ(t.quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ return mpz_cmp(MPZ(quantity), MPZ(t.quantity));
+ }
+}
+
+
+amount_t& amount_t::operator+=(const amount_t& amt)
+{
+ assert(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, "Cannot add an amount to an uninitialized amount");
+ else if (amt.quantity)
+ throw_(amount_error, "Cannot add an uninitialized amount to an amount");
+ else
+ throw_(amount_error, "Cannot add two uninitialized amounts");
+ }
+
+ if (commodity() != amt.commodity())
+ throw_(amount_error,
+ "Adding amounts with different commodities: " <<
+ (has_commodity() ? commodity().symbol() : "NONE") <<
+ " != " <<
+ (amt.has_commodity() ? amt.commodity().symbol() : "NONE"));
+
+ _dup();
+
+ if (quantity->prec == amt.quantity->prec) {
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ _resize(amt.quantity->prec);
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(t.quantity));
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator-=(const amount_t& amt)
+{
+ assert(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, "Cannot subtract an amount from an uninitialized amount");
+ else if (amt.quantity)
+ throw_(amount_error, "Cannot subtract an uninitialized amount from an amount");
+ else
+ throw_(amount_error, "Cannot subtract two uninitialized amounts");
+ }
+
+ if (commodity() != amt.commodity())
+ throw_(amount_error,
+ "Subtracting amounts with different commodities: " <<
+ (has_commodity() ? commodity().symbol() : "NONE") <<
+ " != " <<
+ (amt.has_commodity() ? amt.commodity().symbol() : "NONE"));
+
+ _dup();
+
+ if (quantity->prec == amt.quantity->prec) {
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ _resize(amt.quantity->prec);
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(t.quantity));
+ }
+
+ return *this;
+}
+
+namespace {
+ void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec)
+ {
+ // Round `value', with an encoding precision of `value_prec', to a
+ // rounded value with precision `round_prec'. Result is stored in
+ // `out'.
+
+ assert(value_prec > round_prec);
+
+ mpz_t quotient;
+ mpz_t remainder;
+
+ mpz_init(quotient);
+ mpz_init(remainder);
+
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_tdiv_qr(quotient, remainder, value, divisor);
+ mpz_divexact_ui(divisor, divisor, 10);
+ mpz_mul_ui(divisor, divisor, 5);
+
+ if (mpz_sgn(remainder) < 0) {
+ mpz_neg(divisor, divisor);
+ if (mpz_cmp(remainder, divisor) < 0) {
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_add(remainder, divisor, remainder);
+ mpz_ui_sub(remainder, 0, remainder);
+ mpz_add(out, value, remainder);
+ } else {
+ mpz_sub(out, value, remainder);
+ }
+ } else {
+ if (mpz_cmp(remainder, divisor) >= 0) {
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_sub(remainder, divisor, remainder);
+ mpz_add(out, value, remainder);
+ } else {
+ mpz_sub(out, value, remainder);
+ }
+ }
+ mpz_clear(quotient);
+ mpz_clear(remainder);
+
+ // chop off the rounded bits
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_tdiv_q(out, out, divisor);
+ }
+}
+
+amount_t& amount_t::operator*=(const amount_t& amt)
+{
+ assert(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, "Cannot multiply an amount by an uninitialized amount");
+ else if (amt.quantity)
+ throw_(amount_error, "Cannot multiply an uninitialized amount by an amount");
+ else
+ throw_(amount_error, "Cannot multiply two uninitialized amounts");
+ }
+
+#if 0
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ "Multiplying amounts with different commodities: " <<
+ (has_commodity() ? commodity().symbol() : "NONE") <<
+ " != " <<
+ (amt.has_commodity() ? amt.commodity().symbol() : "NONE"));
+#endif
+
+ _dup();
+
+ mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ quantity->prec += amt.quantity->prec;
+
+ if (! has_commodity())
+ commodity_ = amt.commodity_;
+
+ if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) {
+ precision_t 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;
+ }
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator/=(const amount_t& amt)
+{
+ assert(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, "Cannot divide an amount by an uninitialized amount");
+ else if (amt.quantity)
+ throw_(amount_error, "Cannot divide an uninitialized amount by an amount");
+ else
+ throw_(amount_error, "Cannot divide two uninitialized amounts");
+ }
+
+#if 0
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ "Dividing amounts with different commodities: " <<
+ (has_commodity() ? commodity().symbol() : "NONE") <<
+ " != " <<
+ (amt.has_commodity() ? amt.commodity().symbol() : "NONE"));
+#endif
+
+ if (! amt)
+ throw_(amount_error, "Divide by zero");
+
+ _dup();
+
+ // Increase the value's precision, to capture fractional parts after
+ // the divide. Round up in the last position.
+
+ mpz_ui_pow_ui(divisor, 10, (2 * amt.quantity->prec) + quantity->prec + 7U);
+ mpz_mul(MPZ(quantity), MPZ(quantity), divisor);
+ mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ quantity->prec += amt.quantity->prec + quantity->prec + 7U;
+
+ mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, quantity->prec - 1);
+ quantity->prec -= 1;
+
+ if (! has_commodity())
+ commodity_ = amt.commodity_;
+
+ // If this amount has a commodity, and we're not dealing with plain
+ // numbers, or internal numbers (which keep full precision at all
+ // times), then round the number to within the commodity's precision
+ // plus six places.
+
+ if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) {
+ precision_t 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;
+ }
+ }
+
+ return *this;
+}
+
+
+amount_t::precision_t amount_t::precision() const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot determine precision of an uninitialized amount");
+
+ return quantity->prec;
+}
+
+amount_t& amount_t::in_place_negate()
+{
+ if (quantity) {
+ _dup();
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+ } else {
+ throw_(amount_error, "Cannot negate an uninitialized amount");
+ }
+ return *this;
+}
+
+amount_t amount_t::round() const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot round an uninitialized amount");
+
+ if (! has_commodity())
+ return *this;
+
+ return round(commodity().precision());
+}
+
+amount_t amount_t::round(precision_t prec) const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot round an uninitialized amount");
+
+ amount_t t(*this);
+
+ if (quantity->prec <= prec) {
+ if (quantity && quantity->has_flags(BIGINT_KEEP_PREC)) {
+ t._dup();
+ t.quantity->drop_flags(BIGINT_KEEP_PREC);
+ }
+ return t;
+ }
+
+ t._dup();
+
+ mpz_round(MPZ(t.quantity), MPZ(t.quantity), t.quantity->prec, prec);
+
+ t.quantity->prec = prec;
+ t.quantity->drop_flags(BIGINT_KEEP_PREC);
+
+ return t;
+}
+
+amount_t amount_t::unround() const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot unround an uninitialized amount");
+ else if (quantity->has_flags(BIGINT_KEEP_PREC))
+ return *this;
+
+ amount_t t(*this);
+ t._dup();
+ t.quantity->add_flags(BIGINT_KEEP_PREC);
+
+ return t;
+}
+
+amount_t& amount_t::in_place_reduce()
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot reduce an uninitialized amount");
+
+ while (commodity_ && commodity().smaller()) {
+ *this *= commodity().smaller()->number();
+ commodity_ = commodity().smaller()->commodity_;
+ }
+ return *this;
+}
+
+amount_t& amount_t::in_place_unreduce()
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot unreduce an uninitialized amount");
+
+ while (commodity_ && commodity().larger()) {
+ *this /= commodity().larger()->number();
+ commodity_ = commodity().larger()->commodity_;
+ if (abs() < amount_t(1L))
+ break;
+ }
+ return *this;
+}
+
+optional<amount_t> amount_t::value(const optional<datetime_t>& moment) const
+{
+ if (quantity) {
+ optional<amount_t> amt(commodity().value(moment));
+ if (amt)
+ return (*amt * number()).round();
+ } else {
+ throw_(amount_error, "Cannot determine value of an uninitialized amount");
+ }
+ return none;
+}
+
+
+int amount_t::sign() const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot determine sign of an uninitialized amount");
+
+ return mpz_sgn(MPZ(quantity));
+}
+
+bool amount_t::is_zero() const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot determine if an uninitialized amount is zero");
+
+ if (has_commodity()) {
+ if (quantity->prec <= commodity().precision() ||
+ quantity->has_flags(BIGINT_KEEP_PREC))
+ return is_realzero();
+ else
+ return round(commodity().precision()).sign() == 0;
+ }
+ return is_realzero();
+}
+
+
+#ifdef HAVE_GDTOA
+double amount_t::to_double(bool no_check) const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot convert an uninitialized amount to a double");
+
+ mpz_t remainder;
+ mpz_init(remainder);
+
+ mpz_set(temp, MPZ(quantity));
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_qr(temp, remainder, temp, divisor);
+
+ char * quotient_s = mpz_get_str(NULL, 10, temp);
+ char * remainder_s = mpz_get_str(NULL, 10, remainder);
+
+ std::ostringstream num;
+ num << quotient_s << '.' << remainder_s;
+
+ std::free(quotient_s);
+ std::free(remainder_s);
+
+ mpz_clear(remainder);
+
+ double value = lexical_cast<double>(num.str());
+
+ if (! no_check && *this != value)
+ throw_(amount_error, "Conversion of amount to_double loses precision");
+
+ return value;
+}
+#endif
+
+long amount_t::to_long(bool no_check) const
+{
+ if (! quantity)
+ throw_(amount_error, "Cannot convert an uninitialized amount to a long");
+
+ mpz_set(temp, MPZ(quantity));
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_q(temp, temp, divisor);
+
+ long value = mpz_get_si(temp);
+
+ if (! no_check && *this != value)
+ throw_(amount_error, "Conversion of amount to_long loses precision");
+
+ return value;
+}
+
+#ifdef HAVE_GDTOA
+bool amount_t::fits_in_double() const
+{
+ double value = to_double(true);
+ return *this == amount_t(value);
+}
+#endif
+
+bool amount_t::fits_in_long() const
+{
+ long value = to_long(true);
+ return *this == amount_t(value);
+}
+
+
+void amount_t::annotate(const annotation_t& details)
+{
+ commodity_t * this_base;
+ annotated_commodity_t * this_ann = NULL;
+
+ if (! quantity)
+ throw_(amount_error, "Cannot annotate the commodity of an uninitialized amount");
+ else if (! has_commodity())
+ throw_(amount_error, "Cannot annotate an amount with no commodity");
+
+ if (commodity().annotated) {
+ this_ann = &as_annotated_commodity(commodity());
+ this_base = &this_ann->referent();
+ } else {
+ this_base = &commodity();
+ }
+ assert(this_base);
+
+ DEBUG("amounts.commodities", "Annotating commodity for amount "
+ << *this << std::endl << details);
+
+ if (commodity_t * ann_comm =
+ this_base->parent().find_or_create(*this_base, details))
+ set_commodity(*ann_comm);
+#ifdef ASSERTS_ON
+ else
+ assert(false);
+#endif
+
+ DEBUG("amounts.commodities", " Annotated amount is " << *this);
+}
+
+bool amount_t::is_annotated() const
+{
+ if (! quantity)
+ throw_(amount_error,
+ "Cannot determine if an uninitialized amount's commodity is annotated");
+
+ assert(! commodity().annotated || as_annotated_commodity(commodity()).details);
+ return commodity().annotated;
+}
+
+annotation_t& amount_t::annotation()
+{
+ if (! quantity)
+ throw_(amount_error,
+ "Cannot return commodity annotation details of an uninitialized amount");
+
+ if (! commodity().is_annotated())
+ throw_(amount_error,
+ "Request for annotation details from an unannotated amount");
+
+ annotated_commodity_t& ann_comm(as_annotated_commodity(commodity()));
+ return ann_comm.details;
+}
+
+amount_t amount_t::strip_annotations(const bool _keep_price,
+ const bool _keep_date,
+ const bool _keep_tag) const
+{
+ if (! quantity)
+ throw_(amount_error,
+ "Cannot strip commodity annotations from an uninitialized amount");
+
+ if (! commodity().annotated ||
+ (_keep_price && _keep_date && _keep_tag))
+ return *this;
+
+ amount_t t(*this);
+ t.set_commodity(as_annotated_commodity(commodity()).
+ strip_annotations(_keep_price, _keep_date, _keep_tag));
+ return t;
+}
+
+
+namespace {
+ void parse_quantity(std::istream& in, string& value)
+ {
+ char buf[256];
+ char c = peek_next_nonws(in);
+ READ_INTO(in, buf, 255, c,
+ std::isdigit(c) || c == '-' || c == '.' || c == ',');
+
+ int len = std::strlen(buf);
+ while (len > 0 && ! std::isdigit(buf[len - 1])) {
+ buf[--len] = '\0';
+ in.unget();
+ }
+
+ value = buf;
+ }
+}
+
+bool amount_t::parse(std::istream& in, flags_t flags)
+{
+ // The possible syntax for an amount is:
+ //
+ // [-]NUM[ ]SYM [@ AMOUNT]
+ // SYM[ ][-]NUM [@ AMOUNT]
+
+ string symbol;
+ string quant;
+ annotation_t details;
+ bool negative = false;
+
+ commodity_t::flags_t comm_flags = COMMODITY_STYLE_DEFAULTS;
+
+ char c = peek_next_nonws(in);
+ if (c == '-') {
+ negative = true;
+ in.get(c);
+ c = peek_next_nonws(in);
+ }
+
+ char n;
+ if (std::isdigit(c)) {
+ parse_quantity(in, quant);
+
+ if (! in.eof() && ((n = in.peek()) != '\n')) {
+ if (std::isspace(n))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ commodity_t::parse_symbol(in, symbol);
+
+ if (! symbol.empty())
+ comm_flags |= COMMODITY_STYLE_SUFFIXED;
+
+ if (! in.eof() && ((n = in.peek()) != '\n'))
+ details.parse(in);
+ }
+ } else {
+ commodity_t::parse_symbol(in, symbol);
+
+ if (! in.eof() && ((n = in.peek()) != '\n')) {
+ if (std::isspace(in.peek()))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ parse_quantity(in, quant);
+
+ if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n'))
+ details.parse(in);
+ }
+ }
+
+ if (quant.empty()) {
+ if (flags & AMOUNT_PARSE_SOFT_FAIL)
+ return false;
+ else
+ throw_(amount_error, "No quantity specified for amount");
+ }
+
+ // Allocate memory for the amount's quantity value. We have to
+ // monitor the allocation in an auto_ptr because this function gets
+ // called sometimes from amount_t's constructor; and if there is an
+ // exeception thrown by any of the function calls after this point,
+ // the destructor will never be called and the memory never freed.
+
+ std::auto_ptr<bigint_t> safe_holder;
+
+ if (! quantity) {
+ quantity = new bigint_t;
+ safe_holder.reset(quantity);
+ }
+ else if (quantity->ref > 1) {
+ _release();
+ quantity = new bigint_t;
+ safe_holder.reset(quantity);
+ }
+
+ // Create the commodity if has not already been seen, and update the
+ // precision if something greater was used for the quantity.
+
+ bool newly_created = false;
+
+ if (symbol.empty()) {
+ commodity_ = NULL;
+ } else {
+ commodity_ = current_pool->find(symbol);
+ if (! commodity_) {
+ commodity_ = current_pool->create(symbol);
+ newly_created = true;
+ }
+ assert(commodity_);
+
+ if (details)
+ commodity_ = current_pool->find_or_create(*commodity_, details);
+ }
+
+ // Determine the precision of the amount, based on the usage of
+ // comma or period.
+
+ string::size_type last_comma = quant.rfind(',');
+ string::size_type last_period = quant.rfind('.');
+
+ if (last_comma != string::npos && last_period != string::npos) {
+ comm_flags |= COMMODITY_STYLE_THOUSANDS;
+ if (last_comma > last_period) {
+ comm_flags |= COMMODITY_STYLE_EUROPEAN;
+ quantity->prec = quant.length() - last_comma - 1;
+ } else {
+ quantity->prec = quant.length() - last_period - 1;
+ }
+ }
+ else if (last_comma != string::npos &&
+ commodity().has_flags(COMMODITY_STYLE_EUROPEAN)) {
+ quantity->prec = quant.length() - last_comma - 1;
+ }
+ else if (last_period != string::npos &&
+ ! (commodity().has_flags(COMMODITY_STYLE_EUROPEAN))) {
+ quantity->prec = quant.length() - last_period - 1;
+ }
+ else {
+ quantity->prec = 0;
+ }
+
+ // Set the commodity's flags and precision accordingly
+
+ if (commodity_ && ! (flags & AMOUNT_PARSE_NO_MIGRATE)) {
+ commodity().add_flags(comm_flags);
+
+ if (quantity->prec > commodity().precision())
+ commodity().set_precision(quantity->prec);
+ }
+
+ // Setup the amount's own flags
+
+ if (flags & AMOUNT_PARSE_NO_MIGRATE)
+ quantity->add_flags(BIGINT_KEEP_PREC);
+
+ // Now we have the final number. Remove commas and periods, if
+ // necessary.
+
+ if (last_comma != string::npos || last_period != string::npos) {
+ int len = quant.length();
+ char * buf = new char[len + 1];
+ const char * p = quant.c_str();
+ char * t = buf;
+
+ while (*p) {
+ if (*p == ',' || *p == '.')
+ p++;
+ *t++ = *p++;
+ }
+ *t = '\0';
+
+ mpz_set_str(MPZ(quantity), buf, 10);
+ checked_array_delete(buf);
+ } else {
+ mpz_set_str(MPZ(quantity), quant.c_str(), 10);
+ }
+
+ if (negative)
+ in_place_negate();
+
+ if (! (flags & AMOUNT_PARSE_NO_REDUCE))
+ in_place_reduce();
+
+ safe_holder.release(); // `this->quantity' owns the pointer
+
+ assert(valid());
+
+ return true;
+}
+
+void amount_t::parse_conversion(const string& larger_str,
+ const string& smaller_str)
+{
+ amount_t larger, smaller;
+
+ larger.parse(larger_str, AMOUNT_PARSE_NO_REDUCE);
+ smaller.parse(smaller_str, AMOUNT_PARSE_NO_REDUCE);
+
+ larger *= smaller.number();
+
+ if (larger.commodity()) {
+ larger.commodity().set_smaller(smaller);
+ larger.commodity().add_flags(smaller.commodity().flags() |
+ COMMODITY_STYLE_NOMARKET);
+ }
+ if (smaller.commodity())
+ smaller.commodity().set_larger(larger);
+}
+
+
+void amount_t::print(std::ostream& _out, bool omit_commodity,
+ bool full_precision) const
+{
+ assert(valid());
+
+ if (! quantity) {
+ _out << "<null>";
+ return;
+ }
+
+ amount_t base(*this);
+ if (! amount_t::keep_base)
+ base.in_place_unreduce();
+
+ std::ostringstream out;
+
+ mpz_t quotient;
+ mpz_t rquotient;
+ mpz_t remainder;
+
+ mpz_init(quotient);
+ mpz_init(rquotient);
+ mpz_init(remainder);
+
+ bool negative = false;
+
+ // Ensure the value is rounded to the commodity's precision before
+ // outputting it. NOTE: `rquotient' is used here as a temp variable!
+
+ commodity_t& comm(base.commodity());
+ precision_t precision = 0;
+
+ if (quantity) {
+ if (! comm || full_precision || base.quantity->has_flags(BIGINT_KEEP_PREC)) {
+ 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) {
+ mpz_round(rquotient, MPZ(base.quantity), base.quantity->prec,
+ comm.precision());
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ 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_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (base.quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor);
+ precision = base.quantity->prec;
+ }
+ else {
+ mpz_set(quotient, MPZ(base.quantity));
+ mpz_set_ui(remainder, 0);
+ precision = 0;
+ }
+
+ if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) {
+ negative = true;
+
+ mpz_abs(quotient, quotient);
+ mpz_abs(remainder, remainder);
+ }
+ mpz_set(rquotient, remainder);
+ }
+
+ if (! omit_commodity && ! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) {
+ comm.print(out);
+ if (comm.has_flags(COMMODITY_STYLE_SEPARATED))
+ out << " ";
+ }
+
+ if (negative)
+ out << "-";
+
+ if (! quantity || mpz_sgn(quotient) == 0) {
+ out << '0';
+ }
+ else if (omit_commodity || ! comm.has_flags(COMMODITY_STYLE_THOUSANDS)) {
+ char * p = mpz_get_str(NULL, 10, quotient);
+ out << p;
+ std::free(p);
+ }
+ else {
+ std::list<string> strs;
+ char buf[4];
+
+ for (int powers = 0; true; powers += 3) {
+ if (powers > 0) {
+ mpz_ui_pow_ui(divisor, 10, powers);
+ mpz_tdiv_q(temp, quotient, divisor);
+ if (mpz_sgn(temp) == 0)
+ break;
+ mpz_tdiv_r_ui(temp, temp, 1000);
+ } else {
+ mpz_tdiv_r_ui(temp, quotient, 1000);
+ }
+ mpz_get_str(buf, 10, temp);
+ strs.push_back(buf);
+ }
+
+ bool printed = false;
+
+ for (std::list<string>::reverse_iterator i = strs.rbegin();
+ i != strs.rend();
+ i++) {
+ if (printed) {
+ out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? '.' : ',');
+ out.width(3);
+ out.fill('0');
+ }
+ out << *i;
+
+ printed = true;
+ }
+ }
+
+ if (quantity && precision) {
+ std::ostringstream final;
+ final.width(precision);
+ final.fill('0');
+ char * p = mpz_get_str(NULL, 10, rquotient);
+ final << p;
+ std::free(p);
+
+ const string& str(final.str());
+ int i, len = str.length();
+ const char * q = str.c_str();
+ for (i = len; i > 0; i--)
+ if (q[i - 1] != '0')
+ break;
+
+ string ender;
+ if (i == len)
+ ender = str;
+ else if (i < comm.precision())
+ ender = string(str, 0, comm.precision());
+ else
+ ender = string(str, 0, i);
+
+ if (! ender.empty()) {
+ if (omit_commodity)
+ out << '.';
+ else
+ out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? ',' : '.');
+ out << ender;
+ }
+ }
+
+ if (! omit_commodity && comm.has_flags(COMMODITY_STYLE_SUFFIXED)) {
+ if (comm.has_flags(COMMODITY_STYLE_SEPARATED))
+ out << " ";
+ comm.print(out);
+ }
+
+ mpz_clear(quotient);
+ mpz_clear(rquotient);
+ mpz_clear(remainder);
+
+ // If there are any annotations associated with this commodity,
+ // output them now.
+
+ if (! omit_commodity && comm.annotated) {
+ annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm));
+ assert(&*ann.details.price != this);
+ ann.write_annotations(out);
+ }
+
+ // Things are output to a string first, so that if anyone has
+ // specified a width or fill for _out, it will be applied to the
+ // entire amount string, and not just the first part.
+
+ _out << out.str();
+}
+
+void amount_t::read(std::istream& in)
+{
+ using namespace ledger::binary;
+
+ // Read in the commodity for this amount
+
+ commodity_t::ident_t ident;
+ read_long(in, ident);
+ if (ident == 0xffffffff)
+ commodity_ = NULL;
+ else if (ident == 0)
+ commodity_ = current_pool->null_commodity;
+ else {
+ commodity_ = current_pool->find(ident);
+ assert(commodity_);
+ }
+
+ // Read in the quantity
+
+ char byte;
+ in.read(&byte, sizeof(byte));
+
+ if (byte < 3) {
+ quantity = new bigint_t;
+
+ unsigned short len;
+ in.read(reinterpret_cast<char *>(&len), sizeof(len));
+ assert(len < 4096);
+ static char buf[4096];
+ in.read(buf, len);
+ mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short),
+ 0, 0, buf);
+
+ char negative;
+ in.read(&negative, sizeof(negative));
+ if (negative)
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+
+ in.read(reinterpret_cast<char *>(&quantity->prec), sizeof(quantity->prec));
+
+ bigint_t::flags_t tflags;
+ in.read(reinterpret_cast<char *>(&tflags), sizeof(tflags));
+ quantity->set_flags(tflags);
+ }
+ else {
+ assert(false);
+ }
+}
+
+void amount_t::read(const char *& data)
+{
+ using namespace ledger::binary;
+
+ // Read in the commodity for this amount
+
+ commodity_t::ident_t ident;
+ read_long(data, ident);
+ if (ident == 0xffffffff)
+ commodity_ = NULL;
+ else if (ident == 0)
+ commodity_ = current_pool->null_commodity;
+ else {
+ commodity_ = current_pool->find(ident);
+ assert(commodity_);
+ }
+
+ // Read in the quantity
+
+ char byte = *data++;;
+
+ if (byte < 3) {
+ if (byte == 2) {
+#if 0
+ quantity = new(reinterpret_cast<bigint_t *>(bigints_next)) bigint_t;
+ bigints_next += sizeof(bigint_t);
+#endif
+ } else {
+ quantity = new bigint_t;
+ }
+
+ unsigned short len =
+ *reinterpret_cast<unsigned short *>(const_cast<char *>(data));
+ data += sizeof(unsigned short);
+ mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short),
+ 0, 0, data);
+ data += len;
+
+ char negative = *data++;
+ if (negative)
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+
+ quantity->prec = *reinterpret_cast<precision_t *>(const_cast<char *>(data));
+ data += sizeof(precision_t);
+ quantity->set_flags(*reinterpret_cast<flags_t *>(const_cast<char *>(data)));
+ data += sizeof(flags_t);
+
+ if (byte == 2)
+ quantity->add_flags(BIGINT_BULK_ALLOC);
+ } else {
+#if 0
+ uint_fast32_t index = *reinterpret_cast<uint_fast32_t *>(const_cast<char *>(data));
+ data += sizeof(uint_fast32_t);
+
+ quantity = reinterpret_cast<bigint_t *>(bigints + (index - 1) * sizeof(bigint_t));
+#endif
+ DEBUG("amounts.refs",
+ quantity << " ref++, now " << (quantity->ref + 1));
+ quantity->ref++;
+ }
+}
+
+void amount_t::write(std::ostream& out, bool optimized) const
+{
+ using namespace ledger::binary;
+
+ // Write out the commodity for this amount
+
+ if (! quantity)
+ throw_(amount_error, "Cannot serialize an uninitialized amount");
+
+ if (commodity_)
+ write_long(out, commodity_->ident);
+ else
+ write_long<commodity_t::ident_t>(out, 0xffffffff);
+
+ // Write out the quantity
+
+ char byte;
+
+ if (! optimized || quantity->index == 0) {
+ if (optimized) {
+#if 0
+ quantity->index = ++bigints_index; // if !optimized, this is garbage
+ bigints_count++;
+ byte = 2;
+#endif
+ } else {
+ byte = 1;
+ }
+ out.write(&byte, sizeof(byte));
+
+ std::size_t size;
+ static char buf[4096];
+ mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity));
+ unsigned short len = size * sizeof(short);
+ out.write(reinterpret_cast<char *>(&len), sizeof(len));
+ if (len) {
+ assert(len < 4096);
+ out.write(buf, len);
+ }
+
+ byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0;
+ out.write(&byte, sizeof(byte));
+
+ out.write(reinterpret_cast<char *>(&quantity->prec), sizeof(quantity->prec));
+ bigint_t::flags_t tflags = quantity->flags() & ~BIGINT_BULK_ALLOC;
+ assert(sizeof(tflags) == sizeof(bigint_t::flags_t));
+ out.write(reinterpret_cast<char *>(&tflags), sizeof(tflags));
+ } else {
+ assert(quantity->ref > 1);
+
+ // Since this value has already been written, we simply write
+ // out a reference to which one it was.
+ byte = 3;
+ out.write(&byte, sizeof(byte));
+ out.write(reinterpret_cast<char *>(&quantity->index), sizeof(quantity->index));
+ }
+}
+
+bool amount_t::valid() const
+{
+ if (quantity) {
+ if (! quantity->valid())
+ return false;
+
+ if (quantity->ref == 0) {
+ DEBUG("ledger.validate", "amount_t: quantity->ref == 0");
+ return false;
+ }
+ }
+ else if (commodity_) {
+ DEBUG("ledger.validate", "amount_t: commodity_ != NULL");
+ return false;
+ }
+ return true;
+}
+
+} // namespace ledger
diff --git a/src/amount.h b/src/amount.h
new file mode 100644
index 00000000..a5a05835
--- /dev/null
+++ b/src/amount.h
@@ -0,0 +1,754 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup numerics Core numerics
+ */
+
+/**
+ * @file amount.h
+ * @author John Wiegley
+ * @date Wed Apr 18 22:05:53 2007
+ *
+ * @brief Basic type for handling commoditized math: amount_t.
+ *
+ * @ingroup numerics
+ *
+ * This file contains the most basic numerical type in Ledger:
+ * amount_t, which relies upon commodity.h (commodity_t) for handling
+ * commoditized amounts. This class allows Ledger to handle
+ * mathematical expressions involving differing commodities, as well
+ * as math using no commodities at all (such as increasing a dollar
+ * amount by a multiplier).
+ */
+#ifndef _AMOUNT_H
+#define _AMOUNT_H
+
+#include "utils.h"
+
+namespace ledger {
+
+class commodity_t;
+class annotation_t;
+class commodity_pool_t;
+
+DECLARE_EXCEPTION(amount_error, std::runtime_error);
+
+/**
+ * @class amount_t
+ *
+ * @brief Encapsulates infinite-precision commoditized amounts.
+ *
+ * The amount_t class can be used for commoditized infinite-precision
+ * math, and also for uncommoditized math. In the commoditized case,
+ * commodities keep track of how they are used, and will always
+ * display back to the user after the same fashion. For
+ * uncommoditized numbers, no display truncation is ever done. In
+ * both cases, internal precision is always kept to an excessive
+ * degree.
+ */
+class amount_t
+ : public ordered_field_operators<amount_t,
+#ifdef HAVE_GDTOA
+ ordered_field_operators<amount_t, double,
+#endif
+ ordered_field_operators<amount_t, unsigned long,
+ ordered_field_operators<amount_t, long> > >
+#ifdef HAVE_GDTOA
+ >
+#endif
+{
+ // jww (2007-05-03): Make this private, and then make
+ // ledger::initialize into a member function of session_t.
+public:
+ /**
+ * The initialize and shutdown methods ready the amount subsystem
+ * for use. Normally they are called by `ledger::initialize' and
+ * `ledger::shutdown'.
+ */
+ static void initialize();
+ static void shutdown();
+
+public:
+ typedef uint_least16_t precision_t;
+
+ /**
+ * The current_pool is a static variable indicating which commodity
+ * pool should be used.
+ */
+ static commodity_pool_t * current_pool;
+
+ /**
+ * The `keep_base' member determines whether scalable commodities
+ * are automatically converted to their most reduced form when
+ * printing. The default is true.
+ *
+ * For example, Ledger supports time values specified in seconds
+ * (10s), hours (5.2h) or minutes. Internally, such amounts are
+ * always kept as quantities of seconds. However, when streaming
+ * the amount Ledger will convert it to its "least representation",
+ * which is "5.2h" in the second case. If `keep_base' is true, this
+ * amount is displayed as "18720s".
+ */
+ static bool keep_base;
+
+ /**
+ * The following three members determine whether lot details are
+ * maintained when working with commoditized values. The default is
+ * false for all three.
+ *
+ * Let's say a user adds two values of the following form:
+ * 10 AAPL + 10 AAPL {$20}
+ *
+ * This expression adds ten shares of Apple stock with another ten
+ * shares that were purchased for $20 a share. If `keep_price' is
+ * false, the result of this expression will be an amount equal to
+ * 20 AAPL. If `keep_price' is true, the expression yields an
+ * exception for adding amounts with different commodities. In that
+ * case, a balance_t object must be used to store the combined sum.
+ */
+ static bool keep_price;
+ static bool keep_date;
+ static bool keep_tag;
+
+ /**
+ * The `stream_fullstrings' static member is currently only used by
+ * the unit testing code. It causes amounts written to streams to
+ * use the `to_fullstring' method rather than the `to_string'
+ * method, so that complete precision is always displayed, no matter
+ * what the precision of an individual commodity might be.
+ * @see to_string
+ * @see to_fullstring
+ */
+ static bool stream_fullstrings;
+
+ static uint_fast32_t sizeof_bigint_t();
+
+protected:
+ void _copy(const amount_t& amt);
+ void _dup();
+ void _resize(precision_t prec);
+ void _clear();
+ void _release();
+
+ struct bigint_t;
+
+public: // needed by binary.cc
+ bigint_t * quantity;
+ commodity_t * commodity_;
+
+public:
+ /**
+ * Constructors. amount_t supports several forms of construction:
+ *
+ * amount_t() creates a value for which `is_null' is true, and which
+ * has no value or commodity. If used in value situations it will
+ * be zero, and its commodity equals `commodity_t::null_commodity'.
+ *
+ * amount_t(double), amount_t(unsigned long), amount_t(long) all
+ * convert from the respective numerics type to an amount. No
+ * precision or sign is lost in any of these conversions. The
+ * resulting commodity is always `commodity_t::null_commodity'.
+ *
+ * amount_t(string), amount_t(const char *) both convert from a
+ * string representation of an amount, which may or may not include
+ * a commodity. This is the proper way to initialize an amount like
+ * '$100.00'.
+ */
+ amount_t() : quantity(NULL), commodity_(NULL) {
+ TRACE_CTOR(amount_t, "");
+ }
+#ifdef HAVE_GDTOA
+ amount_t(const double val);
+#endif
+ amount_t(const unsigned long val);
+ amount_t(const long val);
+
+ explicit amount_t(const string& val) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "const string&");
+ parse(val);
+ }
+ explicit amount_t(const char * val) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "const char *");
+ assert(val);
+ parse(val);
+ }
+
+ /**
+ * Static creator function. Calling amount_t::exact(string) will
+ * create an amount whose display precision is never truncated, even
+ * if the amount uses a commodity (which normally causes "round on
+ * streaming" to occur). This function is mostly used by the
+ * debugging code. It is the proper way to initialize '$100.005',
+ * where display of the extra precision is required. If a regular
+ * constructor is used, this amount will stream as '$100.01', even
+ * though its internal value always equals $100.005.
+ */
+ static amount_t exact(const string& value);
+
+ /**
+ * Destructor. Releases the reference count held for the underlying
+ * bigint_t object pointed to be `quantity'.
+ */
+ ~amount_t() {
+ TRACE_DTOR(amount_t);
+ if (quantity)
+ _release();
+ }
+
+ /**
+ * Assignment and copy operators. An amount may be assigned or
+ * copied. If a double, long or unsigned long is assigned to an
+ * amount, a temporary is constructed, and then the temporary is
+ * assigned to `this'. Both the value and the commodity are copied,
+ * causing the result to compare equal to the reference amount.
+ *
+ * Note: `quantity' must be initialized to NULL first, otherwise the
+ * `_copy' function will attempt to release the uninitialized pointer.
+ */
+ amount_t(const amount_t& amt) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "copy");
+ if (amt.quantity)
+ _copy(amt);
+ else
+ commodity_ = NULL;
+ }
+ amount_t& operator=(const amount_t& amt);
+
+#ifdef HAVE_GDTOA
+ amount_t& operator=(const double val) {
+ return *this = amount_t(val);
+ }
+#endif
+ amount_t& operator=(const unsigned long val) {
+ return *this = amount_t(val);
+ }
+ amount_t& operator=(const long val) {
+ return *this = amount_t(val);
+ }
+
+ amount_t& operator=(const string& str) {
+ return *this = amount_t(str);
+ }
+ amount_t& operator=(const char * str) {
+ assert(str);
+ return *this = amount_t(str);
+ }
+
+ /**
+ * Comparison operators. The fundamental comparison operation for
+ * amounts is `compare', which returns a value less than, greater
+ * than or equal to zero. All the other comparison operators are
+ * defined in terms of this method. The only special detail is that
+ * `operator==' will fail immediately if amounts with different
+ * commodities are being compared. Otherwise, if the commodities
+ * are equivalent (@see keep_price, et al), then the amount
+ * quantities are compared numerically.
+ *
+ * Comparison between an amount and a double, long or unsigned long
+ * is allowed. In such cases the non-amount value is constructed as
+ * an amount temporary, which is then compared to `this'.
+ */
+ int compare(const amount_t& amt) const;
+
+ bool operator==(const amount_t& amt) const;
+
+ template <typename T>
+ bool operator==(const T& val) const {
+ return compare(val) == 0;
+ }
+ template <typename T>
+ bool operator<(const T& amt) const {
+ return compare(amt) < 0;
+ }
+ template <typename T>
+ bool operator>(const T& amt) const {
+ return compare(amt) > 0;
+ }
+
+ /**
+ * Binary arithmetic operators. Amounts support addition,
+ * subtraction, multiplication and division -- but not modulus,
+ * bitwise operations, or shifting. Arithmetic is also supported
+ * between amounts, double, long and unsigned long, in which case
+ * temporary amount are constructed for the life of the expression.
+ *
+ * Although only in-place operators are defined here, the remainder
+ * are provided by `boost::ordered_field_operators<>'.
+ */
+ amount_t& operator+=(const amount_t& amt);
+ amount_t& operator-=(const amount_t& amt);
+ amount_t& operator*=(const amount_t& amt);
+ amount_t& operator/=(const amount_t& amt);
+
+ /**
+ * Unary arithmetic operators. There are several unary methods
+ * support on amounts:
+ *
+ * precision() return an amount's current, internal precision. To
+ * find the precision it will be displayed at -- assuming it was not
+ * created using the static method `amount_t::exact' -- refer to
+ * commodity().precision.
+ *
+ * negate(), also unary minus (- x), returns the negated value of an
+ * amount.
+ *
+ * abs() returns the absolute value of an amount. It is equivalent
+ * to: `(x < 0) ? - x : x'.
+ *
+ * round(precision_t) and round() round an amount's internal value
+ * to the given precision, or to the commodity's current display
+ * precision if no precision value is given. This method changes
+ * the internal value of the amount, if it's internal precision was
+ * greater than the rounding precision.
+ *
+ * unround() yields an amount whose display precision is never
+ * truncated, even though its commodity normally displays only
+ * rounded values.
+ *
+ * reduce() reduces a value to its most basic commodity form, for
+ * amounts that utilize "scaling commodities". For example, an
+ * amount of 1h after reduction will be 3600s.
+ *
+ * unreduce(), if used with a "scaling commodity", yields the most
+ * compact form greater than 1.0. That is, 3599s will unreduce to
+ * 59.98m, while 3601 unreduces to 1h.
+ *
+ * value(optional<datetime_t>) returns the historical value for an
+ * amount -- the default moment returns the most recently known
+ * price -- based on the price history of its commodity. For
+ * example, if the amount were 10 AAPL, and on Apr 10, 2000 each
+ * share of AAPL was worth $10, then call value() for that moment in
+ * time would yield the amount $100.00.
+ *
+ * Further, for the sake of efficiency and avoiding temporary
+ * objects, the following methods support "in-place" variants that
+ * act on the amount itself and return a reference to the result
+ * (`*this'):
+ *
+ * in_place_negate()
+ * in_place_reduce()
+ * in_place_unreduce()
+ */
+ precision_t precision() const;
+
+ amount_t negate() const {
+ amount_t temp(*this);
+ temp.in_place_negate();
+ return temp;
+ }
+ amount_t& in_place_negate();
+
+ amount_t operator-() const {
+ return negate();
+ }
+
+ amount_t abs() const {
+ if (sign() < 0)
+ return negate();
+ return *this;
+ }
+
+ amount_t round() const;
+ amount_t round(precision_t prec) const;
+ amount_t unround() const;
+
+ amount_t reduce() const {
+ amount_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+ amount_t& in_place_reduce();
+
+ amount_t unreduce() const {
+ amount_t temp(*this);
+ temp.in_place_unreduce();
+ return temp;
+ }
+ amount_t& in_place_unreduce();
+
+ optional<amount_t> value(const optional<datetime_t>& moment = none) const;
+
+ /**
+ * Truth tests. An amount may be truth test in several ways:
+ *
+ * sign() returns an integer less than, greater than, or equal to
+ * zero depending on whether the amount is negative, zero, or
+ * greater than zero. Note that this function tests the actual
+ * value of the amount -- using its internal precision -- and not
+ * the display value. To test its display value, use:
+ * `round().sign()'.
+ *
+ * is_nonzero(), or operator bool, returns true if an amount's
+ * display value is not zero.
+ *
+ * is_zero() returns true if an amount's display value is zero.
+ * Thus, $0.0001 is considered zero if the current display precision
+ * for dollars is two decimal places.
+ *
+ * is_realzero() returns true if an amount's actual value is zero.
+ * Thus, $0.0001 is never considered realzero.
+ *
+ * is_null() returns true if an amount has no value and no
+ * commodity. This only occurs if an uninitialized amount has never
+ * been assigned a value.
+ */
+ int sign() const;
+
+ operator bool() const {
+ return is_nonzero();
+ }
+ bool is_nonzero() const {
+ return ! is_zero();
+ }
+
+ bool is_zero() const;
+ bool is_realzero() const {
+ return sign() == 0;
+ }
+
+ bool is_null() const {
+ if (! quantity) {
+ assert(! commodity_);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Conversion methods. An amount may be converted to the same types
+ * it can be constructed from -- with the exception of unsigned
+ * long. Implicit conversions are not allowed in C++ (though they
+ * are in Python), rather the following conversion methods must be
+ * called explicitly:
+ *
+ * to_double([bool]) returns an amount as a double. If the optional
+ * boolean argument is true (the default), an exception is thrown if
+ * the conversion would lose information.
+ *
+ * to_long([bool]) returns an amount as a long integer. If the
+ * optional boolean argument is true (the default), an exception is
+ * thrown if the conversion would lose information.
+ *
+ * fits_in_double() returns true if to_double() would not lose
+ * precision.
+ *
+ * fits_in_long() returns true if to_long() would not lose
+ * precision.
+ *
+ * to_string() returns an amount'ss "display value" as a string --
+ * after rounding the value according to the commodity's default
+ * precision. It is equivalent to: `round().to_fullstring()'.
+ *
+ * to_fullstring() returns an amount's "internal value" as a string,
+ * without any rounding.
+ *
+ * quantity_string() returns an amount's "display value", but
+ * without any commodity. Note that this is different from
+ * `number().to_string()', because in that case the commodity has
+ * been stripped and the full, internal precision of the amount
+ * would be displayed.
+ */
+#ifdef HAVE_GDTOA
+ double to_double(bool no_check = false) const;
+#endif
+ long to_long(bool no_check = false) const;
+ string to_string() const;
+ string to_fullstring() const;
+ string quantity_string() const;
+
+#ifdef HAVE_GDTOA
+ bool fits_in_double() const;
+#endif
+ bool fits_in_long() const;
+
+ /**
+ * Commodity-related methods. The following methods relate to an
+ * amount's commodity:
+ *
+ * commodity() returns an amount's commodity. If the amount has no
+ * commodity, the value returned is `current_pool->null_commodity'.
+ *
+ * has_commodity() returns true if the amount has a commodity.
+ *
+ * set_commodity(commodity_t) sets an amount's commodity to the
+ * given value. Note that this merely sets the current amount to
+ * that commodity, it does not "observe" the amount for possible
+ * changes in the maximum display precision of the commodity, the
+ * way that `parse' does.
+ *
+ * clear_commodity() sets an amount's commodity to null, such that
+ * has_commodity() afterwards returns false.
+ *
+ * number() returns a commodity-less version of an amount. This is
+ * useful for accessing just the numeric portion of an amount.
+ */
+ commodity_t& commodity() const;
+
+ bool has_commodity() const;
+ void set_commodity(commodity_t& comm) {
+ if (! quantity)
+ *this = 0L;
+ commodity_ = &comm;
+ }
+ void clear_commodity() {
+ commodity_ = NULL;
+ }
+
+ amount_t number() const {
+ if (! has_commodity())
+ return *this;
+
+ amount_t temp(*this);
+ temp.clear_commodity();
+ return temp;
+ }
+
+ /**
+ * Annotated commodity methods. An amount's commodity may be
+ * annotated with special details, such as the price it was
+ * purchased for, when it was acquired, or an arbitrary note,
+ * identifying perhaps the lot number of an item.
+ *
+ * annotate_commodity(amount_t price, [datetime_t date, string tag])
+ * sets the annotations for the current amount's commodity. Only
+ * the price argument is required, although it can be passed as
+ * `none' if no price is desired.
+ *
+ * commodity_annotated() returns true if an amount's commodity has
+ * any annotation details associated with it.
+ *
+ * annotation_details() returns all of the details of an annotated
+ * commodity's annotations. The structure returns will evaluate as
+ * boolean false if there are no details.
+ *
+ * strip_annotations([keep_price, keep_date, keep_tag]) returns an
+ * amount whose commodity's annotations have been stripped. The
+ * three `keep_' arguments determine which annotation detailed are
+ * kept, meaning that the default is to follow whatever
+ * amount_t::keep_price, amount_t::keep_date and amount_t::keep_tag
+ * have been set to (which all default to false).
+ */
+ void annotate(const annotation_t& details);
+ bool is_annotated() const;
+
+ annotation_t& annotation();
+ const annotation_t& annotation() const {
+ return const_cast<amount_t&>(*this).annotation();
+ }
+
+ amount_t strip_annotations(const bool _keep_price = keep_price,
+ const bool _keep_date = keep_date,
+ const bool _keep_tag = keep_tag) const;
+
+ /**
+ * Parsing methods. The method `parse' is used to parse an amount
+ * from an input stream or a string. A global operator>> is also
+ * defined which simply calls parse on the input stream. The
+ * `parse' method has two forms:
+ *
+ * parse(istream, flags_t) parses an amount from the given input
+ * stream.
+ *
+ * parse(string, flags_t) parses an amount from the given string.
+ *
+ * parse(string, flags_t) also parses an amount from a string.
+ *
+ * The `flags' argument of both parsing may be one or more of the
+ * following:
+ *
+ * AMOUNT_PARSE_NO_MIGRATE means to not pay attention to the way an
+ * amount is used. Ordinarily, if an amount were $100.001, for
+ * example, it would cause the default display precision for $ to be
+ * "widened" to three decimal places. If AMOUNT_PARSE_NO_MIGRATE is
+ * used, the commodity's default display precision is not changed.
+ *
+ * AMOUNT_PARSE_NO_REDUCE means not to call in_place_reduce() on the
+ * resulting amount after it is parsed.
+ *
+ * These parsing methods observe the amounts they parse (unless
+ * AMOUNT_PARSE_NO_MIGRATE is true), and set the display details of
+ * the corresponding commodity accordingly. This way, amounts do
+ * not require commodities to be pre-defined in any way, but merely
+ * displays them back to the user in the same fashion as it saw them
+ * used.
+ *
+ * There is also a static convenience method called
+ * `parse_conversion' which can be used to define a relationship
+ * between scaling commodity values. For example, Ledger uses it to
+ * define the relationships among various time values:
+ *
+ * amount_t::parse_conversion("1.0m", "60s"); // a minute is 60 seconds
+ * amount_t::parse_conversion("1.0h", "60m"); // an hour is 60 minutes
+ */
+#define AMOUNT_PARSE_NO_MIGRATE 0x01
+#define AMOUNT_PARSE_NO_REDUCE 0x02
+#define AMOUNT_PARSE_SOFT_FAIL 0x04
+
+ typedef uint_least8_t flags_t;
+
+ bool parse(std::istream& in, flags_t flags = 0);
+ bool parse(const string& str, flags_t flags = 0) {
+ std::istringstream stream(str);
+ bool result = parse(stream, flags);
+ assert(stream.eof());
+ return result;
+ }
+
+ static void parse_conversion(const string& larger_str,
+ const string& smaller_str);
+
+ /**
+ * Printing methods. An amount may be output to a stream using the
+ * `print' method. There is also a global operator<< defined which
+ * simply calls print for an amount on the given stream. There is
+ * one form of the print method, which takes one required argument
+ * and two arguments with default values:
+ *
+ * print(ostream, bool omit_commodity = false, bool full_precision =
+ * false) prints an amounts to the given output stream, using its
+ * commodity's default display characteristics. If `omit_commodity'
+ * is true, the commodity will not be displayed, only the amount
+ * (although the commodity's display precision is still used). If
+ * `full_precision' is true, the full internal precision of the
+ * amount is displayed, regardless of its commodity's display
+ * precision.
+ */
+ void print(std::ostream& out, bool omit_commodity = false,
+ bool full_precision = false) const;
+
+ /**
+ * Serialization methods. An amount may be deserialized from an
+ * input stream or a character pointer, and it may be serialized to
+ * an output stream. The methods used are:
+ *
+ * read(istream) reads an amount from the given input stream. It
+ * must have been put there using `write(ostream)'. The required
+ * flow of logic is:
+ * amount_t::current_pool->write(out)
+ * amount.write(out) // write out all amounts
+ * amount_t::current_pool->read(in)
+ * amount.read(in)
+ *
+ * read(char *&) reads an amount from data which has been read from
+ * an input stream into a buffer. It advances the pointer passed in
+ * to the end of the deserialized amount.
+ *
+ * write(ostream, [bool]) writes an amount to an output stream in a
+ * compact binary format. If the second parameter is true,
+ * quantities with multiple reference counts will be written in an
+ * optimized fashion. NOTE: This form of usage is valid only for
+ * the binary journal writer, it should not be used otherwise, as it
+ * has strict requirements for reading that only the binary reader
+ * knows about.
+ */
+ void read(std::istream& in);
+ void read(const char *& data);
+ void write(std::ostream& out, bool optimize = false) const;
+
+ /**
+ * Debugging methods. There are two methods defined to help with
+ * debugging:
+ *
+ * dump(ostream) dumps an amount to an output stream. There is
+ * little different from print(), it simply surrounds the display
+ * value with a marker, for example "AMOUNT($1.00)". This code is
+ * used by other dumping code elsewhere in Ledger.
+ *
+ * valid() returns true if an amount is valid. This ensures that if
+ * an amount has a commodity, it has a valid value pointer, for
+ * example, even if that pointer simply points to a zero value.
+ */
+ void dump(std::ostream& out) const {
+ out << "AMOUNT(";
+ print(out);
+ out << ")";
+ }
+
+ bool valid() const;
+};
+
+inline amount_t amount_t::exact(const string& value) {
+ amount_t temp;
+ temp.parse(value, AMOUNT_PARSE_NO_MIGRATE);
+ return temp;
+}
+
+inline string amount_t::to_string() const {
+ std::ostringstream bufstream;
+ print(bufstream);
+ return bufstream.str();
+}
+
+inline string amount_t::to_fullstring() const {
+ std::ostringstream bufstream;
+ print(bufstream, false, true);
+ return bufstream.str();
+}
+
+inline string amount_t::quantity_string() const {
+ std::ostringstream bufstream;
+ print(bufstream, true);
+ return bufstream.str();
+}
+
+inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) {
+ amt.print(out, false, amount_t::stream_fullstrings);
+ return out;
+}
+inline std::istream& operator>>(std::istream& in, amount_t& amt) {
+ amt.parse(in);
+ return in;
+}
+
+} // namespace ledger
+
+#include "commodity.h"
+
+namespace ledger {
+
+inline bool amount_t::operator==(const amount_t& amt) const {
+ if (commodity() != amt.commodity())
+ return false;
+ return compare(amt) == 0;
+}
+
+inline commodity_t& amount_t::commodity() const {
+ return has_commodity() ? *commodity_ : *current_pool->null_commodity;
+}
+
+inline bool amount_t::has_commodity() const {
+ return commodity_ && commodity_ != commodity_->parent().null_commodity;
+}
+
+} // namespace ledger
+
+#endif // _AMOUNT_H
diff --git a/src/balance.cc b/src/balance.cc
new file mode 100644
index 00000000..bac8d40c
--- /dev/null
+++ b/src/balance.cc
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "balance.h"
+
+namespace ledger {
+
+balance_t& balance_t::operator+=(const balance_t& bal)
+{
+ foreach (const amounts_map::value_type& pair, bal.amounts)
+ *this += pair.second;
+ return *this;
+}
+
+balance_t& balance_t::operator+=(const amount_t& amt)
+{
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot add an uninitialized amount to a balance");
+
+ if (amt.is_realzero())
+ return *this;
+
+ amounts_map::iterator i = amounts.find(&amt.commodity());
+ if (i != amounts.end())
+ i->second += amt;
+ else
+ amounts.insert(amounts_map::value_type(&amt.commodity(), amt));
+
+ return *this;
+}
+
+balance_t& balance_t::operator-=(const balance_t& bal)
+{
+ foreach (const amounts_map::value_type& pair, bal.amounts)
+ *this -= pair.second;
+ return *this;
+}
+
+balance_t& balance_t::operator-=(const amount_t& amt)
+{
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot subtract an uninitialized amount from a balance");
+
+ if (amt.is_realzero())
+ return *this;
+
+ amounts_map::iterator i = amounts.find(&amt.commodity());
+ if (i != amounts.end()) {
+ i->second -= amt;
+ if (i->second.is_realzero())
+ amounts.erase(i);
+ } else {
+ amounts.insert(amounts_map::value_type(&amt.commodity(), amt.negate()));
+ }
+ return *this;
+}
+
+balance_t& balance_t::operator*=(const amount_t& amt)
+{
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot multiply a balance by an uninitialized amount");
+
+ if (is_realzero()) {
+ ;
+ }
+ else if (amt.is_realzero()) {
+ *this = amt;
+ }
+ else if (! amt.commodity()) {
+ // Multiplying by an amount with no commodity causes all the
+ // component amounts to be increased by the same factor.
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second *= amt;
+ }
+ else if (amounts.size() == 1) {
+ // Multiplying by a commoditized amount is only valid if the sole
+ // commodity in the balance is of the same kind as the amount's
+ // commodity.
+ if (*amounts.begin()->first == amt.commodity())
+ amounts.begin()->second *= amt;
+ else
+ throw_(balance_error,
+ "Cannot multiply a balance with annotated commodities by a commoditized amount");
+ }
+ else {
+ assert(amounts.size() > 1);
+ throw_(balance_error,
+ "Cannot multiply a multi-commodity balance by a commoditized amount");
+ }
+ return *this;
+}
+
+balance_t& balance_t::operator/=(const amount_t& amt)
+{
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot divide a balance by an uninitialized amount");
+
+ if (is_realzero()) {
+ ;
+ }
+ else if (amt.is_realzero()) {
+ throw_(balance_error, "Divide by zero");
+ }
+ else if (! amt.commodity()) {
+ // Dividing by an amount with no commodity causes all the
+ // component amounts to be divided by the same factor.
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second /= amt;
+ }
+ else if (amounts.size() == 1) {
+ // Dividing by a commoditized amount is only valid if the sole
+ // commodity in the balance is of the same kind as the amount's
+ // commodity.
+ if (*amounts.begin()->first == amt.commodity())
+ amounts.begin()->second /= amt;
+ else
+ throw_(balance_error,
+ "Cannot divide a balance with annotated commodities by a commoditized amount");
+ }
+ else {
+ assert(amounts.size() > 1);
+ throw_(balance_error,
+ "Cannot divide a multi-commodity balance by a commoditized amount");
+ }
+ return *this;
+}
+
+optional<balance_t>
+balance_t::value(const optional<datetime_t>& moment) const
+{
+ optional<balance_t> temp;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (optional<amount_t> val = pair.second.value(moment)) {
+ if (! temp)
+ temp = balance_t();
+ *temp += *val;
+ }
+
+ return temp;
+}
+
+optional<amount_t>
+balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const
+{
+ // jww (2007-05-20): Needs work
+ if (! commodity) {
+ if (amounts.size() == 1) {
+ amounts_map::const_iterator i = amounts.begin();
+ return i->second;
+ }
+ else if (amounts.size() > 1) {
+ // Try stripping annotations before giving an error.
+ balance_t temp(strip_annotations());
+ if (temp.amounts.size() == 1)
+ return temp.commodity_amount(commodity);
+
+ throw_(amount_error,
+ "Requested amount of a balance with multiple commodities: " << temp);
+ }
+ }
+ else if (amounts.size() > 0) {
+ amounts_map::const_iterator i = amounts.find(&*commodity);
+ if (i != amounts.end())
+ return i->second;
+ }
+ return none;
+}
+
+balance_t balance_t::strip_annotations(const bool keep_price,
+ const bool keep_date,
+ const bool keep_tag) const
+{
+ balance_t temp;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.strip_annotations(keep_price, keep_date, keep_tag);
+
+ return temp;
+}
+
+void balance_t::print(std::ostream& out,
+ const int first_width,
+ const int latter_width) const
+{
+ bool first = true;
+ int lwidth = latter_width;
+
+ if (lwidth == -1)
+ lwidth = first_width;
+
+ typedef std::vector<const amount_t *> amounts_array;
+ amounts_array sorted;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (pair.second)
+ sorted.push_back(&pair.second);
+
+ std::stable_sort(sorted.begin(), sorted.end(), compare_amount_commodities());
+
+ foreach (const amount_t * amount, sorted) {
+ int width;
+ if (! first) {
+ out << std::endl;
+ width = lwidth;
+ } else {
+ first = false;
+ width = first_width;
+ }
+
+ out.width(width);
+ out.fill(' ');
+ out << std::right << *amount;
+ }
+
+ if (first) {
+ out.width(first_width);
+ out.fill(' ');
+ out << std::right << "0";
+ }
+}
+
+} // namespace ledger
diff --git a/src/balance.h b/src/balance.h
new file mode 100644
index 00000000..33e3dffb
--- /dev/null
+++ b/src/balance.h
@@ -0,0 +1,532 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup numerics Core numerics
+ */
+
+/**
+ * @file balance.h
+ * @author John Wiegley
+ * @date Sun May 20 15:28:44 2007
+ *
+ * @brief Basic type for adding multiple commodities together.
+ *
+ * @ingroup numerics
+ *
+ * Unlike the amount_t class, which throws an exception if amounts of
+ * differing commodities are added or subtracted, the balance_t class
+ * is designed to allow this, tracking the amounts of each component
+ * commodity separately.
+ */
+#ifndef _BALANCE_H
+#define _BALANCE_H
+
+#include "amount.h"
+
+namespace ledger {
+
+DECLARE_EXCEPTION(balance_error, std::runtime_error);
+
+/**
+ * @class balance_t
+ *
+ * @brief A wrapper around amount_t allowing addition of multiple commodities.
+ *
+ * The balance_t class is appopriate for keeping a running balance
+ * where amounts of multiple commodities may be involved.
+ */
+class balance_t
+ : public equality_comparable<balance_t,
+ equality_comparable<balance_t, amount_t,
+ equality_comparable<balance_t, double,
+ equality_comparable<balance_t, unsigned long,
+ equality_comparable<balance_t, long,
+ additive<balance_t,
+ additive<balance_t, amount_t,
+ additive<balance_t, double,
+ additive<balance_t, unsigned long,
+ additive<balance_t, long,
+ multiplicative<balance_t, amount_t,
+ multiplicative<balance_t, double,
+ multiplicative<balance_t, unsigned long,
+ multiplicative<balance_t, long> > > > > > > > > > > > > >
+{
+public:
+ typedef std::map<const commodity_t *, amount_t> amounts_map;
+
+ amounts_map amounts;
+
+ // jww (2007-05-20): Remove these two by adding access methods
+ friend class value_t;
+ friend class entry_base_t;
+
+ /**
+ * Constructors. balance_t supports similar forms of construction
+ * to amount_t.
+ *
+ * balance_t() creates an empty balance to which amounts or other
+ * balances may be added or subtracted.
+ *
+ * balance_t(amount_t) constructs a balance whose starting value is
+ * equal to the given amount.
+ *
+ * balance_t(double), balance_t(unsigned long) and balance_t(long)
+ * will construct an amount from their arguments and then construct
+ * a balance whose starting value is equal to that amount. This
+ * initial balance will have no commodity.
+ *
+ * balance_t(string) and balance_t(const char *) both convert from a
+ * string representation of an amount to a balance whose initial
+ * value is that amount. This is the proper way to initialize a
+ * balance like '$100.00'.
+ */
+ balance_t() {
+ TRACE_CTOR(balance_t, "");
+ }
+ balance_t(const amount_t& amt) {
+ TRACE_CTOR(balance_t, "const amount_t&");
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot initialize a balance from an uninitialized amount");
+ if (! amt.is_realzero())
+ amounts.insert(amounts_map::value_type(&amt.commodity(), amt));
+ }
+#ifdef HAVE_GDTOA
+ balance_t(const double val) {
+ TRACE_CTOR(balance_t, "const double");
+ amounts.insert
+ (amounts_map::value_type(amount_t::current_pool->null_commodity, val));
+ }
+#endif
+ balance_t(const unsigned long val) {
+ TRACE_CTOR(balance_t, "const unsigned long");
+ amounts.insert
+ (amounts_map::value_type(amount_t::current_pool->null_commodity, val));
+ }
+ balance_t(const long val) {
+ TRACE_CTOR(balance_t, "const long");
+ amounts.insert
+ (amounts_map::value_type(amount_t::current_pool->null_commodity, val));
+ }
+
+ explicit balance_t(const string& val) {
+ TRACE_CTOR(balance_t, "const string&");
+ amount_t temp(val);
+ amounts.insert(amounts_map::value_type(&temp.commodity(), temp));
+ }
+ explicit balance_t(const char * val) {
+ TRACE_CTOR(balance_t, "const char *");
+ amount_t temp(val);
+ amounts.insert(amounts_map::value_type(&temp.commodity(), temp));
+ }
+
+ /**
+ * Destructor. Destroys all of the accumulated amounts in the
+ * balance.
+ */
+ virtual ~balance_t() {
+ TRACE_DTOR(balance_t);
+ }
+
+ /**
+ * Assignment and copy operators. An balance may be assigned or copied.
+ */
+ balance_t(const balance_t& bal) : amounts(bal.amounts) {
+ TRACE_CTOR(balance_t, "copy");
+ }
+
+ balance_t& operator=(const balance_t& bal) {
+ if (this != &bal)
+ amounts = bal.amounts;
+ return *this;
+ }
+ balance_t& operator=(const amount_t& amt) {
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot assign an uninitialized amount to a balance");
+
+ amounts.clear();
+ if (! amt.is_realzero())
+ amounts.insert(amounts_map::value_type(&amt.commodity(), amt));
+
+ return *this;
+ }
+
+ balance_t& operator=(const string& str) {
+ return *this = balance_t(str);
+ }
+ balance_t& operator=(const char * str) {
+ return *this = balance_t(str);
+ }
+
+ /**
+ * Comparison operators. Balances are fairly restrictive in terms
+ * of how they may be compared. They may be compared for equality
+ * or inequality, but this is all, since the concept of "less than"
+ * or "greater than" makes no sense when amounts of multiple
+ * commodities are involved.
+ *
+ * Balances may also be compared to amounts, in which case the sum
+ * of the balance must equal the amount exactly.
+ *
+ * If a comparison between balances is desired, the balances must
+ * first be rendered to value equivalent amounts using the `value'
+ * method, to determine a market valuation at some specific moment
+ * in time.
+ */
+ bool operator==(const balance_t& bal) const {
+ amounts_map::const_iterator i, j;
+ for (i = amounts.begin(), j = bal.amounts.begin();
+ i != amounts.end() && j != bal.amounts.end();
+ i++, j++) {
+ if (! (i->first == j->first && i->second == j->second))
+ return false;
+ }
+ return i == amounts.end() && j == bal.amounts.end();
+ }
+ bool operator==(const amount_t& amt) const {
+ if (amt.is_null())
+ throw_(balance_error,
+ "Cannot compare a balance to an uninitialized amount");
+
+ if (amt.is_realzero())
+ return amounts.empty();
+ else
+ return amounts.size() == 1 && amounts.begin()->second == amt;
+ }
+
+ template <typename T>
+ bool operator==(const T& val) const {
+ return *this == balance_t(val);
+ }
+
+ /**
+ * Binary arithmetic operators. Balances support addition and
+ * subtraction of other balances or amounts, but multiplication and
+ * division are restricted to uncommoditized amounts only.
+ */
+ balance_t& operator+=(const balance_t& bal);
+ balance_t& operator+=(const amount_t& amt);
+ balance_t& operator-=(const balance_t& bal);
+ balance_t& operator-=(const amount_t& amt);
+
+ virtual balance_t& operator*=(const amount_t& amt);
+
+#ifdef HAVE_GDTOA
+ balance_t& operator*=(const double val) {
+ return *this *= amount_t(val);
+ }
+#endif
+ balance_t& operator*=(const unsigned long val) {
+ return *this *= amount_t(val);
+ }
+ balance_t& operator*=(const long val) {
+ return *this *= amount_t(val);
+ }
+
+ virtual balance_t& operator/=(const amount_t& amt);
+
+#ifdef HAVE_GDTOA
+ balance_t& operator/=(const double val) {
+ return *this /= amount_t(val);
+ }
+#endif
+ balance_t& operator/=(const unsigned long val) {
+ return *this /= amount_t(val);
+ }
+ balance_t& operator/=(const long val) {
+ return *this /= amount_t(val);
+ }
+
+ /**
+ * Unary arithmetic operators. There are only a few unary methods
+ * support on balance:
+ *
+ * negate(), also unary minus (- x), returns a balance all of whose
+ * component amounts have been negated. In order words, it inverts
+ * the sign of all member amounts.
+ *
+ * abs() returns a balance where no component amount is negative.
+ *
+ * reduce() reduces the values in a balance to their most basic
+ * commodity forms, for amounts that utilize "scaling commodities".
+ * For example, a balance of 1h and 1m after reduction will be
+ * 3660s.
+ *
+ * unreduce(), if used with amounts that use "scaling commodities",
+ * yields the most compact form greater than 1.0 for each component
+ * amount. That is, a balance of 10m and 1799s will unreduce to
+ * 39.98m.
+ *
+ * value(optional<datetime_t>) returns the total historical value for
+ * a balance -- the default moment returns a value based on the most
+ * recently known price -- based on the price history of its
+ * component commodities. See amount_t::value for an example.
+ *
+ * Further, for the sake of efficiency and avoiding temporary
+ * objects, the following methods support "in-place" variants act on
+ * the balance itself and return a reference to the result
+ * (`*this'):
+ *
+ * in_place_negate()
+ * in_place_reduce()
+ * in_place_unreduce()
+ */
+ balance_t negate() const {
+ balance_t temp(*this);
+ temp.in_place_negate();
+ return temp;
+ }
+ virtual balance_t& in_place_negate() {
+ foreach (amounts_map::value_type& pair, amounts)
+ pair.second.in_place_negate();
+ return *this;
+ }
+ balance_t operator-() const {
+ return negate();
+ }
+
+ balance_t abs() const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.abs();
+ return temp;
+ }
+
+ balance_t round() const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.round();
+ return temp;
+ }
+ balance_t round(amount_t::precision_t prec) const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.round(prec);
+ return temp;
+ }
+ balance_t unround() const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.unround();
+ return temp;
+ }
+
+ balance_t reduce() const {
+ balance_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+ virtual balance_t& in_place_reduce() {
+ // A temporary must be used here because reduction may cause
+ // multiple component amounts to collapse to the same commodity.
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.reduce();
+ return *this = temp;
+ }
+
+ balance_t unreduce() const {
+ balance_t temp(*this);
+ temp.in_place_unreduce();
+ return temp;
+ }
+ virtual balance_t& in_place_unreduce() {
+ // A temporary must be used here because unreduction may cause
+ // multiple component amounts to collapse to the same commodity.
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.unreduce();
+ return *this = temp;
+ }
+
+ optional<balance_t> value(const optional<datetime_t>& moment = none) const;
+
+ /**
+ * Truth tests. An balance may be truth test in two ways:
+ *
+ * is_nonzero(), or operator bool, returns true if a balance's
+ * display value is not zero.
+ *
+ * is_zero() returns true if an balance's display value is zero.
+ * Thus, a balance containing $0.0001 is considered zero if the
+ * current display precision for dollars is two decimal places.
+ *
+ * is_realzero() returns true if an balance's actual value is zero.
+ * Thus, a balance containing $0.0001 is never considered realzero.
+ *
+ * is_empty() returns true if a balance has no amounts within it.
+ * This can occur after a balance has been default initialized, or
+ * if the exact amount it contains is subsequently subtracted from
+ * it.
+ */
+ operator bool() const {
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (pair.second.is_nonzero())
+ return true;
+ return false;
+ }
+
+ bool is_zero() const {
+ if (is_empty())
+ return true;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (! pair.second.is_zero())
+ return false;
+ return true;
+ }
+
+ bool is_realzero() const {
+ if (is_empty())
+ return true;
+
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (! pair.second.is_realzero())
+ return false;
+ return true;
+ }
+
+ bool is_empty() const {
+ return amounts.size() == 0;
+ }
+
+ /**
+ * Conversion methods. A balance can be converted to an amount, but
+ * only if contains a single component amount.
+ */
+ amount_t to_amount() const {
+ if (is_empty())
+ throw_(balance_error, "Cannot convert an empty balance to an amount");
+ else if (amounts.size() == 1)
+ return amounts.begin()->second;
+ else
+ throw_(balance_error,
+ "Cannot convert a balance with multiple commodities to an amount");
+ }
+
+ /**
+ * Commodity-related methods. Balances support two
+ * commodity-related methods:
+ *
+ * commodity_count() returns the number of different commodities
+ * stored in the balance.
+ *
+ * commodity_amount(optional<commodity_t>) returns an (optional)
+ * amount for the given commodity within the balance; if no
+ * commodity is specified, it returns the (optional) uncommoditized
+ * component of the balance. If no matching element can be found,
+ * boost::none is returned.
+ */
+ std::size_t commodity_count() const {
+ return amounts.size();
+ }
+
+ optional<amount_t>
+ commodity_amount(const optional<const commodity_t&>& commodity = none) const;
+
+ /**
+ * Annotated commodity methods. The amounts contained by a balance
+ * may use annotated commodities. The `strip_annotations' method
+ * will return a balance all of whose component amount have had
+ * their commodity annotations likewise stripped. See
+ * amount_t::strip_annotations for more details.
+ */
+ balance_t
+ strip_annotations(const bool keep_price = amount_t::keep_price,
+ const bool keep_date = amount_t::keep_date,
+ const bool keep_tag = amount_t::keep_tag) const;
+
+ /**
+ * Printing methods. A balance may be output to a stream using the
+ * `print' method. There is also a global operator<< defined which
+ * simply calls print for a balance on the given stream. There is
+ * one form of the print method, which takes two required arguments
+ * and one arguments with a default value:
+ *
+ * print(ostream, int first_width, int latter_width) prints a
+ * balance to the given output stream, using each commodity's
+ * default display characteristics. The first_width parameter
+ * specifies the width that should be used for printing amounts
+ * (since they are likely to vary in width). The latter_width, if
+ * specified, gives the width to be used for each line after the
+ * first. This is useful when printing in a column which falls at
+ * the right-hand side of the screen.
+ *
+ * In addition to the width constraints, balances will also print
+ * with commodities in alphabetized order, regardless of the
+ * relative amounts of those commodities. There is no option to
+ * change this behavior.
+ */
+ void print(std::ostream& out, const int first_width,
+ const int latter_width = -1) const;
+
+ /**
+ * Debugging methods. There are two methods defined to help with
+ * debugging:
+ *
+ * dump(ostream) dumps a balance to an output stream. There is
+ * little different from print(), it simply surrounds the display
+ * value with a marker, for example "BALANCE($1.00, DM 12.00)".
+ * This code is used by other dumping code elsewhere in Ledger.
+ *
+ * valid() returns true if the amounts within the balance are valid.
+ */
+ void dump(std::ostream& out) const {
+ out << "BALANCE(";
+ bool first = true;
+ foreach (const amounts_map::value_type& pair, amounts) {
+ if (first)
+ first = false;
+ else
+ out << ", ";
+ pair.second.print(out);
+ }
+ out << ")";
+ }
+
+ virtual bool valid() const {
+ foreach (const amounts_map::value_type& pair, amounts)
+ if (! pair.second.valid())
+ return false;
+ return true;
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) {
+ bal.print(out, 12);
+ return out;
+}
+
+} // namespace ledger
+
+#endif // _BALANCE_H
diff --git a/src/balpair.h b/src/balpair.h
new file mode 100644
index 00000000..0178519f
--- /dev/null
+++ b/src/balpair.h
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup numerics Core numerics
+ */
+
+/**
+ * @file balpair.h
+ * @author John Wiegley
+ * @date Sun May 20 19:11:58 2007
+ *
+ * @brief Provides an abstraction around balance_t for tracking costs.
+ *
+ * @ingroup numerics
+ *
+ * When a transaction's amount is added to a balance, only the "value"
+ * of the amount is added -- not the associated cost of the
+ * transaction. To provide for this, the balance_pair_t type allows
+ * for adding amounts and costs simultaneously to a single balance.
+ * Both are tracked, and any time either the total amount balance or
+ * the total cost balance may be extracted.
+ *
+ * Note: By default, all balance-like operations operate on the amount
+ * balance, and not the cost. Also, the cost is entirely optional, in
+ * which case a balance_pair_t may be used as if it were a balance_t,
+ * from which is it derived.
+ */
+#ifndef _BALPAIR_H
+#define _BARPAIR_H
+
+#include "balance.h"
+
+namespace ledger {
+
+class balance_pair_t
+ : public balance_t,
+ public equality_comparable<balance_pair_t,
+ equality_comparable<balance_pair_t, balance_t,
+ equality_comparable<balance_pair_t, amount_t,
+ equality_comparable<balance_pair_t, double,
+ equality_comparable<balance_pair_t, unsigned long,
+ equality_comparable<balance_pair_t, long,
+ additive<balance_pair_t,
+ additive<balance_pair_t, balance_t,
+ additive<balance_pair_t, amount_t,
+ additive<balance_pair_t, double,
+ additive<balance_pair_t, unsigned long,
+ additive<balance_pair_t, long,
+ multiplicative<balance_pair_t, amount_t,
+ multiplicative<balance_pair_t, balance_t,
+ multiplicative<balance_pair_t, double,
+ multiplicative<balance_pair_t, unsigned long,
+ multiplicative<balance_pair_t, long> > > > > > > > > > > > > > > > >
+{
+ /**
+ * The `cost' member of a balance pair tracks the cost associated
+ * with each transaction amount that is added. This member is
+ * optional, and if not cost-bearing transactions are added, it will
+ * remain uninitialized.
+ */
+ optional<balance_t> cost;
+
+ friend class value_t;
+ friend class entry_base_t;
+
+public:
+ /**
+ * Constructors. balance_pair_t supports identical forms of construction
+ * to balance_t. See balance_t for more information.
+ */
+ balance_pair_t() {
+ TRACE_CTOR(balance_pair_t, "");
+ }
+ balance_pair_t(const balance_t& bal) : balance_t(bal) {
+ TRACE_CTOR(balance_pair_t, "const balance_t&");
+ }
+ balance_pair_t(const balance_t& bal,
+ const balance_t& cost_bal)
+ : balance_t(bal), cost(cost_bal) {
+ TRACE_CTOR(balance_pair_t, "const balance_t&, const balance_t&");
+ }
+ balance_pair_t(const amount_t& amt) : balance_t(amt) {
+ TRACE_CTOR(balance_pair_t, "const amount_t&");
+ }
+ balance_pair_t(const amount_t& amt, const amount_t& cost_amt)
+ : balance_t(amt), cost(cost_amt) {
+ TRACE_CTOR(balance_pair_t, "const amount_t&, const amount_t&");
+ }
+#ifdef HAVE_GDTOA
+ balance_pair_t(const double val) : balance_t(val) {
+ TRACE_CTOR(balance_pair_t, "const double");
+ }
+#endif
+ balance_pair_t(const unsigned long val) : balance_t(val) {
+ TRACE_CTOR(balance_pair_t, "const unsigned long");
+ }
+ balance_pair_t(const long val) : balance_t(val) {
+ TRACE_CTOR(balance_pair_t, "const long");
+ }
+
+ explicit balance_pair_t(const string& val) : balance_t(val) {
+ TRACE_CTOR(balance_pair_t, "const string&");
+ }
+ explicit balance_pair_t(const char * val) : balance_t(val) {
+ TRACE_CTOR(balance_pair_t, "const char *");
+ }
+
+ /**
+ * Destructor.
+ */
+ virtual ~balance_pair_t() {
+ TRACE_DTOR(balance_pair_t);
+ }
+
+ /**
+ * Assignment and copy operators. A balance pair may be assigned or
+ * copied, and assigned or copied from a balance.
+ */
+ balance_pair_t(const balance_pair_t& bal_pair)
+ : balance_t(bal_pair), cost(bal_pair.cost) {
+ TRACE_CTOR(balance_pair_t, "copy");
+ }
+
+ balance_pair_t& operator=(const balance_pair_t& bal_pair) {
+ if (this != &bal_pair) {
+ balance_t::operator=(bal_pair.quantity());
+ cost = bal_pair.cost;
+ }
+ return *this;
+ }
+ balance_pair_t& operator=(const balance_t& bal) {
+ balance_t::operator=(bal);
+ return *this;
+ }
+ balance_pair_t& operator=(const amount_t& amt) {
+ balance_t::operator=(amt);
+ return *this;
+ }
+
+ balance_t& operator=(const string& str) {
+ return *this = balance_t(str);
+ }
+ balance_t& operator=(const char * str) {
+ return *this = balance_t(str);
+ }
+
+ /**
+ * Binary arithmetic operators. Balances support addition and
+ * subtraction of other balance pairs, balances or amounts, but
+ * multiplication and division are restricted to uncommoditized
+ * amounts only.
+ *
+ * There is also an additional additive method called `add' which
+ * allows for adding an amount and an associated cost
+ * simultaneously. The signature is:
+ * add(amount_t amount, optional<amount_t> cost)
+ */
+ balance_pair_t& operator+=(const balance_pair_t& bal_pair) {
+ balance_t::operator+=(bal_pair);
+ if (bal_pair.cost) {
+ if (! cost)
+ cost = quantity();
+ *cost += *bal_pair.cost;
+ }
+ return *this;
+ }
+ balance_pair_t& operator-=(const balance_pair_t& bal_pair) {
+ balance_t::operator+=(bal_pair);
+ if (bal_pair.cost) {
+ if (! cost)
+ cost = quantity();
+ *cost += *bal_pair.cost;
+ }
+ return *this;
+ }
+
+ virtual balance_pair_t& operator*=(const amount_t& amt) {
+ balance_t::operator*=(amt);
+ if (cost)
+ *cost *= amt;
+ return *this;
+ }
+
+ virtual balance_pair_t& operator/=(const amount_t& amt) {
+ balance_t::operator/=(amt);
+ if (cost)
+ *cost /= amt;
+ return *this;
+ }
+
+ balance_pair_t& add(const amount_t& amt,
+ const optional<amount_t>& a_cost = none) {
+ if (a_cost && ! cost)
+ cost = quantity();
+
+ *this += amt;
+
+ if (cost)
+ *cost += a_cost ? *a_cost : amt;
+
+ return *this;
+ }
+
+ /**
+ * Unary arithmetic operators. There are only a few unary methods
+ * supported for balance pairs (otherwise, the operators inherited
+ * from balance_t are used):
+ *
+ * abs() returns the absolute value of both the quantity and the
+ * cost of a balance pair.
+ *
+ * in_place_negate() negates all the amounts in both the quantity
+ * and the cost.
+ *
+ * in_place_reduce() reduces all the amounts in both the quantity
+ * and the cost.
+ *
+ * in_place_unreduce() unreduces all the amounts in both the
+ * quantity and the cost.
+ *
+ * quantity() returns the balance part of a balance. It is the same
+ * as doing a downcast<balance_t>(balance_pair).
+ */
+ balance_pair_t abs() const {
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.abs();
+
+ if (cost) {
+ balance_t cost_temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ cost_temp += pair.second.abs();
+ return balance_pair_t(temp, cost_temp);
+ }
+ return temp;
+ }
+
+ virtual balance_t& in_place_negate() {
+ balance_t::in_place_negate();
+ if (cost)
+ cost->in_place_negate();
+ return *this;
+ }
+
+ virtual balance_t& in_place_reduce() {
+ // A temporary must be used here because reduction may cause
+ // multiple component amounts to collapse to the same commodity.
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.reduce();
+
+ if (cost) {
+ balance_t cost_temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ cost_temp += pair.second.reduce();
+ return *this = balance_pair_t(temp, cost_temp);
+ }
+ return *this = temp;
+ }
+
+ virtual balance_t& in_place_unreduce() {
+ // A temporary must be used here because unreduction may cause
+ // multiple component amounts to collapse to the same commodity.
+ balance_t temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ temp += pair.second.unreduce();
+
+ if (cost) {
+ balance_t cost_temp;
+ foreach (const amounts_map::value_type& pair, amounts)
+ cost_temp += pair.second.unreduce();
+ return *this = balance_pair_t(temp, cost_temp);
+ }
+ return *this = temp;
+ }
+
+ balance_t& quantity() {
+ return *this;
+ }
+ const balance_t& quantity() const {
+ return *this;
+ }
+
+ /**
+ * Truth tests. An balance pair may be truth tested by comparison
+ * to another balance pair, or by using one of the inherited
+ * operators from balance_t.
+ */
+ bool operator==(const balance_pair_t& bal_pair) const {
+ if (quantity() != bal_pair.quantity())
+ return false;
+
+ if ((cost && ! bal_pair.cost) ||
+ (! cost && bal_pair.cost))
+ return false;
+
+ if (*cost != *bal_pair.cost)
+ return false;
+
+ return true;
+ }
+
+ bool operator==(const balance_t& bal) const {
+ return balance_t::operator==(bal);
+ }
+ bool operator==(const amount_t& amt) const {
+ return balance_t::operator==(amt);
+ }
+ template <typename T>
+ bool operator==(const T& val) const {
+ return balance_t::operator==(val);
+ }
+
+ /**
+ * Debugging methods. There is only one method specifically for
+ * balance pairs to help with debugging:
+ *
+ * valid() returns true if the balances within the balance pair are
+ * valid.
+ */
+ bool valid() const {
+ if (! balance_t::valid())
+ return false;
+
+ if (cost && ! cost->valid())
+ return false;
+
+ return true;
+ }
+};
+
+} // namespace ledger
+
+#endif // _BALPAIR_H
diff --git a/src/binary.cc b/src/binary.cc
new file mode 100644
index 00000000..1744bb5d
--- /dev/null
+++ b/src/binary.cc
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "binary.h"
+
+namespace ledger {
+namespace binary {
+
+void read_bool(std::istream& in, bool& num)
+{
+ read_guard(in, 0x2005);
+ unsigned char val;
+ in.read(reinterpret_cast<char *>(&val), sizeof(val));
+ num = val == 1;
+ read_guard(in, 0x2006);
+}
+
+void read_bool(const char *& data, bool& num)
+{
+ read_guard(data, 0x2005);
+ const unsigned char val = *reinterpret_cast<const unsigned char *>(data);
+ data += sizeof(unsigned char);
+ num = val == 1;
+ read_guard(data, 0x2006);
+}
+
+void read_string(std::istream& in, string& str)
+{
+ read_guard(in, 0x3001);
+
+ unsigned char len;
+ read_number_nocheck(in, len);
+ if (len == 0xff) {
+ unsigned short slen;
+ read_number_nocheck(in, slen);
+ char * buf = new char[slen + 1];
+ in.read(buf, slen);
+ buf[slen] = '\0';
+ str = buf;
+ checked_array_delete(buf);
+ }
+ else if (len) {
+ char buf[256];
+ in.read(buf, len);
+ buf[len] = '\0';
+ str = buf;
+ } else {
+ str = "";
+ }
+
+ read_guard(in, 0x3002);
+}
+
+void read_string(const char *& data, string& str)
+{
+ read_guard(data, 0x3001);
+
+ unsigned char len;
+ read_number_nocheck(data, len);
+ if (len == 0xff) {
+ unsigned short slen;
+ read_number_nocheck(data, slen);
+ str = string(data, slen);
+ data += slen;
+ }
+ else if (len) {
+ str = string(data, len);
+ data += len;
+ }
+ else {
+ str = "";
+ }
+
+ read_guard(data, 0x3002);
+}
+
+void read_string(const char *& data, string * str)
+{
+ read_guard(data, 0x3001);
+
+ unsigned char len;
+ read_number_nocheck(data, len);
+ if (len == 0xff) {
+ unsigned short slen;
+ read_number_nocheck(data, slen);
+ new(str) string(data, slen);
+ data += slen;
+ }
+ else if (len) {
+ new(str) string(data, len);
+ data += len;
+ }
+ else {
+ new(str) string("");
+ }
+
+ read_guard(data, 0x3002);
+}
+
+void read_string(std::istream& in, optional<string>& str)
+{
+ if (read_bool(in)) {
+ string temp;
+ read_string(in, temp);
+ str = temp;
+ } else {
+ str = none;
+ }
+}
+
+void read_string(const char *& data, optional<string>& str)
+{
+ if (read_bool(data)) {
+ string temp;
+ read_string(data, temp);
+ str = temp;
+ } else {
+ str = none;
+ }
+}
+
+void write_bool(std::ostream& out, bool num)
+{
+ write_guard(out, 0x2005);
+ unsigned char val = num ? 1 : 0;
+ out.write(reinterpret_cast<char *>(&val), sizeof(val));
+ write_guard(out, 0x2006);
+}
+
+void write_string(std::ostream& out, const string& str)
+{
+ write_guard(out, 0x3001);
+
+ unsigned long len = str.length();
+ if (len > 255) {
+ assert(len < 65536);
+ write_number_nocheck<unsigned char>(out, 0xff);
+ write_number_nocheck<unsigned short>(out, len);
+ } else {
+ write_number_nocheck<unsigned char>(out, len);
+ }
+
+ if (len)
+ out.write(str.c_str(), len);
+
+ write_guard(out, 0x3002);
+}
+
+void write_string(std::ostream& out, const optional<string>& str)
+{
+ if (str) {
+ write_bool(out, true);
+ write_string(out, *str);
+ } else {
+ write_bool(out, false);
+ }
+}
+
+} // namespace binary
+} // namespace ledger
diff --git a/src/binary.h b/src/binary.h
new file mode 100644
index 00000000..8a2ef681
--- /dev/null
+++ b/src/binary.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BINARY_H
+#define BINARY_H
+
+#include "utils.h"
+
+namespace ledger {
+namespace binary {
+
+template <typename T>
+inline void read_number_nocheck(std::istream& in, T& num) {
+ in.read(reinterpret_cast<char *>(&num), sizeof(num));
+}
+
+template <typename T>
+inline void read_number_nocheck(const char *& data, T& num) {
+ num = *reinterpret_cast<const T *>(data);
+ data += sizeof(T);
+}
+
+template <typename T>
+inline T read_number_nocheck(std::istream& in) {
+ T num;
+ read_number_nocheck(in, num);
+ return num;
+}
+
+template <typename T>
+inline T read_number_nocheck(const char *& data) {
+ T num;
+ read_number_nocheck(data, num);
+ return num;
+}
+
+#if DEBUG_LEVEL >= ALPHA
+#define read_guard(in, id) \
+ if (read_number_nocheck<unsigned short>(in) != id) \
+ assert(false);
+#else
+#define read_guard(in, id)
+#endif
+
+template <typename T>
+inline void read_number(std::istream& in, T& num) {
+ read_guard(in, 0x2003);
+ in.read(reinterpret_cast<char *>(&num), sizeof(num));
+ read_guard(in, 0x2004);
+}
+
+template <typename T>
+inline void read_number(const char *& data, T& num) {
+ read_guard(data, 0x2003);
+ num = *reinterpret_cast<const T *>(data);
+ data += sizeof(T);
+ read_guard(data, 0x2004);
+}
+
+template <typename T>
+inline T read_number(std::istream& in) {
+ T num;
+ read_number(in, num);
+ return num;
+}
+
+template <typename T>
+inline T read_number(const char *& data) {
+ T num;
+ read_number(data, num);
+ return num;
+}
+
+void read_bool(std::istream& in, bool& num);
+void read_bool(const char *& data, bool& num);
+
+inline bool read_bool(std::istream& in) {
+ bool num;
+ read_bool(in, num);
+ return num;
+}
+
+inline bool read_bool(const char *& data) {
+ bool num;
+ read_bool(data, num);
+ return num;
+}
+
+template <typename T>
+void read_long(std::istream& in, T& num)
+{
+ read_guard(in, 0x2001);
+
+ unsigned char len;
+ read_number_nocheck(in, len);
+
+ num = 0;
+ unsigned char temp;
+ if (len > 3) {
+ read_number_nocheck(in, temp);
+ num |= static_cast<unsigned long>(temp) << 24;
+ }
+ if (len > 2) {
+ read_number_nocheck(in, temp);
+ num |= static_cast<unsigned long>(temp) << 16;
+ }
+ if (len > 1) {
+ read_number_nocheck(in, temp);
+ num |= static_cast<unsigned long>(temp) << 8;
+ }
+
+ read_number_nocheck(in, temp);
+ num |= static_cast<unsigned long>(temp);
+
+ read_guard(in, 0x2002);
+}
+
+template <typename T>
+void read_long(const char *& data, T& num)
+{
+ read_guard(data, 0x2001);
+
+ unsigned char len;
+ read_number_nocheck(data, len);
+
+ num = 0;
+ unsigned char temp;
+ if (len > 3) {
+ read_number_nocheck(data, temp);
+ num |= static_cast<unsigned long>(temp) << 24;
+ }
+ if (len > 2) {
+ read_number_nocheck(data, temp);
+ num |= static_cast<unsigned long>(temp) << 16;
+ }
+ if (len > 1) {
+ read_number_nocheck(data, temp);
+ num |= static_cast<unsigned long>(temp) << 8;
+ }
+
+ read_number_nocheck(data, temp);
+ num |= static_cast<unsigned long>(temp);
+
+ read_guard(data, 0x2002);
+}
+
+template <typename T>
+inline T read_long(std::istream& in) {
+ T num;
+ read_long(in, num);
+ return num;
+}
+
+template <typename T>
+inline T read_long(const char *& data) {
+ T num;
+ read_long(data, num);
+ return num;
+}
+
+void read_string(std::istream& in, string& str);
+void read_string(const char *& data, string& str);
+void read_string(const char *& data, string * str);
+
+inline string read_string(std::istream& in) {
+ string temp;
+ read_string(in, temp);
+ return temp;
+}
+
+inline string read_string(const char *& data) {
+ string temp;
+ read_string(data, temp);
+ return temp;
+}
+
+void read_string(std::istream& in, optional<string>& str);
+void read_string(const char *& data, optional<string>& str);
+
+
+template <typename T>
+inline void write_number_nocheck(std::ostream& out, T num) {
+ out.write(reinterpret_cast<char *>(&num), sizeof(num));
+}
+
+#if DEBUG_LEVEL >= ALPHA
+#define write_guard(out, id) \
+ write_number_nocheck<unsigned short>(out, id)
+#else
+#define write_guard(in, id)
+#endif
+
+template <typename T>
+inline void write_number(std::ostream& out, T num) {
+ write_guard(out, 0x2003);
+ out.write(reinterpret_cast<char *>(&num), sizeof(num));
+ write_guard(out, 0x2004);
+}
+
+void write_bool(std::ostream& out, bool num);
+
+template <typename T>
+void write_long(std::ostream& out, T num)
+{
+ write_guard(out, 0x2001);
+
+ unsigned char len = 4;
+ if (static_cast<unsigned long>(num) < 0x00000100UL)
+ len = 1;
+ else if (static_cast<unsigned long>(num) < 0x00010000UL)
+ len = 2;
+ else if (static_cast<unsigned long>(num) < 0x01000000UL)
+ len = 3;
+ write_number_nocheck<unsigned char>(out, len);
+
+ unsigned char temp;
+ if (len > 3) {
+ temp = (static_cast<unsigned long>(num) & 0xFF000000UL) >> 24;
+ write_number_nocheck(out, temp);
+ }
+ if (len > 2) {
+ temp = (static_cast<unsigned long>(num) & 0x00FF0000UL) >> 16;
+ write_number_nocheck(out, temp);
+ }
+ if (len > 1) {
+ temp = (static_cast<unsigned long>(num) & 0x0000FF00UL) >> 8;
+ write_number_nocheck(out, temp);
+ }
+
+ temp = (static_cast<unsigned long>(num) & 0x000000FFUL);
+ write_number_nocheck(out, temp);
+
+ write_guard(out, 0x2002);
+}
+
+void write_string(std::ostream& out, const string& str);
+void write_string(std::ostream& out, const optional<string>& str);
+
+} // namespace binary
+} // namespace ledger
+
+#endif // BINARY_H
diff --git a/src/cache.cc b/src/cache.cc
new file mode 100644
index 00000000..d9ee7549
--- /dev/null
+++ b/src/cache.cc
@@ -0,0 +1,871 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cache.h"
+#include "binary.h"
+
+namespace ledger {
+
+using namespace binary;
+
+#if 0
+void read_xact(const char *& data, xact_t * xact)
+{
+ read_number(data, xact->_date);
+ read_number(data, xact->_date_eff);
+ xact->account = accounts[read_long<account_t::ident_t>(data) - 1];
+
+ unsigned char flag = read_number<unsigned char>(data);
+ if (flag == 0) {
+ xact->amount.read(data);
+ }
+ else if (flag == 1) {
+ xact->amount.read(data);
+ xact->amount_expr = expr_t();
+ xact->amount_expr->set_text(read_string(data));
+ }
+ else {
+ xact->amount_expr = expr_t();
+ xact->amount_expr->read(data);
+ }
+
+ if (read_bool(data)) {
+ xact->cost = amount_t();
+ xact->cost->read(data);
+
+ xact->cost_expr = expr_t();
+ xact->cost_expr->read(data);
+ } else {
+ xact->cost = none;
+ }
+
+ read_number(data, xact->state);
+ xact->set_flags(read_number<xact_t::flags_t>(data));
+ xact->add_flags(XACT_BULK_ALLOC);
+ read_string(data, xact->note);
+
+ xact->beg_pos = read_long<unsigned long>(data);
+ read_long(data, xact->beg_line);
+ xact->end_pos = read_long<unsigned long>(data);
+ read_long(data, xact->end_line);
+
+ xact->data = NULL;
+
+ if (xact->amount_expr)
+ expr_t::compute_amount(xact->amount_expr.get(), xact->amount, xact);
+}
+
+void write_xact(std::ostream& out, xact_t * xact,
+ bool ignore_calculated)
+{
+ write_number(out, xact->_date);
+ write_number(out, xact->_date_eff);
+ write_long(out, xact->account->ident);
+
+ if (ignore_calculated && xact->has_flags(XACT_CALCULATED)) {
+ write_number<unsigned char>(out, 0);
+ amount_t().write(out);
+ }
+ else if (xact->amount_expr) {
+ write_number<unsigned char>(out, 2);
+ // jww (2008-07-30): Um, is this right?
+ xact->amount_expr->write(out);
+ }
+ else if (! xact->amount_expr->text().empty()) {
+ write_number<unsigned char>(out, 1);
+ xact->amount.write(out);
+ write_string(out, xact->amount_expr->text());
+ }
+ else {
+ write_number<unsigned char>(out, 0);
+ xact->amount.write(out);
+ }
+
+ if (xact->cost &&
+ (! (ignore_calculated && xact->has_flags(XACT_CALCULATED)))) {
+ write_bool(out, true);
+ xact->cost->write(out);
+ // jww (2008-07-30): What if there is no cost expression?
+ xact->cost_expr->write(out);
+ } else {
+ write_bool(out, false);
+ }
+
+ write_number(out, xact->state);
+ write_number(out, xact->flags());
+ write_string(out, xact->note);
+
+ write_long(out, xact->beg_pos);
+ write_long(out, xact->beg_line);
+ write_long(out, xact->end_pos);
+ write_long(out, xact->end_line);
+}
+
+void read_entry_base(const char *& data, entry_base_t * entry,
+ xact_t *& xact_pool, bool& finalize)
+{
+ read_long(data, entry->src_idx);
+ // jww (2008-07-31): Use istream_pos_type
+ entry->beg_pos = read_long<unsigned long>(data);
+ read_long(data, entry->beg_line);
+ entry->end_pos = read_long<unsigned long>(data);
+ read_long(data, entry->end_line);
+
+ bool ignore_calculated = read_bool(data);
+
+ for (std::size_t i = 0, count = read_long<std::size_t>(data);
+ i < count;
+ i++) {
+ new(xact_pool) xact_t;
+ read_xact(data, xact_pool);
+ if (ignore_calculated && xact_pool->has_flags(XACT_CALCULATED))
+ finalize = true;
+ entry->add_xact(xact_pool++);
+ }
+}
+
+void write_entry_base(std::ostream& out, entry_base_t * entry)
+{
+ write_long(out, entry->src_idx);
+ write_long(out, entry->beg_pos);
+ write_long(out, entry->beg_line);
+ write_long(out, entry->end_pos);
+ write_long(out, entry->end_line);
+
+ bool ignore_calculated = false;
+ foreach (transaction_t * xact, entry->xacts)
+ if (xact->amount_expr) {
+ ignore_calculated = true;
+ break;
+ }
+
+ write_bool(out, ignore_calculated);
+
+ write_long(out, entry->xacts.size());
+ foreach (transaction_t * xact, entry->xacts)
+ write_xact(out, xact, ignore_calculated);
+}
+
+void read_entry(const char *& data, entry_t * entry,
+ xact_t *& xact_pool, bool& finalize)
+{
+ read_entry_base(data, entry, xact_pool, finalize);
+ read_number(data, entry->_date);
+ read_number(data, entry->_date_eff);
+ read_string(data, entry->code);
+ read_string(data, entry->payee);
+}
+
+void write_entry(std::ostream& out, entry_t * entry)
+{
+ write_entry_base(out, entry);
+ write_number(out, entry->_date);
+ write_number(out, entry->_date_eff);
+ write_string(out, entry->code);
+ write_string(out, entry->payee);
+}
+
+void read_auto_entry(const char *& data, auto_entry_t * entry,
+ xact_t *& xact_pool)
+{
+ bool ignore;
+ read_entry_base(data, entry, xact_pool, ignore);
+
+ expr_t expr;
+ expr.read(data);
+ entry->predicate = item_predicate<xact_t>(expr);
+}
+
+void write_auto_entry(std::ostream& out, auto_entry_t * entry)
+{
+ write_entry_base(out, entry);
+ entry->predicate.predicate.write(out);
+}
+
+void read_period_entry(const char *& data, period_entry_t * entry,
+ xact_t *& xact_pool, bool& finalize)
+{
+ read_entry_base(data, entry, xact_pool, finalize);
+ read_string(data, &entry->period_string);
+ std::istringstream stream(entry->period_string);
+ entry->period.parse(stream);
+}
+
+void write_period_entry(std::ostream& out, period_entry_t * entry)
+{
+ write_entry_base(out, entry);
+ write_string(out, entry->period_string);
+}
+
+commodity_t::base_t * read_commodity_base(const char *& data)
+{
+ string str;
+
+ read_string(data, str);
+
+ std::auto_ptr<commodity_t::base_t> commodity(new commodity_t::base_t(str));
+
+ read_string(data, str);
+ if (! str.empty())
+ commodity->name = str;
+
+ read_string(data, str);
+ if (! str.empty())
+ commodity->note = str;
+
+ read_number(data, commodity->precision);
+ unsigned long flags;
+ read_number(data, flags);
+ commodity->set_flags(flags);
+
+ return commodity.release();
+}
+
+void write_commodity_base(std::ostream& out, commodity_t::base_t * commodity)
+{
+ // jww (2008-04-22): Not using this anymore?
+ //commodity->ident = ++base_commodity_index;
+
+ write_string(out, commodity->symbol);
+ // jww (2008-04-22): What to do with optional members?
+ write_string(out, *commodity->name);
+ write_string(out, *commodity->note);
+ write_number(out, commodity->precision);
+ write_number(out, commodity->flags());
+}
+
+void read_commodity_base_extra(const char *& data,
+ commodity_t::ident_t ident)
+{
+ commodity_t::base_t * commodity = base_commodities[ident];
+
+ bool read_history = false;
+ // jww (2008-07-31): create a function read_size which does
+ // read_long<std::size_t>. Don't use read_number<std::size_t>, but it
+ // wastes too much space.
+ for (std::size_t i = 0, count = read_long<std::size_t>(data);
+ i < count;
+ i++) {
+ datetime_t when;
+ read_number(data, when);
+ amount_t amt;
+ amt.read(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 = commodity_t::history_t();
+ commodity->history->prices.insert(commodity_t::base_t::history_pair(when, amt));
+
+ read_history = true;
+ }
+ if (read_history)
+ read_number(data, commodity->history->last_lookup);
+
+ if (read_bool(data)) {
+ amount_t amt;
+ amt.read(data);
+ commodity->smaller = amount_t(amt);
+ }
+
+ if (read_bool(data)) {
+ amount_t amt;
+ amt.read(data);
+ commodity->larger = amount_t(amt);
+ }
+}
+
+void write_commodity_base_extra(std::ostream& out,
+ commodity_t::base_t * commodity)
+{
+#if 0
+ // jww (2008-04-22): What did bogus_time used to do?
+ if (commodity->history && commodity->history->bogus_time)
+ commodity->remove_price(commodity->history->bogus_time);
+#endif
+
+ if (! commodity->history) {
+ write_long<unsigned long>(out, 0);
+ } else {
+ write_long<unsigned long>(out, commodity->history->prices.size());
+ foreach (commodity_t::history_map::value_type& pair,
+ commodity->history->prices) {
+ write_number(out, pair.first);
+ pair.second.write(out);
+ }
+ write_number(out, commodity->history->last_lookup);
+ }
+
+ if (commodity->smaller) {
+ write_bool(out, true);
+ commodity->smaller->write(out);
+ } else {
+ write_bool(out, false);
+ }
+
+ if (commodity->larger) {
+ write_bool(out, true);
+ commodity->larger->write(out);
+ } else {
+ write_bool(out, false);
+ }
+}
+
+commodity_t * read_commodity(const char *& data)
+{
+ commodity_t::base_t * base =
+ base_commodities[read_long<commodity_t::ident_t>(data) - 1];
+
+ commodity_t * commodity =
+ new commodity_t(amount_t::current_pool,
+ shared_ptr<commodity_t::base_t>(base));
+
+ *commodities_next++ = commodity;
+
+ string str;
+ read_string(data, str);
+ if (! str.empty())
+ commodity->qualified_symbol = str;
+ commodity->annotated = false;
+
+ return commodity;
+}
+
+void write_commodity(std::ostream& out, commodity_t * commodity)
+{
+ commodity->ident = ++commodity_index;
+
+ // jww (2008-04-22): Is this used anymore?
+ //write_long(out, commodity->base->ident);
+ // jww (2008-04-22): Optional!
+ write_string(out, *commodity->qualified_symbol);
+}
+
+commodity_t * read_commodity_annotated(const char *& data)
+{
+ commodity_t * commodity =
+ commodities[read_long<commodity_t::ident_t>(data) - 1];
+
+ annotation_t details;
+
+ string str;
+ read_string(data, str);
+
+ // This read-and-then-assign causes a new amount to be allocated which does
+ // not live within the bulk allocation pool, since that pool will be deleted
+ // *before* the commodities are destroyed.
+ amount_t amt;
+ amt.read(data);
+ details.price = amt;
+
+#if 0
+ // jww (2008-04-22): These are optional members!
+ read_number(data, details.date);
+ read_string(data, details.tag);
+#endif
+
+ annotated_commodity_t * ann_comm =
+ new annotated_commodity_t(commodity, details);
+ *commodities_next++ = ann_comm;
+
+ if (! str.empty())
+ ann_comm->qualified_symbol = str;
+
+ return ann_comm;
+}
+
+void write_commodity_annotated(std::ostream& out,
+ commodity_t * commodity)
+{
+ commodity->ident = ++commodity_index;
+
+ // jww (2008-04-22): No longer needed?
+ //write_long(out, commodity->base->ident);
+ // jww (2008-04-22): Optional!
+ write_string(out, *commodity->qualified_symbol);
+
+ annotated_commodity_t * ann_comm =
+ static_cast<annotated_commodity_t *>(commodity);
+
+ // jww (2008-04-22): No longer needed?
+ //write_long(out, ann_comm->base->ident);
+ // jww (2008-04-22): Make a write_annotation_details function; and optional!
+ ann_comm->details.price->write(out);
+ ann_comm->details.date->write(out);
+ ann_comm->details.tag->write(out);
+}
+
+inline
+account_t * read_account(const char *& data, account_t * master = NULL)
+{
+ account_t * acct = new account_t(NULL);
+
+ accounts[account_ident++] = acct;
+
+ account_t::ident_t id;
+ read_long(data, id); // parent id
+ if (id == 0xffffffff)
+ acct->parent = NULL;
+ else
+ acct->parent = accounts[id - 1];
+
+ read_string(data, acct->name);
+ read_string(data, acct->note);
+ read_number(data, acct->depth);
+
+ // If all of the subaccounts will be added to a different master
+ // account, throw away what we've learned about the recorded
+ // journal's own master account.
+
+ if (master && acct != master) {
+ checked_delete(acct);
+ acct = master;
+ }
+
+ for (std::size_t i = 0, count = read_long<std::size_t>(data);
+ i < count;
+ i++) {
+ account_t * child = read_account(data);
+ child->parent = acct;
+ assert(acct != child);
+ acct->add_account(child);
+ }
+
+ return acct;
+}
+
+namespace {
+ inline account_t::ident_t count_accounts(account_t * account)
+ {
+ account_t::ident_t count = 1;
+
+ foreach (accounts_map::value_type& pair, account->accounts)
+ count += count_accounts(pair.second);
+
+ return count;
+ }
+}
+
+void write_account(std::ostream& out, account_t * account)
+{
+ account->ident = ++account_ident;
+
+ if (account->parent)
+ write_long(out, account->parent->ident);
+ else
+ write_long<account_t::ident_t>(out, 0xffffffff);
+
+ write_string(out, account->name);
+ write_string(out, account->note);
+ write_number(out, account->depth);
+
+ write_number<std::size_t>(out, account->accounts.size());
+
+ foreach (accounts_map::value_type& pair, account->accounts)
+ write_account(out, pair.second);
+}
+
+unsigned int read_journal(std::istream& in,
+ const path& file,
+ journal_t& journal,
+ account_t * master)
+{
+ using namespace binary;
+
+ // Read in the files that participated in this journal, so that they
+ // can be checked for changes on reading.
+
+ if (! file.empty()) {
+ for (unsigned short i = 0, count = read_number<unsigned short>(in);
+ i < count;
+ i++) {
+ path pathname = read_string(in);
+ std::time_t old_mtime;
+ read_number(in, old_mtime);
+ struct stat info;
+ // jww (2008-04-22): can this be done differently now?
+ stat(pathname.string().c_str(), &info);
+ if (std::difftime(info.st_mtime, old_mtime) > 0)
+ return 0;
+
+ sources.push_back(pathname);
+ }
+
+ // Make sure that the cache uses the same price database,
+ // otherwise it means that LEDGER_PRICE_DB has been changed, and
+ // we should ignore this cache file.
+ if (read_bool(in)) {
+ string pathname;
+ read_string(in, pathname);
+ if (! price_db ||
+ price_db->string() != std::string(pathname))
+ return 0;
+ }
+ }
+
+ // jww (2008-07-31): bind master to session.master
+
+ if (read_bool(data))
+ basket = accounts[read_long<account_t::ident_t>(data) - 1];
+
+ // Read in the entries and xacts
+
+ for (std::size_t i = 0; i < count; i++) {
+ new(entry_pool) entry_t;
+ bool finalize = false;
+ read_entry(data, entry_pool, xact_pool, finalize);
+ entry_pool->journal = &journal;
+ if (finalize && ! entry_pool->finalize())
+ continue;
+ entries.push_back(entry_pool++);
+ }
+
+ for (std::size_t i = 0; i < auto_count; i++) {
+ auto_entry_t * auto_entry = new auto_entry_t;
+ read_auto_entry(data, auto_entry, xact_pool);
+ auto_entry->journal = &journal;
+ auto_entries.push_back(auto_entry);
+ }
+
+ for (std::size_t i = 0; i < period_count; i++) {
+ period_entry_t * period_entry = new period_entry_t;
+ bool finalize = false;
+ read_period_entry(data, period_entry, xact_pool, finalize);
+ period_entry->journal = &journal;
+ if (finalize && ! period_entry->finalize())
+ continue;
+ period_entries.push_back(period_entry);
+ }
+
+ VERIFY(journal.valid());
+
+ return count;
+}
+
+std::pair<std::size_t, std::size_t>
+write_journal(std::ostream& out, const journal_t& journal)
+{
+ using namespace binary;
+
+ // Write out the files that participated in this journal, so that
+ // they can be checked for changes on reading.
+
+ if (sources.empty()) {
+ write_number<unsigned short>(out, 0);
+ } else {
+ write_number<unsigned short>(out, sources.size());
+ foreach (const path& path, sources) {
+ write_string(out, path.string());
+ struct stat info;
+ stat(path.string().c_str(), &info);
+ write_number(out, std::time_t(info.st_mtime));
+ }
+
+ // Write out the price database that relates to this data file, so
+ // that if it ever changes the cache can be invalidated.
+ if (price_db) {
+ write_bool(out, true);
+ write_string(out, price_db->string());
+ } else {
+ write_bool(out, false);
+ }
+ }
+
+ // Write out the basket accounts
+
+ if (basket) {
+ write_bool(out, true);
+ write_long(out, basket->ident);
+ } else {
+ write_bool(out, false);
+ }
+
+ // Write out the entries and xacts
+
+ std::size_t this_entry_count = 0;
+ std::size_t this_xact_count = 0;
+
+ foreach (entry_t * entry, entries) {
+ write_entry(out, entry);
+
+ this_entry_count++;
+ this_xact_count += entry->xacts.size();
+ }
+
+ foreach (auto_entry_t * entry, auto_entries) {
+ write_auto_entry(out, entry);
+
+ this_entry_count++;
+ this_xact_count += entry->xacts.size();
+ }
+
+ foreach (period_entry_t * entry, period_entries) {
+ write_period_entry(out, entry);
+
+ this_entry_count++;
+ this_xact_count += entry->xacts.size();
+ }
+
+ return std::pair<std::size_t, std::size_t>(this_entry_count,
+ this_xact_count);
+}
+
+std::size_t read_session(std::istream& in,
+ const path& file,
+ session_t& session)
+{
+ using namespace binary;
+
+ // Read all of the data in at once, so that we're just dealing with
+ // a big data buffer.
+
+ std::size_t data_size = read_number<std::size_t>(in);
+
+ scoped_array<char> data_pool(new char[data_size]);
+
+ in.read(data_pool, data_size);
+
+ const char * data = data_pool.get();
+
+ // Read in the accounts
+
+ accounts.resize(read_number<std::size_t>(data));
+ account_ident = 0;
+
+ if (session.master)
+ checked_delete(session.master);
+ session.master = read_account(data);
+
+ // Allocate the memory needed for the entries, xacts and bigints in one
+ // large block, which is then chopped up and custom constructed as
+ // necessary.
+
+ entry_count = read_number<std::size_t>(data);
+ auto_entry_count = read_number<std::size_t>(data);
+ period_entry_count = read_number<std::size_t>(data);
+ xact_count = read_number<std::size_t>(data);
+ bigints_count = read_number<std::size_t>(data);
+
+#define ENTRIES_SIZE (sizeof(entry_t) * entry_count)
+#define XACTS_SIZE (sizeof(xact_t) * xact_count)
+#define BIGINTS_SIZE (amount_t::sizeof_bigint_t() * bigints_count)
+
+#define ENTRIES_OFFSET 0
+#define XACTS_OFFSET ENTRIES_SIZE
+#define BIGINTS_OFFSET (ENTRIES_SIZE + XACTS_SIZE)
+
+ item_pool.reset(new char[ENTRIES_SIZE + XACTS_SIZE + BIGINTS_SIZE]);
+
+ entry_pool = reinterpret_cast<entry_t *>(item_pool.get() + ENTRIES_OFFSET);
+ xact_pool = reinterpret_cast<xact_t *>(item_pool.get() + XACTS_OFFSET);
+ bigints = item_pool.get() + BIGINTS_OFFSET;
+ bigints_next = bigints;
+ bigint_ident = 0;
+
+#if 0
+ // Read in the base commodities and the derived commodities
+
+ base_commodity_count = read_number<std::size_t>(data);
+ base_commodities.resize(base_commodity_count);
+
+ for (std::size_t i = 0; i < base_commodity_count; i++) {
+ commodity_t::base_t * base = read_commodity_base(data);
+ session.commodity_pool->commodities.push_back(base);
+
+ std::pair<base_commodities_map::iterator, bool> result =
+ commodity_base_t::commodities.insert
+ (base_commodities_pair(commodity->symbol, commodity));
+ if (! result.second) {
+ base_commodities_map::iterator c =
+ commodity_t::base_t::commodities.find(commodity->symbol);
+
+ // It's possible the user might have used a commodity in a value
+ // expression passed to an option, we'll just override the flags, but
+ // keep the commodity pointer intact.
+ if (c == commodity_t::base_t::commodities.end())
+ throw_(cache_error, "Failed to read base commodity from cache: "
+ << commodity->symbol);
+
+ (*c).second->name = commodity->name;
+ (*c).second->note = commodity->note;
+ (*c).second->precision = commodity->precision;
+ (*c).second->flags = commodity->flags;
+
+ if ((*c).second->smaller)
+ checked_delete((*c).second->smaller);
+ (*c).second->smaller = commodity->smaller;
+ if ((*c).second->larger)
+ checked_delete((*c).second->larger);
+ (*c).second->larger = commodity->larger;
+
+ *(base_commodities_next - 1) = (*c).second;
+
+ checked_delete(commodity);
+ }
+ }
+
+ commodity_count = read_number<std::size_t>(data);
+ commodities.resize(commodity_count);
+
+ for (std::size_t i = 0; i < commodity_count; i++) {
+ commodity_t * commodity;
+ string mapping_key;
+
+ if (! read_bool(data)) {
+ commodity = read_commodity(data);
+ mapping_key = commodity->base->symbol;
+ } else {
+ read_string(data, mapping_key);
+ commodity = read_commodity_annotated(data);
+ }
+
+ session.commodity_pool->commodities.push_back(commodity);
+
+ if (! result.second) {
+ commodities_map::iterator c =
+ commodity_t::commodities.find(mapping_key);
+ if (c == commodity_t::commodities.end())
+ throw_(cache_error, "Failed to read commodity from cache: "
+ << commodity->symbol());
+
+ *(commodities_next - 1) = (*c).second;
+ checked_delete(commodity);
+ }
+ }
+
+ for (std::size_t i = 0; i < base_commodity_count; i++)
+ read_commodity_base_extra(data, i);
+
+ commodity_t::ident_t ident = read_number<commodity_t::ident_t>(data);
+ if (ident == 0xffffffff || ident == 0)
+ session.commodity_pool->default_commodity = NULL;
+ else
+ session.commodity_pool->default_commodity = commodities[ident - 1];
+#endif
+
+ // Clean up and return the number of entries read
+
+ accounts.clear();
+ commodities.clear();
+
+ VERIFY(session.valid());
+
+ return count;
+}
+
+void write_session(std::ostream& out, session_t& session)
+{
+ using namespace binary;
+
+ write_number_nocheck(out, binary_magic_number);
+ write_number_nocheck(out, format_version);
+
+ // This number gets patched at the end of the function
+ ostream_pos_type data_val = out.tellp();
+ write_number<std::size_t>(out, 0);
+
+ // Write out the accounts
+
+ write_number<std::size_t>(out, count_accounts(session.master));
+ write_account(out, session.master);
+
+ // Write out the number of entries, xacts, and amounts
+
+ write_number<std::size_t>(out, entries.size());
+ write_number<std::size_t>(out, auto_entries.size());
+ write_number<std::size_t>(out, period_entries.size());
+
+ // These two numbers get patched at the end of the function
+ ostream_pos_type xacts_val = out.tellp();
+ write_number<std::size_t>(out, 0);
+ ostream_pos_type bigints_val = out.tellp();
+ write_number<std::size_t>(out, 0);
+
+ bigint_ident = 0;
+
+#if 0
+ // Write out the commodities
+ // jww (2008-04-22): This whole section needs to be reworked
+
+ write_number<std::size_t>(out, session.commodity_pool->commodities.size());
+ write_number<std::size_t>(out, session.commodity_pool->commodities.size());
+
+ for (base_commodities_map::value_type pair, commodity_t::base_t::commodities)
+ write_commodity_base(out, pair.second);
+
+ write_number<commodity_t::ident_t>
+ (out, commodity_t::commodities.size());
+
+ for (commodities_map::value_type pair, commodity_t::commodities) {
+ if (! pair.second->annotated) {
+ write_bool(out, false);
+ write_commodity(out, pair.second);
+ }
+ }
+
+ for (commodities_map::value_type pair, commodity_t::commodities) {
+ if (pair.second->annotated) {
+ write_bool(out, true);
+ write_string(out, pair.first); // the mapping key
+ write_commodity_annotated(out, pair.second);
+ }
+ }
+
+ // Write out the history and smaller/larger convertible links after
+ // both the base and the main commodities have been written, since
+ // the amounts in both will refer to the mains.
+
+ for (base_commodities_map::const_iterator i =
+ commodity_t::base_t::commodities.begin();
+ i != commodity_t::base_t::commodities.end();
+ i++)
+ write_commodity_base_extra(out, (*i).second);
+
+ if (commodity_t::default_commodity)
+ write_number(out, commodity_t::default_commodity->ident);
+ else
+ write_number<commodity_t::ident_t>(out, 0xffffffff);
+#endif
+
+ // Back-patch several counts which were not known beforehand
+
+ out.seekp(data_val);
+ write_number<std::size_t>(out, (static_cast<std::size_t>(out.tellp()) -
+ static_cast<std::size_t>(data_val) -
+ sizeof(std::size_t)));
+ out.seekp(xacts_val);
+ write_number<std::size_t>(out, xact_count);
+ out.seekp(bigints_val);
+ write_number<std::size_t>(out, bigints_count);
+}
+#endif
+
+} // namespace ledger
diff --git a/src/cache.h b/src/cache.h
new file mode 100644
index 00000000..43123f9f
--- /dev/null
+++ b/src/cache.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CACHE_H
+#define CACHE_H
+
+#include "utils.h"
+#include "session.h"
+#include "journal.h"
+#include "account.h"
+
+namespace ledger {
+
+DECLARE_EXCEPTION(cache_error, std::runtime_error);
+
+class binary_cache_t
+{
+ static const unsigned long binary_magic_number = 0xFFEED765;
+#if defined(DEBUG_ON)
+ static const unsigned long format_version = 0x0002060d;
+#else
+ static const unsigned long format_version = 0x0002060c;
+#endif
+
+ scoped_array<char> item_pool;
+
+ std::vector<account_t *> accounts;
+ account_t::ident_t account_ident;
+
+ entry_t * entry_pool; // points into item_pool
+ std::size_t entry_count;
+ std::size_t auto_entry_count;
+ std::size_t period_entry_count;
+
+ xact_t * xact_pool; // points into item_pool
+ std::size_t xact_count;
+
+#if 0
+ commodity_base_t ** base_commodities; // allocated
+ commodity_base_t ** base_commodities_next;
+ uint_fast32_t base_commodity_index;
+ std::size_t base_commodity_count;
+#endif
+
+ commodity_t ** commodities; // allocated
+ commodity_t ** commodities_next;
+ uint_fast32_t commodity_ident;
+ std::size_t commodity_count;
+
+ char * bigints; // points into item_pool
+ char * bigints_next;
+ uint_fast32_t bigints_index;
+ std::size_t bigints_count;
+
+ void read_xact(const char *& data, xact_t * xact);
+ void write_xact(std::ostream& out, xact_t * xact,
+ bool ignore_calculated);
+
+ void read_entry_base(const char *& data, entry_base_t * entry,
+ xact_t *& xact_pool, bool& finalize);
+ void write_entry_base(std::ostream& out, entry_base_t * entry);
+ void read_entry(const char *& data, entry_t * entry,
+ xact_t *& xact_pool, bool& finalize);
+ void write_entry(std::ostream& out, entry_t * entry);
+ void read_auto_entry(const char *& data, auto_entry_t * entry,
+ xact_t *& xact_pool);
+ void write_auto_entry(std::ostream& out, auto_entry_t * entry);
+ void read_period_entry(const char *& data, period_entry_t * entry,
+ xact_t *& xact_pool, bool& finalize);
+ void write_period_entry(std::ostream& out, period_entry_t * entry);
+
+#if 0
+ commodity_t::base_t * read_commodity_base(const char *& data);
+ void write_commodity_base(std::ostream& out, commodity_t::base_t * commodity);
+ void read_commodity_base_extra(const char *& data,
+ commodity_t::ident_t ident);
+ void write_commodity_base_extra(std::ostream& out,
+ commodity_t::base_t * commodity);
+#endif
+
+ commodity_t * read_commodity(const char *& data);
+ void write_commodity(std::ostream& out, commodity_t * commodity);
+ commodity_t * read_commodity_annotated(const char *& data);
+ void write_commodity_annotated(std::ostream& out,
+ commodity_t * commodity);
+
+ account_t * read_account(const char *& data, account_t * master = NULL);
+ void write_account(std::ostream& out);
+
+ std::size_t read_journal(std::istream& in,
+ const path& file,
+ journal_t& journal,
+ account_t * master);
+ void write_journal(std::ostream& out,
+ const journal_t& journal);
+
+public:
+ binary_cache_t()
+ : account_ident(0),
+#if 0
+ base_commodity_ident(0),
+#endif
+ commodity_ident(0)
+ {
+ }
+
+ std::size_t read_session(std::istream& in, const path& file);
+ void write_session(std::ostream& out, session_t& session);
+
+};
+
+} // namespace ledger
+
+#endif // CACHE_H
diff --git a/src/commodity.cc b/src/commodity.cc
new file mode 100644
index 00000000..a1c1e2c9
--- /dev/null
+++ b/src/commodity.cc
@@ -0,0 +1,667 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file commodity.cc
+ * @author John Wiegley
+ * @date Thu Apr 26 15:19:46 2007
+ *
+ * @brief Types for dealing with commodities.
+ *
+ * This file defines member functions for flavors of commodity_t.
+ */
+
+#include "amount.h"
+
+namespace ledger {
+
+void commodity_t::add_price(const datetime_t& date,
+ const amount_t& price)
+{
+ if (! base->history)
+ base->history = history_t();
+
+ history_map::iterator i = base->history->prices.find(date);
+ if (i != base->history->prices.end()) {
+ (*i).second = price;
+ } else {
+ std::pair<history_map::iterator, bool> result
+ = base->history->prices.insert(history_map::value_type(date, price));
+ assert(result.second);
+ }
+}
+
+bool commodity_t::remove_price(const datetime_t& date)
+{
+ if (base->history) {
+ history_map::size_type n = base->history->prices.erase(date);
+ if (n > 0) {
+ if (base->history->prices.empty())
+ base->history.reset();
+ return true;
+ }
+ }
+ return false;
+}
+
+optional<amount_t> commodity_t::value(const optional<datetime_t>& moment)
+{
+ optional<datetime_t> age;
+ optional<amount_t> price;
+
+ if (base->history) {
+ assert(base->history->prices.size() > 0);
+
+ if (! moment) {
+ history_map::reverse_iterator r = base->history->prices.rbegin();
+ age = (*r).first;
+ price = (*r).second;
+ } else {
+ history_map::iterator i = base->history->prices.lower_bound(*moment);
+ if (i == base->history->prices.end()) {
+ history_map::reverse_iterator r = base->history->prices.rbegin();
+ age = (*r).first;
+ price = (*r).second;
+ } else {
+ age = (*i).first;
+ if (*moment != *age) {
+ if (i != base->history->prices.begin()) {
+ --i;
+ age = (*i).first;
+ price = (*i).second;
+ } else {
+ age = none;
+ }
+ } else {
+ price = (*i).second;
+ }
+ }
+ }
+ }
+
+ if (! has_flags(COMMODITY_STYLE_NOMARKET) && parent().get_quote) {
+ if (optional<amount_t> quote = parent().get_quote
+ (*this, age, moment,
+ (base->history && base->history->prices.size() > 0 ?
+ (*base->history->prices.rbegin()).first : optional<datetime_t>())))
+ return *quote;
+ }
+ return price;
+}
+
+amount_t commodity_t::exchange(const amount_t& amount,
+ amount_t& final_cost, // out
+ amount_t& basis_cost, // out
+ const optional<amount_t>& total_cost_,
+ const optional<amount_t>& per_unit_cost_,
+ const optional<datetime_t>& moment,
+ const optional<string>& tag)
+{
+ // (assert (or (and total-cost (not per-unit-cost))
+ // (and per-unit-cost (not total-cost))))
+
+ assert((total_cost_ && ! per_unit_cost_) || (per_unit_cost_ && ! total_cost_));
+
+ // (let* ((commodity (amount-commodity amount))
+ // (current-annotation
+ // (and (annotated-commodity-p commodity)
+ // (commodity-annotation commodity)))
+ // (base-commodity (if (annotated-commodity-p commodity)
+ // (get-referent commodity)
+ // commodity))
+ // (per-unit-cost (or per-unit-cost
+ // (divide total-cost amount)))
+ // (total-cost (or total-cost
+ // (multiply per-unit-cost amount))))
+
+ commodity_t& commodity(amount.commodity());
+
+ annotation_t * current_annotation = NULL;
+ if (commodity.annotated)
+ current_annotation = &as_annotated_commodity(commodity).details;
+
+ commodity_t& base_commodity
+ (current_annotation ?
+ as_annotated_commodity(commodity).referent() : commodity);
+
+ amount_t per_unit_cost(per_unit_cost_ ?
+ *per_unit_cost_ : *total_cost_ / amount);
+ final_cost = total_cost_ ? *total_cost_ : *per_unit_cost_ * amount;
+
+ // Add a price history entry for this conversion if we know when it took
+ // place
+
+ // (if (and moment (not (commodity-no-market-price-p base-commodity)))
+ // (add-price base-commodity per-unit-cost moment))
+
+ if (moment && ! commodity.has_flags(COMMODITY_STYLE_NOMARKET))
+ base_commodity.add_price(*moment, per_unit_cost);
+
+ // ;; returns: ANNOTATED-AMOUNT TOTAL-COST BASIS-COST
+ // (values (annotate-commodity
+ // amount
+ // (make-commodity-annotation :price per-unit-cost
+ // :date moment
+ // :tag tag))
+ // total-cost
+ // (if current-annotation
+ // (multiply (annotation-price current-annotation) amount)
+ // total-cost))))
+
+ if (current_annotation && current_annotation->price)
+ basis_cost = *current_annotation->price * amount;
+ else
+ basis_cost = final_cost;
+
+ amount_t ann_amount(amount);
+ ann_amount.annotate(annotation_t(per_unit_cost, moment->date(), tag));
+ return ann_amount;
+}
+
+commodity_t::operator bool() const
+{
+ return this != parent().null_commodity;
+}
+
+bool commodity_t::symbol_needs_quotes(const string& symbol)
+{
+ foreach (char ch, symbol)
+ if (std::isspace(ch) || std::isdigit(ch) || ch == '-' || ch == '.')
+ return true;
+
+ return false;
+}
+
+void commodity_t::parse_symbol(std::istream& in, string& symbol)
+{
+ // Invalid commodity characters:
+ // SPACE, TAB, NEWLINE, RETURN
+ // 0-9 . , ; - + * / ^ ? : & | ! =
+ // < > { } [ ] ( ) @
+
+ static int invalid_chars[256] = {
+ /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
+ /* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
+ /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ /* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
+ /* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
+ /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ /* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+
+ char buf[256];
+ char c = peek_next_nonws(in);
+ if (c == '"') {
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != '"');
+ if (c == '"')
+ in.get(c);
+ else
+ throw_(amount_error, "Quoted commodity symbol lacks closing quote");
+ } else {
+ READ_INTO(in, buf, 255, c, ! invalid_chars[static_cast<unsigned char>(c)]);
+ }
+ symbol = buf;
+}
+
+void commodity_t::parse_symbol(char *& p, string& symbol)
+{
+ if (*p == '"') {
+ char * q = std::strchr(p + 1, '"');
+ if (! q)
+ throw_(amount_error, "Quoted commodity symbol lacks closing quote");
+ symbol = string(p + 1, 0, q - p - 1);
+ p = q + 2;
+ } else {
+ char * q = next_element(p);
+ symbol = p;
+ if (q)
+ p = q;
+ else
+ p += symbol.length();
+ }
+ if (symbol.empty())
+ throw_(amount_error, "Failed to parse commodity");
+}
+
+bool commodity_t::valid() const
+{
+ if (symbol().empty() && this != parent().null_commodity) {
+ DEBUG("ledger.validate",
+ "commodity_t: symbol().empty() && this != null_commodity");
+ return false;
+ }
+
+ if (annotated && ! base) {
+ DEBUG("ledger.validate", "commodity_t: annotated && ! base");
+ return false;
+ }
+
+ if (precision() > 16) {
+ DEBUG("ledger.validate", "commodity_t: precision() > 16");
+ return false;
+ }
+
+ return true;
+}
+
+void annotation_t::parse(std::istream& in)
+{
+ do {
+ char buf[256];
+ char c = peek_next_nonws(in);
+ if (c == '{') {
+ if (price)
+ throw_(amount_error, "Commodity specifies more than one price");
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != '}');
+ if (c == '}')
+ in.get(c);
+ else
+ throw_(amount_error, "Commodity price lacks closing brace");
+
+ amount_t temp;
+ temp.parse(buf, AMOUNT_PARSE_NO_MIGRATE);
+ temp.in_place_reduce();
+
+ // Since this price will maintain its own precision, make sure
+ // it is at least as large as the base commodity, since the user
+ // may have only specified {$1} or something similar.
+
+ if (temp.has_commodity() &&
+ temp.precision() < temp.commodity().precision())
+ temp = temp.round(); // no need to retain individual precision
+
+ price = temp;
+ }
+ else if (c == '[') {
+ if (date)
+ throw_(amount_error, "Commodity specifies more than one date");
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ']');
+ if (c == ']')
+ in.get(c);
+ else
+ throw_(amount_error, "Commodity date lacks closing bracket");
+
+ date = parse_date(buf);
+ }
+ else if (c == '(') {
+ if (tag)
+ throw_(amount_error, "Commodity specifies more than one tag");
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ')');
+ if (c == ')')
+ in.get(c);
+ else
+ throw_(amount_error, "Commodity tag lacks closing parenthesis");
+
+ tag = buf;
+ }
+ else {
+ break;
+ }
+ } while (true);
+
+ DEBUG("amounts.commodities",
+ "Parsed commodity annotations: " << std::endl << *this);
+}
+
+bool annotated_commodity_t::operator==(const commodity_t& comm) const
+{
+ // If the base commodities don't match, the game's up.
+ if (base != comm.base)
+ return false;
+
+ assert(annotated);
+ if (! comm.annotated)
+ return false;
+
+ if (details != as_annotated_commodity(comm).details)
+ return false;
+
+ return true;
+}
+
+commodity_t&
+annotated_commodity_t::strip_annotations(const bool _keep_price,
+ const bool _keep_date,
+ const bool _keep_tag)
+{
+ DEBUG("commodity.annotated.strip",
+ "Reducing commodity " << *this << std::endl
+ << " keep price " << _keep_price << " "
+ << " keep date " << _keep_date << " "
+ << " keep tag " << _keep_tag);
+
+ commodity_t * new_comm;
+
+ if ((_keep_price && details.price) ||
+ (_keep_date && details.date) ||
+ (_keep_tag && details.tag))
+ {
+ new_comm = parent().find_or_create
+ (referent(),
+ annotation_t(_keep_price ? details.price : none,
+ _keep_date ? details.date : none,
+ _keep_tag ? details.tag : none));
+ } else {
+ new_comm = parent().find_or_create(base_symbol());
+ }
+
+ assert(new_comm);
+ return *new_comm;
+}
+
+void annotated_commodity_t::write_annotations(std::ostream& out,
+ const annotation_t& info)
+{
+ if (info.price)
+ out << " {" << *info.price << '}';
+
+ if (info.date)
+ out << " [" << *info.date << ']';
+
+ if (info.tag)
+ out << " (" << *info.tag << ')';
+}
+
+bool compare_amount_commodities::operator()(const amount_t * left,
+ const amount_t * right) const
+{
+ commodity_t& leftcomm(left->commodity());
+ commodity_t& rightcomm(right->commodity());
+
+ int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol());
+ if (cmp != 0)
+ return cmp < 0;
+
+ if (! leftcomm.annotated) {
+ assert(rightcomm.annotated);
+ return true;
+ }
+ else if (! rightcomm.annotated) {
+ assert(leftcomm.annotated);
+ return false;
+ }
+ else {
+ annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm));
+ annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm));
+
+ if (! aleftcomm.details.price && arightcomm.details.price)
+ return true;
+ if (aleftcomm.details.price && ! arightcomm.details.price)
+ return false;
+
+ if (aleftcomm.details.price && arightcomm.details.price) {
+ amount_t leftprice(*aleftcomm.details.price);
+ leftprice.in_place_reduce();
+ amount_t rightprice(*arightcomm.details.price);
+ rightprice.in_place_reduce();
+
+ if (leftprice.commodity() == rightprice.commodity()) {
+ return (leftprice - rightprice).sign() < 0;
+ } else {
+ // Since we have two different amounts, there's really no way
+ // to establish a true sorting order; we'll just do it based
+ // on the numerical values.
+ leftprice.clear_commodity();
+ rightprice.clear_commodity();
+ return (leftprice - rightprice).sign() < 0;
+ }
+ }
+
+ if (! aleftcomm.details.date && arightcomm.details.date)
+ return true;
+ if (aleftcomm.details.date && ! arightcomm.details.date)
+ return false;
+
+ if (aleftcomm.details.date && arightcomm.details.date) {
+ date_duration_t diff = *aleftcomm.details.date - *arightcomm.details.date;
+ return diff.is_negative();
+ }
+
+ if (! aleftcomm.details.tag && arightcomm.details.tag)
+ return true;
+ if (aleftcomm.details.tag && ! arightcomm.details.tag)
+ return false;
+
+ if (aleftcomm.details.tag && arightcomm.details.tag)
+ return *aleftcomm.details.tag < *arightcomm.details.tag;
+
+ assert(false);
+ return true;
+ }
+}
+
+commodity_pool_t::commodity_pool_t() : default_commodity(NULL)
+{
+ TRACE_CTOR(commodity_pool_t, "");
+ null_commodity = create("");
+ null_commodity->add_flags(COMMODITY_STYLE_NOMARKET |
+ COMMODITY_STYLE_BUILTIN);
+}
+
+commodity_t * commodity_pool_t::create(const string& symbol)
+{
+ shared_ptr<commodity_t::base_t>
+ base_commodity(new commodity_t::base_t(symbol));
+ std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity));
+
+ DEBUG("amounts.commodities", "Creating base commodity " << symbol);
+
+ // Create the "qualified symbol" version of this commodity's symbol
+ if (commodity_t::symbol_needs_quotes(symbol)) {
+ commodity->qualified_symbol = "\"";
+ *commodity->qualified_symbol += symbol;
+ *commodity->qualified_symbol += "\"";
+ }
+
+ DEBUG("amounts.commodities",
+ "Creating commodity '" << commodity->symbol() << "'");
+
+ // Start out the new commodity with the default commodity's flags
+ // and precision, if one has been defined.
+#if 0
+ // jww (2007-05-02): This doesn't do anything currently!
+ if (default_commodity)
+ commodity->drop_flags(COMMODITY_STYLE_THOUSANDS |
+ COMMODITY_STYLE_NOMARKET);
+#endif
+
+ commodity->ident = commodities.size();
+
+ commodities.push_back(commodity.get());
+ return commodity.release();
+}
+
+commodity_t * commodity_pool_t::find_or_create(const string& symbol)
+{
+ DEBUG("amounts.commodities", "Find-or-create commodity " << symbol);
+
+ commodity_t * commodity = find(symbol);
+ if (commodity)
+ return commodity;
+ return create(symbol);
+}
+
+commodity_t * commodity_pool_t::find(const string& symbol)
+{
+ DEBUG("amounts.commodities", "Find commodity " << symbol);
+
+ typedef commodity_pool_t::commodities_t::nth_index<1>::type
+ commodities_by_name;
+
+ commodities_by_name& name_index = commodities.get<1>();
+ commodities_by_name::const_iterator i = name_index.find(symbol);
+ if (i != name_index.end())
+ return *i;
+ else
+ return NULL;
+}
+
+commodity_t * commodity_pool_t::find(const commodity_t::ident_t ident)
+{
+ DEBUG("amounts.commodities", "Find commodity by ident " << ident);
+
+ typedef commodity_pool_t::commodities_t::nth_index<0>::type
+ commodities_by_ident;
+
+ commodities_by_ident& ident_index = commodities.get<0>();
+ return ident_index[ident];
+}
+
+commodity_t *
+commodity_pool_t::create(const string& symbol, const annotation_t& details)
+{
+ commodity_t * new_comm = create(symbol);
+ if (! new_comm)
+ return NULL;
+
+ if (details)
+ return find_or_create(*new_comm, details);
+ else
+ return new_comm;
+}
+
+namespace {
+ string make_qualified_name(const commodity_t& comm,
+ const annotation_t& details)
+ {
+ assert(details);
+
+ if (details.price && details.price->sign() < 0)
+ throw_(amount_error, "A commodity's price may not be negative");
+
+ std::ostringstream name;
+ comm.print(name);
+ annotated_commodity_t::write_annotations(name, details);
+
+ DEBUG("amounts.commodities", "make_qualified_name for "
+ << comm.qualified_symbol << std::endl << details);
+ DEBUG("amounts.commodities", "qualified_name is " << name.str());
+
+ return name.str();
+ }
+}
+
+commodity_t *
+commodity_pool_t::find(const string& symbol, const annotation_t& details)
+{
+ commodity_t * comm = find(symbol);
+ if (! comm)
+ return NULL;
+
+ if (details) {
+ string name = make_qualified_name(*comm, details);
+
+ if (commodity_t * ann_comm = find(name)) {
+ assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
+ return ann_comm;
+ }
+ return NULL;
+ } else {
+ return comm;
+ }
+}
+
+commodity_t *
+commodity_pool_t::find_or_create(const string& symbol,
+ const annotation_t& details)
+{
+ commodity_t * comm = find(symbol);
+ if (! comm)
+ return NULL;
+
+ if (details)
+ return find_or_create(*comm, details);
+ else
+ return comm;
+}
+
+commodity_t *
+commodity_pool_t::create(commodity_t& comm,
+ const annotation_t& details,
+ const string& mapping_key)
+{
+ assert(comm);
+ assert(details);
+ assert(! mapping_key.empty());
+
+ std::auto_ptr<commodity_t> commodity
+ (new annotated_commodity_t(&comm, details));
+
+ commodity->qualified_symbol = comm.symbol();
+ assert(! commodity->qualified_symbol->empty());
+
+ DEBUG("amounts.commodities", "Creating annotated commodity "
+ << "symbol " << commodity->symbol()
+ << " key " << mapping_key << std::endl << details);
+
+ // Add the fully annotated name to the map, so that this symbol may
+ // quickly be found again.
+ commodity->ident = commodities.size();
+ commodity->mapping_key_ = mapping_key;
+
+ commodities.push_back(commodity.get());
+ return commodity.release();
+}
+
+commodity_t * commodity_pool_t::find_or_create(commodity_t& comm,
+ const annotation_t& details)
+{
+ assert(comm);
+ assert(details);
+
+ string name = make_qualified_name(comm, details);
+ assert(! name.empty());
+
+ if (commodity_t * ann_comm = find(name)) {
+ assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details);
+ return ann_comm;
+ }
+ return create(comm, details, name);
+}
+
+} // namespace ledger
diff --git a/src/commodity.h b/src/commodity.h
new file mode 100644
index 00000000..27827c1a
--- /dev/null
+++ b/src/commodity.h
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup numerics Core numerics
+ */
+
+/**
+ * @file commodity.h
+ * @author John Wiegley
+ * @date Wed Apr 18 22:05:53 2007
+ *
+ * @brief Types for handling commodities.
+ *
+ * @ingroup numerics
+ *
+ * This file contains one of the most basic types in Ledger:
+ * commodity_t, and its annotated cousin, annotated_commodity_t.
+ */
+#ifndef _COMMODITY_H
+#define _COMMODITY_H
+
+namespace ledger {
+
+class commodity_t
+ : public delegates_flags<>,
+ public equality_comparable1<commodity_t, noncopyable>
+{
+ friend class commodity_pool_t;
+
+public:
+ class base_t : public noncopyable, public supports_flags<>
+ {
+ base_t();
+
+ public:
+ typedef std::map<const datetime_t, amount_t> history_map;
+ typedef std::pair<const datetime_t, amount_t> history_pair;
+
+ struct history_t {
+ history_map prices;
+ ptime last_lookup;
+ };
+
+#define COMMODITY_STYLE_DEFAULTS 0x00
+#define COMMODITY_STYLE_SUFFIXED 0x01
+#define COMMODITY_STYLE_SEPARATED 0x02
+#define COMMODITY_STYLE_EUROPEAN 0x04
+#define COMMODITY_STYLE_THOUSANDS 0x08
+#define COMMODITY_STYLE_NOMARKET 0x10
+#define COMMODITY_STYLE_BUILTIN 0x20
+
+ string symbol;
+ amount_t::precision_t precision;
+ optional<string> name;
+ optional<string> note;
+ optional<history_t> history;
+ optional<amount_t> smaller;
+ optional<amount_t> larger;
+
+ public:
+ explicit base_t(const string& _symbol)
+ : supports_flags<>(COMMODITY_STYLE_DEFAULTS),
+ symbol(_symbol), precision(0) {
+ TRACE_CTOR(base_t, "const string&");
+ }
+ ~base_t() {
+ TRACE_DTOR(base_t);
+ }
+ };
+
+public:
+ static bool symbol_needs_quotes(const string& symbol);
+
+ typedef base_t::history_t history_t;
+ typedef base_t::history_map history_map;
+ typedef uint_least32_t ident_t;
+
+ shared_ptr<base_t> base;
+
+ commodity_pool_t * parent_;
+ ident_t ident;
+ optional<string> qualified_symbol;
+ optional<string> mapping_key_;
+ bool annotated;
+
+public:
+ explicit commodity_t(commodity_pool_t * _parent,
+ const shared_ptr<base_t>& _base)
+ : delegates_flags<>(*_base.get()), base(_base),
+ parent_(_parent), annotated(false) {
+ TRACE_CTOR(commodity_t, "");
+ }
+ virtual ~commodity_t() {
+ TRACE_DTOR(commodity_t);
+ }
+
+ operator bool() const;
+
+ bool is_annotated() const {
+ return annotated;
+ }
+
+ virtual bool operator==(const commodity_t& comm) const {
+ if (comm.annotated)
+ return comm == *this;
+ return base.get() == comm.base.get();
+ }
+
+ commodity_pool_t& parent() const {
+ return *parent_;
+ }
+
+ string base_symbol() const {
+ return base->symbol;
+ }
+ string symbol() const {
+ return qualified_symbol ? *qualified_symbol : base_symbol();
+ }
+
+ string mapping_key() const {
+ if (mapping_key_)
+ return *mapping_key_;
+ else
+ return base_symbol();
+ }
+
+ optional<string> name() const {
+ return base->name;
+ }
+ void set_name(const optional<string>& arg = none) {
+ base->name = arg;
+ }
+
+ optional<string> note() const {
+ return base->note;
+ }
+ void set_note(const optional<string>& arg = none) {
+ base->note = arg;
+ }
+
+ amount_t::precision_t precision() const {
+ return base->precision;
+ }
+ void set_precision(amount_t::precision_t arg) {
+ base->precision = arg;
+ }
+
+ optional<amount_t> smaller() const {
+ return base->smaller;
+ }
+ void set_smaller(const optional<amount_t>& arg = none) {
+ base->smaller = arg;
+ }
+
+ optional<amount_t> larger() const {
+ return base->larger;
+ }
+ void set_larger(const optional<amount_t>& arg = none) {
+ base->larger = arg;
+ }
+
+ optional<history_t> history() const {
+ return base->history;
+ }
+
+ void add_price(const datetime_t& date, const amount_t& price);
+ bool remove_price(const datetime_t& date);
+
+ optional<amount_t> value(const optional<datetime_t>& moment = none);
+
+ static amount_t exchange(const amount_t& amount,
+ amount_t& final_cost, // out
+ amount_t& basis_cost, // out
+ const optional<amount_t>& total_cost,
+ const optional<amount_t>& per_unit_cost = none,
+ const optional<datetime_t>& moment = none,
+ const optional<string>& tag = none);
+
+ static void parse_symbol(std::istream& in, string& symbol);
+ static void parse_symbol(char *& p, string& symbol);
+ static string parse_symbol(std::istream& in) {
+ string temp;
+ parse_symbol(in, temp);
+ return temp;
+ }
+
+ void print(std::ostream& out) const {
+ out << symbol();
+ }
+
+ void read(std::istream& in);
+ void read(char *& data);
+ void write(std::ostream& out) const;
+
+ bool valid() const;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) {
+ comm.print(out);
+ return out;
+}
+
+struct annotation_t : public equality_comparable<annotation_t>
+{
+ optional<amount_t> price;
+ optional<date_t> date;
+ optional<string> tag;
+
+ explicit annotation_t(const optional<amount_t>& _price = none,
+ const optional<date_t>& _date = none,
+ const optional<string>& _tag = none)
+ : price(_price), date(_date), tag(_tag) {
+ TRACE_CTOR(annotation_t, "const optional<amount_t>& + date_t + string");
+ }
+ annotation_t(const annotation_t& other)
+ : price(other.price), date(other.date), tag(other.tag) {
+ TRACE_CTOR(annotation_t, "copy");
+ }
+
+ ~annotation_t() {
+ TRACE_DTOR(annotation_t);
+ }
+
+ operator bool() const {
+ return price || date || tag;
+ }
+
+ bool operator==(const annotation_t& rhs) const {
+ return (price == rhs.price &&
+ date == rhs.date &&
+ tag == rhs.tag);
+ }
+
+ void parse(std::istream& in);
+ void print(std::ostream& out) const {
+ out << "price " << (price ? price->to_string() : "NONE") << " "
+ << "date " << (date ? *date : date_t()) << " "
+ << "tag " << (tag ? *tag : "NONE");
+ }
+
+ bool valid() const {
+ assert(*this);
+ return true;
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& out, const annotation_t& details) {
+ details.print(out);
+ return out;
+}
+
+class annotated_commodity_t
+ : public commodity_t,
+ public equality_comparable<annotated_commodity_t,
+ equality_comparable2<annotated_commodity_t, commodity_t,
+ noncopyable> >
+{
+public:
+ commodity_t * ptr;
+ annotation_t details;
+
+ explicit annotated_commodity_t(commodity_t * _ptr,
+ const annotation_t& _details)
+ : commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) {
+ TRACE_CTOR(annotated_commodity_t, "");
+ annotated = true;
+ }
+ virtual ~annotated_commodity_t() {
+ TRACE_DTOR(annotated_commodity_t);
+ }
+
+ virtual bool operator==(const commodity_t& comm) const;
+ virtual bool operator==(const annotated_commodity_t& comm) const {
+ return *this == static_cast<const commodity_t&>(comm);
+ }
+
+ commodity_t& referent() {
+ return *ptr;
+ }
+ const commodity_t& referent() const {
+ return *ptr;
+ }
+
+ commodity_t& strip_annotations(const bool _keep_price,
+ const bool _keep_date,
+ const bool _keep_tag);
+
+ void write_annotations(std::ostream& out) const {
+ annotated_commodity_t::write_annotations(out, details);
+ }
+
+ static void write_annotations(std::ostream& out,
+ const annotation_t& info);
+};
+
+inline annotated_commodity_t&
+as_annotated_commodity(commodity_t& commodity) {
+ return downcast<annotated_commodity_t>(commodity);
+}
+inline const annotated_commodity_t&
+as_annotated_commodity(const commodity_t& commodity) {
+ return downcast<const annotated_commodity_t>(commodity);
+}
+
+
+struct compare_amount_commodities {
+ bool operator()(const amount_t * left, const amount_t * right) const;
+};
+
+class commodity_pool_t : public noncopyable
+{
+ /**
+ * The commodities collection in commodity_pool_t maintains pointers
+ * to all the commodities which have ever been created by the user,
+ * whether explicitly by calling the create methods of
+ * commodity_pool_t, or implicitly by parsing a commoditized amount.
+ *
+ * The `commodities' member variable represents a collection which
+ * is indexed by two vertices: first, and ordered sequence of unique
+ * integer which identify commodities by a numerical identifier; and
+ * second, by a hashed set of symbolic names which reflect how the
+ * commodity was referred to by the user.
+ */
+ typedef multi_index_container<
+ commodity_t *,
+ multi_index::indexed_by<
+ multi_index::random_access<>,
+ multi_index::hashed_unique<
+ multi_index::const_mem_fun<commodity_t,
+ string, &commodity_t::mapping_key> >
+ >
+ > commodities_t;
+
+public:
+ typedef commodity_pool_t::commodities_t::nth_index<0>::type
+ commodities_by_ident;
+
+ commodities_t commodities;
+
+ commodity_t * null_commodity;
+ commodity_t * default_commodity;
+
+private:
+ template<typename T>
+ struct first_initialized
+ {
+ typedef T result_type;
+
+ template<typename InputIterator>
+ T operator()(InputIterator first, InputIterator last) const
+ {
+ for (; first != last; first++)
+ if (*first)
+ return *first;
+ return T();
+ }
+ };
+
+public:
+ boost::function<optional<amount_t>
+ (commodity_t& commodity,
+ const optional<datetime_t>& date,
+ const optional<datetime_t>& moment,
+ const optional<datetime_t>& last)> get_quote;
+
+ explicit commodity_pool_t();
+
+ ~commodity_pool_t() {
+ TRACE_DTOR(commodity_pool_t);
+ commodities_by_ident& ident_index = commodities.get<0>();
+ for (commodities_by_ident::iterator i = ident_index.begin();
+ i != ident_index.end();
+ i++)
+ checked_delete(*i);
+ }
+
+ commodity_t * create(const string& symbol);
+ commodity_t * find(const string& name);
+ commodity_t * find(const commodity_t::ident_t ident);
+ commodity_t * find_or_create(const string& symbol);
+
+ commodity_t * create(const string& symbol, const annotation_t& details);
+ commodity_t * find(const string& symbol, const annotation_t& details);
+ commodity_t * find_or_create(const string& symbol,
+ const annotation_t& details);
+
+ commodity_t * create(commodity_t& comm,
+ const annotation_t& details,
+ const string& mapping_key);
+
+ commodity_t * find_or_create(commodity_t& comm,
+ const annotation_t& details);
+};
+
+} // namespace ledger
+
+#endif // _COMMODITY_H
diff --git a/src/compare.cc b/src/compare.cc
new file mode 100644
index 00000000..1cbe7082
--- /dev/null
+++ b/src/compare.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "compare.h"
+
+namespace ledger {
+
+template <>
+bool compare_items<xact_t>::operator()(xact_t * left, xact_t * right)
+{
+ assert(left);
+ assert(right);
+
+ xact_t::xdata_t& lxdata(left->xdata());
+ if (! lxdata.has_flags(XACT_EXT_SORT_CALC)) {
+ lxdata.sort_value = sort_order.calc(*left);
+ lxdata.sort_value.reduce();
+ lxdata.add_flags(XACT_EXT_SORT_CALC);
+ }
+
+ xact_t::xdata_t& rxdata(right->xdata());
+ if (! rxdata.has_flags(XACT_EXT_SORT_CALC)) {
+ rxdata.sort_value = sort_order.calc(*right);
+ rxdata.sort_value.reduce();
+ rxdata.add_flags(XACT_EXT_SORT_CALC);
+ }
+
+ DEBUG("ledger.walk.compare_items_xact",
+ "lxdata.sort_value = " << lxdata.sort_value);
+ DEBUG("ledger.walk.compare_items_xact",
+ "rxdata.sort_value = " << rxdata.sort_value);
+
+ return lxdata.sort_value < rxdata.sort_value;
+}
+
+template <>
+bool compare_items<account_t>::operator()(account_t * left, account_t * right)
+{
+ assert(left);
+ assert(right);
+
+ account_t::xdata_t& lxdata(left->xdata());
+ if (! lxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) {
+ lxdata.sort_value = sort_order.calc(*left);
+ lxdata.add_flags(ACCOUNT_EXT_SORT_CALC);
+ }
+
+ account_t::xdata_t& rxdata(right->xdata());
+ if (! rxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) {
+ rxdata.sort_value = sort_order.calc(*right);
+ rxdata.add_flags(ACCOUNT_EXT_SORT_CALC);
+ }
+
+ return lxdata.sort_value < rxdata.sort_value;
+}
+
+} // namespace ledger
diff --git a/src/compare.h b/src/compare.h
new file mode 100644
index 00000000..d86771ef
--- /dev/null
+++ b/src/compare.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _COMPARE_H
+#define _COMPARE_H
+
+#include "expr.h"
+#include "xact.h"
+#include "account.h"
+
+namespace ledger {
+
+template <typename T>
+class compare_items
+{
+ expr_t sort_order;
+
+ compare_items();
+
+public:
+ compare_items(const compare_items& other) : sort_order(other.sort_order) {
+ TRACE_CTOR(compare_items, "copy");
+ }
+ compare_items(const expr_t& _sort_order) : sort_order(_sort_order) {
+ TRACE_CTOR(compare_items, "const value_expr&");
+ }
+ ~compare_items() throw() {
+ TRACE_DTOR(compare_items);
+ }
+ bool operator()(T * left, T * right);
+};
+
+template <typename T>
+bool compare_items<T>::operator()(T * left, T * right)
+{
+ assert(left);
+ assert(right);
+ return sort_order.calc(*left) < sort_order.calc(*right);
+}
+
+template <>
+bool compare_items<xact_t>::operator()(xact_t * left, xact_t * right);
+template <>
+bool compare_items<account_t>::operator()(account_t * left,
+ account_t * right);
+
+} // namespace ledger
+
+#endif // _COMPARE_H
diff --git a/src/csv.cc b/src/csv.cc
new file mode 100644
index 00000000..c075622e
--- /dev/null
+++ b/src/csv.cc
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "csv.h"
+
+namespace ledger {
+
+namespace {
+ inline void write_escaped_string(std::ostream& out, const string& xact)
+ {
+ out << "\"";
+ foreach (char ch, xact)
+ if (ch == '"') {
+ out << "\\";
+ out << "\"";
+ } else {
+ out << ch;
+ }
+ out << "\"";
+ }
+}
+
+void format_csv_xacts::operator()(xact_t& xact)
+{
+ if (! xact.has_xdata() ||
+ ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) {
+ {
+ format_t fmt("%D");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << ',';
+
+ {
+ format_t fmt("%P");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << ',';
+
+ {
+ format_t fmt("%A");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << ',';
+
+ {
+ format_t fmt("%t");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << ',';
+
+ {
+ format_t fmt("%T");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << ',';
+
+ switch (xact.state) {
+ case xact_t::CLEARED:
+ write_escaped_string(out, "*");
+ break;
+ case xact_t::PENDING:
+ write_escaped_string(out, "!");
+ break;
+ default: {
+ xact_t::state_t state;
+ if (xact.entry->get_state(&state))
+ switch (state) {
+ case xact_t::CLEARED:
+ write_escaped_string(out, "*");
+ break;
+ case xact_t::PENDING:
+ write_escaped_string(out, "!");
+ break;
+ default:
+ write_escaped_string(out, "");
+ break;
+ }
+ }
+ }
+ out << ',';
+
+ if (xact.entry->code)
+ write_escaped_string(out, *xact.entry->code);
+ out << ',';
+
+ {
+ format_t fmt("%N");
+ std::ostringstream str;
+#if 0
+ fmt.format(str, details_t(xact));
+#endif
+ write_escaped_string(out, str.str());
+ }
+ out << '\n';
+
+ xact.xdata().add_flags(XACT_EXT_DISPLAYED);
+ }
+}
+
+} // namespace ledger
diff --git a/src/csv.h b/src/csv.h
new file mode 100644
index 00000000..bef58ad2
--- /dev/null
+++ b/src/csv.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _CSV_H
+#define _CSV_H
+
+#include "handler.h"
+#include "format.h"
+
+namespace ledger {
+
+class format_csv_xacts : public item_handler<xact_t>
+{
+ format_csv_xacts();
+
+protected:
+ std::ostream& out;
+
+public:
+ format_csv_xacts(std::ostream& _out) : out(_out) {
+ TRACE_CTOR(format_csv_xacts, "std::ostream&");
+ }
+ ~format_csv_xacts() {
+ TRACE_DTOR(format_csv_xacts);
+ }
+
+ virtual void flush() {
+ out.flush();
+ }
+ virtual void operator()(xact_t& xact);
+};
+
+} // namespace ledger
+
+#endif // _REPORT_H
diff --git a/src/derive.cc b/src/derive.cc
new file mode 100644
index 00000000..dd9dc4ca
--- /dev/null
+++ b/src/derive.cc
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "derive.h"
+#include "session.h"
+#include "iterators.h"
+
+namespace ledger {
+
+entry_t * derive_new_entry(report_t& report,
+ strings_list::iterator i,
+ strings_list::iterator end)
+{
+ session_t& session(report.session);
+
+ std::auto_ptr<entry_t> added(new entry_t);
+
+ entry_t * matching = NULL;
+
+ added->_date = parse_date(*i++);
+ if (i == end)
+ throw std::runtime_error("Too few arguments to 'entry'");
+
+ mask_t regexp(*i++);
+
+ journals_iterator iter(session);
+ entries_list::reverse_iterator j;
+
+ for (journal_t * journal = iter(); journal; journal = iter()) {
+ for (j = journal->entries.rbegin();
+ j != journal->entries.rend();
+ j++) {
+ if (regexp.match((*j)->payee)) {
+ matching = *j;
+ break;
+ }
+ }
+ if (matching) break;
+ }
+
+ added->payee = matching ? matching->payee : regexp.expr.str();
+
+ if (! matching) {
+ account_t * acct;
+ if (i == end || ((*i)[0] == '-' || std::isdigit((*i)[0]))) {
+ acct = session.find_account("Expenses");
+ }
+ else if (i != end) {
+ acct = session.find_account_re(*i);
+ if (! acct)
+ acct = session.find_account(*i);
+ assert(acct);
+ i++;
+ }
+
+ if (i == end) {
+ added->add_xact(new xact_t(acct));
+ } else {
+ xact_t * xact = new xact_t(acct, amount_t(*i++));
+ added->add_xact(xact);
+
+ if (! xact->amount.commodity()) {
+ // If the amount has no commodity, we can determine it given
+ // the account by creating a final for the account and then
+ // checking if it contains only a single commodity. An
+ // account to which only dollars are applied would imply that
+ // dollars are wanted now too.
+
+ report.sum_all_accounts();
+
+ value_t total = acct->xdata().total;
+ if (total.is_type(value_t::AMOUNT))
+ xact->amount.set_commodity(total.as_amount().commodity());
+ }
+ }
+
+ acct = NULL;
+
+ if (i != end) {
+ if (! acct)
+ acct = session.find_account_re(*i);
+ if (! acct)
+ acct = session.find_account(*i);
+ }
+
+ if (! acct) {
+ if (matching && matching->journal->basket)
+ acct = matching->journal->basket;
+ else
+ acct = session.find_account("Equity");
+ }
+
+ added->add_xact(new xact_t(acct));
+ }
+ else if (i == end) {
+ // If no argument were given but the payee, assume the user wants
+ // to see the same xact as last time.
+ added->code = matching->code;
+
+ foreach (xact_t * xact, matching->xacts)
+ added->add_xact(new xact_t(*xact));
+ }
+ else if ((*i)[0] == '-' || std::isdigit((*i)[0])) {
+ xact_t * m_xact, * xact, * first;
+ m_xact = matching->xacts.front();
+
+ first = xact = new xact_t(m_xact->account, amount_t(*i++));
+ added->add_xact(xact);
+
+ if (! xact->amount.commodity())
+ xact->amount.set_commodity(m_xact->amount.commodity());
+
+ m_xact = matching->xacts.back();
+
+ xact = new xact_t(m_xact->account, - first->amount);
+ added->add_xact(xact);
+
+ if (i != end) {
+ account_t * acct = session.find_account_re(*i);
+ if (! acct)
+ acct = session.find_account(*i);
+ assert(acct);
+ added->xacts.back()->account = acct;
+ }
+ }
+ else {
+ account_t * draw_acct = NULL;
+
+ while (i != end) {
+ string& re_pat(*i++);
+ account_t * acct = NULL;
+ amount_t * amt = NULL;
+
+ mask_t acct_regex(re_pat);
+
+ for (; j != matching->journal->entries.rend(); j++)
+ if (regexp.match((*j)->payee)) {
+ entry_t * entry = *j;
+ foreach (xact_t * xact, entry->xacts)
+ if (acct_regex.match(xact->account->fullname())) {
+ acct = xact->account;
+ amt = &xact->amount;
+ matching = entry;
+ goto found;
+ }
+ }
+
+ found:
+ xact_t * xact;
+ if (i == end) {
+ if (amt)
+ xact = new xact_t(acct, *amt);
+ else
+ xact = new xact_t(acct);
+ } else {
+ amount_t amount(*i++);
+
+ strings_list::iterator x = i;
+ if (i != end && ++x == end) {
+ draw_acct = session.find_account_re(*i);
+ if (! draw_acct)
+ draw_acct = session.find_account(*i);
+ i++;
+ }
+
+ if (! acct)
+ acct = session.find_account_re(re_pat);
+ if (! acct)
+ acct = session.find_account(re_pat);
+
+ xact = new xact_t(acct, amount);
+ if (! xact->amount.commodity()) {
+ if (amt)
+ xact->amount.set_commodity(amt->commodity());
+ else if (amount_t::current_pool->default_commodity)
+ xact->amount.set_commodity(*amount_t::current_pool->default_commodity);
+ }
+ }
+ added->add_xact(xact);
+ }
+
+ if (! draw_acct) {
+ assert(matching->xacts.back()->account);
+ draw_acct = matching->xacts.back()->account;
+ }
+ if (draw_acct)
+ added->add_xact(new xact_t(draw_acct));
+ }
+
+ if ((matching &&
+ ! matching->journal->entry_finalize_hooks.run_hooks(*added, false)) ||
+ ! added->finalize() ||
+ (matching &&
+ ! matching->journal->entry_finalize_hooks.run_hooks(*added, true)))
+ throw std::runtime_error("Failed to finalize derived entry (check commodities)");
+
+ return added.release();
+}
+
+} // namespace ledger
diff --git a/src/derive.h b/src/derive.h
new file mode 100644
index 00000000..5de86cc8
--- /dev/null
+++ b/src/derive.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _DERIVE_H
+#define _DERIVE_H
+
+#include "report.h"
+
+namespace ledger {
+
+entry_t * derive_new_entry(report_t& report,
+ strings_list::iterator begin,
+ strings_list::iterator end);
+
+} // namespace ledger
+
+#endif // _DERIVE_H
diff --git a/src/emacs.cc b/src/emacs.cc
new file mode 100644
index 00000000..cf787e75
--- /dev/null
+++ b/src/emacs.cc
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "emacs.h"
+#include "account.h"
+
+namespace ledger {
+
+void format_emacs_xacts::write_entry(entry_t& entry)
+{
+ int idx = entry.src_idx;
+ foreach (const path& path, entry.journal->sources)
+ if (! idx--) {
+ out << "\"" << path << "\" ";
+ break;
+ }
+
+ out << (static_cast<unsigned long>(entry.beg_line) + 1) << " ";
+
+ tm when = gregorian::to_tm(entry.date());
+ std::time_t date = std::mktime(&when); // jww (2008-04-20): Is this GMT or local?
+
+ out << "(" << (date / 65536) << " " << (date % 65536) << " 0) ";
+
+ if (! entry.code)
+ out << "nil ";
+ else
+ out << "\"" << *entry.code << "\" ";
+
+ if (entry.payee.empty())
+ out << "nil";
+ else
+ out << "\"" << entry.payee << "\"";
+
+ out << "\n";
+}
+
+void format_emacs_xacts::operator()(xact_t& xact)
+{
+ if (! xact.has_xdata() ||
+ ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) {
+ if (! last_entry) {
+ out << "((";
+ write_entry(*xact.entry);
+ }
+ else if (xact.entry != last_entry) {
+ out << ")\n (";
+ write_entry(*xact.entry);
+ }
+ else {
+ out << "\n";
+ }
+
+ out << " (" << (static_cast<unsigned long>(xact.beg_line) + 1) << " ";
+ out << "\"" << xact.reported_account()->fullname() << "\" \""
+ << xact.amount << "\"";
+
+ switch (xact.state) {
+ case xact_t::CLEARED:
+ out << " t";
+ break;
+ case xact_t::PENDING:
+ out << " pending";
+ break;
+ default:
+ out << " nil";
+ break;
+ }
+
+ if (xact.cost)
+ out << " \"" << *xact.cost << "\"";
+ if (xact.note)
+ out << " \"" << *xact.note << "\"";
+ out << ")";
+
+ last_entry = xact.entry;
+
+ xact.xdata().add_flags(XACT_EXT_DISPLAYED);
+ }
+}
+
+} // namespace ledger
diff --git a/src/emacs.h b/src/emacs.h
new file mode 100644
index 00000000..59b937f8
--- /dev/null
+++ b/src/emacs.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _EMACS_H
+#define _EMACS_H
+
+#include "handler.h"
+#include "format.h"
+
+namespace ledger {
+
+class format_emacs_xacts : public item_handler<xact_t>
+{
+ format_emacs_xacts();
+
+protected:
+ std::ostream& out;
+ entry_t * last_entry;
+
+public:
+ format_emacs_xacts(std::ostream& _out)
+ : out(_out), last_entry(NULL) {
+ TRACE_CTOR(format_emacs_xacts, "std::ostream&");
+ }
+ ~format_emacs_xacts() {
+ TRACE_DTOR(format_emacs_xacts);
+ }
+
+ virtual void write_entry(entry_t& entry);
+ virtual void flush() {
+ if (last_entry)
+ out << "))\n";
+ out.flush();
+ }
+ virtual void operator()(xact_t& xact);
+};
+
+} // namespace ledger
+
+#endif // _REPORT_H
diff --git a/src/entry.cc b/src/entry.cc
new file mode 100644
index 00000000..1a906cc4
--- /dev/null
+++ b/src/entry.cc
@@ -0,0 +1,497 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "entry.h"
+#include "journal.h"
+#include "format.h"
+#include "session.h"
+#include "report.h"
+
+namespace ledger {
+
+entry_base_t::entry_base_t(const entry_base_t& e)
+ : supports_flags<>(), journal(NULL),
+ beg_pos(0), beg_line(0), end_pos(0), end_line(0)
+{
+ TRACE_CTOR(entry_base_t, "copy");
+ xacts.insert(xacts.end(), e.xacts.begin(), e.xacts.end());
+}
+
+entry_base_t::~entry_base_t()
+{
+ TRACE_DTOR(entry_base_t);
+
+ foreach (xact_t * xact, xacts) {
+ // If the transaction is a temporary, it will be destructed when the
+ // temporary is. If it's from a binary cache, we can safely destruct it
+ // but its memory will be deallocated with the cache.
+ if (! xact->has_flags(XACT_TEMP)) {
+ if (! xact->has_flags(XACT_IN_CACHE))
+ checked_delete(xact);
+ else
+ xact->~xact_t();
+ }
+ }
+}
+
+void entry_base_t::add_xact(xact_t * xact)
+{
+ xacts.push_back(xact);
+}
+
+bool entry_base_t::remove_xact(xact_t * xact)
+{
+ xacts.remove(xact);
+ return true;
+}
+
+bool entry_base_t::finalize()
+{
+ // Scan through and compute the total balance for the entry. This is used
+ // for auto-calculating the value of entries with no cost, and the per-unit
+ // price of unpriced commodities.
+
+ // (let ((balance 0)
+ // null-xact)
+
+ value_t balance;
+ xact_t * null_xact = NULL;
+
+ // (do-xacts (xact entry)
+ // (when (xact-must-balance-p xact)
+ // (let ((amt (xact-amount* xact)))
+ // (if amt
+ // (setf balance (add balance (or (xact-cost xact) amt)))
+ // (if null-xact
+ // (error "Only one xact with null amount allowed ~
+ // per entry (beg ~S end ~S)"
+ // (item-position-begin-line (entry-position entry))
+ // (item-position-end-line (entry-position entry)))
+ // (setf null-xact xact))))))
+ //
+
+ foreach (xact_t * xact, xacts) {
+ if (xact->must_balance()) {
+ amount_t& p(xact->cost ? *xact->cost : xact->amount);
+ if (! p.is_null()) {
+ if (balance.is_null())
+ balance = p;
+ else
+ balance += p;
+ } else {
+ if (null_xact)
+ throw_(std::logic_error,
+ "Only one xact with null amount allowed per entry");
+ else
+ null_xact = xact;
+ }
+ }
+ }
+ assert(balance.valid());
+
+ DEBUG("ledger.journal.finalize", "initial balance = " << balance);
+
+ // If there is only one xact, balance against the default account if
+ // one has been set.
+
+ // (when (= 1 (length (entry-xacts entry)))
+ // (if-let ((default-account
+ // (journal-default-account (entry-journal entry))))
+ // (setf null-xact
+ // (make-xact :entry entry
+ // :status (xact-status
+ // (first (entry-xacts entry)))
+ // :account default-account
+ // :generatedp t))
+ // (add-xact entry null-xact)))
+
+ if (journal && journal->basket && xacts.size() == 1) {
+ // jww (2008-07-24): Need to make the rest of the code aware of what to do
+ // when it sees a generated xact.
+ null_xact = new xact_t(journal->basket, XACT_GENERATED);
+ null_xact->state = (*xacts.begin())->state;
+ add_xact(null_xact);
+ }
+
+ if (null_xact != NULL) {
+ // If one xact has no value at all, its value will become the
+ // inverse of the rest. If multiple commodities are involved, multiple
+ // xacts are generated to balance them all.
+
+ // (progn
+ // (if (balance-p balance)
+ // (let ((first t))
+ // (dolist (amount (balance-amounts balance))
+ // (if first
+ // (setf (xact-amount* null-xact) (negate amount)
+ // first nil)
+ // (add-xact
+ // entry
+ // (make-xact :entry entry
+ // :account (xact-account null-xact)
+ // :amount (negate amount)
+ // :generatedp t)))))
+ // (setf (xact-amount* null-xact) (negate balance)
+ // (xact-calculatedp null-xact) t))
+ //
+ // (setf balance 0))
+
+ if (balance.is_balance()) {
+ bool first = true;
+ const balance_t& bal(balance.as_balance());
+ foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) {
+ if (first) {
+ null_xact->amount = pair.second.negate();
+ first = false;
+ } else {
+ add_xact(new xact_t(null_xact->account, pair.second.negate(),
+ XACT_GENERATED));
+ }
+ }
+ } else {
+ null_xact->amount = balance.as_amount().negate();
+ null_xact->add_flags(XACT_CALCULATED);
+ }
+ balance = NULL_VALUE;
+
+ }
+ else if (balance.is_balance() &&
+ balance.as_balance().amounts.size() == 2) {
+ // When an entry involves two different commodities (regardless of how
+ // many xacts there are) determine the conversion ratio by dividing
+ // the total value of one commodity by the total value of the other. This
+ // establishes the per-unit cost for this xact for both
+ // commodities.
+
+ // (when (and (balance-p balance)
+ // (= 2 (balance-commodity-count balance)))
+ // (destructuring-bind (x y) (balance-amounts balance)
+ // (let ((a-commodity (amount-commodity x))
+ // (per-unit-cost (value-abs (divide x y))))
+ // (do-xacts (xact entry)
+ // (let ((amount (xact-amount* xact)))
+ // (unless (or (xact-cost xact)
+ // (not (xact-must-balance-p xact))
+ // (commodity-equal (amount-commodity amount)
+ // a-commodity))
+ // (setf balance (subtract balance amount)
+ // (xact-cost xact) (multiply per-unit-cost amount)
+ // balance (add balance (xact-cost xact))))))))))
+
+ const balance_t& bal(balance.as_balance());
+
+ balance_t::amounts_map::const_iterator a = bal.amounts.begin();
+
+ const amount_t& x((*a++).second);
+ const amount_t& y((*a++).second);
+
+ if (! y.is_realzero()) {
+ amount_t per_unit_cost = (x / y).abs();
+
+ commodity_t& comm(x.commodity());
+
+ foreach (xact_t * xact, xacts) {
+ const amount_t& x_amt(xact->amount);
+
+ if (! (xact->cost ||
+ ! xact->must_balance() ||
+ x_amt.commodity() == comm)) {
+ DEBUG("ledger.journal.finalize", "before operation 1 = " << balance);
+ balance -= x_amt;
+ DEBUG("ledger.journal.finalize", "after operation 1 = " << balance);
+ DEBUG("ledger.journal.finalize", "x_amt = " << x_amt);
+ DEBUG("ledger.journal.finalize", "per_unit_cost = " << per_unit_cost);
+
+ xact->cost = per_unit_cost * x_amt;
+ DEBUG("ledger.journal.finalize", "*xact->cost = " << *xact->cost);
+
+ balance += *xact->cost;
+ DEBUG("ledger.journal.finalize", "after operation 2 = " << balance);
+ }
+
+ }
+ }
+
+ DEBUG("ledger.journal.finalize", "resolved balance = " << balance);
+ }
+
+ // Now that the xact list has its final form, calculate the balance
+ // once more in terms of total cost, accounting for any possible gain/loss
+ // amounts.
+
+ // (do-xacts (xact entry)
+ // (when (xact-cost xact)
+ // (let ((amount (xact-amount* xact)))
+ // (assert (not (commodity-equal (amount-commodity amount)
+ // (amount-commodity (xact-cost xact)))))
+ // (multiple-value-bind (annotated-amount total-cost basis-cost)
+ // (exchange-commodity amount :total-cost (xact-cost xact)
+ // :moment (entry-date entry)
+ // :tag (entry-code entry))
+ // (if (annotated-commodity-p (amount-commodity amount))
+ // (if-let ((price (annotation-price
+ // (commodity-annotation
+ // (amount-commodity amount)))))
+ // (setf balance
+ // (add balance (subtract basis-cost total-cost))))
+ // (setf (xact-amount* xact) annotated-amount))))))
+
+ foreach (xact_t * xact, xacts) {
+ if (xact->cost) {
+ const amount_t& x_amt(xact->amount);
+
+ assert(x_amt.commodity() != xact->cost->commodity());
+
+ entry_t * entry = dynamic_cast<entry_t *>(this);
+
+ // jww (2008-07-24): Pass the entry's code here if we can, as the
+ // auto-tag
+ amount_t final_cost;
+ amount_t basis_cost;
+ amount_t ann_amount =
+ commodity_t::exchange(x_amt, final_cost, basis_cost, xact->cost, none,
+ datetime_t(xact->actual_date(),
+ time_duration_t(0, 0, 0)),
+ entry ? entry->code : optional<string>());
+
+ if (xact->amount.is_annotated()) {
+ if (ann_amount.annotation().price) {
+ if (balance.is_null())
+ balance = basis_cost - final_cost;
+ else
+ balance += basis_cost - final_cost;
+ }
+ } else {
+ xact->amount = ann_amount;
+ }
+ }
+ }
+
+ DEBUG("ledger.journal.finalize", "final balance = " << balance);
+
+ // (if (value-zerop balance)
+ // (prog1
+ // entry
+ // (setf (entry-normalizedp entry) t))
+ // (error "Entry does not balance (beg ~S end ~S); remaining balance is:~%~A"
+ // (item-position-begin-line (entry-position entry))
+ // (item-position-end-line (entry-position entry))
+ // (format-value balance :width 20)))
+
+ if (! balance.is_null()) {
+ balance.round();
+ if (! balance.is_zero()) {
+#if 0
+ error * err =
+ new balance_error("Entry does not balance",
+ new entry_context(*this, "While balancing entry:"));
+ err->context.push_front
+ (new value_context(balance, "Unbalanced remainder is:"));
+ throw err;
+#endif
+ }
+ }
+
+ // Add the final calculated totals each to their related account
+
+ if (dynamic_cast<entry_t *>(this)) {
+ foreach (xact_t * xact, xacts) {
+ account_t::xdata_t& xdata(xact->account->xdata());
+
+ // jww (2008-08-09): For now, this feature only works for
+ // non-specific commodities.
+ if (xdata.value.is_null())
+ xdata.value = xact->amount.strip_annotations();
+ else
+ xdata.value += xact->amount.strip_annotations();
+ }
+ }
+
+ return true;
+}
+
+entry_t::entry_t(const entry_t& e)
+ : entry_base_t(e), scope_t(), _date(e._date), _date_eff(e._date_eff),
+ code(e.code), payee(e.payee)
+{
+ TRACE_CTOR(entry_t, "copy");
+
+ foreach (xact_t * xact, xacts)
+ xact->entry = this;
+}
+
+bool entry_t::get_state(xact_t::state_t * state) const
+{
+ bool first = true;
+ bool hetero = false;
+
+ foreach (xact_t * xact, xacts) {
+ if (first) {
+ *state = xact->state;
+ first = false;
+ }
+ else if (*state != xact->state) {
+ hetero = true;
+ break;
+ }
+ }
+
+ return ! hetero;
+}
+
+void entry_t::add_xact(xact_t * xact)
+{
+ xact->entry = this;
+ entry_base_t::add_xact(xact);
+}
+
+namespace {
+ value_t get_date(entry_t& entry) {
+ return entry.date();
+ }
+
+ value_t get_payee(entry_t& entry) {
+ return string_value(entry.payee);
+ }
+
+ template <value_t (*Func)(entry_t&)>
+ value_t get_wrapper(call_scope_t& scope) {
+ return (*Func)(find_scope<entry_t>(scope));
+ }
+}
+
+expr_t::ptr_op_t entry_t::lookup(const string& name)
+{
+ switch (name[0]) {
+ case 'd':
+ if (name[1] == '\0' || name == "date")
+ return WRAP_FUNCTOR(get_wrapper<&get_date>);
+ break;
+
+ case 'f':
+ if (name.find("fmt_") == 0) {
+ switch (name[4]) {
+ case 'D':
+ return WRAP_FUNCTOR(get_wrapper<&get_date>);
+ case 'P':
+ return WRAP_FUNCTOR(get_wrapper<&get_payee>);
+ }
+ }
+ break;
+
+ case 'p':
+ if (name[1] == '\0' || name == "payee")
+ return WRAP_FUNCTOR(get_wrapper<&get_payee>);
+ break;
+ }
+ return journal->owner->current_report->lookup(name);
+}
+
+bool entry_t::valid() const
+{
+ if (! is_valid(_date) || ! journal) {
+ DEBUG("ledger.validate", "entry_t: ! _date || ! journal");
+ return false;
+ }
+
+ foreach (xact_t * xact, xacts)
+ if (xact->entry != this || ! xact->valid()) {
+ DEBUG("ledger.validate", "entry_t: xact not valid");
+ return false;
+ }
+
+ return true;
+}
+
+#if 0
+void entry_context::describe(std::ostream& out) const throw()
+{
+ if (! desc.empty())
+ out << desc << std::endl;
+
+ print_entry(out, entry, " ");
+}
+#endif
+
+void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
+{
+ xacts_list initial_xacts(entry.xacts.begin(),
+ entry.xacts.end());
+
+ foreach (xact_t * initial_xact, initial_xacts) {
+ if (predicate(*initial_xact)) {
+ foreach (xact_t * xact, xacts) {
+ amount_t amt;
+ assert(xact->amount);
+ if (! xact->amount.commodity()) {
+ if (! post)
+ continue;
+ assert(initial_xact->amount);
+ amt = initial_xact->amount * xact->amount;
+ } else {
+ if (post)
+ continue;
+ amt = xact->amount;
+ }
+
+ account_t * account = xact->account;
+ string fullname = account->fullname();
+ assert(! fullname.empty());
+ if (fullname == "$account" || fullname == "@account")
+ account = initial_xact->account;
+
+ xact_t * new_xact
+ = new xact_t(account, amt, xact->flags() | XACT_AUTO);
+
+ // Copy over details so that the resulting xact is a mirror of
+ // the automated entry's one.
+ new_xact->state = xact->state;
+ new_xact->_date = xact->_date;
+ new_xact->_date_eff = xact->_date_eff;
+ new_xact->note = xact->note;
+ new_xact->beg_pos = xact->beg_pos;
+ new_xact->beg_line = xact->beg_line;
+ new_xact->end_pos = xact->end_pos;
+ new_xact->end_line = xact->end_line;
+
+ entry.add_xact(new_xact);
+ }
+ }
+ }
+}
+
+void extend_entry_base(journal_t * journal, entry_base_t& base, bool post)
+{
+ foreach (auto_entry_t * entry, journal->auto_entries)
+ entry->extend_entry(base, post);
+}
+
+} // namespace ledger
diff --git a/src/entry.h b/src/entry.h
new file mode 100644
index 00000000..2834942f
--- /dev/null
+++ b/src/entry.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ENTRY_H
+#define _ENTRY_H
+
+#include "xact.h"
+#include "predicate.h"
+
+namespace ledger {
+
+class journal_t;
+
+class entry_base_t : public supports_flags<>
+{
+public:
+#define ENTRY_IN_CACHE 0x1
+
+ journal_t * journal;
+ string note;
+ unsigned long src_idx;
+ istream_pos_type beg_pos;
+ unsigned long beg_line;
+ istream_pos_type end_pos;
+ unsigned long end_line;
+ xacts_list xacts;
+
+ entry_base_t() : journal(NULL),
+ beg_pos(0), beg_line(0), end_pos(0), end_line(0)
+ {
+ TRACE_CTOR(entry_base_t, "");
+ }
+ entry_base_t(const entry_base_t& e);
+
+ virtual ~entry_base_t();
+
+ bool operator==(const entry_base_t& entry) {
+ return this == &entry;
+ }
+ bool operator!=(const entry_base_t& entry) {
+ return ! (*this == entry);
+ }
+
+ virtual void add_xact(xact_t * xact);
+ virtual bool remove_xact(xact_t * xact);
+
+ virtual bool finalize();
+ virtual bool valid() const = 0;
+};
+
+class entry_t : public entry_base_t, public scope_t
+{
+public:
+ date_t _date;
+ optional<date_t> _date_eff;
+ optional<string> code;
+ string payee;
+
+ entry_t() {
+ TRACE_CTOR(entry_t, "");
+ }
+ entry_t(const entry_t& e);
+
+ virtual ~entry_t() {
+ TRACE_DTOR(entry_t);
+ }
+
+ date_t actual_date() const {
+ return _date;
+ }
+ date_t effective_date() const {
+ if (! _date_eff)
+ return _date;
+ return *_date_eff;
+ }
+ date_t date() const {
+ if (xact_t::use_effective_date)
+ return effective_date();
+ else
+ return actual_date();
+ }
+
+ bool get_state(xact_t::state_t * state) const;
+
+ virtual void add_xact(xact_t * xact);
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+
+ virtual bool valid() const;
+};
+
+struct entry_finalizer_t {
+ virtual ~entry_finalizer_t() {}
+ virtual bool operator()(entry_t& entry, bool post) = 0;
+};
+
+class auto_entry_t : public entry_base_t
+{
+public:
+ item_predicate<xact_t> predicate;
+
+ auto_entry_t() {
+ TRACE_CTOR(auto_entry_t, "");
+ }
+ auto_entry_t(const auto_entry_t& other)
+ : entry_base_t(), predicate(other.predicate) {
+ TRACE_CTOR(auto_entry_t, "copy");
+ }
+ auto_entry_t(const string& _predicate)
+ : predicate(_predicate)
+ {
+ TRACE_CTOR(auto_entry_t, "const string&");
+ }
+
+ virtual ~auto_entry_t() {
+ TRACE_DTOR(auto_entry_t);
+ }
+
+ virtual void extend_entry(entry_base_t& entry, bool post);
+ virtual bool valid() const {
+ return true;
+ }
+};
+
+struct auto_entry_finalizer_t : public entry_finalizer_t
+{
+ journal_t * journal;
+
+ auto_entry_finalizer_t() : journal(NULL) {
+ TRACE_CTOR(auto_entry_finalizer_t, "");
+ }
+ auto_entry_finalizer_t(const auto_entry_finalizer_t& other)
+ : entry_finalizer_t(), journal(other.journal) {
+ TRACE_CTOR(auto_entry_finalizer_t, "copy");
+ }
+ auto_entry_finalizer_t(journal_t * _journal) : journal(_journal) {
+ TRACE_CTOR(auto_entry_finalizer_t, "journal_t *");
+ }
+ ~auto_entry_finalizer_t() throw() {
+ TRACE_DTOR(auto_entry_finalizer_t);
+ }
+
+ virtual bool operator()(entry_t& entry, bool post);
+};
+
+class period_entry_t : public entry_base_t
+{
+ public:
+ interval_t period;
+ string period_string;
+
+ period_entry_t() {
+ TRACE_CTOR(period_entry_t, "");
+ }
+ period_entry_t(const period_entry_t& e)
+ : entry_base_t(e), period(e.period), period_string(e.period_string) {
+ TRACE_CTOR(period_entry_t, "copy");
+ }
+ period_entry_t(const string& _period)
+ : period(_period), period_string(_period) {
+ TRACE_CTOR(period_entry_t, "const string&");
+ }
+
+ virtual ~period_entry_t() throw() {
+ TRACE_DTOR(period_entry_t);
+ }
+
+ virtual bool valid() const {
+ return period;
+ }
+};
+
+class func_finalizer_t : public entry_finalizer_t
+{
+ func_finalizer_t();
+
+public:
+ typedef function<bool (entry_t& entry, bool post)> func_t;
+
+ func_t func;
+
+ func_finalizer_t(func_t _func) : func(_func) {
+ TRACE_CTOR(func_finalizer_t, "func_t");
+ }
+ func_finalizer_t(const func_finalizer_t& other) :
+ entry_finalizer_t(), func(other.func) {
+ TRACE_CTOR(func_finalizer_t, "copy");
+ }
+ ~func_finalizer_t() throw() {
+ TRACE_DTOR(func_finalizer_t);
+ }
+
+ virtual bool operator()(entry_t& entry, bool post) {
+ return func(entry, post);
+ }
+};
+
+void extend_entry_base(journal_t * journal, entry_base_t& entry, bool post);
+
+inline bool auto_entry_finalizer_t::operator()(entry_t& entry, bool post) {
+ extend_entry_base(journal, entry, post);
+ return true;
+}
+
+typedef std::list<entry_t *> entries_list;
+typedef std::list<auto_entry_t *> auto_entries_list;
+typedef std::list<period_entry_t *> period_entries_list;
+
+} // namespace ledger
+
+#endif // _ENTRY_H
diff --git a/src/error.h b/src/error.h
new file mode 100644
index 00000000..fafef08e
--- /dev/null
+++ b/src/error.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ERROR_H
+#define _ERROR_H
+
+namespace ledger {
+
+extern std::ostringstream _desc_buffer;
+
+template <typename T>
+inline void throw_func(const string& message) {
+ _desc_buffer.str("");
+ throw T(message);
+}
+
+#define throw_(cls, msg) \
+ ((_desc_buffer << msg), throw_func<cls>(_desc_buffer.str()))
+
+extern std::ostringstream _ctxt_buffer;
+
+#define add_error_context(msg) \
+ ((static_cast<unsigned long>(_ctxt_buffer.tellp()) == 0) ? \
+ (_ctxt_buffer << msg) : (_ctxt_buffer << std::endl << msg))
+
+inline string error_context() {
+ string context = _ctxt_buffer.str();
+ _ctxt_buffer.str("");
+ return context;
+}
+
+inline string file_context(const path& file, std::size_t line) {
+ std::ostringstream buf;
+ buf << "\"" << file << "\", line " << line << ": ";
+ return buf.str();
+}
+
+inline string line_context(const string& line, long pos) {
+ std::ostringstream buf;
+ buf << " " << line << std::endl << " ";
+ long idx = pos < 0 ? line.length() - 1 : pos;
+ for (int i = 0; i < idx; i++)
+ buf << " ";
+ buf << "^" << std::endl;
+ return buf.str();
+}
+
+#define DECLARE_EXCEPTION(name, kind) \
+ class name : public kind { \
+ public: \
+ explicit name(const string& why) throw() : kind(why) {} \
+ virtual ~name() throw() {} \
+ }
+
+} // namespace ledger
+
+#endif // _ERROR_H
diff --git a/src/expr.cc b/src/expr.cc
new file mode 100644
index 00000000..37433179
--- /dev/null
+++ b/src/expr.cc
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "expr.h"
+#include "parser.h"
+#include "op.h"
+#include "scope.h" // jww (2008-08-01): not necessary
+
+namespace ledger {
+
+std::auto_ptr<expr_t::parser_t> expr_t::parser;
+
+expr_t::expr_t() : compiled(false)
+{
+ TRACE_CTOR(expr_t, "");
+}
+
+expr_t::expr_t(const expr_t& other)
+ : ptr(other.ptr), str(other.str), compiled(other.compiled)
+{
+ TRACE_CTOR(expr_t, "copy");
+}
+
+expr_t::expr_t(const string& _str, const unsigned int flags)
+ : str(_str), compiled(false)
+{
+ TRACE_CTOR(expr_t, "const string&");
+
+ if (! _str.empty())
+ ptr = parser->parse(str, flags);
+}
+
+expr_t::expr_t(std::istream& in, const unsigned int flags)
+ : compiled(false)
+{
+ TRACE_CTOR(expr_t, "std::istream&");
+
+ ptr = parser->parse(in, flags);
+}
+
+expr_t::expr_t(const ptr_op_t& _ptr, const string& _str)
+ : ptr(_ptr), str(_str), compiled(false)
+{
+ TRACE_CTOR(expr_t, "const ptr_op_t&, const string&");
+}
+
+expr_t::~expr_t() throw()
+{
+ TRACE_DTOR(expr_t);
+}
+
+expr_t& expr_t::operator=(const expr_t& _expr)
+{
+ if (this != &_expr) {
+ str = _expr.str;
+ ptr = _expr.ptr;
+ compiled = _expr.compiled;
+ }
+ return *this;
+}
+
+void expr_t::parse(const string& _str, const unsigned int flags)
+{
+ if (! parser.get())
+ throw_(parse_error, "Value expression parser not initialized");
+
+ str = _str;
+ ptr = parser->parse(str, flags);
+ compiled = false;
+}
+
+void expr_t::parse(std::istream& in, const unsigned int flags)
+{
+ if (! parser.get())
+ throw_(parse_error, "Value expression parser not initialized");
+
+ str = "<stream>";
+ ptr = parser->parse(in, flags);
+ compiled = false;
+}
+
+void expr_t::compile(scope_t& scope)
+{
+ if (ptr.get() && ! compiled) {
+ ptr = ptr->compile(scope);
+ compiled = true;
+ }
+}
+
+value_t expr_t::calc(scope_t& scope)
+{
+ if (ptr.get()) {
+ if (! compiled)
+ compile(scope);
+ return ptr->calc(scope);
+ }
+ return NULL_VALUE;
+}
+
+bool expr_t::is_constant() const
+{
+ assert(compiled);
+ return ptr.get() && ptr->is_value();
+}
+
+bool expr_t::is_function() const
+{
+ assert(compiled);
+ return ptr.get() && ptr->is_function();
+}
+
+value_t& expr_t::constant_value()
+{
+ assert(is_constant());
+ return ptr->as_value_lval();
+}
+
+const value_t& expr_t::constant_value() const
+{
+ assert(is_constant());
+ return ptr->as_value();
+}
+
+function_t& expr_t::get_function()
+{
+ assert(is_function());
+ return ptr->as_function_lval();
+}
+
+value_t expr_t::eval(const string& _expr, scope_t& scope)
+{
+ return expr_t(_expr).calc(scope);
+}
+
+void expr_t::print(std::ostream& out, scope_t& scope) const
+{
+ if (ptr) {
+ op_t::print_context_t context(scope);
+ ptr->print(out, context);
+ }
+}
+
+void expr_t::dump(std::ostream& out) const
+{
+ if (ptr) ptr->dump(out, 0);
+}
+
+void expr_t::read(const char *& data)
+{
+ if (ptr) ptr->read(data);
+}
+
+void expr_t::write(std::ostream& out) const
+{
+ if (ptr) ptr->write(out);
+}
+
+void expr_t::initialize()
+{
+ parser.reset(new expr_t::parser_t);
+}
+
+void expr_t::shutdown()
+{
+ parser.reset();
+}
+
+std::ostream& operator<<(std::ostream& out, const expr_t& expr) {
+ // jww (2008-08-01): shouldn't be necessary
+ symbol_scope_t scope;
+ expr.print(out, scope);
+ return out;
+}
+
+} // namespace ledger
diff --git a/src/expr.h b/src/expr.h
new file mode 100644
index 00000000..8c110ad9
--- /dev/null
+++ b/src/expr.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _EXPR_H
+#define _EXPR_H
+
+#include "value.h"
+
+namespace ledger {
+
+DECLARE_EXCEPTION(parse_error, std::runtime_error);
+DECLARE_EXCEPTION(compile_error, std::runtime_error);
+DECLARE_EXCEPTION(calc_error, std::runtime_error);
+DECLARE_EXCEPTION(usage_error, std::runtime_error);
+
+class scope_t;
+class call_scope_t;
+
+typedef function<value_t (call_scope_t&)> function_t;
+
+class expr_t
+{
+ struct token_t;
+
+ class parser_t;
+ static std::auto_ptr<parser_t> parser;
+
+public:
+ class op_t;
+ typedef intrusive_ptr<op_t> ptr_op_t;
+
+private:
+ ptr_op_t ptr;
+ string str;
+ bool compiled;
+
+ static void initialize();
+ static void shutdown();
+
+ friend class session_t;
+
+public:
+ expr_t();
+ expr_t(const expr_t& other);
+ expr_t(const ptr_op_t& _ptr, const string& _str = "");
+
+ expr_t(const string& _str, const unsigned int flags = 0);
+ expr_t(std::istream& in, const unsigned int flags = 0);
+
+ virtual ~expr_t() throw();
+
+ expr_t& operator=(const expr_t& _expr);
+ expr_t& operator=(const string& _expr) {
+ parse(_expr);
+ return *this;
+ }
+
+ operator bool() const throw() {
+ return ptr.get() != NULL;
+ }
+ string text() const throw() {
+ return str;
+ }
+
+ // This has special use in the textual parser
+ void set_text(const string& txt) {
+ str = txt;
+ }
+
+ void parse(const string& _str, const unsigned int flags = 0);
+ void parse(std::istream& in, const unsigned int flags = 0);
+
+ void compile(scope_t& scope);
+ value_t calc(scope_t& scope);
+ value_t calc(scope_t& scope) const;
+
+ bool is_constant() const;
+ bool is_function() const;
+
+ value_t& constant_value();
+ const value_t& constant_value() const;
+
+ function_t& get_function();
+
+ void print(std::ostream& out, scope_t& scope) const;
+ void dump(std::ostream& out) const;
+ void read(const char *& data);
+ void write(std::ostream& out) const;
+
+ static value_t eval(const string& _expr, scope_t& scope);
+};
+
+std::ostream& operator<<(std::ostream& out, const expr_t& expr);
+
+} // namespace ledger
+
+#endif // _EXPR_H
diff --git a/fdstream.hpp b/src/fdstream.h
index a74a5781..a74a5781 100644
--- a/fdstream.hpp
+++ b/src/fdstream.h
diff --git a/src/filters.cc b/src/filters.cc
new file mode 100644
index 00000000..e5455423
--- /dev/null
+++ b/src/filters.cc
@@ -0,0 +1,755 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "filters.h"
+#include "iterators.h"
+#include "compare.h"
+#include "session.h"
+#include "format.h"
+#include "textual.h"
+
+namespace ledger {
+
+pass_down_xacts::pass_down_xacts(xact_handler_ptr handler,
+ xacts_iterator& iter)
+ : item_handler<xact_t>(handler)
+{
+ TRACE_CTOR(pass_down_xacts, "xact_handler_ptr, xacts_iterator");
+
+ for (xact_t * xact = iter(); xact; xact = iter())
+ item_handler<xact_t>::operator()(*xact);
+}
+
+void truncate_entries::flush()
+{
+ if (! xacts.size())
+ return;
+
+ entry_t * last_entry = (*xacts.begin())->entry;
+
+ int l = 0;
+ foreach (xact_t * xact, xacts)
+ if (last_entry != xact->entry) {
+ l++;
+ last_entry = xact->entry;
+ }
+ l++;
+
+ last_entry = (*xacts.begin())->entry;
+
+ int i = 0;
+ foreach (xact_t * xact, xacts) {
+ if (last_entry != xact->entry) {
+ last_entry = xact->entry;
+ i++;
+ }
+
+ bool print = false;
+ if (head_count) {
+ if (head_count > 0 && i < head_count)
+ print = true;
+ else if (head_count < 0 && i >= - head_count)
+ print = true;
+ }
+
+ if (! print && tail_count) {
+ if (tail_count > 0 && l - i <= tail_count)
+ print = true;
+ else if (tail_count < 0 && l - i > - tail_count)
+ print = true;
+ }
+
+ if (print)
+ item_handler<xact_t>::operator()(*xact);
+ }
+ xacts.clear();
+
+ item_handler<xact_t>::flush();
+}
+
+void set_account_value::operator()(xact_t& xact)
+{
+ account_t * acct = xact.reported_account();
+
+ account_t::xdata_t& xdata(acct->xdata());
+ xact.add_to_value(xdata.value);
+
+ xdata.count++;
+ if (xact.has_flags(XACT_VIRTUAL))
+ xdata.virtuals++;
+
+ item_handler<xact_t>::operator()(xact);
+}
+
+void sort_xacts::post_accumulated_xacts()
+{
+ std::stable_sort(xacts.begin(), xacts.end(),
+ compare_items<xact_t>(sort_order));
+
+ foreach (xact_t * xact, xacts) {
+ xact->xdata().drop_flags(XACT_EXT_SORT_CALC);
+ item_handler<xact_t>::operator()(*xact);
+ }
+
+ xacts.clear();
+}
+
+void calc_xacts::operator()(xact_t& xact)
+{
+ try {
+ xact_t::xdata_t& xdata(xact.xdata());
+
+ if (last_xact && last_xact->has_xdata()) {
+ if (xdata.total.is_null())
+ xdata.total = last_xact->xdata().total;
+ else
+ xdata.total += last_xact->xdata().total;
+ xdata.index = last_xact->xdata().index + 1;
+ } else {
+ xdata.index = 0;
+ }
+
+ if (! xdata.has_flags(XACT_EXT_NO_TOTAL))
+ xact.add_to_value(xdata.total);
+
+ item_handler<xact_t>::operator()(xact);
+
+ last_xact = &xact;
+ }
+ catch (const std::exception& err) {
+ add_error_context("Calculating transaction at");
+#if 0
+ add_error_context(xact_context(xact));
+#endif
+ throw err;
+ }
+}
+
+void invert_xacts::operator()(xact_t& xact)
+{
+ if (xact.has_xdata() &&
+ xact.xdata().has_flags(XACT_EXT_COMPOUND)) {
+ xact.xdata().value.negate();
+ } else {
+ xact.amount.negate();
+ if (xact.cost)
+ xact.cost->negate();
+ }
+
+ item_handler<xact_t>::operator()(xact);
+}
+
+
+static inline
+void handle_value(const value_t& value,
+ account_t * account,
+ entry_t * entry,
+ unsigned int flags,
+ std::list<xact_t>& temps,
+ item_handler<xact_t>& handler,
+ const date_t& date = date_t(),
+ xacts_list * component_xacts = NULL)
+{
+ temps.push_back(xact_t(account));
+ xact_t& xact(temps.back());
+ xact.entry = entry;
+ xact.add_flags(XACT_TEMP);
+ entry->add_xact(&xact);
+
+ // If there are component xacts to associate with this
+ // temporary, do so now.
+
+ if (component_xacts)
+ xact.xdata().copy_component_xacts(*component_xacts);
+
+ // If the account for this xact is all virtual, then report
+ // the xact as such. This allows subtotal reports to show
+ // "(Account)" for accounts that contain only virtual xacts.
+
+ if (account && account->has_xdata())
+ if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS)) {
+ xact.add_flags(XACT_VIRTUAL);
+ if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS))
+ xact.add_flags(XACT_BALANCE);
+ }
+
+ xact_t::xdata_t& xdata(xact.xdata());
+
+ if (is_valid(date))
+ xdata.date = date;
+
+ value_t temp(value);
+
+ switch (value.type()) {
+ case value_t::BOOLEAN:
+ case value_t::DATETIME:
+ case value_t::DATE:
+ case value_t::INTEGER:
+ temp.cast(value_t::AMOUNT);
+ // fall through...
+
+ case value_t::AMOUNT:
+ xact.amount = temp.as_amount();
+ break;
+
+ case value_t::BALANCE:
+ case value_t::BALANCE_PAIR:
+ xdata.value = temp;
+ flags |= XACT_EXT_COMPOUND;
+ break;
+
+ default:
+ assert(false); // jww (2008-04-24): What to do here?
+ break;
+ }
+
+ if (flags)
+ xdata.add_flags(flags);
+
+ handler(xact);
+}
+
+void collapse_xacts::report_subtotal()
+{
+ assert(count >= 1);
+
+ if (count == 1) {
+ item_handler<xact_t>::operator()(*last_xact);
+ } else {
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = last_entry->payee;
+ entry._date = last_entry->_date;
+
+ handle_value(subtotal, &totals_account, last_entry, 0, xact_temps,
+ *handler);
+ }
+
+ last_entry = NULL;
+ last_xact = NULL;
+ subtotal = 0L;
+ count = 0;
+}
+
+void collapse_xacts::operator()(xact_t& xact)
+{
+ // If we've reached a new entry, report on the subtotal
+ // accumulated thus far.
+
+ if (last_entry && last_entry != xact.entry && count > 0)
+ report_subtotal();
+
+ xact.add_to_value(subtotal);
+ count++;
+
+ last_entry = xact.entry;
+ last_xact = &xact;
+}
+
+void related_xacts::flush()
+{
+ if (xacts.size() > 0) {
+ foreach (xact_t * xact, xacts) {
+ if (xact->entry) {
+ foreach (xact_t * r_xact, xact->entry->xacts) {
+ xact_t::xdata_t& xdata(r_xact->xdata());
+ if (! xdata.has_flags(XACT_EXT_HANDLED) &&
+ (! xdata.has_flags(XACT_EXT_RECEIVED) ?
+ ! r_xact->has_flags(XACT_AUTO | XACT_VIRTUAL) :
+ also_matching)) {
+ xdata.add_flags(XACT_EXT_HANDLED);
+ item_handler<xact_t>::operator()(*r_xact);
+ }
+ }
+ } else {
+ // This code should only be reachable from the "output"
+ // command, since that is the only command which attempts to
+ // output auto or period entries.
+ xact_t::xdata_t& xdata(xact->xdata());
+ if (! xdata.has_flags(XACT_EXT_HANDLED) &&
+ ! xact->has_flags(XACT_AUTO)) {
+ xdata.add_flags(XACT_EXT_HANDLED);
+ item_handler<xact_t>::operator()(*xact);
+ }
+ }
+ }
+ }
+
+ item_handler<xact_t>::flush();
+}
+
+void changed_value_xacts::output_diff(const date_t& date)
+{
+ value_t cur_bal;
+
+ last_xact->xdata().date = date;
+#if 0
+ compute_total(cur_bal, details_t(*last_xact));
+#endif
+ cur_bal.round();
+
+#if 0
+ // jww (2008-04-24): What does this do?
+ last_xact->xdata().date = 0;
+#endif
+
+ if (value_t diff = cur_bal - last_balance) {
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = "Commodities revalued";
+ entry._date = date;
+
+ handle_value(diff, NULL, &entry, XACT_EXT_NO_TOTAL, xact_temps,
+ *handler);
+ }
+}
+
+void changed_value_xacts::operator()(xact_t& xact)
+{
+ if (last_xact)
+ output_diff(last_xact->reported_date());
+
+ if (changed_values_only)
+ xact.xdata().add_flags(XACT_EXT_DISPLAYED);
+
+ item_handler<xact_t>::operator()(xact);
+
+#if 0
+ compute_total(last_balance, details_t(xact));
+#endif
+ last_balance.round();
+
+ last_xact = &xact;
+}
+
+void component_xacts::operator()(xact_t& xact)
+{
+ if (handler && pred(xact)) {
+ if (xact.has_xdata() &&
+ xact.xdata().has_component_xacts())
+#if 0
+ xact.xdata().walk_component_xacts(*handler);
+#else
+ ;
+#endif
+ else
+ (*handler)(xact);
+ }
+}
+
+void subtotal_xacts::report_subtotal(const char * spec_fmt)
+{
+ std::ostringstream out_date;
+ if (! spec_fmt) {
+ string fmt = "- ";
+ fmt += output_date_format;
+ out_date << format_date(finish, string(fmt));
+ } else {
+ out_date << format_date(finish, string(spec_fmt));
+ }
+
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = out_date.str();
+ entry._date = start;
+
+ foreach (values_map::value_type& pair, values)
+ handle_value(pair.second.value, pair.second.account, &entry, 0,
+ xact_temps, *handler, finish, &pair.second.components);
+
+ values.clear();
+}
+
+void subtotal_xacts::operator()(xact_t& xact)
+{
+ if (! is_valid(start) || xact.date() < start)
+ start = xact.date();
+ if (! is_valid(finish) || xact.date() > finish)
+ finish = xact.date();
+
+ account_t * acct = xact.reported_account();
+ assert(acct);
+
+ values_map::iterator i = values.find(acct->fullname());
+ if (i == values.end()) {
+ value_t temp;
+ xact.add_to_value(temp);
+ std::pair<values_map::iterator, bool> result
+ = values.insert(values_pair(acct->fullname(), acct_value_t(acct, temp)));
+ assert(result.second);
+
+ if (remember_components)
+ (*result.first).second.components.push_back(&xact);
+ } else {
+ xact.add_to_value((*i).second.value);
+
+ if (remember_components)
+ (*i).second.components.push_back(&xact);
+ }
+
+ // If the account for this xact is all virtual, mark it as
+ // such, so that `handle_value' can show "(Account)" for accounts
+ // that contain only virtual xacts.
+
+ if (! xact.has_flags(XACT_VIRTUAL))
+ xact.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS);
+ else if (! xact.has_flags(XACT_BALANCE))
+ xact.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS);
+}
+
+void interval_xacts::report_subtotal(const date_t& date)
+{
+ assert(last_xact);
+
+ start = interval.begin;
+ if (is_valid(date))
+ finish = date - gregorian::days(1);
+ else
+ finish = last_xact->date();
+
+ subtotal_xacts::report_subtotal();
+
+ last_xact = NULL;
+}
+
+void interval_xacts::operator()(xact_t& xact)
+{
+ const date_t& date(xact.date());
+
+ if ((is_valid(interval.begin) && date < interval.begin) ||
+ (is_valid(interval.end) && date >= interval.end))
+ return;
+
+ if (interval) {
+ if (! started) {
+ if (! is_valid(interval.begin))
+ interval.start(date);
+ start = interval.begin;
+ started = true;
+ }
+
+ date_t quant = interval.increment(interval.begin);
+ if (date >= quant) {
+ if (last_xact)
+ report_subtotal(quant);
+
+ date_t temp;
+ while (date >= (temp = interval.increment(quant))) {
+ if (quant == temp)
+ break;
+ quant = temp;
+ }
+ start = interval.begin = quant;
+ }
+
+ subtotal_xacts::operator()(xact);
+ } else {
+ item_handler<xact_t>::operator()(xact);
+ }
+
+ last_xact = &xact;
+}
+
+by_payee_xacts::~by_payee_xacts()
+{
+ TRACE_DTOR(by_payee_xacts);
+
+ foreach (payee_subtotals_map::value_type& pair, payee_subtotals)
+ checked_delete(pair.second);
+}
+
+void by_payee_xacts::flush()
+{
+ foreach (payee_subtotals_map::value_type& pair, payee_subtotals)
+ pair.second->report_subtotal(pair.first.c_str());
+
+ item_handler<xact_t>::flush();
+
+ payee_subtotals.clear();
+}
+
+void by_payee_xacts::operator()(xact_t& xact)
+{
+ payee_subtotals_map::iterator i = payee_subtotals.find(xact.entry->payee);
+ if (i == payee_subtotals.end()) {
+ payee_subtotals_pair
+ temp(xact.entry->payee,
+ new subtotal_xacts(handler, remember_components));
+ std::pair<payee_subtotals_map::iterator, bool> result
+ = payee_subtotals.insert(temp);
+
+ assert(result.second);
+ if (! result.second)
+ return;
+ i = result.first;
+ }
+
+ if (xact.date() > (*i).second->start)
+ (*i).second->start = xact.date();
+
+ (*(*i).second)(xact);
+}
+
+void set_comm_as_payee::operator()(xact_t& xact)
+{
+ entry_temps.push_back(*xact.entry);
+ entry_t& entry = entry_temps.back();
+ entry._date = xact.date();
+ entry.code = xact.entry->code;
+
+ if (xact.amount.commodity())
+ entry.payee = xact.amount.commodity().symbol();
+ else
+ entry.payee = "<none>";
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.state = xact.state;
+ temp.add_flags(XACT_TEMP);
+
+ entry.add_xact(&temp);
+
+ item_handler<xact_t>::operator()(temp);
+}
+
+void set_code_as_payee::operator()(xact_t& xact)
+{
+ entry_temps.push_back(*xact.entry);
+ entry_t& entry = entry_temps.back();
+ entry._date = xact.date();
+
+ if (xact.entry->code)
+ entry.payee = *xact.entry->code;
+ else
+ entry.payee = "<none>";
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.state = xact.state;
+ temp.add_flags(XACT_TEMP);
+
+ entry.add_xact(&temp);
+
+ item_handler<xact_t>::operator()(temp);
+}
+
+void dow_xacts::flush()
+{
+ for (int i = 0; i < 7; i++) {
+ start = finish = date_t();
+ foreach (xact_t * xact, days_of_the_week[i])
+ subtotal_xacts::operator()(*xact);
+ subtotal_xacts::report_subtotal("%As");
+ days_of_the_week[i].clear();
+ }
+
+ subtotal_xacts::flush();
+}
+
+void generate_xacts::add_period_entries
+ (period_entries_list& period_entries)
+{
+ foreach (period_entry_t * entry, period_entries)
+ foreach (xact_t * xact, entry->xacts)
+ add_xact(entry->period, *xact);
+}
+
+void generate_xacts::add_xact(const interval_t& period,
+ xact_t& xact)
+{
+ pending_xacts.push_back(pending_xacts_pair(period, &xact));
+}
+
+void budget_xacts::report_budget_items(const date_t& date)
+{
+ if (pending_xacts.size() == 0)
+ return;
+
+ bool reported;
+ do {
+ reported = false;
+ foreach (pending_xacts_list::value_type& pair, pending_xacts) {
+ date_t& begin = pair.first.begin;
+ if (! is_valid(begin)) {
+ pair.first.start(date);
+ begin = pair.first.begin;
+ }
+
+ if (begin < date &&
+ (! is_valid(pair.first.end) || begin < pair.first.end)) {
+ xact_t& xact = *pair.second;
+
+ DEBUG("ledger.walk.budget", "Reporting budget for "
+ << xact.reported_account()->fullname());
+
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = "Budget entry";
+ entry._date = begin;
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.add_flags(XACT_AUTO | XACT_TEMP);
+ temp.amount.negate();
+ entry.add_xact(&temp);
+
+ begin = pair.first.increment(begin);
+
+ item_handler<xact_t>::operator()(temp);
+
+ reported = true;
+ }
+ }
+ } while (reported);
+}
+
+void budget_xacts::operator()(xact_t& xact)
+{
+ bool xact_in_budget = false;
+
+ foreach (pending_xacts_list::value_type& pair, pending_xacts)
+ for (account_t * acct = xact.reported_account();
+ acct;
+ acct = acct->parent) {
+ if (acct == (*pair.second).reported_account()) {
+ xact_in_budget = true;
+ // Report the xact as if it had occurred in the parent
+ // account.
+ if (xact.reported_account() != acct)
+ xact.xdata().account = acct;
+ goto handle;
+ }
+ }
+
+ handle:
+ if (xact_in_budget && flags & BUDGET_BUDGETED) {
+ report_budget_items(xact.date());
+ item_handler<xact_t>::operator()(xact);
+ }
+ else if (! xact_in_budget && flags & BUDGET_UNBUDGETED) {
+ item_handler<xact_t>::operator()(xact);
+ }
+}
+
+void forecast_xacts::add_xact(const interval_t& period, xact_t& xact)
+{
+ generate_xacts::add_xact(period, xact);
+
+ interval_t& i = pending_xacts.back().first;
+ if (! is_valid(i.begin)) {
+ i.start(current_date);
+ i.begin = i.increment(i.begin);
+ } else {
+ while (i.begin < current_date)
+ i.begin = i.increment(i.begin);
+ }
+}
+
+void forecast_xacts::flush()
+{
+ xacts_list passed;
+ date_t last;
+
+ while (pending_xacts.size() > 0) {
+ pending_xacts_list::iterator least = pending_xacts.begin();
+ for (pending_xacts_list::iterator i = ++pending_xacts.begin();
+ i != pending_xacts.end();
+ i++)
+ if ((*i).first.begin < (*least).first.begin)
+ least = i;
+
+ date_t& begin = (*least).first.begin;
+
+ if (is_valid((*least).first.end) && begin >= (*least).first.end) {
+ pending_xacts.erase(least);
+ passed.remove((*least).second);
+ continue;
+ }
+
+ xact_t& xact = *(*least).second;
+
+ entry_temps.push_back(entry_t());
+ entry_t& entry = entry_temps.back();
+ entry.payee = "Forecast entry";
+ entry._date = begin;
+
+ xact_temps.push_back(xact);
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry;
+ temp.add_flags(XACT_AUTO | XACT_TEMP);
+ entry.add_xact(&temp);
+
+ date_t next = (*least).first.increment(begin);
+ if (next < begin || (is_valid(last) && (next - last).days() > 365 * 5))
+ break;
+ begin = next;
+
+ item_handler<xact_t>::operator()(temp);
+
+ if (temp.has_xdata() &&
+ temp.xdata().has_flags(XACT_EXT_MATCHES)) {
+ if (! pred(temp))
+ break;
+ last = temp.date();
+ passed.clear();
+ } else {
+ bool found = false;
+ foreach (xact_t * x, passed)
+ if (x == &xact) {
+ found = true;
+ break;
+ }
+
+ if (! found) {
+ passed.push_back(&xact);
+ if (passed.size() >= pending_xacts.size())
+ break;
+ }
+ }
+ }
+
+ item_handler<xact_t>::flush();
+}
+
+pass_down_accounts::pass_down_accounts(acct_handler_ptr handler,
+ accounts_iterator& iter)
+ : item_handler<account_t>(handler)
+{
+ TRACE_CTOR(pass_down_accounts,
+ "acct_handler_ptr, accounts_iterator");
+ for (account_t * account = iter(); account; account = iter())
+ item_handler<account_t>::operator()(*account);
+}
+
+} // namespace ledger
diff --git a/src/filters.h b/src/filters.h
new file mode 100644
index 00000000..7f67cbd8
--- /dev/null
+++ b/src/filters.h
@@ -0,0 +1,702 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FILTERS_H
+#define _FILTERS_H
+
+#include "handler.h"
+#include "predicate.h"
+#include "entry.h"
+
+namespace ledger {
+
+//////////////////////////////////////////////////////////////////////
+//
+// Transaction filters
+//
+
+class ignore_xacts : public item_handler<xact_t>
+{
+public:
+ virtual void operator()(xact_t&) {}
+};
+
+class clear_xact_xdata : public item_handler<xact_t>
+{
+public:
+ virtual void operator()(xact_t& xact) {
+ xact.clear_xdata();
+ }
+};
+
+class xacts_iterator;
+
+class pass_down_xacts : public item_handler<xact_t>
+{
+ pass_down_xacts();
+
+public:
+ pass_down_xacts(xact_handler_ptr handler, xacts_iterator& iter);
+
+ virtual ~pass_down_xacts() {
+ TRACE_DTOR(pass_down_xacts);
+ }
+};
+
+class push_to_xacts_list : public item_handler<xact_t>
+{
+ push_to_xacts_list();
+
+public:
+ xacts_list& xacts;
+
+ push_to_xacts_list(xacts_list& _xacts) : xacts(_xacts) {
+ TRACE_CTOR(push_to_xacts_list, "xacts_list&");
+ }
+ virtual ~push_to_xacts_list() {
+ TRACE_DTOR(push_to_xacts_list);
+ }
+
+ virtual void operator()(xact_t& xact) {
+ xacts.push_back(&xact);
+ }
+};
+
+class truncate_entries : public item_handler<xact_t>
+{
+ int head_count;
+ int tail_count;
+
+ xacts_list xacts;
+
+ truncate_entries();
+
+public:
+ truncate_entries(xact_handler_ptr handler,
+ int _head_count, int _tail_count)
+ : item_handler<xact_t>(handler),
+ head_count(_head_count), tail_count(_tail_count) {
+ TRACE_CTOR(truncate_entries, "xact_handler_ptr, int, int");
+ }
+ virtual ~truncate_entries() {
+ TRACE_DTOR(truncate_entries);
+ }
+
+ virtual void flush();
+ virtual void operator()(xact_t& xact) {
+ if (tail_count == 0 && head_count > 0 &&
+ xacts.size() >= static_cast<unsigned int>(head_count))
+ return;
+ xacts.push_back(&xact);
+ }
+};
+
+class set_account_value : public item_handler<xact_t>
+{
+public:
+ set_account_value(xact_handler_ptr handler = xact_handler_ptr())
+ : item_handler<xact_t>(handler) {}
+
+ virtual void operator()(xact_t& xact);
+};
+
+class sort_xacts : public item_handler<xact_t>
+{
+ typedef std::deque<xact_t *> xacts_deque;
+
+ xacts_deque xacts;
+ const expr_t sort_order;
+
+ sort_xacts();
+
+public:
+ sort_xacts(xact_handler_ptr handler,
+ const expr_t& _sort_order)
+ : item_handler<xact_t>(handler),
+ sort_order(_sort_order) {
+ TRACE_CTOR(sort_xacts,
+ "xact_handler_ptr, const value_expr&");
+ }
+ sort_xacts(xact_handler_ptr handler,
+ const string& _sort_order)
+ : item_handler<xact_t>(handler),
+ sort_order(_sort_order) {
+ TRACE_CTOR(sort_xacts,
+ "xact_handler_ptr, const string&");
+ }
+ virtual ~sort_xacts() {
+ TRACE_DTOR(sort_xacts);
+ }
+
+ virtual void post_accumulated_xacts();
+
+ virtual void flush() {
+ post_accumulated_xacts();
+ item_handler<xact_t>::flush();
+ }
+
+ virtual void operator()(xact_t& xact) {
+ xacts.push_back(&xact);
+ }
+};
+
+class sort_entries : public item_handler<xact_t>
+{
+ sort_xacts sorter;
+ entry_t * last_entry;
+
+ sort_entries();
+
+public:
+ sort_entries(xact_handler_ptr handler,
+ const expr_t& _sort_order)
+ : sorter(handler, _sort_order) {
+ TRACE_CTOR(sort_entries,
+ "xact_handler_ptr, const value_expr&");
+ }
+ sort_entries(xact_handler_ptr handler,
+ const string& _sort_order)
+ : sorter(handler, _sort_order) {
+ TRACE_CTOR(sort_entries,
+ "xact_handler_ptr, const string&");
+ }
+ virtual ~sort_entries() {
+ TRACE_DTOR(sort_entries);
+ }
+
+ virtual void flush() {
+ sorter.flush();
+ item_handler<xact_t>::flush();
+ }
+
+ virtual void operator()(xact_t& xact) {
+ if (last_entry && xact.entry != last_entry)
+ sorter.post_accumulated_xacts();
+
+ sorter(xact);
+
+ last_entry = xact.entry;
+ }
+};
+
+class filter_xacts : public item_handler<xact_t>
+{
+ item_predicate<xact_t> pred;
+
+ filter_xacts();
+
+public:
+ filter_xacts(xact_handler_ptr handler,
+ const expr_t& predicate)
+ : item_handler<xact_t>(handler), pred(predicate) {
+ TRACE_CTOR(filter_xacts,
+ "xact_handler_ptr, const value_expr&");
+ }
+
+ filter_xacts(xact_handler_ptr handler,
+ const string& predicate)
+ : item_handler<xact_t>(handler), pred(predicate) {
+ TRACE_CTOR(filter_xacts,
+ "xact_handler_ptr, const string&");
+ }
+ virtual ~filter_xacts() {
+ TRACE_DTOR(filter_xacts);
+ }
+
+ virtual void operator()(xact_t& xact) {
+ if (pred(xact)) {
+ xact.xdata().add_flags(XACT_EXT_MATCHES);
+ (*handler)(xact);
+ }
+ }
+};
+
+class calc_xacts : public item_handler<xact_t>
+{
+ xact_t * last_xact;
+
+ calc_xacts();
+
+public:
+ calc_xacts(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler), last_xact(NULL) {
+ TRACE_CTOR(calc_xacts, "xact_handler_ptr");
+ }
+ virtual ~calc_xacts() {
+ TRACE_DTOR(calc_xacts);
+ }
+
+ virtual void operator()(xact_t& xact);
+};
+
+class invert_xacts : public item_handler<xact_t>
+{
+ invert_xacts();
+
+public:
+ invert_xacts(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler) {}
+
+ virtual void operator()(xact_t& xact);
+};
+
+inline void clear_entries_xacts(std::list<entry_t>& entries_list) {
+ foreach (entry_t& entry, entries_list)
+ entry.xacts.clear();
+}
+
+class collapse_xacts : public item_handler<xact_t>
+{
+ value_t subtotal;
+ unsigned int count;
+ entry_t * last_entry;
+ xact_t * last_xact;
+ account_t totals_account;
+
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+ collapse_xacts();
+
+public:
+ collapse_xacts(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler), count(0),
+ last_entry(NULL), last_xact(NULL),
+ totals_account(NULL, "<Total>") {
+ TRACE_CTOR(collapse_xacts, "xact_handler_ptr");
+ }
+ virtual ~collapse_xacts() {
+ TRACE_DTOR(collapse_xacts);
+ clear_entries_xacts(entry_temps);
+ }
+
+ virtual void flush() {
+ if (subtotal)
+ report_subtotal();
+ item_handler<xact_t>::flush();
+ }
+
+ void report_subtotal();
+
+ virtual void operator()(xact_t& xact);
+};
+
+class component_xacts : public item_handler<xact_t>
+{
+ item_predicate<xact_t> pred;
+
+ component_xacts();
+
+public:
+ component_xacts(xact_handler_ptr handler,
+ const expr_t& predicate)
+ : item_handler<xact_t>(handler), pred(predicate) {
+ TRACE_CTOR(component_xacts,
+ "xact_handler_ptr, const value_expr&");
+ }
+ component_xacts(xact_handler_ptr handler,
+ const string& predicate)
+ : item_handler<xact_t>(handler), pred(predicate) {
+ TRACE_CTOR(component_xacts,
+ "xact_handler_ptr, const string&");
+ }
+ virtual ~component_xacts() throw() {
+ TRACE_DTOR(component_xacts);
+ }
+
+ virtual void operator()(xact_t& xact);
+};
+
+class related_xacts : public item_handler<xact_t>
+{
+ xacts_list xacts;
+ bool also_matching;
+
+ related_xacts();
+
+public:
+ related_xacts(xact_handler_ptr handler,
+ const bool _also_matching = false)
+ : item_handler<xact_t>(handler),
+ also_matching(_also_matching) {
+ TRACE_CTOR(related_xacts,
+ "xact_handler_ptr, const bool");
+ }
+ virtual ~related_xacts() throw() {
+ TRACE_DTOR(related_xacts);
+ }
+
+ virtual void flush();
+ virtual void operator()(xact_t& xact) {
+ xact.xdata().add_flags(XACT_EXT_RECEIVED);
+ xacts.push_back(&xact);
+ }
+};
+
+class changed_value_xacts : public item_handler<xact_t>
+{
+ // This filter requires that calc_xacts be used at some point
+ // later in the chain.
+
+ bool changed_values_only;
+ xact_t * last_xact;
+ value_t last_balance;
+
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+ changed_value_xacts();
+
+public:
+ changed_value_xacts(xact_handler_ptr handler,
+ bool _changed_values_only)
+ : item_handler<xact_t>(handler),
+ changed_values_only(_changed_values_only), last_xact(NULL) {
+ TRACE_CTOR(changed_value_xacts,
+ "xact_handler_ptr, bool");
+ }
+ virtual ~changed_value_xacts() {
+ TRACE_DTOR(changed_value_xacts);
+ clear_entries_xacts(entry_temps);
+ }
+
+ virtual void flush() {
+ if (last_xact) {
+ output_diff(current_date);
+ last_xact = NULL;
+ }
+ item_handler<xact_t>::flush();
+ }
+
+ void output_diff(const date_t& current);
+
+ virtual void operator()(xact_t& xact);
+};
+
+class subtotal_xacts : public item_handler<xact_t>
+{
+ class acct_value_t
+ {
+ acct_value_t();
+
+ public:
+ account_t * account;
+ value_t value;
+
+ xacts_list components;
+
+ acct_value_t(account_t * a) : account(a) {
+ TRACE_CTOR(acct_value_t, "acount_t *");
+ }
+ acct_value_t(account_t * a, value_t& v) : account(a), value(v) {
+ TRACE_CTOR(acct_value_t, "acount_t *, value_t&");
+ }
+ acct_value_t(const acct_value_t& av)
+ : account(av.account), value(av.value),
+ components(av.components) {
+ TRACE_CTOR(acct_value_t, "copy");
+ }
+ ~acct_value_t() throw() {
+ TRACE_DTOR(acct_value_t);
+ }
+ };
+
+ typedef std::map<string, acct_value_t> values_map;
+ typedef std::pair<string, acct_value_t> values_pair;
+
+ subtotal_xacts();
+
+protected:
+ values_map values;
+ bool remember_components;
+
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+public:
+ date_t start;
+ date_t finish;
+
+ subtotal_xacts(xact_handler_ptr handler,
+ bool _remember_components = false)
+ : item_handler<xact_t>(handler),
+ remember_components(_remember_components) {
+ TRACE_CTOR(subtotal_xacts,
+ "xact_handler_ptr, bool");
+ }
+ virtual ~subtotal_xacts() {
+ TRACE_DTOR(subtotal_xacts);
+ clear_entries_xacts(entry_temps);
+ }
+
+ void report_subtotal(const char * spec_fmt = NULL);
+
+ virtual void flush() {
+ if (values.size() > 0)
+ report_subtotal();
+ item_handler<xact_t>::flush();
+ }
+ virtual void operator()(xact_t& xact);
+};
+
+class interval_xacts : public subtotal_xacts
+{
+ interval_t interval;
+ xact_t * last_xact;
+ bool started;
+
+ interval_xacts();
+
+public:
+ interval_xacts(xact_handler_ptr _handler,
+ const interval_t& _interval,
+ bool remember_components = false)
+ : subtotal_xacts(_handler, remember_components),
+ interval(_interval), last_xact(NULL), started(false) {
+ TRACE_CTOR(interval_xacts,
+ "xact_handler_ptr, const interval_t&, bool");
+ }
+ interval_xacts(xact_handler_ptr _handler,
+ const string& _interval,
+ bool remember_components = false)
+ : subtotal_xacts(_handler, remember_components),
+ interval(_interval), last_xact(NULL), started(false) {
+ TRACE_CTOR(interval_xacts,
+ "xact_handler_ptr, const string&, bool");
+ }
+ virtual ~interval_xacts() throw() {
+ TRACE_DTOR(interval_xacts);
+ }
+
+ void report_subtotal(const date_t& moment = date_t());
+
+ virtual void flush() {
+ if (last_xact)
+ report_subtotal();
+ subtotal_xacts::flush();
+ }
+ virtual void operator()(xact_t& xact);
+};
+
+class by_payee_xacts : public item_handler<xact_t>
+{
+ typedef std::map<string, subtotal_xacts *> payee_subtotals_map;
+ typedef std::pair<string, subtotal_xacts *> payee_subtotals_pair;
+
+ payee_subtotals_map payee_subtotals;
+ bool remember_components;
+
+ by_payee_xacts();
+
+ public:
+ by_payee_xacts(xact_handler_ptr handler,
+ bool _remember_components = false)
+ : item_handler<xact_t>(handler),
+ remember_components(_remember_components) {
+ TRACE_CTOR(by_payee_xacts,
+ "xact_handler_ptr, bool");
+ }
+ virtual ~by_payee_xacts();
+
+ virtual void flush();
+ virtual void operator()(xact_t& xact);
+};
+
+class set_comm_as_payee : public item_handler<xact_t>
+{
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+ set_comm_as_payee();
+
+public:
+ set_comm_as_payee(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler) {
+ TRACE_CTOR(set_comm_as_payee, "xact_handler_ptr");
+ }
+ virtual ~set_comm_as_payee() {
+ TRACE_DTOR(set_comm_as_payee);
+ clear_entries_xacts(entry_temps);
+ }
+
+ virtual void operator()(xact_t& xact);
+};
+
+class set_code_as_payee : public item_handler<xact_t>
+{
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+ set_code_as_payee();
+
+public:
+ set_code_as_payee(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler) {
+ TRACE_CTOR(set_code_as_payee, "xact_handler_ptr");
+ }
+ virtual ~set_code_as_payee() {
+ TRACE_DTOR(set_code_as_payee);
+ clear_entries_xacts(entry_temps);
+ }
+
+ virtual void operator()(xact_t& xact);
+};
+
+class dow_xacts : public subtotal_xacts
+{
+ xacts_list days_of_the_week[7];
+
+ dow_xacts();
+
+public:
+ dow_xacts(xact_handler_ptr handler,
+ bool remember_components = false)
+ : subtotal_xacts(handler, remember_components) {
+ TRACE_CTOR(dow_xacts, "xact_handler_ptr, bool");
+ }
+ virtual ~dow_xacts() throw() {
+ TRACE_DTOR(dow_xacts);
+ }
+
+ virtual void flush();
+ virtual void operator()(xact_t& xact) {
+ days_of_the_week[xact.date().day_of_week()].push_back(&xact);
+ }
+};
+
+class generate_xacts : public item_handler<xact_t>
+{
+ generate_xacts();
+
+protected:
+ typedef std::pair<interval_t, xact_t *> pending_xacts_pair;
+ typedef std::list<pending_xacts_pair> pending_xacts_list;
+
+ pending_xacts_list pending_xacts;
+ std::list<entry_t> entry_temps;
+ std::list<xact_t> xact_temps;
+
+public:
+ generate_xacts(xact_handler_ptr handler)
+ : item_handler<xact_t>(handler) {
+ TRACE_CTOR(dow_xacts, "xact_handler_ptr");
+ }
+
+ virtual ~generate_xacts() {
+ TRACE_DTOR(generate_xacts);
+ clear_entries_xacts(entry_temps);
+ }
+
+ void add_period_entries(period_entries_list& period_entries);
+
+ virtual void add_xact(const interval_t& period, xact_t& xact);
+};
+
+class budget_xacts : public generate_xacts
+{
+#define BUDGET_NO_BUDGET 0x00
+#define BUDGET_BUDGETED 0x01
+#define BUDGET_UNBUDGETED 0x02
+
+ unsigned short flags;
+
+ budget_xacts();
+
+public:
+ budget_xacts(xact_handler_ptr handler,
+ unsigned long _flags = BUDGET_BUDGETED)
+ : generate_xacts(handler), flags(_flags) {
+ TRACE_CTOR(budget_xacts,
+ "xact_handler_ptr, unsigned long");
+ }
+ virtual ~budget_xacts() throw() {
+ TRACE_DTOR(budget_xacts);
+ }
+
+ void report_budget_items(const date_t& date);
+
+ virtual void operator()(xact_t& xact);
+};
+
+class forecast_xacts : public generate_xacts
+{
+ item_predicate<xact_t> pred;
+
+ public:
+ forecast_xacts(xact_handler_ptr handler,
+ const expr_t& predicate)
+ : generate_xacts(handler), pred(predicate) {
+ TRACE_CTOR(forecast_xacts, "xact_handler_ptr, const expr_t&");
+ }
+ forecast_xacts(xact_handler_ptr handler,
+ const string& predicate)
+ : generate_xacts(handler), pred(predicate) {
+ TRACE_CTOR(forecast_xacts, "xact_handler_ptr, const string&");
+ }
+ virtual ~forecast_xacts() throw() {
+ TRACE_DTOR(forecast_xacts);
+ }
+
+ virtual void add_xact(const interval_t& period,
+ xact_t& xact);
+ virtual void flush();
+};
+
+//////////////////////////////////////////////////////////////////////
+//
+// Account filters
+//
+
+class clear_account_xdata : public item_handler<account_t>
+{
+public:
+ virtual void operator()(account_t& acct) {
+ acct.clear_xdata();
+ }
+};
+
+class accounts_iterator;
+
+class pass_down_accounts : public item_handler<account_t>
+{
+ pass_down_accounts();
+
+public:
+ pass_down_accounts(acct_handler_ptr handler, accounts_iterator& iter);
+
+ virtual ~pass_down_accounts() {
+ TRACE_DTOR(pass_down_accounts);
+ }
+};
+
+} // namespace ledger
+
+#endif // _FILTERS_H
diff --git a/src/flags.h b/src/flags.h
new file mode 100644
index 00000000..b75fdc21
--- /dev/null
+++ b/src/flags.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FLAGS_H
+#define _FLAGS_H
+
+template <typename T = boost::uint_least8_t>
+class supports_flags
+{
+public:
+ typedef T flags_t;
+
+protected:
+ flags_t flags_;
+
+public:
+ supports_flags() : flags_(0) {
+ TRACE_CTOR(supports_flags, "");
+ }
+ supports_flags(const flags_t& arg) : flags_(arg) {
+ TRACE_CTOR(supports_flags, "copy");
+ }
+ ~supports_flags() throw() {
+ TRACE_DTOR(supports_flags);
+ }
+
+ flags_t flags() const {
+ return flags_;
+ }
+ bool has_flags(const flags_t arg) const {
+ return flags_ & arg;
+ }
+
+ void set_flags(const flags_t arg) {
+ flags_ = arg;
+ }
+ void clear_flags() {
+ flags_ = 0;
+ }
+ void add_flags(const flags_t arg) {
+ flags_ |= arg;
+ }
+ void drop_flags(const flags_t arg) {
+ flags_ &= ~arg;
+ }
+};
+
+template <typename T = boost::uint_least8_t>
+class delegates_flags : public boost::noncopyable
+{
+public:
+ typedef T flags_t;
+
+protected:
+ supports_flags<T>& flags_;
+
+public:
+ delegates_flags() : flags_() {
+ TRACE_CTOR(delegates_flags, "");
+ }
+ delegates_flags(supports_flags<T>& arg) : flags_(arg) {
+ TRACE_CTOR(delegates_flags, "const supports_flags<T>&");
+ }
+ ~delegates_flags() throw() {
+ TRACE_DTOR(delegates_flags);
+ }
+
+ flags_t flags() const {
+ return flags_.flags();
+ }
+ bool has_flags(const flags_t arg) const {
+ return flags_.has_flags(arg);
+ }
+
+ void set_flags(const flags_t arg) {
+ flags_.set_flags(arg);
+ }
+ void clear_flags() {
+ flags_.clear_flags();
+ }
+ void add_flags(const flags_t arg) {
+ flags_.add_flags(arg);
+ }
+ void drop_flags(const flags_t arg) {
+ flags_.drop_flags(arg);
+ }
+};
+
+#endif // _FLAGS_H
diff --git a/src/format.cc b/src/format.cc
new file mode 100644
index 00000000..65dfce95
--- /dev/null
+++ b/src/format.cc
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "format.h"
+#include "account.h"
+
+namespace ledger {
+
+format_t::elision_style_t
+ format_t::elision_style = ABBREVIATE;
+int format_t::abbrev_length = 2;
+
+bool format_t::ansi_codes = false;
+bool format_t::ansi_invert = false;
+
+void format_t::element_t::dump(std::ostream& out) const
+{
+ out << "Element: ";
+
+ switch (type) {
+ case STRING: out << " STRING"; break;
+ case EXPR: out << " EXPR"; break;
+ }
+
+ out << " flags: " << int(flags);
+ out << " min: ";
+ out << std::right;
+ out.width(2);
+ out << int(min_width);
+ out << " max: ";
+ out << std::right;
+ out.width(2);
+ out << int(max_width);
+
+ switch (type) {
+ case STRING: out << " str: '" << chars << "'" << std::endl; break;
+ case EXPR: out << " expr: " << expr << std::endl; break;
+ }
+}
+
+namespace {
+ string partial_account_name(account_t& account)
+ {
+ string name;
+
+ for (account_t * acct = &account;
+ acct && acct->parent;
+ acct = acct->parent) {
+ if (acct->has_xdata() &&
+ acct->xdata().has_flags(ACCOUNT_EXT_DISPLAYED))
+ break;
+
+ if (name.empty())
+ name = acct->name;
+ else
+ name = acct->name + ":" + name;
+ }
+
+ return name;
+ }
+}
+
+format_t::element_t * format_t::parse_elements(const string& fmt)
+{
+ std::auto_ptr<element_t> result;
+
+ element_t * current = NULL;
+
+ char buf[1024];
+ char * q = buf;
+
+ // The following format codes need to be implemented as functions:
+ //
+ // d: COMPLETE_DATE_STRING
+ // D: DATE_STRING
+ // S: SOURCE; break
+ // B: ENTRY_BEG_POS
+ // b: ENTRY_BEG_LINE
+ // E: ENTRY_END_POS
+ // e: ENTRY_END_LINE
+ // X: CLEARED
+ // Y: ENTRY_CLEARED
+ // C: CODE
+ // P: PAYEE
+ // W: OPT_ACCOUNT
+ // a: ACCOUNT_NAME
+ // A: ACCOUNT_FULLNAME
+ // t: AMOUNT
+ // o: OPT_AMOUNT
+ // T: TOTAL
+ // N: NOTE
+ // n: OPT_NOTE
+ // _: DEPTH_SPACER
+ //
+ // xB: XACT_BEG_POS
+ // xb: XACT_BEG_LINE
+ // xE: XACT_END_POS
+ // xe: XACT_END_LINE
+
+ for (const char * p = fmt.c_str(); *p; p++) {
+ if (*p != '%' && *p != '\\') {
+ *q++ = *p;
+ continue;
+ }
+
+ if (! result.get()) {
+ result.reset(new element_t);
+ current = result.get();
+ } else {
+ current->next.reset(new element_t);
+ current = current->next.get();
+ }
+
+ if (q != buf) {
+ current->type = element_t::STRING;
+ current->chars = string(buf, q);
+ q = buf;
+
+ current->next.reset(new element_t);
+ current = current->next.get();
+ }
+
+ if (*p == '\\') {
+ p++;
+ current->type = element_t::STRING;
+ switch (*p) {
+ case 'b': current->chars = "\b"; break;
+ case 'f': current->chars = "\f"; break;
+ case 'n': current->chars = "\n"; break;
+ case 'r': current->chars = "\r"; break;
+ case 't': current->chars = "\t"; break;
+ case 'v': current->chars = "\v"; break;
+ }
+ continue;
+ }
+
+ ++p;
+ while (*p == '!' || *p == '-') {
+ switch (*p) {
+ case '-':
+ current->flags |= ELEMENT_ALIGN_LEFT;
+ break;
+ case '!':
+ current->flags |= ELEMENT_HIGHLIGHT;
+ break;
+ }
+ ++p;
+ }
+
+ int num = 0;
+ while (*p && std::isdigit(*p)) {
+ num *= 10;
+ num += *p++ - '0';
+ }
+ current->min_width = num;
+
+ if (*p == '.') {
+ ++p;
+ num = 0;
+ while (*p && std::isdigit(*p)) {
+ num *= 10;
+ num += *p++ - '0';
+ }
+ current->max_width = num;
+ if (current->min_width == 0)
+ current->min_width = current->max_width;
+ }
+
+ switch (*p) {
+ case '%':
+ current->type = element_t::STRING;
+ current->chars = "%";
+ break;
+
+ case '|':
+ current->type = element_t::STRING;
+ current->chars = " ";
+ break;
+
+ case '(':
+ case '[': {
+ std::istringstream str(p);
+ current->type = element_t::EXPR;
+ current->expr.parse(str);
+ current->expr.set_text(string(p, p + str.tellg()));
+ p += str.tellg();
+ break;
+ }
+
+ default: {
+ current->type = element_t::EXPR;
+ char buf[2];
+ buf[0] = *p;
+ buf[1] = '\0';
+ current->chars = buf;
+ current->expr.parse(string("fmt_") + *p);
+ break;
+ }
+ }
+ }
+
+ if (q != buf) {
+ if (! result.get()) {
+ result.reset(new element_t);
+ current = result.get();
+ } else {
+ current->next.reset(new element_t);
+ current = current->next.get();
+ }
+ current->type = element_t::STRING;
+ current->chars = string(buf, q);
+ }
+
+ return result.release();
+}
+
+namespace {
+ inline void mark_plain(std::ostream& out) {
+ out << "\e[0m";
+ }
+}
+
+void format_t::format(std::ostream& out_str, scope_t& scope)
+{
+ for (element_t * elem = elements.get(); elem; elem = elem->next.get()) {
+ std::ostringstream out;
+ string name;
+ bool ignore_max_width = false;
+
+ if (elem->flags & ELEMENT_ALIGN_LEFT)
+ out << std::left;
+ else
+ out << std::right;
+
+ if (elem->min_width > 0)
+ out.width(elem->min_width);
+
+ switch (elem->type) {
+ case element_t::STRING:
+ out << elem->chars;
+ break;
+
+ case element_t::EXPR:
+ try {
+ elem->expr.compile(scope);
+
+ value_t value;
+ if (elem->expr.is_function()) {
+ call_scope_t args(scope);
+ args.push_back(long(elem->max_width));
+ value = elem->expr.get_function()(args);
+ } else {
+ value = elem->expr.calc(scope);
+ }
+ value.strip_annotations().dump(out, elem->min_width);
+ }
+ catch (const calc_error&) {
+ out << (string("%") + elem->chars);
+ }
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ string temp = out.str();
+
+ if (! ignore_max_width &&
+ elem->max_width > 0 && elem->max_width < temp.length())
+ out_str << truncate(temp, elem->max_width);
+ else
+ out_str << temp;
+ }
+}
+
+string format_t::truncate(const string& str, unsigned int width,
+ const bool is_account)
+{
+ const unsigned int len = str.length();
+ if (len <= width)
+ return str;
+
+ assert(width < 4095);
+
+ char buf[4096];
+
+ switch (elision_style) {
+ case TRUNCATE_LEADING:
+ // This method truncates at the beginning.
+ std::strncpy(buf, str.c_str() + (len - width), width);
+ buf[0] = '.';
+ buf[1] = '.';
+ break;
+
+ case TRUNCATE_MIDDLE:
+ // This method truncates in the middle.
+ std::strncpy(buf, str.c_str(), width / 2);
+ std::strncpy(buf + width / 2,
+ str.c_str() + (len - (width / 2 + width % 2)),
+ width / 2 + width % 2);
+ buf[width / 2 - 1] = '.';
+ buf[width / 2] = '.';
+ break;
+
+ case ABBREVIATE:
+ if (is_account) {
+ std::list<string> parts;
+ string::size_type beg = 0;
+ for (string::size_type pos = str.find(':');
+ pos != string::npos;
+ beg = pos + 1, pos = str.find(':', beg))
+ parts.push_back(string(str, beg, pos - beg));
+ parts.push_back(string(str, beg));
+
+ string result;
+ unsigned int newlen = len;
+ for (std::list<string>::iterator i = parts.begin();
+ i != parts.end();
+ i++) {
+ // Don't contract the last element
+ std::list<string>::iterator x = i;
+ if (++x == parts.end()) {
+ result += *i;
+ break;
+ }
+
+ if (newlen > width) {
+ result += string(*i, 0, abbrev_length);
+ result += ":";
+ newlen -= (*i).length() - abbrev_length;
+ } else {
+ result += *i;
+ result += ":";
+ }
+ }
+
+ if (newlen > width) {
+ // Even abbreviated its too big to show the last account, so
+ // abbreviate all but the last and truncate at the beginning.
+ std::strncpy(buf, result.c_str() + (result.length() - width), width);
+ buf[0] = '.';
+ buf[1] = '.';
+ } else {
+ std::strcpy(buf, result.c_str());
+ }
+ break;
+ }
+ // fall through...
+
+ case TRUNCATE_TRAILING:
+ // This method truncates at the end (the default).
+ std::strncpy(buf, str.c_str(), width - 2);
+ buf[width - 2] = '.';
+ buf[width - 1] = '.';
+ break;
+ }
+ buf[width] = '\0';
+
+ return buf;
+}
+
+} // namespace ledger
diff --git a/src/format.h b/src/format.h
new file mode 100644
index 00000000..31f2bb1d
--- /dev/null
+++ b/src/format.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FORMAT_H
+#define _FORMAT_H
+
+#include "journal.h"
+#include "expr.h"
+
+namespace ledger {
+
+DECLARE_EXCEPTION(format_error, std::runtime_error);
+
+class format_t : public noncopyable
+{
+ struct element_t : public noncopyable
+ {
+#define ELEMENT_ALIGN_LEFT 0x01
+#define ELEMENT_HIGHLIGHT 0x02
+
+ enum kind_t {
+ STRING,
+ EXPR,
+#if 0
+ DEPTH_SPACER
+#endif
+ };
+
+ kind_t type;
+ unsigned char flags;
+ unsigned char min_width;
+ unsigned char max_width;
+ string chars;
+ expr_t expr;
+
+ scoped_ptr<struct element_t> next;
+
+ element_t() throw()
+ : type(STRING), flags(false), min_width(0), max_width(0) {
+ TRACE_CTOR(element_t, "");
+ }
+ ~element_t() throw() {
+ TRACE_DTOR(element_t);
+ }
+
+ friend inline void mark_red(std::ostream& out, const element_t * elem) {
+ out.setf(std::ios::left);
+ out.width(0);
+ out << "\e[31m";
+
+ if (elem->flags & ELEMENT_ALIGN_LEFT)
+ out << std::left;
+ else
+ out << std::right;
+
+ if (elem->min_width > 0)
+ out.width(elem->min_width);
+ }
+
+ void dump(std::ostream& out) const;
+ };
+
+ string format_string;
+ scoped_ptr<element_t> elements;
+
+public:
+ enum elision_style_t {
+ TRUNCATE_TRAILING,
+ TRUNCATE_MIDDLE,
+ TRUNCATE_LEADING,
+ ABBREVIATE
+ };
+
+private:
+ // jww (2008-08-02): Should these four be here, or in session_t?
+ static elision_style_t elision_style;
+ static int abbrev_length;
+
+ static bool ansi_codes;
+ static bool ansi_invert;
+
+ static element_t * parse_elements(const string& fmt);
+
+public:
+ format_t() {
+ TRACE_CTOR(format_t, "");
+ }
+ format_t(const string& _format) {
+ TRACE_CTOR(format_t, "const string&");
+ parse(_format);
+ }
+ ~format_t() {
+ TRACE_DTOR(format_t);
+ }
+
+ void parse(const string& _format) {
+ elements.reset(parse_elements(_format));
+ format_string = _format;
+ }
+
+ void format(std::ostream& out, scope_t& scope);
+
+ void dump(std::ostream& out) const {
+ for (const element_t * elem = elements.get();
+ elem;
+ elem = elem->next.get())
+ elem->dump(out);
+ }
+
+ static string truncate(const string& str, unsigned int width,
+ const bool is_account = false);
+};
+
+} // namespace ledger
+
+#endif // _FORMAT_H
diff --git a/gnucash.cc b/src/gnucash.cc
index 7d31526d..5a171ace 100644
--- a/gnucash.cc
+++ b/src/gnucash.cc
@@ -1,27 +1,42 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
#include "gnucash.h"
-#include "journal.h"
-#include "format.h"
-#include "error.h"
-#include "acconf.h"
-
-#include <iostream>
-#include <sstream>
-#include <cstring>
-
-extern "C" {
-#if defined(HAVE_EXPAT)
-#include <expat.h> // expat XML parser
-#elif defined(HAVE_XMLPARSE)
-#include <xmlparse.h> // expat XML parser
-#else
-#error "No XML parser library defined."
-#endif
-}
+#include "session.h"
+#include "account.h"
namespace ledger {
-typedef std::map<const std::string, account_t *> accounts_map;
-typedef std::pair<const std::string, account_t *> accounts_pair;
+typedef std::map<const string, account_t *> accounts_map;
+typedef std::pair<const string, account_t *> accounts_pair;
typedef std::map<account_t *, commodity_t *> account_comm_map;
typedef std::pair<account_t *, commodity_t *> account_comm_pair;
@@ -29,7 +44,7 @@ typedef std::pair<account_t *, commodity_t *> account_comm_pair;
static journal_t * curr_journal;
static account_t * master_account;
static account_t * curr_account;
-static std::string curr_account_id;
+static string curr_account_id;
static entry_t * curr_entry;
static commodity_t * entry_comm;
static commodity_t * curr_comm;
@@ -39,17 +54,17 @@ static XML_Parser current_parser;
static accounts_map accounts_by_id;
static account_comm_map account_comms;
static unsigned int count;
-static std::string have_error;
+static string have_error;
static std::istream * instreamp;
static unsigned int offset;
static XML_Parser parser;
-static std::string path;
+static path pathname;
static unsigned int src_idx;
static istream_pos_type beg_pos;
static unsigned long beg_line;
-static transaction_t::state_t curr_state;
+static xact_t::state_t curr_state;
static enum action_t {
NO_ACTION,
@@ -71,7 +86,7 @@ static enum action_t {
XACT_NOTE
} action;
-static void startElement(void *userData, const char *name, const char **atts)
+static void startElement(void *, const char *name, const char **)
{
if (std::strcmp(name, "gnc:account") == 0) {
curr_account = new account_t(master_account);
@@ -90,7 +105,7 @@ static void startElement(void *userData, const char *name, const char **atts)
action = COMM_NAME;
else if (std::strcmp(name, "cmdty:fraction") == 0)
action = COMM_PREC;
- else if (std::strcmp(name, "gnc:transaction") == 0) {
+ else if (std::strcmp(name, "gnc:xact") == 0) {
assert(! curr_entry);
curr_entry = new entry_t;
}
@@ -104,7 +119,7 @@ static void startElement(void *userData, const char *name, const char **atts)
action = ENTRY_DESC;
else if (std::strcmp(name, "trn:split") == 0) {
assert(curr_entry);
- curr_entry->add_transaction(new transaction_t(curr_account));
+ curr_entry->add_xact(new xact_t(curr_account));
}
else if (std::strcmp(name, "split:reconciled-state") == 0)
action = XACT_STATE;
@@ -120,7 +135,7 @@ static void startElement(void *userData, const char *name, const char **atts)
action = XACT_NOTE;
}
-static void endElement(void *userData, const char *name)
+static void endElement(void *, const char *name)
{
if (std::strcmp(name, "gnc:account") == 0) {
assert(curr_account);
@@ -132,15 +147,17 @@ static void endElement(void *userData, const char *name)
else if (std::strcmp(name, "gnc:commodity") == 0) {
curr_comm = NULL;
}
- else if (std::strcmp(name, "gnc:transaction") == 0) {
+ else if (std::strcmp(name, "gnc:xact") == 0) {
assert(curr_entry);
- // Add the new entry (what gnucash calls a 'transaction') to the
+ // Add the new entry (what gnucash calls a 'xact') to the
// journal
if (! curr_journal->add_entry(curr_entry)) {
+#if 0
print_entry(std::cerr, *curr_entry);
+#endif
have_error = "The above entry does not balance";
- delete curr_entry;
+ checked_delete(curr_entry);
} else {
curr_entry->src_idx = src_idx;
curr_entry->beg_pos = beg_pos;
@@ -155,11 +172,11 @@ static void endElement(void *userData, const char *name)
entry_comm = NULL;
}
else if (std::strcmp(name, "trn:split") == 0) {
- transaction_t * xact = curr_entry->transactions.back();
+ xact_t * xact = curr_entry->xacts.back();
// Identify the commodity to use for the value of this
- // transaction. The quantity indicates how many times that value
- // the transaction is worth.
+ // xact. The quantity indicates how many times that value
+ // the xact is worth.
amount_t value;
commodity_t * default_commodity = NULL;
account_comm_map::iterator ac = account_comms.find(xact->account);
@@ -179,7 +196,7 @@ static void endElement(void *userData, const char *name)
xact->state = curr_state;
xact->amount = value;
if (value != curr_value)
- xact->cost = new amount_t(curr_value);
+ xact->cost = curr_value;
xact->beg_pos = beg_pos;
xact->beg_line = beg_line;
@@ -187,7 +204,7 @@ static void endElement(void *userData, const char *name)
xact->end_line = XML_GetCurrentLineNumber(parser) - offset;
// Clear the relevant variables for the next run
- curr_state = transaction_t::UNCLEARED;
+ curr_state = xact_t::UNCLEARED;
curr_value = amount_t();
curr_quant = amount_t();
}
@@ -196,14 +213,14 @@ static void endElement(void *userData, const char *name)
}
-static amount_t convert_number(const std::string& number,
+static amount_t convert_number(const string& number,
int * precision = NULL)
{
const char * num = number.c_str();
if (char * p = std::strchr(num, '/')) {
- std::string numer_str(num, p - num);
- std::string denom_str(p + 1);
+ string numer_str(num, p - num);
+ string denom_str(p + 1);
amount_t amt(numer_str);
amount_t den(denom_str);
@@ -222,19 +239,19 @@ static amount_t convert_number(const std::string& number,
}
}
-static void dataHandler(void *userData, const char *s, int len)
+static void dataHandler(void *, const char *s, int len)
{
switch (action) {
case ACCOUNT_NAME:
- curr_account->name = std::string(s, len);
+ curr_account->name = string(s, len);
break;
case ACCOUNT_ID:
- curr_account_id = std::string(s, len);
+ curr_account_id = string(s, len);
break;
case ACCOUNT_PARENT: {
- accounts_map::iterator i = accounts_by_id.find(std::string(s, len));
+ accounts_map::iterator i = accounts_by_id.find(string(s, len));
assert(i != accounts_by_id.end());
curr_account->parent = (*i).second;
curr_account->depth = curr_account->parent->depth + 1;
@@ -243,10 +260,10 @@ static void dataHandler(void *userData, const char *s, int len)
}
case COMM_SYM: {
- std::string symbol(s, len);
+ string symbol(s, len);
if (symbol == "USD") symbol = "$";
- curr_comm = commodity_t::find_or_create(symbol);
+ curr_comm = amount_t::current_pool->find_or_create(symbol);
assert(curr_comm);
if (symbol != "$")
@@ -260,7 +277,7 @@ static void dataHandler(void *userData, const char *s, int len)
}
case COMM_NAME:
- curr_comm->set_name(std::string(s, len));
+ curr_comm->set_name(string(s, len));
break;
case COMM_PREC:
@@ -268,30 +285,30 @@ static void dataHandler(void *userData, const char *s, int len)
break;
case ENTRY_NUM:
- curr_entry->code = std::string(s, len);
+ curr_entry->code = string(s, len);
break;
case ENTRY_DATE:
- curr_entry->_date = std::string(s, len);
+ curr_entry->_date = parse_date(string(s, len));
break;
case ENTRY_DESC:
- curr_entry->payee = std::string(s, len);
+ curr_entry->payee = string(s, len);
break;
case XACT_STATE:
if (*s == 'y')
- curr_state = transaction_t::CLEARED;
+ curr_state = xact_t::CLEARED;
else if (*s == 'n')
- curr_state = transaction_t::UNCLEARED;
+ curr_state = xact_t::UNCLEARED;
else
- curr_state = transaction_t::PENDING;
+ curr_state = xact_t::PENDING;
break;
case XACT_VALUE: {
int precision;
assert(entry_comm);
- curr_value = convert_number(std::string(s, len), &precision);
+ curr_value = convert_number(string(s, len), &precision);
curr_value.set_commodity(*entry_comm);
if (precision > entry_comm->precision())
@@ -300,26 +317,26 @@ static void dataHandler(void *userData, const char *s, int len)
}
case XACT_QUANTITY:
- curr_quant = convert_number(std::string(s, len));
+ curr_quant = convert_number(string(s, len));
break;
case XACT_ACCOUNT: {
- transaction_t * xact = curr_entry->transactions.back();
+ xact_t * xact = curr_entry->xacts.back();
- accounts_map::iterator i = accounts_by_id.find(std::string(s, len));
+ accounts_map::iterator i = accounts_by_id.find(string(s, len));
if (i != accounts_by_id.end()) {
xact->account = (*i).second;
} else {
xact->account = curr_journal->find_account("<Unknown>");
- have_error = (std::string("Could not find account ") +
- std::string(s, len));
+ have_error = (string("Could not find account ") +
+ string(s, len));
}
break;
}
case XACT_NOTE:
- curr_entry->transactions.back()->note = std::string(s, len);
+ curr_entry->xacts.back()->note = string(s, len);
break;
case NO_ACTION:
@@ -328,7 +345,7 @@ static void dataHandler(void *userData, const char *s, int len)
break;
default:
- assert(0);
+ assert(false);
break;
}
}
@@ -343,35 +360,38 @@ bool gnucash_parser_t::test(std::istream& in) const
return std::strncmp(buf, "<?xml", 5) == 0;
}
-unsigned int gnucash_parser_t::parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master,
- const std::string * original_file)
+unsigned int gnucash_parser_t::parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
{
char buf[BUFSIZ];
+#if 0
+ // jww (2008-05-08): Replace this
// This is the date format used by Gnucash, so override whatever the
// user specified.
date_t::input_format = "%Y-%m-%d %H:%M:%S %z";
+#endif
count = 0;
action = NO_ACTION;
- curr_journal = journal;
- master_account = master ? master : journal->master;
+ curr_journal = &journal;
+ master_account = master ? master : session.master.get();
curr_account = NULL;
curr_entry = NULL;
curr_comm = NULL;
entry_comm = NULL;
- curr_state = transaction_t::UNCLEARED;
+ curr_state = xact_t::UNCLEARED;
instreamp = &in;
- path = original_file ? *original_file : "<gnucash>";
- src_idx = journal->sources.size() - 1;
+ pathname = original_file ? *original_file : "<gnucash>";
+ src_idx = journal.sources.size() - 1;
// GnuCash uses the USD commodity without defining it, which really
// means $.
- commodity_t * usd = commodity_t::find_or_create("$");
+ commodity_t * usd = amount_t::current_pool->find_or_create("$");
usd->set_precision(2);
usd->add_flags(COMMODITY_STYLE_THOUSANDS);
@@ -388,14 +408,14 @@ unsigned int gnucash_parser_t::parse(std::istream& in,
in.getline(buf, BUFSIZ - 1);
std::strcat(buf, "\n");
if (! XML_Parse(parser, buf, std::strlen(buf), in.eof())) {
- unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
const char * msg = XML_ErrorString(XML_GetErrorCode(parser));
XML_ParserFree(parser);
- throw new parse_error(msg);
+ throw parse_error(msg);
}
if (! have_error.empty()) {
- unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
parse_error err(have_error);
std::cerr << "Error: " << err.what() << std::endl;
have_error = "";
diff --git a/src/gnucash.h b/src/gnucash.h
new file mode 100644
index 00000000..b8273a19
--- /dev/null
+++ b/src/gnucash.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _GNUCASH_H
+#define _GNUCASH_H
+
+#include "journal.h"
+
+namespace ledger {
+
+class gnucash_parser_t : public journal_t::parser_t
+{
+ public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+};
+
+} // namespace ledger
+
+#endif // _GNUCASH_H
diff --git a/src/handler.h b/src/handler.h
new file mode 100644
index 00000000..6ebd6a5d
--- /dev/null
+++ b/src/handler.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _HANDLER_H
+#define _HANDLER_H
+
+#include "utils.h"
+#include "xact.h"
+#include "account.h"
+
+namespace ledger {
+
+template <typename T>
+struct item_handler : public noncopyable
+{
+ shared_ptr<item_handler> handler;
+
+public:
+ item_handler() {
+ TRACE_CTOR(item_handler, "");
+ }
+ item_handler(shared_ptr<item_handler> _handler) : handler(_handler) {
+ TRACE_CTOR(item_handler, "shared_ptr<item_handler>");
+ }
+ virtual ~item_handler() {
+ TRACE_DTOR(item_handler);
+ }
+
+ virtual void flush() {
+ if (handler.get())
+ handler->flush();
+ }
+ virtual void operator()(T& item) {
+ if (handler.get())
+ (*handler.get())(item);
+ }
+};
+
+typedef shared_ptr<item_handler<xact_t> > xact_handler_ptr;
+typedef shared_ptr<item_handler<account_t> > acct_handler_ptr;
+
+} // namespace ledger
+
+#endif // _HANDLER_H
diff --git a/src/help.cc b/src/help.cc
new file mode 100644
index 00000000..84d5a178
--- /dev/null
+++ b/src/help.cc
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "help.h"
+
+namespace ledger {
+
+void help(std::ostream& out)
+{
+ out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\
+Use -H to see all the help text on one page, or:\n\
+ --help-calc calculation options\n\
+ --help-disp display options\n\
+ --help-comm commodity options\n\n\
+Basic options:\n\
+ -h, --help display this help text\n\
+ -v, --version show version information\n\
+ -f, --file FILE read ledger data from FILE\n\
+ -o, --output FILE write output to FILE\n\
+ -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\
+ --cache FILE use FILE as a binary cache when --file is not used\n\
+ --no-cache don't use a cache, even if it would be appropriate\n\
+ -a, --account NAME use NAME for the default account (useful with QIF)\n\n\
+Commands:\n\
+ balance [REGEXP]... show balance totals for matching accounts\n\
+ register [REGEXP]... show register of matching transactions\n\
+ print [REGEXP]... print all matching entries\n\
+ xml [REGEXP]... print matching entries in XML format\n\
+ equity [REGEXP]... output equity entries for matching accounts\n\
+ prices [REGEXP]... display price history for matching commodities\n\
+ entry DATE PAYEE AMT output a derived entry, based on the arguments\n";
+}
+
+void calc_help(std::ostream& out)
+{
+ out << "Options to control how a report is calculated:\n\
+ -c, --current show only current and past entries (not future)\n\
+ -b, --begin DATE set report begin date\n\
+ -e, --end DATE set report end date\n\
+ -p, --period STR report using the given period\n\
+ --period-sort EXPR sort each report period's entries by EXPR\n\
+ -C, --cleared consider only cleared transactions\n\
+ -U, --uncleared consider only uncleared transactions\n\
+ -R, --real consider only real (non-virtual) transactions\n\
+ -L, --actual consider only actual (non-automated) transactions\n\
+ -r, --related calculate report using related transactions\n\
+ --budget generate budget entries based on periodic entries\n\
+ --add-budget show all transactions plus the budget\n\
+ --unbudgeted show only unbudgeted transactions\n\
+ --forecast EXPR generate forecast entries while EXPR is true\n\
+ -l, --limit EXPR calculate only transactions matching EXPR\n\
+ -t, --amount EXPR use EXPR to calculate the displayed amount\n\
+ -T, --total EXPR use EXPR to calculate the displayed total\n";
+}
+
+void disp_help(std::ostream& out)
+{
+ out << "Output to control how report results are displayed:\n\
+ -n, --collapse register: collapse entries; balance: no grand total\n\
+ -s, --subtotal balance: show sub-accounts; other: show subtotals\n\
+ -P, --by-payee show summarized totals by payee\n\
+ -x, --comm-as-payee set commodity name as the payee, for reporting\n\
+ -E, --empty balance: show accounts with zero balance\n\
+ -W, --weekly show weekly sub-totals\n\
+ -M, --monthly show monthly sub-totals\n\
+ -Y, --yearly show yearly sub-totals\n\
+ --dow show a days-of-the-week report\n\
+ -S, --sort EXPR sort report according to the value expression EXPR\n\
+ -w, --wide for the default register report, use 132 columns\n\
+ --head COUNT show only the first COUNT entries (negative inverts)\n\
+ --tail COUNT show only the last COUNT entries (negative inverts)\n\
+ --pager PAGER send all output through the given PAGER program\n\
+ -A, --average report average transaction amount\n\
+ -D, --deviation report deviation from the average\n\
+ -%, --percentage report balance totals as a percentile of the parent\n\
+ --totals in the \"xml\" report, include running total\n\
+ -j, --amount-data print only raw amount data (useful for scripting)\n\
+ -J, --total-data print only raw total data\n\
+ -d, --display EXPR display only transactions matching EXPR\n\
+ -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\
+ -F, --format STR use STR as the format; for each report type, use:\n\
+ --balance-format --register-format --print-format\n\
+ --plot-amount-format --plot-total-format --equity-format\n\
+ --prices-format --wide-register-format\n";
+}
+
+void comm_help(std::ostream& out)
+{
+ out << "Options to control how commodity values are determined:\n\
+ --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\
+ -Z, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\
+ -Q, --download download price information when needed\n\
+ -O, --quantity report commodity totals (this is the default)\n\
+ -B, --basis report cost basis of commodities\n\
+ -V, --market report last known market value\n\
+ -g, --performance report gain/loss for each displayed transaction\n\
+ -G, --gain report net gain/loss\n";
+}
+
+void full_help(std::ostream& out)
+{
+ out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\
+Basic options:\n\
+ -H, --full-help display this help text\n\
+ -h, --help display summarized help text\n\
+ -v, --version show version information\n\
+ -f, --file FILE read ledger data from FILE\n\
+ -o, --output FILE write output to FILE\n\
+ -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\
+ --cache FILE use FILE as a binary cache when --file is not used\n\
+ --no-cache don't use a cache, even if it would be appropriate\n\
+ -a, --account NAME use NAME for the default account (useful with QIF)\n\n\
+Report filtering:\n\
+ -c, --current show only current and past entries (not future)\n\
+ -b, --begin DATE set report begin date\n\
+ -e, --end DATE set report end date\n\
+ -p, --period STR report using the given period\n\
+ --period-sort EXPR sort each report period's entries by EXPR\n\
+ -C, --cleared consider only cleared transactions\n\
+ -U, --uncleared consider only uncleared transactions\n\
+ -R, --real consider only real (non-virtual) transactions\n\
+ -L, --actual consider only actual (non-automated) transactions\n\
+ -r, --related calculate report using related transactions\n\
+ --budget generate budget entries based on periodic entries\n\
+ --add-budget show all transactions plus the budget\n\
+ --unbudgeted show only unbudgeted transactions\n\
+ --forecast EXPR generate forecast entries while EXPR is true\n\
+ -l, --limit EXPR calculate only transactions matching EXPR\n\
+ -t, --amount EXPR use EXPR to calculate the displayed amount\n\
+ -T, --total EXPR use EXPR to calculate the displayed total\n\n\
+Output customization:\n\
+ -n, --collapse register: collapse entries; balance: no grand total\n\
+ -s, --subtotal balance: show sub-accounts; other: show subtotals\n\
+ -P, --by-payee show summarized totals by payee\n\
+ -x, --comm-as-payee set commodity name as the payee, for reporting\n\
+ -E, --empty balance: show accounts with zero balance\n\
+ -W, --weekly show weekly sub-totals\n\
+ -M, --monthly show monthly sub-totals\n\
+ -Y, --yearly show yearly sub-totals\n\
+ --dow show a days-of-the-week report\n\
+ -S, --sort EXPR sort report according to the value expression EXPR\n\
+ -w, --wide for the default register report, use 132 columns\n\
+ --head COUNT show only the first COUNT entries (negative inverts)\n\
+ --tail COUNT show only the last COUNT entries (negative inverts)\n\
+ --pager PAGER send all output through the given PAGER program\n\
+ -A, --average report average transaction amount\n\
+ -D, --deviation report deviation from the average\n\
+ -%, --percentage report balance totals as a percentile of the parent\n\
+ --totals in the \"xml\" report, include running total\n\
+ -j, --amount-data print only raw amount data (useful for scripting)\n\
+ -J, --total-data print only raw total data\n\
+ -d, --display EXPR display only transactions matching EXPR\n\
+ -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\
+ -F, --format STR use STR as the format; for each report type, use:\n\
+ --balance-format --register-format --print-format\n\
+ --plot-amount-format --plot-total-format --equity-format\n\
+ --prices-format --wide-register-format\n\n\
+Commodity reporting:\n\
+ --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\
+ -L, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\
+ -Q, --download download price information when needed\n\
+ -O, --quantity report commodity totals (this is the default)\n\
+ -B, --basis report cost basis of commodities\n\
+ -V, --market report last known market value\n\
+ -g, --performance report gain/loss for each displayed transaction\n\
+ -G, --gain report net gain/loss\n\n\
+Commands:\n\
+ balance [REGEXP]... show balance totals for matching accounts\n\
+ register [REGEXP]... show register of matching transactions\n\
+ print [REGEXP]... print all matching entries\n\
+ xml [REGEXP]... print matching entries in XML format\n\
+ equity [REGEXP]... output equity entries for matching accounts\n\
+ prices [REGEXP]... display price history for matching commodities\n\
+ entry DATE PAYEE AMT output a derived entry, based on the arguments\n";
+}
+
+} // namespace ledger
diff --git a/src/help.h b/src/help.h
new file mode 100644
index 00000000..14067be4
--- /dev/null
+++ b/src/help.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _HELP_H
+#define _HELP_H
+
+#include "utils.h"
+
+namespace ledger {
+
+void help(std::ostream& out);
+
+void calc_help(std::ostream& out);
+void disp_help(std::ostream& out);
+void comm_help(std::ostream& out);
+
+void full_help(std::ostream& out);
+
+} // namespace ledger
+
+#endif // _HELP_H
diff --git a/src/hooks.h b/src/hooks.h
new file mode 100644
index 00000000..da197cdd
--- /dev/null
+++ b/src/hooks.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _HOOKS_H
+#define _HOOKS_H
+
+template <typename T, typename Data>
+class hooks_t : public boost::noncopyable
+{
+public:
+ typedef boost::function<bool (Data&, bool)> function_t;
+
+protected:
+ std::list<T *> list;
+
+public:
+ hooks_t() {
+ TRACE_CTOR(hooks_t, "");
+ }
+ ~hooks_t() throw() {
+ TRACE_DTOR(hooks_t);
+ }
+
+ void add_hook(T * func, const bool prepend = false) {
+ if (prepend)
+ list.push_front(func);
+ else
+ list.push_back(func);
+ }
+
+ void remove_hook(T * func) {
+ list.remove(func);
+ }
+
+ bool run_hooks(Data& item, bool post) {
+ foreach (T * func, list)
+ if (! (*func)(item, post))
+ return false;
+ return true;
+ }
+};
+
+#endif // _HOOKS_H
diff --git a/src/iterators.cc b/src/iterators.cc
new file mode 100644
index 00000000..34beba7e
--- /dev/null
+++ b/src/iterators.cc
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "iterators.h"
+#include "session.h"
+#include "compare.h"
+
+namespace ledger {
+
+void entries_iterator::reset(session_t& session)
+{
+ journals_i = session.journals.begin();
+ journals_end = session.journals.end();
+
+ journals_uninitialized = false;
+
+ if (journals_i != journals_end) {
+ entries_i = (*journals_i).entries.begin();
+ entries_end = (*journals_i).entries.end();
+
+ entries_uninitialized = false;
+ } else {
+ entries_uninitialized = true;
+ }
+}
+
+entry_t * entries_iterator::operator()()
+{
+ if (entries_i == entries_end) {
+ journals_i++;
+ if (journals_i == journals_end)
+ return NULL;
+
+ entries_i = (*journals_i).entries.begin();
+ entries_end = (*journals_i).entries.end();
+ }
+ return *entries_i++;
+}
+
+void session_xacts_iterator::reset(session_t& session)
+{
+ entries.reset(session);
+ entry_t * entry = entries();
+ if (entry != NULL)
+ xacts.reset(*entry);
+}
+
+xact_t * session_xacts_iterator::operator()()
+{
+ xact_t * xact = xacts();
+ if (xact == NULL) {
+ entry_t * entry = entries();
+ if (entry != NULL) {
+ xacts.reset(*entry);
+ xact = xacts();
+ }
+ }
+ return xact;
+}
+
+account_t * basic_accounts_iterator::operator()()
+{
+ while (! accounts_i.empty() &&
+ accounts_i.back() == accounts_end.back()) {
+ accounts_i.pop_back();
+ accounts_end.pop_back();
+ }
+ if (accounts_i.empty())
+ return NULL;
+
+ account_t * account = (*(accounts_i.back()++)).second;
+ assert(account);
+
+ // If this account has children, queue them up to be iterated next.
+ if (! account->accounts.empty())
+ push_back(*account);
+
+ return account;
+}
+
+void sorted_accounts_iterator::sort_accounts(account_t& account,
+ accounts_deque_t& deque)
+{
+ foreach (accounts_map::value_type& pair, account.accounts)
+ deque.push_back(pair.second);
+
+ std::stable_sort(deque.begin(), deque.end(),
+ compare_items<account_t>(sort_cmp));
+}
+
+account_t * sorted_accounts_iterator::operator()()
+{
+ while (! sorted_accounts_i.empty() &&
+ sorted_accounts_i.back() == sorted_accounts_end.back()) {
+ sorted_accounts_i.pop_back();
+ sorted_accounts_end.pop_back();
+ assert(! accounts_list.empty());
+ accounts_list.pop_back();
+ }
+ if (sorted_accounts_i.empty())
+ return NULL;
+
+ account_t * account = *sorted_accounts_i.back()++;
+ assert(account);
+
+ // If this account has children, queue them up to be iterated next.
+ if (! account->accounts.empty())
+ push_back(*account);
+
+ account->xdata().drop_flags(ACCOUNT_EXT_SORT_CALC);
+ return account;
+}
+
+void journals_iterator::reset(session_t& session)
+{
+ journals_i = session.journals.begin();
+ journals_end = session.journals.end();
+}
+
+journal_t * journals_iterator::operator()()
+{
+ if (journals_i == journals_end)
+ return NULL;
+ return &(*journals_i++);
+}
+
+#if 0
+// jww (2008-08-03): This needs to be changed into a commodities->xacts
+// iterator.
+
+// jww (2008-08-03): We then could also use a payees->xacts iterator
+
+void walk_commodities(commodity_pool_t::commodities_by_ident& commodities,
+ item_handler<xact_t>& handler)
+{
+ std::list<xact_t> xact_temps;
+ std::list<entry_t> entry_temps;
+ std::list<account_t> acct_temps;
+
+ for (commodity_pool_t::commodities_by_ident::iterator
+ i = commodities.begin();
+ i != commodities.end();
+ i++) {
+ if ((*i)->has_flags(COMMODITY_STYLE_NOMARKET))
+ continue;
+
+ entry_temps.push_back(entry_t());
+ acct_temps.push_back(account_t(NULL, (*i)->symbol()));
+
+ if ((*i)->history())
+ foreach (const commodity_t::history_map::value_type& pair,
+ (*i)->history()->prices) {
+ entry_temps.back()._date = pair.first.date();
+
+ xact_temps.push_back(xact_t(&acct_temps.back()));
+ xact_t& temp = xact_temps.back();
+ temp.entry = &entry_temps.back();
+ temp.amount = pair.second;
+ temp.add_flags(XACT_TEMP);
+ entry_temps.back().add_xact(&temp);
+
+ handler(xact_temps.back());
+ }
+ }
+
+ handler.flush();
+
+ clear_entries_xacts(entry_temps);
+}
+#endif
+
+} // namespace ledger
diff --git a/src/iterators.h b/src/iterators.h
new file mode 100644
index 00000000..50cbffae
--- /dev/null
+++ b/src/iterators.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ITERATORS_H
+#define _ITERATORS_H
+
+#include "journal.h"
+#include "entry.h"
+#include "account.h"
+
+namespace ledger {
+
+class xacts_iterator : public noncopyable
+{
+public:
+ virtual ~xacts_iterator() throw() {}
+ virtual xact_t * operator()() = 0;
+};
+
+class entry_xacts_iterator : public xacts_iterator
+{
+ xacts_list::iterator xacts_i;
+ xacts_list::iterator xacts_end;
+
+ bool xacts_uninitialized;
+
+public:
+ entry_xacts_iterator() : xacts_uninitialized(true) {
+ TRACE_CTOR(entry_xacts_iterator, "");
+ }
+ entry_xacts_iterator(entry_t& entry)
+ : xacts_uninitialized(true) {
+ TRACE_CTOR(entry_xacts_iterator, "entry_t&");
+ reset(entry);
+ }
+ virtual ~entry_xacts_iterator() throw() {
+ TRACE_DTOR(entry_xacts_iterator);
+ }
+
+ void reset(entry_t& entry) {
+ xacts_i = entry.xacts.begin();
+ xacts_end = entry.xacts.end();
+
+ xacts_uninitialized = false;
+ }
+
+ virtual xact_t * operator()() {
+ if (xacts_i == xacts_end || xacts_uninitialized)
+ return NULL;
+ return *xacts_i++;
+ }
+};
+
+class entries_iterator : public noncopyable
+{
+ ptr_list<journal_t>::iterator journals_i;
+ ptr_list<journal_t>::iterator journals_end;
+
+ bool journals_uninitialized;
+
+ entries_list::iterator entries_i;
+ entries_list::iterator entries_end;
+
+ bool entries_uninitialized;
+
+public:
+ entries_iterator()
+ : journals_uninitialized(true), entries_uninitialized(true) {
+ TRACE_CTOR(entries_iterator, "");
+ }
+ entries_iterator(session_t& session)
+ : journals_uninitialized(true), entries_uninitialized(true) {
+ TRACE_CTOR(entries_iterator, "session_t&");
+ reset(session);
+ }
+ virtual ~entries_iterator() throw() {
+ TRACE_DTOR(entries_iterator);
+ }
+
+ void reset(session_t& session);
+
+ entry_t * operator()();
+};
+
+class session_xacts_iterator : public xacts_iterator
+{
+ entries_iterator entries;
+ entry_xacts_iterator xacts;
+
+public:
+ session_xacts_iterator() {
+ TRACE_CTOR(session_xacts_iterator, "");
+ }
+ session_xacts_iterator(session_t& session) {
+ TRACE_CTOR(session_xacts_iterator, "session_t&");
+ reset(session);
+ }
+ virtual ~session_xacts_iterator() throw() {
+ TRACE_DTOR(session_xacts_iterator);
+ }
+
+ void reset(session_t& session);
+
+ virtual xact_t * operator()();
+};
+
+class accounts_iterator : public noncopyable
+{
+public:
+ virtual ~accounts_iterator() throw() {}
+ virtual account_t * operator()() = 0;
+};
+
+class basic_accounts_iterator : public accounts_iterator
+{
+ std::list<accounts_map::const_iterator> accounts_i;
+ std::list<accounts_map::const_iterator> accounts_end;
+
+public:
+ basic_accounts_iterator() {
+ TRACE_CTOR(basic_accounts_iterator, "");
+ }
+ basic_accounts_iterator(account_t& account) {
+ TRACE_CTOR(basic_accounts_iterator, "account_t&");
+ push_back(account);
+ }
+ virtual ~basic_accounts_iterator() throw() {
+ TRACE_DTOR(basic_accounts_iterator);
+ }
+
+ void push_back(account_t& account) {
+ accounts_i.push_back(account.accounts.begin());
+ accounts_end.push_back(account.accounts.end());
+ }
+
+ virtual account_t * operator()();
+};
+
+class sorted_accounts_iterator : public accounts_iterator
+{
+ expr_t sort_cmp;
+
+ typedef std::deque<account_t *> accounts_deque_t;
+
+ std::list<accounts_deque_t> accounts_list;
+ std::list<accounts_deque_t::const_iterator> sorted_accounts_i;
+ std::list<accounts_deque_t::const_iterator> sorted_accounts_end;
+
+public:
+ sorted_accounts_iterator(const string& sort_order) {
+ TRACE_CTOR(sorted_accounts_iterator, "const string&");
+ sort_cmp = expr_t(sort_order);
+ }
+ sorted_accounts_iterator(account_t& account, const string& sort_order) {
+ TRACE_CTOR(sorted_accounts_iterator, "account_t&, const string&");
+ sort_cmp = expr_t(sort_order);
+ push_back(account);
+ }
+ virtual ~sorted_accounts_iterator() throw() {
+ TRACE_DTOR(sorted_accounts_iterator);
+ }
+
+ void sort_accounts(account_t& account, accounts_deque_t& deque);
+
+ void push_back(account_t& account) {
+ accounts_list.push_back(accounts_deque_t());
+ sort_accounts(account, accounts_list.back());
+
+ sorted_accounts_i.push_back(accounts_list.back().begin());
+ sorted_accounts_end.push_back(accounts_list.back().end());
+ }
+
+ virtual account_t * operator()();
+};
+
+class journals_iterator : public noncopyable
+{
+ ptr_list<journal_t>::iterator journals_i;
+ ptr_list<journal_t>::iterator journals_end;
+
+public:
+ journals_iterator() {
+ TRACE_CTOR(journals_iterator, "");
+ }
+ journals_iterator(session_t& session) {
+ TRACE_CTOR(journals_iterator, "session_t&");
+ reset(session);
+ }
+ virtual ~journals_iterator() throw() {
+ TRACE_DTOR(journals_iterator);
+ }
+
+ void reset(session_t& session);
+
+ virtual journal_t * operator()();
+};
+
+} // namespace ledger
+
+#endif // _ITERATORS_H
diff --git a/src/journal.cc b/src/journal.cc
new file mode 100644
index 00000000..7d95d72f
--- /dev/null
+++ b/src/journal.cc
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "journal.h"
+#include "session.h"
+
+namespace ledger {
+
+const string version = PACKAGE_VERSION;
+
+journal_t::journal_t(session_t * _owner)
+ : owner(_owner), basket(NULL)
+{
+ TRACE_CTOR(journal_t, "");
+ master = owner->master.get();
+}
+
+journal_t::~journal_t()
+{
+ TRACE_DTOR(journal_t);
+
+ // Don't bother unhooking each entry's xacts from the
+ // accounts they refer to, because all accounts are about to
+ // be deleted.
+ foreach (entry_t * entry, entries)
+ if (! entry->has_flags(ENTRY_IN_CACHE))
+ checked_delete(entry);
+ else
+ entry->~entry_t();
+
+ foreach (auto_entry_t * entry, auto_entries)
+ if (! entry->has_flags(ENTRY_IN_CACHE))
+ checked_delete(entry);
+ else
+ entry->~auto_entry_t();
+
+ foreach (period_entry_t * entry, period_entries)
+ if (! entry->has_flags(ENTRY_IN_CACHE))
+ checked_delete(entry);
+ else
+ entry->~period_entry_t();
+}
+
+void journal_t::add_account(account_t * acct)
+{
+ owner->add_account(acct);
+}
+
+bool journal_t::remove_account(account_t * acct)
+{
+ return owner->remove_account(acct);
+}
+
+account_t * journal_t::find_account(const string& name, bool auto_create)
+{
+ return owner->find_account(name, auto_create);
+}
+
+account_t * journal_t::find_account_re(const string& regexp)
+{
+ return owner->find_account_re(regexp);
+}
+
+bool journal_t::add_entry(entry_t * entry)
+{
+ entry->journal = this;
+
+ if (! entry_finalize_hooks.run_hooks(*entry, false) ||
+ ! entry->finalize() ||
+ ! entry_finalize_hooks.run_hooks(*entry, true)) {
+ entry->journal = NULL;
+ return false;
+ }
+
+ entries.push_back(entry);
+
+ foreach (const xact_t * xact, entry->xacts)
+ if (xact->cost) {
+ assert(xact->amount);
+ xact->amount.commodity().add_price(datetime_t(entry->date(),
+ time_duration_t(0, 0, 0)),
+ *xact->cost / xact->amount.number());
+ }
+
+ return true;
+}
+
+bool journal_t::remove_entry(entry_t * entry)
+{
+ bool found = false;
+ entries_list::iterator i;
+ for (i = entries.begin(); i != entries.end(); i++)
+ if (*i == entry) {
+ found = true;
+ break;
+ }
+ if (! found)
+ return false;
+
+ entries.erase(i);
+ entry->journal = NULL;
+
+ return true;
+}
+
+bool journal_t::valid() const
+{
+ if (! master->valid()) {
+ DEBUG("ledger.validate", "journal_t: master not valid");
+ return false;
+ }
+
+ foreach (const entry_t * entry, entries)
+ if (! entry->valid()) {
+ DEBUG("ledger.validate", "journal_t: entry not valid");
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace ledger
diff --git a/src/journal.h b/src/journal.h
new file mode 100644
index 00000000..293439bb
--- /dev/null
+++ b/src/journal.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _JOURNAL_H
+#define _JOURNAL_H
+
+#include "utils.h"
+#include "hooks.h"
+#include "entry.h"
+
+namespace ledger {
+
+typedef std::list<path> paths_list;
+
+class session_t;
+class account_t;
+
+class journal_t : public noncopyable
+{
+public:
+ session_t * owner;
+ account_t * master;
+ account_t * basket;
+ entries_list entries;
+ paths_list sources;
+ optional<path> price_db;
+
+ auto_entries_list auto_entries;
+ period_entries_list period_entries;
+
+ hooks_t<entry_finalizer_t, entry_t> entry_finalize_hooks;
+
+ journal_t(session_t * _owner);
+ ~journal_t();
+
+ // These four methods are delegated to 'owner', since all accounts processed
+ // are gathered together at the session level.
+ void add_account(account_t * acct);
+ bool remove_account(account_t * acct);
+ account_t * find_account(const string& name, bool auto_create = true);
+ account_t * find_account_re(const string& regexp);
+
+ bool add_entry(entry_t * entry);
+ bool remove_entry(entry_t * entry);
+
+ void add_entry_finalizer(entry_finalizer_t * finalizer) {
+ entry_finalize_hooks.add_hook(finalizer);
+ }
+ void remove_entry_finalizer(entry_finalizer_t * finalizer) {
+ entry_finalize_hooks.remove_hook(finalizer);
+ }
+
+ /**
+ * @class journal_t::parser_t
+ *
+ * @brief Provides an abstract interface for writing journal parsers.
+ *
+ * Any data format for Ledger data is possible, as long as it can be parsed
+ * into a journal_t data tree. This class provides the basic interface which
+ * must be implemented by every such journal parser.
+ */
+ class parser_t : public noncopyable
+ {
+ public:
+ parser_t() {
+ TRACE_CTOR(journal_t::parser_t, "");
+ }
+ virtual ~parser_t() {
+ TRACE_DTOR(journal_t::parser_t);
+ }
+
+ virtual bool test(std::istream& in) const = 0;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL) = 0;
+ };
+
+ class binary_parser_t : public parser_t
+ {
+ public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+ };
+
+ bool valid() const;
+};
+
+extern const string version;
+
+} // namespace ledger
+
+#endif // _JOURNAL_H
diff --git a/src/ledger.h b/src/ledger.h
new file mode 100644
index 00000000..a87c3d55
--- /dev/null
+++ b/src/ledger.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _LEDGER_H
+#define _LEDGER_H
+
+//////////////////////////////////////////////////////////////////////
+//
+// Ledger Accounting Tool
+//
+// A command-line tool for general double-entry accounting.
+//
+// Copyright (c) 2003-2008, John Wiegley <johnw@newartisans.com>
+//
+
+#include <utils.h>
+#include <value.h>
+#include <expr.h>
+#include <scope.h>
+#include <predicate.h>
+#include <format.h>
+#include <option.h>
+
+#include <journal.h>
+#include <entry.h>
+#include <xact.h>
+#include <account.h>
+#include <iterators.h>
+#include <compare.h>
+
+#include <textual.h>
+#include <cache.h>
+#include <emacs.h>
+#include <qif.h>
+#include <xml.h>
+#include <csv.h>
+#include <gnucash.h>
+#include <ofx.h>
+
+#include <session.h>
+#include <report.h>
+#include <handler.h>
+#include <filters.h>
+#include <output.h>
+#include <help.h>
+
+#include <derive.h>
+#include <reconcile.h>
+#include <quotes.h>
+
+#include <ledger.h>
+
+#endif // _LEDGER_H
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 00000000..a5ca02d0
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,593 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "session.h"
+#include "report.h"
+#include "option.h"
+#include "output.h"
+#include "help.h"
+
+#include "textual.h"
+#include "qif.h"
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+#include "xml.h"
+#include "gnucash.h"
+#endif
+#ifdef HAVE_LIBOFX
+#include "ofx.h"
+#endif
+
+#ifdef HAVE_UNIX_PIPES
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "fdstream.h"
+#endif
+
+namespace ledger {
+ string args_to_predicate(value_t::sequence_t::const_iterator begin,
+ value_t::sequence_t::const_iterator end)
+ {
+ string acct_value_expr;
+ string payee_value_expr;
+ string note_value_expr;
+
+ string * value_expr;
+
+ enum regexp_kind_t {
+ ACCOUNT_REGEXP,
+ PAYEE_REGEXP,
+ NOTE_REGEXP
+ }
+ kind = ACCOUNT_REGEXP;
+
+ value_expr = &acct_value_expr;
+
+ for ( ; begin != end; begin++) {
+ const string& arg((*begin).as_string());
+
+ if (arg == "--") {
+ kind = PAYEE_REGEXP;
+ value_expr = &payee_value_expr;
+ }
+ else if (arg == "/") {
+ kind = NOTE_REGEXP;
+ value_expr = &note_value_expr;
+ }
+ else {
+ if (! value_expr->empty())
+ *value_expr += "|";
+
+ switch (kind) {
+ case ACCOUNT_REGEXP:
+ *value_expr += "account =~ ";
+ break;
+ case PAYEE_REGEXP:
+ *value_expr += "payee =~ ";
+ break;
+ case NOTE_REGEXP:
+ *value_expr += "note =~ ";
+ break;
+ }
+
+ const char * p = arg.c_str();
+ if (*p == '-') {
+ *value_expr += "!";
+ p++;
+ }
+
+ *value_expr += "/";
+ if (kind == NOTE_REGEXP) *value_expr += ":";
+ while (*p) {
+ if (*p == '/')
+ *value_expr += "\\";
+ *value_expr += *p;
+ p++;
+ }
+ if (kind == NOTE_REGEXP) *value_expr += ":";
+ *value_expr += "/";
+ }
+ }
+
+ string final_value_expr;
+
+ if (! acct_value_expr.empty()) {
+ if (! payee_value_expr.empty() ||
+ ! note_value_expr.empty())
+ final_value_expr = string("(") + acct_value_expr + ")";
+ else
+ final_value_expr = acct_value_expr;
+ }
+
+ if (! payee_value_expr.empty()) {
+ if (! acct_value_expr.empty())
+ final_value_expr += string("&(") + payee_value_expr + ")";
+ else if (! note_value_expr.empty())
+ final_value_expr = string("(") + payee_value_expr + ")";
+ else
+ final_value_expr = payee_value_expr;
+ }
+
+ if (! note_value_expr.empty()) {
+ if (! acct_value_expr.empty() ||
+ ! payee_value_expr.empty())
+ final_value_expr += string("&(") + note_value_expr + ")";
+ else if (acct_value_expr.empty() &&
+ payee_value_expr.empty())
+ final_value_expr = note_value_expr;
+ }
+
+ DEBUG("report.predicate",
+ "Regexp predicate expression = " << final_value_expr);
+
+ return final_value_expr;
+ }
+
+ template <class Formatter = format_xacts,
+ class handler_ptr = xact_handler_ptr,
+ void (report_t::*report_method)(handler_ptr) =
+ &report_t::xacts_report>
+ class reporter
+ {
+ string format_name;
+
+ public:
+ reporter(const string& _format_name)
+ : format_name(_format_name) {}
+
+ value_t operator()(call_scope_t& args)
+ {
+ report_t& report(find_scope<report_t>(args));
+ var_t<string> format(args, format_name);
+
+ if (! report.format_string.empty())
+ *format = report.format_string;
+
+ if (args.value().is_sequence() &&
+ args.value().size() > 1) {
+ if (! report.predicate.empty())
+ report.predicate = string("(") + report.predicate + ")&";
+ report.predicate +=
+ args_to_predicate(++args.value().as_sequence().begin(),
+ args.value().as_sequence().end());
+ }
+
+ (report.*report_method)(handler_ptr(new Formatter(report, *format)));
+
+ return true;
+ }
+ };
+
+ int read_and_report(ledger::report_t& report,
+ int argc, char * argv[], char * envp[])
+ {
+ using namespace ledger;
+
+ session_t& session(report.session);
+
+ // Handle the command-line arguments
+
+ strings_list args;
+ process_arguments(argc - 1, argv + 1, false, report, args);
+
+ if (args.empty()) {
+ ledger::help(std::cout);
+ return 1;
+ }
+ strings_list::iterator arg = args.begin();
+
+ if (! session.cache_file)
+ session.use_cache = false;
+ else
+ session.use_cache = ! session.data_file.empty() && session.price_db;
+
+ DEBUG("ledger.session.cache", "1. use_cache = " << session.use_cache);
+
+ // Process the environment settings
+
+ TRACE_START(environment, 1, "Processed environment variables");
+ process_environment(const_cast<const char **>(envp), "LEDGER_", report);
+ TRACE_FINISH(environment, 1);
+
+ optional<path> home;
+ if (const char * home_var = std::getenv("HOME"))
+ home = home_var;
+
+ if (! session.init_file)
+ session.init_file = home ? *home / ".ledgerrc" : "./.ledgerrc";
+ if (! session.price_db)
+ session.price_db = home ? *home / ".pricedb" : "./.pricedb";
+
+ if (! session.cache_file)
+ session.cache_file = home ? *home / ".ledger-cache" : "./.ledger-cache";
+
+ if (session.data_file == *session.cache_file)
+ session.use_cache = false;
+
+ DEBUG("ledger.session.cache", "2. use_cache = " << session.use_cache);
+
+ INFO("Initialization file is " << session.init_file->string());
+ INFO("Price database is " << session.price_db->string());
+ INFO("Binary cache is " << session.cache_file->string());
+ INFO("Journal file is " << session.data_file.string());
+
+ if (! session.use_cache)
+ INFO("Binary cache mechanism will not be used");
+
+ // Configure the output stream
+
+#ifdef HAVE_UNIX_PIPES
+ int status, pfd[2]; // Pipe file descriptors
+#endif
+ report.output_stream = &std::cout;
+
+ if (report.output_file) {
+ report.output_stream = new ofstream(*report.output_file);
+ }
+#ifdef HAVE_UNIX_PIPES
+ else if (report.pager) {
+ status = pipe(pfd);
+ if (status == -1)
+ throw_(std::logic_error, "Failed to create pipe");
+
+ status = fork();
+ if (status < 0) {
+ throw_(std::logic_error, "Failed to fork child process");
+ }
+ else if (status == 0) { // child
+ // Duplicate pipe's reading end into stdin
+ status = dup2(pfd[0], STDIN_FILENO);
+ if (status == -1)
+ perror("dup2");
+
+ // Close unuseful file descriptors: the pipe's writing and
+ // reading ends (the latter is not needed anymore, after the
+ // duplication).
+ close(pfd[1]);
+ close(pfd[0]);
+
+ // Find command name: its the substring starting right of the
+ // rightmost '/' character in the pager pathname. See manpage
+ // for strrchr.
+ execlp(report.pager->native_file_string().c_str(),
+ basename(*report.pager).c_str(), NULL);
+ perror("execl");
+ exit(1);
+ }
+ else { // parent
+ close(pfd[0]);
+ report.output_stream = new boost::fdostream(pfd[1]);
+ }
+ }
+#endif
+
+ // Read the command word and see if it's any of the debugging commands
+ // that Ledger supports.
+
+ std::ostream& out(*report.output_stream);
+
+ string verb = *arg++;
+
+ if (verb == "parse") {
+ expr_t expr(*arg);
+
+ out << "Value expression as input: " << *arg << std::endl;
+
+ out << "Value expression as parsed: ";
+ expr.print(out, report);
+ out << std::endl;
+
+ out << std::endl;
+ out << "--- Parsed tree ---" << std::endl;
+ expr.dump(out);
+
+ out << std::endl;
+ out << "--- Calculated value ---" << std::endl;
+ expr.calc(report).print(out);
+ out << std::endl;
+
+ return 0;
+ }
+ else if (verb == "compile") {
+ expr_t expr(*arg);
+
+ out << "Value expression as input: " << *arg << std::endl;
+ out << "Value expression as parsed: ";
+ expr.print(out, report);
+ out << std::endl;
+
+ out << std::endl;
+ out << "--- Parsed tree ---" << std::endl;
+ expr.dump(out);
+
+ expr.compile(report);
+
+ out << std::endl;
+ out << "--- Compiled tree ---" << std::endl;
+ expr.dump(out);
+
+ out << std::endl;
+ out << "--- Calculated value ---" << std::endl;
+ expr.calc(report).print(out);
+ out << std::endl;
+
+ return 0;
+ }
+ else if (verb == "eval") {
+ expr_t expr(*arg);
+ out << expr.calc(report).strip_annotations() << std::endl;
+ return 0;
+ }
+ else if (verb == "format") {
+ format_t fmt(*arg);
+ fmt.dump(out);
+ return 0;
+ }
+ else if (verb == "period") {
+ interval_t interval(*arg);
+
+ if (! is_valid(interval.begin)) {
+ out << "Time period has no beginning." << std::endl;
+ } else {
+ out << "begin: " << format_date(interval.begin) << std::endl;
+ out << " end: " << format_date(interval.end) << std::endl;
+ out << std::endl;
+
+ date_t date = interval.first();
+
+ for (int i = 0; i < 20; i++) {
+ out << std::right;
+ out.width(2);
+
+ out << i << ": " << format_date(date) << std::endl;
+
+ date = interval.increment(date);
+ if (is_valid(interval.end) && date >= interval.end)
+ break;
+ }
+ }
+ return 0;
+ }
+
+ // Parse the initialization file, which can only be textual; then
+ // parse the journal data.
+
+ session.read_init();
+
+ INFO_START(journal, "Read journal file");
+
+ journal_t& journal(*session.create_journal());
+
+ std::size_t count = session.read_data(journal, report.account);
+ if (count == 0)
+ throw_(parse_error, "Failed to locate any journal entries; "
+ "did you specify a valid file with -f?");
+
+ INFO_FINISH(journal);
+
+ INFO("Found " << count << " entries");
+
+ TRACE_FINISH(entry_text, 1);
+ TRACE_FINISH(entry_date, 1);
+ TRACE_FINISH(entry_details, 1);
+ TRACE_FINISH(entry_xacts, 1);
+ TRACE_FINISH(entries, 1);
+ TRACE_FINISH(parsing_total, 1);
+
+ // Create a command object based on the command verb that was seen
+ // above.
+
+ function_t command;
+
+ if (verb == "register" || verb == "reg" || verb == "r")
+ command = reporter<>("register_format");
+ else if (verb == "print" || verb == "p")
+ command = reporter<>("print_format");
+ else if (verb == "balance" || verb == "bal" || verb == "b")
+ command = reporter<format_accounts, acct_handler_ptr,
+ &report_t::accounts_report>("balance_format");
+ else if (verb == "equity")
+ command = reporter<format_equity, acct_handler_ptr,
+ &report_t::accounts_report>("print_format");
+#if 0
+ else if (verb == "entry")
+ command = entry_command();
+ else if (verb == "dump")
+ command = dump_command();
+ else if (verb == "output")
+ command = output_command();
+ else if (verb == "prices")
+ command = prices_command();
+ else if (verb == "pricesdb")
+ command = pricesdb_command();
+ else if (verb == "csv")
+ command = csv_command();
+ else if (verb == "emacs" || verb == "lisp")
+ command = emacs_command();
+ else if (verb == "xml")
+ command = xml_command();
+#endif
+ else {
+ char buf[128];
+ std::strcpy(buf, "cmd_");
+ std::strcat(buf, verb.c_str());
+
+ if (expr_t::ptr_op_t def = report.lookup(buf))
+ command = def->as_function();
+
+ if (! command)
+ throw_(std::logic_error, string("Unrecognized command '") + verb + "'");
+ }
+
+ // Create an argument scope containing the report command's
+ // arguments, and then invoke the command.
+
+ call_scope_t command_args(report);
+
+ for (strings_list::iterator i = arg; i != args.end(); i++)
+ command_args.push_back(string_value(*i));
+
+ INFO_START(command, "Did user command '" << verb << "'");
+
+ command(command_args);
+
+ INFO_FINISH(command);
+
+#if 0
+ // Write out the binary cache, if need be
+
+ if (session.use_cache && session.cache_dirty && session.cache_file) {
+ TRACE_START(binary_cache, 1, "Wrote binary journal file");
+
+ ofstream stream(*session.cache_file);
+ journal.write(stream);
+
+ TRACE_FINISH(binary_cache, 1);
+ }
+#endif
+
+ // If the user specified a pager, wait for it to exit now
+
+#ifdef HAVE_UNIX_PIPES
+ if (! report.output_file && report.pager) {
+ checked_delete(report.output_stream);
+ close(pfd[1]);
+
+ // Wait for child to finish
+ wait(&status);
+ if (status & 0xffff != 0)
+ throw_(std::logic_error, "Something went wrong in the pager");
+ }
+#endif
+ else if (DO_VERIFY() && report.output_file) {
+ checked_delete(report.output_stream);
+ }
+
+ return 0;
+ }
+}
+
+int main(int argc, char * argv[], char * envp[])
+{
+ int status = 1;
+
+ for (int i = 1; i < argc; i++)
+ if (argv[i][0] == '-') {
+ if (std::strcmp(argv[i], "--verify") == 0) {
+#if defined(VERIFY_ON)
+ ledger::verify_enabled = true;
+#endif
+ }
+ else if (std::strcmp(argv[i], "--verbose") == 0 ||
+ std::strcmp(argv[i], "-v") == 0) {
+#if defined(LOGGING_ON)
+ ledger::_log_level = ledger::LOG_INFO;
+#endif
+ }
+ else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) {
+#if defined(DEBUG_ON)
+ ledger::_log_level = ledger::LOG_DEBUG;
+ ledger::_log_category = argv[i + 1];
+ i++;
+#endif
+ }
+ else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) {
+#if defined(TRACING_ON)
+ ledger::_log_level = ledger::LOG_TRACE;
+ try {
+ ledger::_trace_level = boost::lexical_cast<int>(argv[i + 1]);
+ }
+ catch (const boost::bad_lexical_cast& e) {
+ std::cerr << "Argument to --trace must be an integer."
+ << std::endl;
+ return 1;
+ }
+ i++;
+#endif
+ }
+ }
+
+ IF_VERIFY()
+ ledger::initialize_memory_tracing();
+
+ try {
+ std::ios::sync_with_stdio(false);
+
+ boost::filesystem::path::default_name_check
+ (boost::filesystem::portable_posix_name);
+
+ INFO("Ledger starting");
+
+ std::auto_ptr<ledger::session_t> session(new ledger::session_t);
+
+ ledger::set_session_context(session.get());
+
+#if 0
+ session->register_parser(new ledger::journal_t::binary_parser_t);
+#endif
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+ session->register_parser(new ledger::xml_parser_t);
+ session->register_parser(new ledger::gnucash_parser_t);
+#endif
+#ifdef HAVE_LIBOFX
+ session->register_parser(new ledger::ofx_parser_t);
+#endif
+ session->register_parser(new ledger::qif_parser_t);
+ session->register_parser(new ledger::textual_parser_t);
+
+ session->current_report.reset(new ledger::report_t(*session.get()));
+
+ status = read_and_report(*session->current_report.get(), argc, argv, envp);
+
+ if (DO_VERIFY())
+ ledger::set_session_context();
+ else
+ session.release(); // don't free anything! just let it leak
+ }
+ catch (const std::exception& err) {
+ std::cout.flush();
+ std::cerr << "Error: " << ledger::error_context() << err.what()
+ << std::endl;
+ }
+ catch (int _status) {
+ status = _status;
+ }
+
+ IF_VERIFY() {
+ INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
+ ledger::shutdown_memory_tracing();
+ } else {
+ INFO("Ledger ended");
+ }
+
+ return status;
+}
+
+// main.cc ends here.
diff --git a/src/mask.cc b/src/mask.cc
new file mode 100644
index 00000000..b32be359
--- /dev/null
+++ b/src/mask.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mask.h"
+#include "binary.h"
+
+namespace ledger {
+
+mask_t::mask_t(const string& pat) : expr()
+{
+ TRACE_CTOR(mask_t, "const string&");
+ *this = pat;
+}
+
+mask_t& mask_t::operator=(const string& pat)
+{
+ expr.assign(pat.c_str(), regex::perl | regex::icase);
+ return *this;
+}
+
+void mask_t::read(const char *& data)
+{
+ *this = binary::read_string(data);
+}
+
+void mask_t::write(std::ostream& out) const
+{
+ binary::write_string(out, expr.str());
+}
+
+} // namespace ledger
diff --git a/src/mask.h b/src/mask.h
new file mode 100644
index 00000000..06161d12
--- /dev/null
+++ b/src/mask.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _MASK_H
+#define _MASK_H
+
+#include "utils.h"
+
+namespace ledger {
+
+class mask_t
+{
+ mask_t();
+
+public:
+ boost::regex expr;
+
+ explicit mask_t(const string& pattern);
+
+ mask_t(const mask_t& m) : expr(m.expr) {
+ TRACE_CTOR(mask_t, "copy");
+ }
+ ~mask_t() throw() {
+ TRACE_DTOR(mask_t);
+ }
+
+ mask_t& operator=(const string& other);
+
+ bool match(const string& str) const {
+ return boost::regex_search(str, expr);
+ }
+
+ void read(const char *& data);
+ void write(std::ostream& out) const;
+};
+
+} // namespace ledger
+
+#endif // _MASK_H
diff --git a/src/ofx.cc b/src/ofx.cc
new file mode 100644
index 00000000..8940f62a
--- /dev/null
+++ b/src/ofx.cc
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ofx.h"
+#include "amount.h"
+#include "account.h"
+#include "session.h"
+
+#include <libofx.h>
+
+namespace ledger {
+
+typedef std::map<const string, account_t *> accounts_map;
+typedef std::pair<const string, account_t *> accounts_pair;
+
+typedef std::map<const string, commodity_t *> commodities_map;
+typedef std::pair<const string, commodity_t *> commodities_pair;
+
+journal_t * curr_journal;
+accounts_map ofx_accounts;
+commodities_map ofx_account_currencies;
+commodities_map ofx_securities;
+account_t * master_account;
+
+int ofx_proc_statement_cb(struct OfxStatementData, void *)
+{
+ return 0;
+}
+
+int ofx_proc_account_cb(struct OfxAccountData data, void *)
+{
+ if (! data.account_id_valid)
+ return -1;
+
+ DEBUG("ledger.ofx.parse", "account " << data.account_name);
+ account_t * account = new account_t(master_account, data.account_name);
+ curr_journal->add_account(account);
+ ofx_accounts.insert(accounts_pair(data.account_id, account));
+
+ if (data.currency_valid) {
+ commodity_t * commodity = amount_t::current_pool->find_or_create(data.currency);
+ commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED);
+
+ commodities_map::iterator i = ofx_account_currencies.find(data.account_id);
+ if (i == ofx_account_currencies.end())
+ ofx_account_currencies.insert(commodities_pair(data.account_id,
+ commodity));
+ }
+
+ return 0;
+}
+
+int ofx_proc_transaction_cb(struct OfxTransactionData data, void *)
+{
+ if (! data.account_id_valid || ! data.units_valid)
+ return -1;
+
+ accounts_map::iterator i = ofx_accounts.find(data.account_id);
+ assert(i != ofx_accounts.end());
+ account_t * account = (*i).second;
+
+ entry_t * entry = new entry_t;
+
+ entry->add_xact(new xact_t(account));
+ xact_t * xact = entry->xacts.back();
+
+ // get the account's default currency
+ commodities_map::iterator ac = ofx_account_currencies.find(data.account_id);
+ assert(ac != ofx_account_currencies.end());
+ commodity_t * default_commodity = (*ac).second;
+
+ std::ostringstream stream;
+ stream << - data.units;
+
+ // jww (2005-02-09): what if the amount contains fees?
+
+ if (data.unique_id_valid) {
+ commodities_map::iterator s = ofx_securities.find(data.unique_id);
+ assert(s != ofx_securities.end());
+ xact->amount = string(stream.str()) + " " + (*s).second->base_symbol();
+ } else {
+ xact->amount = string(stream.str()) + " " + default_commodity->base_symbol();
+ }
+
+ if (data.unitprice_valid && (data.unitprice < 1.0 || data.unitprice > 1.0)) {
+ std::ostringstream cstream;
+ stream << - data.unitprice;
+ xact->cost = amount_t(string(stream.str()) + " " + default_commodity->base_symbol());
+ }
+
+ DEBUG("ledger.ofx.parse", "xact " << xact->amount
+ << " from " << *xact->account);
+
+#if 0
+ if (data.date_initiated_valid)
+ entry->_date = gregorian::from_time_t(data.date_initiated);
+ else if (data.date_posted_valid)
+ entry->_date = from_time_t(data.date_posted);
+#endif
+
+ if (data.check_number_valid)
+ entry->code = data.check_number;
+ else if (data.reference_number_valid)
+ entry->code = data.reference_number;
+
+ if (data.name_valid)
+ entry->payee = data.name;
+
+ if (data.memo_valid)
+ xact->note = data.memo;
+
+ // jww (2005-02-09): check for fi_id_corrected? or is this handled
+ // by the library?
+
+ // Balance all entries into <Unknown>, since it is not specified.
+ account = curr_journal->find_account("<Unknown>");
+ entry->add_xact(new xact_t(account));
+
+ if (! curr_journal->add_entry(entry)) {
+#if 0
+ print_entry(std::cerr, *entry);
+#endif
+ // jww (2005-02-09): uncomment
+ //have_error = "The above entry does not balance";
+ checked_delete(entry);
+ return -1;
+ }
+ return 0;
+}
+
+int ofx_proc_security_cb(struct OfxSecurityData data, void *)
+{
+ if (! data.unique_id_valid)
+ return -1;
+
+ string symbol;
+ if (data.ticker_valid)
+ symbol = data.ticker;
+ else if (data.currency_valid)
+ symbol = data.currency;
+ else
+ return -1;
+
+ commodity_t * commodity = amount_t::current_pool->find_or_create(symbol);
+ commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED);
+
+ if (data.secname_valid)
+ commodity->set_name(string(data.secname));
+
+ if (data.memo_valid)
+ commodity->set_note(string(data.memo));
+
+ commodities_map::iterator i = ofx_securities.find(data.unique_id);
+ if (i == ofx_securities.end()) {
+ DEBUG("ledger.ofx.parse", "security " << symbol);
+ ofx_securities.insert(commodities_pair(data.unique_id, commodity));
+ }
+
+ // jww (2005-02-09): What is the commodity for data.unitprice?
+ if (data.date_unitprice_valid && data.unitprice_valid) {
+ DEBUG("ledger.ofx.parse", " price " << data.unitprice);
+#if 0
+ // jww (2008-08-07): Need to convert from double here
+ commodity->add_price(data.date_unitprice, amount_t(data.unitprice));
+#endif
+ }
+
+ return 0;
+}
+
+int ofx_proc_status_cb(struct OfxStatusData, void *)
+{
+ return 0;
+}
+
+bool ofx_parser_t::test(std::istream& in) const
+{
+ char buf[80];
+
+ in.getline(buf, 79);
+ if (std::strncmp(buf, "OFXHEADER", 9) == 0) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return true;
+ }
+ else if (std::strncmp(buf, "<?xml", 5) != 0) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return false;
+ }
+
+ in.getline(buf, 79);
+ if (std::strncmp(buf, "<?OFX", 5) != 0 &&
+ std::strncmp(buf, "<?ofx", 5) != 0) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return false;
+ }
+
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return true;
+}
+
+unsigned int ofx_parser_t::parse(std::istream&,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
+{
+ if (! original_file)
+ return 0;
+
+ curr_journal = &journal;
+ master_account = master ? master : session.master.get();
+
+ LibofxContextPtr libofx_context = libofx_get_new_context();
+
+ ofx_set_statement_cb (libofx_context, ofx_proc_statement_cb, 0);
+ ofx_set_account_cb (libofx_context, ofx_proc_account_cb, 0);
+ ofx_set_transaction_cb (libofx_context, ofx_proc_transaction_cb, 0);
+ ofx_set_security_cb (libofx_context, ofx_proc_security_cb, 0);
+ ofx_set_status_cb (libofx_context, ofx_proc_status_cb, 0);
+
+ // The processing is done by way of callbacks, which are all defined
+ // above.
+ libofx_proc_file(libofx_context, original_file->string().c_str(),
+ AUTODETECT);
+
+ libofx_free_context(libofx_context);
+
+ return 1; // jww (2005-02-09): count;
+}
+
+} // namespace ledger
diff --git a/src/ofx.h b/src/ofx.h
new file mode 100644
index 00000000..67dec6d3
--- /dev/null
+++ b/src/ofx.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _OFX_H
+#define _OFX_H
+
+#include "journal.h"
+
+namespace ledger {
+
+class ofx_parser_t : public journal_t::parser_t
+{
+public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+};
+
+} // namespace ledger
+
+#endif // _OFX_H
diff --git a/src/op.cc b/src/op.cc
new file mode 100644
index 00000000..b412c47f
--- /dev/null
+++ b/src/op.cc
@@ -0,0 +1,1130 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "op.h"
+#include "scope.h"
+#include "binary.h"
+
+namespace ledger {
+
+#if 0
+void expr_t::op_t::compute(value_t& result,
+ const details_t& details,
+ ptr_op_t context) const
+{
+ try {
+ switch (kind) {
+ case INDEX:
+ throw compute_error("Cannot directly compute an argument index");
+
+ case VALUE:
+ result = as_value();
+ break;
+
+ case F_NOW:
+ result = terminus;
+ break;
+
+ case AMOUNT:
+ if (details.xact) {
+ if (xact_has_xdata(*details.xact) &&
+ xact_xdata_(*details.xact).dflags & XACT_COMPOUND)
+ result = xact_xdata_(*details.xact).value;
+ else
+ result = details.xact->amount;
+ }
+ else if (details.account && account_has_xdata(*details.account)) {
+ result = account_xdata(*details.account).value;
+ }
+ else {
+ result = 0L;
+ }
+ break;
+
+ case PRICE:
+ if (details.xact) {
+ bool set = false;
+ if (xact_has_xdata(*details.xact)) {
+ xact_xdata_t& xdata(xact_xdata_(*details.xact));
+ if (xdata.dflags & XACT_COMPOUND) {
+ result = xdata.value.value();
+ set = true;
+ }
+ }
+ if (! set) {
+ optional<amount_t> value = details.xact->amount.value();
+ if (value)
+ result = *value;
+ else
+ result = 0L;
+ }
+ }
+ else if (details.account && account_has_xdata(*details.account)) {
+ result = account_xdata(*details.account).value.value();
+ }
+ else {
+ result = 0L;
+ }
+ break;
+
+ case COST:
+ if (details.xact) {
+ bool set = false;
+ if (xact_has_xdata(*details.xact)) {
+ xact_xdata_t& xdata(xact_xdata_(*details.xact));
+ if (xdata.dflags & XACT_COMPOUND) {
+ result = xdata.value.cost();
+ set = true;
+ }
+ }
+
+ if (! set) {
+ if (details.xact->cost)
+ result = *details.xact->cost;
+ else
+ result = details.xact->amount;
+ }
+ }
+ else if (details.account && account_has_xdata(*details.account)) {
+ result = account_xdata(*details.account).value.cost();
+ }
+ else {
+ result = 0L;
+ }
+ break;
+
+ case TOTAL:
+ if (details.xact && xact_has_xdata(*details.xact))
+ result = xact_xdata_(*details.xact).total;
+ else if (details.account && account_has_xdata(*details.account))
+ result = account_xdata(*details.account).total;
+ else
+ result = 0L;
+ break;
+ case PRICE_TOTAL:
+ if (details.xact && xact_has_xdata(*details.xact))
+ result = xact_xdata_(*details.xact).total.value();
+ else if (details.account && account_has_xdata(*details.account))
+ result = account_xdata(*details.account).total.value();
+ else
+ result = 0L;
+ break;
+ case COST_TOTAL:
+ if (details.xact && xact_has_xdata(*details.xact))
+ result = xact_xdata_(*details.xact).total.cost();
+ else if (details.account && account_has_xdata(*details.account))
+ result = account_xdata(*details.account).total.cost();
+ else
+ result = 0L;
+ break;
+
+ case VALUE_EXPR:
+ if (value_expr::amount_expr.get())
+ value_expr::amount_expr->compute(result, details, context);
+ else
+ result = 0L;
+ break;
+ case TOTAL_EXPR:
+ if (value_expr::total_expr.get())
+ value_expr::total_expr->compute(result, details, context);
+ else
+ result = 0L;
+ break;
+
+ case DATE:
+ if (details.xact && xact_has_xdata(*details.xact) &&
+ is_valid(xact_xdata_(*details.xact).date))
+ result = xact_xdata_(*details.xact).date;
+ else if (details.xact)
+ result = details.xact->date();
+ else if (details.entry)
+ result = details.entry->date();
+ else
+ result = terminus;
+ break;
+
+ case ACT_DATE:
+ if (details.xact && xact_has_xdata(*details.xact) &&
+ is_valid(xact_xdata_(*details.xact).date))
+ result = xact_xdata_(*details.xact).date;
+ else if (details.xact)
+ result = details.xact->actual_date();
+ else if (details.entry)
+ result = details.entry->actual_date();
+ else
+ result = terminus;
+ break;
+
+ case EFF_DATE:
+ if (details.xact && xact_has_xdata(*details.xact) &&
+ is_valid(xact_xdata_(*details.xact).date))
+ result = xact_xdata_(*details.xact).date;
+ else if (details.xact)
+ result = details.xact->effective_date();
+ else if (details.entry)
+ result = details.entry->effective_date();
+ else
+ result = terminus;
+ break;
+
+ case CLEARED:
+ if (details.xact)
+ result = details.xact->state == xact_t::CLEARED;
+ else
+ result = false;
+ break;
+ case PENDING:
+ if (details.xact)
+ result = details.xact->state == xact_t::PENDING;
+ else
+ result = false;
+ break;
+
+ case REAL:
+ if (details.xact)
+ result = ! (details.xact->has_flags(XACT_VIRTUAL));
+ else
+ result = true;
+ break;
+
+ case ACTUAL:
+ if (details.xact)
+ result = ! (details.xact->has_flags(XACT_AUTO));
+ else
+ result = true;
+ break;
+
+ case INDEX:
+ if (details.xact && xact_has_xdata(*details.xact))
+ result = long(xact_xdata_(*details.xact).index + 1);
+ else if (details.account && account_has_xdata(*details.account))
+ result = long(account_xdata(*details.account).count);
+ else
+ result = 0L;
+ break;
+
+ case COUNT:
+ if (details.xact && xact_has_xdata(*details.xact))
+ result = long(xact_xdata_(*details.xact).index + 1);
+ else if (details.account && account_has_xdata(*details.account))
+ result = long(account_xdata(*details.account).total_count);
+ else
+ result = 0L;
+ break;
+
+ case DEPTH:
+ if (details.account)
+ result = long(details.account->depth);
+ else
+ result = 0L;
+ break;
+
+ case F_PRICE: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ result = result.value();
+ break;
+ }
+
+ case F_DATE: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ result = result.as_datetime();
+ break;
+ }
+
+ case F_DATECMP: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ result = result.as_datetime();
+ if (! result)
+ break;
+
+ arg_index = 0;
+ expr = find_leaf(context, 1, arg_index);
+ value_t moment;
+ expr->compute(moment, details, context);
+ if (moment.is_type(value_t::DATETIME)) {
+ result.cast(value_t::INTEGER);
+ moment.cast(value_t::INTEGER);
+ result -= moment;
+ } else {
+ add_error_context(expr_context(expr));
+ throw compute_error("Invalid date passed to datecmp(value,date)");
+ }
+ break;
+ }
+
+ case F_YEAR:
+ case F_MONTH:
+ case F_DAY: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+
+ if (! result.is_type(value_t::DATETIME)) {
+ add_error_context(expr_context(expr));
+ throw compute_error("Invalid date passed to year|month|day(date)");
+ }
+
+ const date_t& moment(result.as_date());
+ switch (kind) {
+ case F_YEAR:
+ result = (long)moment.year();
+ break;
+ case F_MONTH:
+ result = (long)moment.month();
+ break;
+ case F_DAY:
+ result = (long)moment.day();
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ case F_ARITH_MEAN: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ if (details.xact && xact_has_xdata(*details.xact)) {
+ expr->compute(result, details, context);
+ result /= amount_t(long(xact_xdata_(*details.xact).index + 1));
+ }
+ else if (details.account && account_has_xdata(*details.account) &&
+ account_xdata(*details.account).total_count) {
+ expr->compute(result, details, context);
+ result /= amount_t(long(account_xdata(*details.account).total_count));
+ }
+ else {
+ result = 0L;
+ }
+ break;
+ }
+
+ case F_PARENT:
+ if (details.account && details.account->parent)
+ left()->compute(result, details_t(*details.account->parent), context);
+ break;
+
+ case F_ABS: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ result.abs();
+ break;
+ }
+
+ case F_ROUND: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ result.round();
+ break;
+ }
+
+ case F_COMMODITY: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+ if (! result.is_type(value_t::AMOUNT)) {
+ add_error_context(expr_context(expr));
+ throw compute_error("Argument to commodity() must be a commoditized amount");
+ }
+ amount_t temp("1");
+ temp.set_commodity(result.as_amount().commodity());
+ result = temp;
+ break;
+ }
+
+ case F_SET_COMMODITY: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ value_t temp;
+ expr->compute(temp, details, context);
+
+ arg_index = 0;
+ expr = find_leaf(context, 1, arg_index);
+ expr->compute(result, details, context);
+ if (! result.is_type(value_t::AMOUNT)) {
+ add_error_context(expr_context(expr));
+ throw compute_error("Second argument to set_commodity() must be a commoditized amount");
+ }
+ amount_t one("1");
+ one.set_commodity(result.as_amount().commodity());
+ result = one;
+
+ result *= temp;
+ break;
+ }
+
+ case F_QUANTITY: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+
+ const balance_t * bal = NULL;
+ switch (result.type()) {
+ case value_t::BALANCE_PAIR:
+ bal = &(result.as_balance_pair().quantity());
+ // fall through...
+
+ case value_t::BALANCE:
+ if (! bal)
+ bal = &result.as_balance();
+
+ if (bal->amounts.size() < 2) {
+ result.cast(value_t::AMOUNT);
+ } else {
+ value_t temp;
+ for (balance_t::amounts_map::value_type pair, bal->amounts) {
+ amount_t x = pair.second;
+ x.clear_commodity();
+ temp += x;
+ }
+ result = temp;
+ assert(temp.is_type(value_t::AMOUNT));
+ }
+ // fall through...
+
+ case value_t::AMOUNT:
+ result.as_amount_lval().clear_commodity();
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ case O_ARG: {
+ long arg_index = 0;
+ assert(left()->kind == INDEX);
+ ptr_op_t expr = find_leaf(context, left()->as_long(), arg_index);
+ if (expr)
+ expr->compute(result, details, context);
+ else
+ result = 0L;
+ break;
+ }
+
+ case O_COMMA:
+ if (! left()) {
+ add_error_context(expr_context(*this));
+ throw compute_error("Comma operator missing left operand");
+ }
+ if (! right()) {
+ add_error_context(expr_context(*this));
+ throw compute_error("Comma operator missing right operand");
+ }
+ right()->compute(result, details, context);
+ break;
+
+ case O_DEF:
+ result = 0L;
+ break;
+
+ case O_REF: {
+ assert(left());
+ if (right()) {
+ value_expr args(reduce_leaves(right(), details, context));
+ left()->compute(result, details, args.get());
+ } else {
+ left()->compute(result, details, context);
+ }
+ break;
+ }
+
+ case F_VALUE: {
+ long arg_index = 0;
+ ptr_op_t expr = find_leaf(context, 0, arg_index);
+ expr->compute(result, details, context);
+
+ arg_index = 0;
+ expr = find_leaf(context, 1, arg_index);
+ value_t moment;
+ expr->compute(moment, details, context);
+ if (! moment.is_type(value_t::DATETIME)) {
+ add_error_context(expr_context(expr));
+ throw compute_error("Invalid date passed to P(value,date)");
+ }
+ result = result.value(moment.as_datetime());
+ break;
+ }
+
+ case O_NOT:
+ left()->compute(result, details, context);
+ if (result.strip_annotations())
+ result = false;
+ else
+ result = true;
+ break;
+
+ case O_QUES: {
+ assert(left());
+ assert(right());
+ assert(right()->kind == O_COL);
+ left()->compute(result, details, context);
+ if (result.strip_annotations())
+ right()->left()->compute(result, details, context);
+ else
+ right()->right()->compute(result, details, context);
+ break;
+ }
+
+ case O_AND:
+ assert(left());
+ assert(right());
+ left()->compute(result, details, context);
+ result = result.strip_annotations();
+ if (result)
+ right()->compute(result, details, context);
+ break;
+
+ case O_OR:
+ assert(left());
+ assert(right());
+ left()->compute(result, details, context);
+ if (! result.strip_annotations())
+ right()->compute(result, details, context);
+ break;
+
+ case O_NEQ:
+ case O_EQ:
+ case O_LT:
+ case O_LTE:
+ case O_GT:
+ case O_GTE: {
+ assert(left());
+ assert(right());
+ value_t temp;
+ left()->compute(temp, details, context);
+ right()->compute(result, details, context);
+ switch (kind) {
+ case O_NEQ: result = temp != result; break;
+ case O_EQ: result = temp == result; break;
+ case O_LT: result = temp < result; break;
+ case O_LTE: result = temp <= result; break;
+ case O_GT: result = temp > result; break;
+ case O_GTE: result = temp >= result; break;
+ default: assert(false); break;
+ }
+ break;
+ }
+
+ case O_NEG:
+ assert(left());
+ left()->compute(result, details, context);
+ result.negate();
+ break;
+
+ case O_ADD:
+ case O_SUB:
+ case O_MUL:
+ case O_DIV: {
+ assert(left());
+ assert(right());
+ value_t temp;
+ right()->compute(temp, details, context);
+ left()->compute(result, details, context);
+ switch (kind) {
+ case O_ADD: result += temp; break;
+ case O_SUB: result -= temp; break;
+ case O_MUL: result *= temp; break;
+ case O_DIV: result /= temp; break;
+ default: assert(false); break;
+ }
+ break;
+ }
+
+ case O_PERC: {
+ assert(left());
+ result = "100.0%";
+ value_t temp;
+ left()->compute(temp, details, context);
+ result *= temp;
+ break;
+ }
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context(expr_context(*this));
+ throw err;
+ }
+}
+#endif
+
+expr_t::ptr_op_t expr_t::op_t::compile(scope_t& scope)
+{
+ switch (kind) {
+ case IDENT:
+ if (ptr_op_t def = scope.lookup(as_ident())) {
+#if 1
+ return def;
+#else
+ // jww (2008-08-02): Aren't definitions compiled when they go in?
+ // Would recompiling here really add any benefit?
+ return def->compile(scope);
+#endif
+ }
+ return this;
+
+ default:
+ break;
+ }
+
+ if (kind < TERMINALS)
+ return this;
+
+ ptr_op_t lhs(left()->compile(scope));
+ ptr_op_t rhs(kind > UNARY_OPERATORS ? right()->compile(scope) : ptr_op_t());
+
+ if (lhs == left() && (! rhs || rhs == right()))
+ return this;
+
+ ptr_op_t intermediate(copy(lhs, rhs));
+
+ if (lhs->is_value() && (! rhs || rhs->is_value()))
+ return wrap_value(intermediate->calc(scope));
+
+ return intermediate;
+}
+
+value_t expr_t::op_t::calc(scope_t& scope)
+{
+ switch (kind) {
+ case VALUE:
+ return as_value();
+
+ case IDENT:
+#if 0
+ if (ptr_op_t reference = compile(scope)) {
+ if (reference != this)
+ return reference->calc(scope);
+ }
+#endif
+ throw_(calc_error, "Unknown identifier '" << as_ident() << "'");
+
+ case FUNCTION: {
+ // Evaluating a FUNCTION is the same as calling it directly; this happens
+ // when certain functions-that-look-like-variables (such as "amount") are
+ // resolved.
+ call_scope_t call_args(scope);
+ return as_function()(call_args);
+ }
+
+ case O_CALL: {
+ call_scope_t call_args(scope);
+
+ if (right())
+ call_args.set_args(right()->calc(scope));
+
+ ptr_op_t func = left();
+ string name;
+
+#if 0
+ // The expression must be compiled beforehand in order to resolve this
+ // into a function.
+ if (func->kind == IDENT) {
+ name = func->as_ident();
+ ptr_op_t def = func->compile(scope);
+ if (def == func)
+ throw_(calc_error,
+ "Calling unknown function '" << name << "'");
+ func = def;
+ }
+#endif
+
+ if (func->kind != FUNCTION)
+ throw_(calc_error, "Calling non-function");
+
+ return func->as_function()(call_args);
+ }
+
+ case O_MATCH:
+ assert(right()->is_mask());
+ return right()->as_mask().match(left()->calc(scope).to_string());
+
+ case INDEX: {
+ const call_scope_t& args(downcast<const call_scope_t>(scope));
+
+ if (as_index() < args.size())
+ return args[as_index()];
+ else
+ throw_(calc_error, "Reference to non-existing argument " << as_index());
+ break;
+ }
+
+ case O_NEQ:
+ return left()->calc(scope) != right()->calc(scope);
+ case O_EQ:
+ return left()->calc(scope) == right()->calc(scope);
+ case O_LT:
+ return left()->calc(scope) < right()->calc(scope);
+ case O_LTE:
+ return left()->calc(scope) <= right()->calc(scope);
+ case O_GT:
+ return left()->calc(scope) > right()->calc(scope);
+ case O_GTE:
+ return left()->calc(scope) >= right()->calc(scope);
+
+ case O_ADD:
+ return left()->calc(scope) + right()->calc(scope);
+ case O_SUB:
+ return left()->calc(scope) - right()->calc(scope);
+ case O_MUL:
+ return left()->calc(scope) * right()->calc(scope);
+ case O_DIV:
+ return left()->calc(scope) / right()->calc(scope);
+
+ case O_NEG:
+ assert(! right());
+ return left()->calc(scope).negate();
+
+ case O_NOT:
+ assert(! right());
+ return ! left()->calc(scope);
+
+ case O_AND:
+ return ! left()->calc(scope) ? value_t(false) : right()->calc(scope);
+
+ case O_OR:
+ if (value_t temp = left()->calc(scope))
+ return temp;
+ else
+ return right()->calc(scope);
+
+ case O_COMMA: {
+ value_t result(left()->calc(scope));
+
+ ptr_op_t next = right();
+ while (next) {
+ ptr_op_t value_op;
+ if (next->kind == O_COMMA /* || next->kind == O_UNION */) {
+ value_op = next->left();
+ next = next->right();
+ } else {
+ value_op = next;
+ next = NULL;
+ }
+
+ result.push_back(value_op->calc(scope));
+ }
+ return result;
+ }
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+ return NULL_VALUE;
+}
+
+bool expr_t::op_t::print(std::ostream& out, print_context_t& context) const
+{
+ bool found = false;
+
+ if (context.start_pos && this == context.op_to_find) {
+ *context.start_pos = static_cast<unsigned long>(out.tellp()) - 1;
+ found = true;
+ }
+
+ string symbol;
+
+ switch (kind) {
+ case VALUE: {
+ as_value().print(out, context.relaxed);
+ break;
+ }
+
+ case IDENT:
+ out << as_ident();
+ break;
+
+ case FUNCTION:
+ out << "<FUNCTION>";
+ break;
+
+ case INDEX:
+ out << '@' << as_index();
+ break;
+
+ case O_NOT:
+ out << "!";
+ if (left() && left()->print(out, context))
+ found = true;
+ break;
+ case O_NEG:
+ out << "-";
+ if (left() && left()->print(out, context))
+ found = true;
+ break;
+
+ case O_ADD:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " + ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_SUB:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " - ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_MUL:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " * ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_DIV:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " / ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_NEQ:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " != ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_EQ:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " == ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_LT:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " < ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_LTE:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " <= ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_GT:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " > ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_GTE:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " >= ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_AND:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " & ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+ case O_OR:
+ out << "(";
+ if (left() && left()->print(out, context))
+ found = true;
+ out << " | ";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_COMMA:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << ", ";
+ if (right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case O_CALL:
+ if (left() && left()->print(out, context))
+ found = true;
+ out << "(";
+ if (right() && right()->print(out, context))
+ found = true;
+ out << ")";
+ break;
+
+ case O_MATCH:
+ out << '/';
+ if (left() && left()->print(out, context))
+ found = true;
+ out << "/ =~ ";
+ if (right() && right()->print(out, context))
+ found = true;
+ break;
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+ if (! symbol.empty()) {
+ if (amount_t::current_pool->find(symbol))
+ out << '@';
+ out << symbol;
+ }
+
+ if (context.end_pos && this == context.op_to_find)
+ *context.end_pos = static_cast<unsigned long>(out.tellp()) - 1;
+
+ return found;
+}
+
+void expr_t::op_t::dump(std::ostream& out, const int depth) const
+{
+ out.setf(std::ios::left);
+ out.width(10);
+ out << this;
+
+ for (int i = 0; i < depth; i++)
+ out << " ";
+
+ switch (kind) {
+ case VALUE:
+ out << "VALUE - " << as_value();
+ break;
+
+ case IDENT:
+ out << "IDENT - " << as_ident();
+ break;
+
+ case INDEX:
+ out << "INDEX - " << as_index();
+ break;
+
+ case FUNCTION:
+ out << "FUNCTION";
+ break;
+
+ case O_CALL: out << "O_CALL"; break;
+ case O_MATCH: out << "O_MATCH"; break;
+
+ case O_NOT: out << "O_NOT"; break;
+ case O_NEG: out << "O_NEG"; break;
+
+ case O_ADD: out << "O_ADD"; break;
+ case O_SUB: out << "O_SUB"; break;
+ case O_MUL: out << "O_MUL"; break;
+ case O_DIV: out << "O_DIV"; break;
+
+ case O_NEQ: out << "O_NEQ"; break;
+ case O_EQ: out << "O_EQ"; break;
+ case O_LT: out << "O_LT"; break;
+ case O_LTE: out << "O_LTE"; break;
+ case O_GT: out << "O_GT"; break;
+ case O_GTE: out << "O_GTE"; break;
+
+ case O_AND: out << "O_AND"; break;
+ case O_OR: out << "O_OR"; break;
+
+ case O_COMMA: out << "O_COMMA"; break;
+
+ case LAST:
+ default:
+ assert(false);
+ break;
+ }
+
+ out << " (" << refc << ')' << std::endl;
+
+ if (kind > TERMINALS) {
+ if (left()) {
+ left()->dump(out, depth + 1);
+ if (right())
+ right()->dump(out, depth + 1);
+ } else {
+ assert(! right());
+ }
+ }
+}
+
+void expr_t::op_t::read(const char *& data)
+{
+#if 0
+ if (! read_bool(data))
+ return expr_t::ptr_op_t();
+
+ expr_t::op_t::kind_t kind;
+ read_number(data, kind);
+
+ expr_t::ptr_op_t expr = new expr_t::op_t(kind);
+
+ if (kind > expr_t::op_t::TERMINALS)
+ expr->set_left(read_value_expr(data));
+
+ switch (expr->kind) {
+ case expr_t::op_t::INDEX: {
+ long temp;
+ read_long(data, temp);
+ expr->set_index(temp);
+ break;
+ }
+ case expr_t::op_t::VALUE: {
+ value_t temp;
+ read_value(data, temp);
+ expr->set_value(temp);
+ break;
+ }
+
+ case expr_t::op_t::MASK:
+ if (read_bool(data))
+ read_mask(data, expr->as_mask_lval());
+ break;
+
+ default:
+ if (kind > expr_t::op_t::TERMINALS)
+ expr->set_right(read_value_expr(data));
+ break;
+ }
+
+ return expr;
+#endif
+}
+
+void expr_t::op_t::write(std::ostream& out) const
+{
+#if 0
+ if (! expr) {
+ write_bool(out, false);
+ return;
+ }
+
+ write_bool(out, true);
+ write_number(out, expr->kind);
+
+ if (expr->kind > expr_t::op_t::TERMINALS)
+ write_value_expr(out, expr->left());
+
+ switch (expr->kind) {
+ case expr_t::op_t::INDEX:
+ write_long(out, expr->as_index());
+ break;
+ case expr_t::op_t::IDENT:
+ write_long(out, expr->as_ident());
+ break;
+ case expr_t::op_t::VALUE:
+ write_value(out, expr->as_value());
+ break;
+
+ case expr_t::op_t::MASK:
+ if (expr->as_mask()) {
+ write_bool(out, true);
+ write_mask(out, expr->as_mask());
+ } else {
+ write_bool(out, false);
+ }
+ break;
+
+ default:
+ if (expr->kind > expr_t::op_t::TERMINALS)
+ write_value_expr(out, expr->right());
+ break;
+ }
+#endif
+}
+
+#if 0
+class op_predicate : public noncopyable
+{
+ ptr_op_t op;
+
+ op_predicate();
+
+public:
+ explicit op_predicate(ptr_op_t _op) : op(_op) {
+ TRACE_CTOR(op_predicate, "ptr_op_t");
+ }
+ ~op_predicate() throw() {
+ TRACE_DTOR(op_predicate);
+ }
+ bool operator()(scope_t& scope) {
+ return op->calc(scope).to_boolean();
+ }
+};
+
+#endif
+
+} // namespace ledger
diff --git a/src/op.h b/src/op.h
new file mode 100644
index 00000000..66618360
--- /dev/null
+++ b/src/op.h
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _OP_H
+#define _OP_H
+
+#include "expr.h"
+#include "mask.h"
+
+namespace ledger {
+
+class expr_t::op_t : public noncopyable
+{
+ friend class expr_t;
+ friend class expr_t::parser_t;
+
+ op_t();
+
+public:
+ typedef expr_t::ptr_op_t ptr_op_t;
+
+private:
+ mutable short refc;
+ ptr_op_t left_;
+
+ variant<unsigned int, // used by constant INDEX
+ value_t, // used by constant VALUE
+ string, // used by constant IDENT
+ mask_t, // used by constant MASK
+ function_t, // used by terminal FUNCTION
+ ptr_op_t> // used by all binary operators
+ data;
+
+public:
+ enum kind_t {
+ // Constants
+ VALUE,
+ IDENT,
+ MASK,
+ INDEX,
+
+ CONSTANTS,
+
+ FUNCTION,
+
+ TERMINALS,
+
+ // Binary operators
+ O_NOT,
+ O_NEG,
+
+ UNARY_OPERATORS,
+
+ O_EQ,
+ O_NEQ,
+ O_LT,
+ O_LTE,
+ O_GT,
+ O_GTE,
+
+ O_AND,
+ O_OR,
+
+ O_ADD,
+ O_SUB,
+ O_MUL,
+ O_DIV,
+
+ O_QUERY,
+ O_COLON,
+
+ O_COMMA,
+
+ O_CALL,
+ O_MATCH,
+
+ BINARY_OPERATORS,
+
+ OPERATORS,
+
+ LAST
+ };
+
+ kind_t kind;
+
+ explicit op_t(const kind_t _kind) : refc(0), kind(_kind) {
+ TRACE_CTOR(op_t, "const kind_t");
+ }
+ ~op_t() {
+ TRACE_DTOR(op_t);
+ assert(refc == 0);
+ }
+
+ bool is_index() const {
+ return data.type() == typeid(unsigned int);
+ }
+ unsigned int& as_index_lval() {
+ assert(kind == INDEX);
+ return boost::get<unsigned int>(data);
+ }
+ const unsigned int& as_index() const {
+ return const_cast<op_t *>(this)->as_index_lval();
+ }
+ void set_index(unsigned int val) {
+ data = val;
+ }
+
+ bool is_value() const {
+ if (kind == VALUE) {
+ assert(data.type() == typeid(value_t));
+ return true;
+ }
+ return false;
+ }
+ value_t& as_value_lval() {
+ assert(is_value());
+ value_t& val(boost::get<value_t>(data));
+ assert(val.valid());
+ return val;
+ }
+ const value_t& as_value() const {
+ return const_cast<op_t *>(this)->as_value_lval();
+ }
+ void set_value(const value_t& val) {
+ assert(val.valid());
+ data = val;
+ }
+
+ bool is_ident() const {
+ if (kind == IDENT) {
+ assert(data.type() == typeid(string));
+ return true;
+ }
+ return false;
+ }
+ string& as_ident_lval() {
+ assert(is_ident());
+ return boost::get<string>(data);
+ }
+ const string& as_ident() const {
+ return const_cast<op_t *>(this)->as_ident_lval();
+ }
+ void set_ident(const string& val) {
+ data = val;
+ }
+
+ bool is_mask() const {
+ if (kind == MASK) {
+ assert(data.type() == typeid(mask_t));
+ return true;
+ }
+ return false;
+ }
+ mask_t& as_mask_lval() {
+ assert(is_mask());
+ return boost::get<mask_t>(data);
+ }
+ const mask_t& as_mask() const {
+ return const_cast<op_t *>(this)->as_mask_lval();
+ }
+ void set_mask(const mask_t& val) {
+ data = val;
+ }
+ void set_mask(const string& expr) {
+ data = mask_t(expr);
+ }
+
+ bool is_function() const {
+ return kind == FUNCTION;
+ }
+ function_t& as_function_lval() {
+ assert(kind == FUNCTION);
+ return boost::get<function_t>(data);
+ }
+ const function_t& as_function() const {
+ return const_cast<op_t *>(this)->as_function_lval();
+ }
+ void set_function(const function_t& val) {
+ data = val;
+ }
+
+ ptr_op_t& left() {
+ return left_;
+ }
+ const ptr_op_t& left() const {
+ assert(kind > TERMINALS);
+ return left_;
+ }
+ void set_left(const ptr_op_t& expr) {
+ assert(kind > TERMINALS);
+ left_ = expr;
+ }
+
+ ptr_op_t& as_op_lval() {
+ assert(kind > TERMINALS);
+ return boost::get<ptr_op_t>(data);
+ }
+ const ptr_op_t& as_op() const {
+ return const_cast<op_t *>(this)->as_op_lval();
+ }
+
+ ptr_op_t& right() {
+ assert(kind > TERMINALS);
+ return as_op_lval();
+ }
+ const ptr_op_t& right() const {
+ assert(kind > TERMINALS);
+ return as_op();
+ }
+ void set_right(const ptr_op_t& expr) {
+ assert(kind > TERMINALS);
+ data = expr;
+ }
+
+private:
+ void acquire() const {
+ DEBUG("ledger.xpath.memory",
+ "Acquiring " << this << ", refc now " << refc + 1);
+ assert(refc >= 0);
+ refc++;
+ }
+ void release() const {
+ DEBUG("ledger.xpath.memory",
+ "Releasing " << this << ", refc now " << refc - 1);
+ assert(refc > 0);
+ if (--refc == 0)
+ checked_delete(this);
+ }
+
+ friend inline void intrusive_ptr_add_ref(op_t * op) {
+ op->acquire();
+ }
+ friend inline void intrusive_ptr_release(op_t * op) {
+ op->release();
+ }
+
+ static ptr_op_t new_node(kind_t _kind, ptr_op_t _left = NULL,
+ ptr_op_t _right = NULL);
+
+ ptr_op_t copy(ptr_op_t _left = NULL, ptr_op_t _right = NULL) const {
+ return new_node(kind, _left, _right);
+ }
+
+public:
+ ptr_op_t compile(scope_t& scope);
+ value_t calc(scope_t& scope);
+
+ struct print_context_t
+ {
+ scope_t& scope;
+ const bool relaxed;
+ const ptr_op_t& op_to_find;
+ unsigned long * start_pos;
+ unsigned long * end_pos;
+
+ // jww (2008-08-01): Is a scope needed here?
+ print_context_t(scope_t& _scope,
+ const bool _relaxed = false,
+ const ptr_op_t& _op_to_find = ptr_op_t(),
+ unsigned long * _start_pos = NULL,
+ unsigned long * _end_pos = NULL)
+ : scope(_scope), relaxed(_relaxed), op_to_find(_op_to_find),
+ start_pos(_start_pos), end_pos(_end_pos) {}
+ };
+
+ bool print(std::ostream& out, print_context_t& context) const;
+ void dump(std::ostream& out, const int depth) const;
+
+ void read(const char *& data);
+ void write(std::ostream& out) const;
+
+ static ptr_op_t wrap_value(const value_t& val);
+ static ptr_op_t wrap_functor(const function_t& fobj);
+};
+
+inline expr_t::ptr_op_t
+expr_t::op_t::new_node(kind_t _kind, ptr_op_t _left, ptr_op_t _right)
+{
+ ptr_op_t node(new op_t(_kind));
+ node->set_left(_left);
+ node->set_right(_right);
+ return node;
+}
+
+inline expr_t::ptr_op_t expr_t::op_t::wrap_value(const value_t& val) {
+ ptr_op_t temp(new op_t(op_t::VALUE));
+ temp->set_value(val);
+ return temp;
+}
+
+inline expr_t::ptr_op_t expr_t::op_t::wrap_functor(const function_t& fobj) {
+ ptr_op_t temp(new op_t(op_t::FUNCTION));
+ temp->set_function(fobj);
+ return temp;
+}
+
+#define MAKE_FUNCTOR(x) expr_t::op_t::wrap_functor(bind(&x, this, _1))
+#define WRAP_FUNCTOR(x) expr_t::op_t::wrap_functor(x)
+
+} // namespace ledger
+
+#endif // _OP_H
diff --git a/src/option.cc b/src/option.cc
new file mode 100644
index 00000000..b633de32
--- /dev/null
+++ b/src/option.cc
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "option.h"
+
+namespace ledger {
+
+namespace {
+ typedef tuple<expr_t::ptr_op_t, bool> op_bool_tuple;
+
+ op_bool_tuple find_option(scope_t& scope, const string& name)
+ {
+ char buf[128];
+ std::strcpy(buf, "opt_");
+ char * p = &buf[4];
+ foreach (char ch, name) {
+ if (ch == '-')
+ *p++ = '_';
+ else
+ *p++ = ch;
+ }
+ *p = '\0';
+
+ expr_t::ptr_op_t op = scope.lookup(buf);
+ if (op)
+ return op_bool_tuple(op, false);
+
+ *p++ = '_';
+ *p = '\0';
+
+ return op_bool_tuple(scope.lookup(buf), true);
+ }
+
+ op_bool_tuple find_option(scope_t& scope, const char letter)
+ {
+ char buf[10];
+ std::strcpy(buf, "opt_");
+ buf[4] = letter;
+ buf[5] = '\0';
+
+ expr_t::ptr_op_t op = scope.lookup(buf);
+ if (op)
+ return op_bool_tuple(op, false);
+
+ buf[5] = '_';
+ buf[6] = '\0';
+
+ return op_bool_tuple(scope.lookup(buf), true);
+ }
+
+ void process_option(const function_t& opt, scope_t& scope,
+ const char * arg)
+ {
+ try {
+ call_scope_t args(scope);
+ if (arg)
+ args.push_back(string_value(arg));
+
+ opt(args);
+ }
+ catch (const std::exception& err) {
+#if 0
+ add_error_context("While parsing option '--" << opt->long_opt
+ << "'" << (opt->short_opt != '\0' ?
+ (string(" (-") + opt->short_opt + "):") :
+ ": "));
+#endif
+ throw err;
+ }
+ }
+}
+
+void process_option(const string& name, scope_t& scope,
+ const char * arg)
+{
+ op_bool_tuple opt(find_option(scope, name));
+ if (opt.get<0>())
+ process_option(opt.get<0>()->as_function(), scope, arg);
+}
+
+void process_environment(const char ** envp, const string& tag,
+ scope_t& scope)
+{
+ const char * tag_p = tag.c_str();
+ unsigned int tag_len = tag.length();
+
+ for (const char ** p = envp; *p; p++)
+ if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) {
+ char buf[128];
+ char * r = buf;
+ const char * q;
+ for (q = *p + tag_len;
+ *q && *q != '=' && r - buf < 128;
+ q++)
+ if (*q == '_')
+ *r++ = '-';
+ else
+ *r++ = std::tolower(*q);
+ *r = '\0';
+
+ if (*q == '=') {
+ try {
+ process_option(string(buf), scope, q + 1);
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing environment variable option '"
+ << *p << "':");
+ throw err;
+ }
+ }
+ }
+}
+
+void process_arguments(int, char ** argv, const bool anywhere,
+ scope_t& scope, std::list<string>& args)
+{
+ for (char ** i = argv; *i; i++) {
+ if ((*i)[0] != '-') {
+ if (anywhere) {
+ args.push_back(*i);
+ continue;
+ } else {
+ for (; *i; i++)
+ args.push_back(*i);
+ break;
+ }
+ }
+
+ // --long-option or -s
+ if ((*i)[1] == '-') {
+ if ((*i)[2] == '\0')
+ break;
+
+ char * name = *i + 2;
+ char * value = NULL;
+ if (char * p = std::strchr(name, '=')) {
+ *p++ = '\0';
+ value = p;
+ }
+
+ op_bool_tuple opt(find_option(scope, name));
+ if (! opt.get<0>())
+ throw_(option_error, "illegal option --" << name);
+
+ if (opt.get<1>() && value == NULL) {
+ value = *++i;
+ if (value == NULL)
+ throw_(option_error, "missing option argument for --" << name);
+ }
+ process_option(opt.get<0>()->as_function(), scope, value);
+ }
+ else if ((*i)[1] == '\0') {
+ throw_(option_error, "illegal option -");
+ }
+ else {
+ typedef tuple<expr_t::ptr_op_t, bool, char> op_bool_char_tuple;
+
+ std::list<op_bool_char_tuple> option_queue;
+
+ int x = 1;
+ for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) {
+ op_bool_tuple opt(find_option(scope, c));
+ if (! opt.get<0>())
+ throw_(option_error, "illegal option -" << c);
+
+ option_queue.push_back
+ (op_bool_char_tuple(opt.get<0>(), opt.get<1>(), c));
+ }
+
+ foreach (op_bool_char_tuple& o, option_queue) {
+ char * value = NULL;
+ if (o.get<1>()) {
+ value = *++i;
+ if (value == NULL)
+ throw_(option_error,
+ "missing option argument for -" << o.get<2>());
+ }
+ process_option(o.get<0>()->as_function(), scope, value);
+ }
+ }
+ }
+}
+
+} // namespace ledger
diff --git a/src/option.h b/src/option.h
new file mode 100644
index 00000000..23439b9d
--- /dev/null
+++ b/src/option.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _OPTION_H
+#define _OPTION_H
+
+#include "scope.h"
+
+namespace ledger {
+
+void process_option(const string& name, scope_t& scope,
+ const char * arg = NULL);
+
+void process_environment(const char ** envp, const string& tag,
+ scope_t& scope);
+
+void process_arguments(int argc, char ** argv, const bool anywhere,
+ scope_t& scope, std::list<string>& args);
+
+DECLARE_EXCEPTION(option_error, std::runtime_error);
+
+} // namespace ledger
+
+#endif // _OPTION_H
diff --git a/src/output.cc b/src/output.cc
new file mode 100644
index 00000000..324a20e4
--- /dev/null
+++ b/src/output.cc
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "output.h"
+
+namespace ledger {
+
+format_xacts::format_xacts(report_t& _report, const string& format)
+ : report(_report), last_entry(NULL), last_xact(NULL)
+{
+ TRACE_CTOR(format_xacts, "report&, const string&");
+
+ const char * f = format.c_str();
+
+ if (const char * p = std::strstr(f, "%/")) {
+ first_line_format.parse(string(f, 0, p - f));
+ next_lines_format.parse(string(p + 2));
+ } else {
+ first_line_format.parse(format);
+ next_lines_format.parse(format);
+ }
+}
+
+void format_xacts::operator()(xact_t& xact)
+{
+ std::ostream& out(*report.output_stream);
+
+ if (! xact.has_xdata() ||
+ ! xact.xdata().has_flags(XACT_EXT_DISPLAYED)) {
+ if (last_entry != xact.entry) {
+ first_line_format.format(out, xact);
+ last_entry = xact.entry;
+ }
+ else if (last_xact && last_xact->date() != xact.date()) {
+ first_line_format.format(out, xact);
+ }
+ else {
+ next_lines_format.format(out, xact);
+ }
+
+ xact.xdata().add_flags(XACT_EXT_DISPLAYED);
+ last_xact = &xact;
+ }
+}
+
+void format_entries::format_last_entry()
+{
+ bool first = true;
+ std::ostream& out(*report.output_stream);
+
+ foreach (xact_t * xact, last_entry->xacts) {
+ if (xact->has_xdata() &&
+ xact->xdata().has_flags(XACT_EXT_TO_DISPLAY)) {
+ if (first) {
+ first_line_format.format(out, *xact);
+ first = false;
+ } else {
+ next_lines_format.format(out, *xact);
+ }
+ xact->xdata().add_flags(XACT_EXT_DISPLAYED);
+ }
+ }
+}
+
+void format_entries::operator()(xact_t& xact)
+{
+ xact.xdata().add_flags(XACT_EXT_TO_DISPLAY);
+
+ if (last_entry && xact.entry != last_entry)
+ format_last_entry();
+
+ last_entry = xact.entry;
+}
+
+void print_entry(std::ostream& out, const entry_base_t& entry_base,
+ const string& prefix)
+{
+ string print_format;
+
+ if (dynamic_cast<const entry_t *>(&entry_base)) {
+ print_format = (prefix + "%D %X%C%P\n" +
+ prefix + " %-34A %12o\n%/" +
+ prefix + " %-34A %12o\n");
+ }
+ else if (const auto_entry_t * entry =
+ dynamic_cast<const auto_entry_t *>(&entry_base)) {
+ out << "= " << entry->predicate.predicate.text() << '\n';
+ print_format = prefix + " %-34A %12o\n";
+ }
+ else if (const period_entry_t * entry =
+ dynamic_cast<const period_entry_t *>(&entry_base)) {
+ out << "~ " << entry->period_string << '\n';
+ print_format = prefix + " %-34A %12o\n";
+ }
+ else {
+ assert(false);
+ }
+
+#if 0
+ format_entries formatter(out, print_format);
+ walk_xacts(const_cast<xacts_list&>(entry_base.xacts), formatter);
+ formatter.flush();
+
+ clear_xact_xdata cleaner;
+ walk_xacts(const_cast<xacts_list&>(entry_base.xacts), cleaner);
+#endif
+}
+
+void format_accounts::flush()
+{
+ std::ostream& out(*report.output_stream);
+
+#if 0
+ // jww (2008-08-02): I need access to the output formatter before this is
+ // going to work.
+ if (print_final_total) {
+ assert(out);
+ assert(account_has_xdata(*session.master));
+
+ account_xdata_t& xdata(account_xdata(*session.master));
+
+ if (! show_collapsed && xdata.total) {
+ out << "--------------------\n";
+ xdata.value = xdata.total;
+ handler->format.format(out, *session.master);
+ }
+ }
+#endif
+
+ out.flush();
+}
+
+void format_accounts::operator()(account_t& account)
+{
+ if (display_account(account)) {
+ if (! account.parent) {
+ account.xdata().add_flags(ACCOUNT_EXT_TO_DISPLAY);
+ } else {
+ format.format(*report.output_stream, account);
+ account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED);
+ }
+ }
+}
+
+bool format_accounts::disp_subaccounts_p(account_t& account,
+ account_t *& to_show)
+{
+ bool display = false;
+ unsigned int counted = 0;
+ bool matches = disp_pred(account);
+ bool computed = false;
+ value_t acct_total;
+ value_t result;
+
+ to_show = NULL;
+
+ foreach (accounts_map::value_type pair, account.accounts) {
+ if (! disp_pred(*pair.second))
+ continue;
+
+ call_scope_t args(*pair.second);
+ result = report.get_total_expr(args);
+ if (! computed) {
+ call_scope_t args(account);
+ acct_total = report.get_total_expr(args);
+ computed = true;
+ }
+
+ if ((result != acct_total) || counted > 0) {
+ display = matches;
+ break;
+ }
+ to_show = pair.second;
+ counted++;
+ }
+
+ return display;
+}
+
+bool format_accounts::display_account(account_t& account)
+{
+ // Never display an account that has already been displayed.
+ if (account.has_xdata() &&
+ account.xdata().has_flags(ACCOUNT_EXT_DISPLAYED))
+ return false;
+
+ // At this point, one of two possibilities exists: the account is a
+ // leaf which matches the predicate restrictions; or it is a parent
+ // and two or more children must be subtotaled; or it is a parent
+ // and its child has been hidden by the predicate. So first,
+ // determine if it is a parent that must be displayed regardless of
+ // the predicate.
+
+ account_t * account_to_show = NULL;
+ if (disp_subaccounts_p(account, account_to_show))
+ return true;
+
+ return ! account_to_show && disp_pred(account);
+}
+
+format_equity::format_equity(report_t& _report,
+ const string& _format,
+ const string& display_predicate)
+ : format_accounts(_report, "", display_predicate)
+{
+ const char * f = _format.c_str();
+
+ if (const char * p = std::strstr(f, "%/")) {
+ first_line_format.parse(string(f, 0, p - f));
+ next_lines_format.parse(string(p + 2));
+ } else {
+ first_line_format.parse(_format);
+ next_lines_format.parse(_format);
+ }
+
+ entry_t header_entry;
+ header_entry.payee = "Opening Balances";
+ header_entry._date = current_date;
+ first_line_format.format(*report.output_stream, header_entry);
+}
+
+void format_equity::flush()
+{
+ account_t summary(NULL, "Equity:Opening Balances");
+
+ account_t::xdata_t& xdata(summary.xdata());
+ std::ostream& out(*report.output_stream);
+
+ xdata.value = total.negate();
+
+ if (total.type() >= value_t::BALANCE) {
+ const balance_t * bal;
+ if (total.is_type(value_t::BALANCE))
+ bal = &(total.as_balance());
+ else if (total.is_type(value_t::BALANCE_PAIR))
+ bal = &(total.as_balance_pair().quantity());
+ else
+ assert(false);
+
+ foreach (balance_t::amounts_map::value_type pair, bal->amounts) {
+ xdata.value = pair.second;
+ xdata.value.negate();
+ next_lines_format.format(out, summary);
+ }
+ } else {
+ next_lines_format.format(out, summary);
+ }
+ out.flush();
+}
+
+void format_equity::operator()(account_t& account)
+{
+ std::ostream& out(*report.output_stream);
+
+ if (display_account(account)) {
+ if (account.has_xdata()) {
+ value_t val = account.xdata().value;
+
+ if (val.type() >= value_t::BALANCE) {
+ const balance_t * bal;
+ if (val.is_type(value_t::BALANCE))
+ bal = &(val.as_balance());
+ else if (val.is_type(value_t::BALANCE_PAIR))
+ bal = &(val.as_balance_pair().quantity());
+ else
+ assert(false);
+
+ foreach (balance_t::amounts_map::value_type pair, bal->amounts) {
+ account.xdata().value = pair.second;
+ next_lines_format.format(out, account);
+ }
+ account.xdata().value = val;
+ } else {
+ next_lines_format.format(out, account);
+ }
+ total += val;
+ }
+ account.xdata().add_flags(ACCOUNT_EXT_DISPLAYED);
+ }
+}
+
+} // namespace ledger
diff --git a/src/output.h b/src/output.h
new file mode 100644
index 00000000..885b8bc9
--- /dev/null
+++ b/src/output.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _OUTPUT_H
+#define _OUTPUT_H
+
+#include "report.h"
+#include "handler.h"
+#include "format.h"
+
+namespace ledger {
+
+class format_xacts : public item_handler<xact_t>
+{
+protected:
+ report_t& report;
+ format_t first_line_format;
+ format_t next_lines_format;
+ entry_t * last_entry;
+ xact_t * last_xact;
+
+public:
+ format_xacts(report_t& _report, const string& format);
+ virtual ~format_xacts() {
+ TRACE_DTOR(format_xacts);
+ }
+
+ virtual void flush() {
+ report.output_stream->flush();
+ }
+ virtual void operator()(xact_t& xact);
+};
+
+class format_entries : public format_xacts
+{
+ public:
+ format_entries(report_t& _report, const string& format)
+ : format_xacts(_report, format) {
+ TRACE_CTOR(format_entries, "report_t&, const string&");
+ }
+ virtual ~format_entries() {
+ TRACE_DTOR(format_entries);
+ }
+
+ virtual void format_last_entry();
+
+ virtual void flush() {
+ if (last_entry) {
+ format_last_entry();
+ last_entry = NULL;
+ }
+ format_xacts::flush();
+ }
+ virtual void operator()(xact_t& xact);
+
+private:
+ void print_entry(std::ostream& out,
+ const entry_base_t& entry,
+ const string& prefix = "");
+};
+
+class format_accounts : public item_handler<account_t>
+{
+protected:
+ report_t& report;
+
+ item_predicate<account_t> disp_pred;
+
+ bool disp_subaccounts_p(account_t& account, account_t *& to_show);
+ bool display_account(account_t& account);
+
+public:
+ format_t format;
+
+ format_accounts(report_t& _report,
+ const string& _format,
+ const string& display_predicate = "" /*,
+ const bool print_final_total = true */)
+ : report(_report), disp_pred(display_predicate), format(_format) {
+ TRACE_CTOR(format_accounts, "report&, const string&, const string&");
+ }
+ virtual ~format_accounts() {
+ TRACE_DTOR(format_accounts);
+ }
+
+ virtual void flush();
+
+ virtual void operator()(account_t& account);
+};
+
+class format_equity : public format_accounts
+{
+ format_t first_line_format;
+ format_t next_lines_format;
+
+ mutable value_t total;
+
+ public:
+ format_equity(report_t& _report,
+ const string& _format,
+ const string& display_predicate = "");
+ virtual ~format_equity() {
+ TRACE_DTOR(format_equity);
+ }
+
+ virtual void flush();
+ virtual void operator()(account_t& account);
+};
+
+} // namespace ledger
+
+#endif // _OUTPUT_H
diff --git a/src/parser.cc b/src/parser.cc
new file mode 100644
index 00000000..1aab840e
--- /dev/null
+++ b/src/parser.cc
@@ -0,0 +1,409 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "parser.h"
+
+namespace ledger {
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_value_term(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node;
+
+ token_t& tok = next_token(in, tflags);
+
+ switch (tok.kind) {
+ case token_t::VALUE:
+ node = new op_t(op_t::VALUE);
+ node->set_value(tok.value);
+ break;
+
+ case token_t::MASK:
+ node = new op_t(op_t::MASK);
+ node->set_mask(tok.value.as_string());
+ break;
+
+ case token_t::IDENT: {
+ string ident = tok.value.as_string();
+
+ // An identifier followed by ( represents a function call
+ tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::LPAREN) {
+ node = new op_t(op_t::IDENT);
+ node->set_ident(ident);
+
+ ptr_op_t call_node(new op_t(op_t::O_CALL));
+ call_node->set_left(node);
+ call_node->set_right(parse_value_expr(in, tflags | EXPR_PARSE_PARTIAL));
+
+ tok = next_token(in, tflags);
+ if (tok.kind != token_t::RPAREN)
+ tok.expected(')');
+
+ node = call_node;
+ } else {
+ if (std::isdigit(ident[0])) {
+ node = new op_t(op_t::INDEX);
+ node->set_index(lexical_cast<unsigned int>(ident.c_str()));
+ } else {
+ node = new op_t(op_t::IDENT);
+ node->set_ident(ident);
+ }
+ push_token(tok);
+ }
+ break;
+ }
+
+ case token_t::LPAREN:
+ node = parse_value_expr(in, tflags | EXPR_PARSE_PARTIAL);
+ if (! node)
+ throw_(parse_error, tok.symbol << " operator not followed by expression");
+
+ tok = next_token(in, tflags);
+ if (tok.kind != token_t::RPAREN)
+ tok.expected(')');
+ break;
+
+ default:
+ push_token(tok);
+ break;
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_unary_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node;
+
+ token_t& tok = next_token(in, tflags);
+
+ switch (tok.kind) {
+ case token_t::EXCLAM: {
+ ptr_op_t term(parse_value_term(in, tflags));
+ if (! term)
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+
+ // A very quick optimization
+ if (term->kind == op_t::VALUE) {
+ term->as_value_lval().in_place_negate();
+ node = term;
+ } else {
+ node = new op_t(op_t::O_NOT);
+ node->set_left(term);
+ }
+ break;
+ }
+
+ case token_t::MINUS: {
+ ptr_op_t term(parse_value_term(in, tflags));
+ if (! term)
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+
+ // A very quick optimization
+ if (term->kind == op_t::VALUE) {
+ term->as_value_lval().in_place_negate();
+ node = term;
+ } else {
+ node = new op_t(op_t::O_NEG);
+ node->set_left(term);
+ }
+ break;
+ }
+
+ default:
+ push_token(tok);
+ node = parse_value_term(in, tflags);
+ break;
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_mul_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_unary_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::STAR || tok.kind == token_t::KW_DIV) {
+ ptr_op_t prev(node);
+ node = new op_t(tok.kind == token_t::STAR ?
+ op_t::O_MUL : op_t::O_DIV);
+ node->set_left(prev);
+ node->set_right(parse_mul_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+
+ tok = next_token(in, tflags);
+ }
+ push_token(tok);
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_add_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_mul_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::PLUS ||
+ tok.kind == token_t::MINUS) {
+ ptr_op_t prev(node);
+ node = new op_t(tok.kind == token_t::PLUS ?
+ op_t::O_ADD : op_t::O_SUB);
+ node->set_left(prev);
+ node->set_right(parse_add_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+
+ tok = next_token(in, tflags);
+ }
+ push_token(tok);
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_logic_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_add_expr(in, tflags));
+
+ if (node) {
+ op_t::kind_t kind = op_t::LAST;
+ flags_t _flags = tflags;
+ token_t& tok = next_token(in, tflags);
+
+ switch (tok.kind) {
+ case token_t::EQUAL:
+ if (tflags & EXPR_PARSE_NO_ASSIGN)
+ tok.rewind(in);
+ else
+ kind = op_t::O_EQ;
+ break;
+ case token_t::NEQUAL:
+ kind = op_t::O_NEQ;
+ break;
+ case token_t::MATCH:
+ kind = op_t::O_MATCH;
+ break;
+ case token_t::LESS:
+ kind = op_t::O_LT;
+ break;
+ case token_t::LESSEQ:
+ kind = op_t::O_LTE;
+ break;
+ case token_t::GREATER:
+ kind = op_t::O_GT;
+ break;
+ case token_t::GREATEREQ:
+ kind = op_t::O_GTE;
+ break;
+ default:
+ push_token(tok);
+ break;
+ }
+
+ if (kind != op_t::LAST) {
+ ptr_op_t prev(node);
+ node = new op_t(kind);
+ node->set_left(prev);
+ node->set_right(parse_add_expr(in, _flags));
+
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+ }
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_and_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_logic_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::KW_AND) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_AND);
+ node->set_left(prev);
+ node->set_right(parse_and_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+ } else {
+ push_token(tok);
+ }
+ }
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_or_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_and_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::KW_OR) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_OR);
+ node->set_left(prev);
+ node->set_right(parse_or_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+ } else {
+ push_token(tok);
+ }
+ }
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_querycolon_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_or_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::QUERY) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_AND);
+ node->set_left(prev);
+ node->set_right(parse_or_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+
+ token_t& next_tok = next_token(in, tflags);
+ if (next_tok.kind != token_t::COLON)
+ next_tok.expected(':');
+
+ prev = node;
+ node = new op_t(op_t::O_OR);
+ node->set_left(prev);
+ node->set_right(parse_or_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+ } else {
+ push_token(tok);
+ }
+ }
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse_value_expr(std::istream& in,
+ const flags_t tflags) const
+{
+ ptr_op_t node(parse_querycolon_expr(in, tflags));
+
+ if (node) {
+ token_t& tok = next_token(in, tflags);
+
+ if (tok.kind == token_t::COMMA) {
+ ptr_op_t prev(node);
+ node = new op_t(op_t::O_COMMA);
+ node->set_left(prev);
+ node->set_right(parse_value_expr(in, tflags));
+ if (! node->right())
+ throw_(parse_error,
+ tok.symbol << " operator not followed by argument");
+ tok = next_token(in, tflags);
+ }
+
+ if (tok.kind != token_t::TOK_EOF) {
+ if (tflags & EXPR_PARSE_PARTIAL)
+ push_token(tok);
+ else
+ tok.unexpected();
+ }
+ }
+ else if (! (tflags & EXPR_PARSE_PARTIAL)) {
+ throw_(parse_error, "Failed to parse value expression");
+ }
+
+ return node;
+}
+
+expr_t::ptr_op_t
+expr_t::parser_t::parse(std::istream& in, const flags_t flags)
+{
+ try {
+ ptr_op_t top_node = parse_value_expr(in, flags);
+
+ if (use_lookahead) {
+ use_lookahead = false;
+ lookahead.rewind(in);
+ }
+ lookahead.clear();
+
+ return top_node;
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing value expression:\n");
+#if 0
+ add_error_context(line_context(str, (long)in.tellg() - 1));
+#endif
+ throw err;
+ }
+}
+
+} // namespace ledger
diff --git a/src/parser.h b/src/parser.h
new file mode 100644
index 00000000..294d28ff
--- /dev/null
+++ b/src/parser.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PARSER_H
+#define _PARSER_H
+
+#include "token.h"
+#include "op.h"
+
+namespace ledger {
+
+class expr_t::parser_t : public noncopyable
+{
+#define EXPR_PARSE_NORMAL 0x00
+#define EXPR_PARSE_PARTIAL 0x01
+#define EXPR_PARSE_NO_MIGRATE 0x02
+#define EXPR_PARSE_NO_REDUCE 0x04
+#define EXPR_PARSE_NO_ASSIGN 0x08
+#define EXPR_PARSE_NO_DATES 0x10
+
+public:
+ typedef uint_least8_t flags_t;
+
+private:
+ mutable token_t lookahead;
+ mutable bool use_lookahead;
+
+ token_t& next_token(std::istream& in, flags_t tflags) const
+ {
+ if (use_lookahead)
+ use_lookahead = false;
+ else
+ lookahead.next(in, tflags);
+ return lookahead;
+ }
+
+ void push_token(const token_t& tok) const
+ {
+ assert(&tok == &lookahead);
+ use_lookahead = true;
+ }
+
+ void push_token() const
+ {
+ use_lookahead = true;
+ }
+
+ ptr_op_t parse_value_term(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_unary_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_mul_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_add_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_logic_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_and_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_or_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_querycolon_expr(std::istream& in, const flags_t flags) const;
+ ptr_op_t parse_value_expr(std::istream& in, const flags_t flags) const;
+
+public:
+ parser_t() : use_lookahead(false) {
+ TRACE_CTOR(parser_t, "");
+ }
+ ~parser_t() throw() {
+ TRACE_DTOR(parser_t);
+ }
+
+ ptr_op_t parse(std::istream& in, const flags_t flags = EXPR_PARSE_NORMAL);
+ ptr_op_t parse(string& str, const flags_t flags = EXPR_PARSE_NORMAL) {
+ std::istringstream stream(str);
+ return parse(stream, flags);
+ }
+};
+
+} // namespace ledger
+
+#endif // _PARSER_H
diff --git a/src/predicate.h b/src/predicate.h
new file mode 100644
index 00000000..624d3d65
--- /dev/null
+++ b/src/predicate.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PREDICATE_H
+#define _PREDICATE_H
+
+#include "expr.h"
+#include "scope.h"
+
+namespace ledger {
+
+template <typename T>
+class item_predicate
+{
+public:
+ expr_t predicate;
+
+ item_predicate() {
+ TRACE_CTOR(item_predicate, "");
+ }
+ item_predicate(const item_predicate& other) : predicate(other.predicate) {
+ TRACE_CTOR(item_predicate, "copy");
+ }
+ item_predicate(const expr_t& _predicate) : predicate(_predicate) {
+ TRACE_CTOR(item_predicate, "const expr_t&");
+ }
+ item_predicate(const string& _predicate) : predicate(expr_t(_predicate)) {
+ TRACE_CTOR(item_predicate, "const string&");
+ }
+ ~item_predicate() throw() {
+ TRACE_DTOR(item_predicate);
+ }
+
+ bool operator()(T& item) {
+ return ! predicate || predicate.calc(item).strip_annotations();
+ }
+};
+
+} // namespace ledger
+
+#endif // _PREDICATE_H
diff --git a/src/pushvar.h b/src/pushvar.h
new file mode 100644
index 00000000..a6ec0fab
--- /dev/null
+++ b/src/pushvar.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file pushvar.h
+ * @author John Wiegley
+ * @date Sun May 6 20:10:52 2007
+ *
+ * @brief Adds a facility to C++ for handling "scoped yet global".
+ */
+
+#ifndef _PUSHVAR_H
+#define _PUSHVAR_H
+
+template <typename T>
+class push_variable : public boost::noncopyable
+{
+ push_variable();
+
+public:
+ T& var;
+ T prev;
+ bool enabled;
+
+ explicit push_variable(T& _var)
+ : var(_var), prev(var), enabled(true) {
+ TRACE_CTOR(push_variable, "T&");
+ }
+ explicit push_variable(T& _var, const T& value)
+ : var(_var), prev(var), enabled(true) {
+ TRACE_CTOR(push_variable, "T&, constT&");
+ var = value;
+ }
+ ~push_variable() throw() {
+ TRACE_DTOR(push_variable);
+ if (enabled)
+ var = prev;
+ }
+
+ T& operator*() {
+ return var;
+ }
+ T * operator->() {
+ return &var;
+ }
+
+ void clear() {
+ enabled = false;
+ }
+};
+
+#endif // _PUSHVAR_H
diff --git a/qif.cc b/src/qif.cc
index faad30ca..1c0f01b7 100644
--- a/qif.cc
+++ b/src/qif.cc
@@ -1,18 +1,44 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
#include "journal.h"
#include "qif.h"
-#include "datetime.h"
-#include "error.h"
-#include "util.h"
-
-#include <cstring>
-#include <memory>
+#include "utils.h"
namespace ledger {
#define MAX_LINE 1024
static char line[MAX_LINE + 1];
-static std::string path;
+static path pathname;
static unsigned int src_idx;
static unsigned int linenum;
@@ -38,30 +64,30 @@ bool qif_parser_t::test(std::istream& in) const
std::strcmp(magic, "\r\n!T") == 0);
}
-unsigned int qif_parser_t::parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master,
- const std::string * original_file)
+unsigned int qif_parser_t::parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
{
std::auto_ptr<entry_t> entry;
std::auto_ptr<amount_t> amount;
- transaction_t * xact;
- unsigned int count = 0;
- account_t * misc = NULL;
- commodity_t * def_commodity = NULL;
- bool saw_splits = false;
- bool saw_category = false;
- transaction_t * total = NULL;
+ xact_t * xact;
+ unsigned int count = 0;
+ account_t * misc = NULL;
+ commodity_t * def_commodity = NULL;
+ bool saw_splits = false;
+ bool saw_category = false;
+ xact_t * total = NULL;
entry.reset(new entry_t);
- xact = new transaction_t(master);
- entry->add_transaction(xact);
+ xact = new xact_t(master);
+ entry->add_xact(xact);
- path = journal->sources.back();
- src_idx = journal->sources.size() - 1;
- linenum = 1;
+ pathname = journal.sources.back();
+ src_idx = journal.sources.size() - 1;
+ linenum = 1;
istream_pos_type beg_pos = 0;
unsigned long beg_line = 0;
@@ -80,7 +106,7 @@ unsigned int qif_parser_t::parse(std::istream& in,
case '\t':
if (peek_next_nonws(in) != '\n') {
get_line(in);
- throw new parse_error("Line begins with whitespace");
+ throw parse_error("Line begins with whitespace");
}
// fall through...
@@ -97,14 +123,14 @@ unsigned int qif_parser_t::parse(std::istream& in,
std::strcmp(line, "Type:Cat") == 0 ||
std::strcmp(line, "Type:Class") == 0 ||
std::strcmp(line, "Type:Memorized") == 0)
- throw new parse_error(std::string("QIF files of type ") + line +
- " are not supported.");
+ throw_(parse_error, "QIF files of type " << line << " are not supported.");
break;
case 'D':
SET_BEG_POS_AND_LINE();
get_line(in);
- entry->_date = line;
+ // jww (2008-08-01): Is this just a date?
+ entry->_date = parse_date(line);
break;
case 'T':
@@ -117,7 +143,7 @@ unsigned int qif_parser_t::parse(std::istream& in,
unsigned char prec = xact->amount.commodity().precision();
if (! def_commodity) {
- def_commodity = commodity_t::find_or_create("$");
+ def_commodity = amount_t::current_pool->find_or_create("$");
assert(def_commodity);
}
xact->amount.set_commodity(*def_commodity);
@@ -140,7 +166,7 @@ unsigned int qif_parser_t::parse(std::istream& in,
c = in.peek();
if (c == '*' || c == 'X') {
in.get(c);
- xact->state = transaction_t::CLEARED;
+ xact->state = xact_t::CLEARED;
}
break;
@@ -164,14 +190,14 @@ unsigned int qif_parser_t::parse(std::istream& in,
break;
case 'S':
- xact = new transaction_t(NULL);
- entry->add_transaction(xact);
+ xact = new xact_t(NULL);
+ entry->add_xact(xact);
// fall through...
case 'L': {
int len = std::strlen(line);
if (line[len - 1] == ']')
line[len - 1] = '\0';
- xact->account = journal->find_account(line[0] == '[' ?
+ xact->account = journal.find_account(line[0] == '[' ?
line + 1 : line);
if (c == 'L')
saw_category = true;
@@ -196,7 +222,7 @@ unsigned int qif_parser_t::parse(std::istream& in,
account_t * other;
if (xact->account == master) {
if (! misc)
- misc = journal->find_account("Miscellaneous");
+ misc = journal.find_account("Miscellaneous");
other = misc;
} else {
other = master;
@@ -210,13 +236,13 @@ unsigned int qif_parser_t::parse(std::istream& in,
}
if (! saw_splits) {
- transaction_t * nxact = new transaction_t(other);
+ xact_t * nxact = new xact_t(other);
// The amount doesn't need to be set because the code below
- // will balance this transaction against the other.
- entry->add_transaction(nxact);
+ // will balance this xact against the other.
+ entry->add_xact(nxact);
}
- if (journal->add_entry(entry.get())) {
+ if (journal.add_entry(entry.get())) {
entry->src_idx = src_idx;
entry->beg_pos = beg_pos;
entry->beg_line = beg_line;
@@ -228,8 +254,8 @@ unsigned int qif_parser_t::parse(std::istream& in,
// reset things for the next entry
entry.reset(new entry_t);
- xact = new transaction_t(master);
- entry->add_transaction(xact);
+ xact = new xact_t(master);
+ entry->add_xact(xact);
saw_splits = false;
saw_category = false;
diff --git a/src/qif.h b/src/qif.h
new file mode 100644
index 00000000..018f85bf
--- /dev/null
+++ b/src/qif.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QIF_H
+#define _QIF_H
+
+#include "journal.h"
+
+namespace ledger {
+
+class qif_parser_t : public journal_t::parser_t
+{
+ public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+};
+
+} // namespace ledger
+
+#endif // _QIF_H
diff --git a/src/quotes.cc b/src/quotes.cc
new file mode 100644
index 00000000..ac6cb96e
--- /dev/null
+++ b/src/quotes.cc
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "quotes.h"
+#include "utils.h"
+
+namespace ledger {
+
+#if 0
+void quotes_by_script::operator()(commodity_base_t& commodity,
+ const datetime_t& moment,
+ const datetime_t& date,
+ const datetime_t& last,
+ amount_t& price)
+{
+ DEBUG_CLASS("ledger.quotes.download");
+
+ DEBUG_("commodity: " << commodity.symbol);
+ DEBUG_TIME_(current_moment);
+ DEBUG_TIME_(moment);
+ DEBUG_TIME_(date);
+ DEBUG_TIME_(last);
+ if (commodity.history)
+ DEBUG_TIME_(commodity.history->last_lookup);
+ DEBUG_("pricing_leeway is " << pricing_leeway);
+
+ if ((commodity.history &&
+ (current_moment - commodity.history->last_lookup) < pricing_leeway) ||
+ (current_moment - last) < pricing_leeway ||
+ (price && moment > date && (moment - date) <= pricing_leeway))
+ return;
+
+ using namespace std;
+
+ DEBUG_("downloading quote for symbol " << commodity.symbol);
+
+ char buf[256];
+ buf[0] = '\0';
+
+ bool success = true;
+
+ if (FILE * fp = popen((string("getquote \"") +
+ commodity.symbol + "\"").c_str(), "r")) {
+ if (feof(fp) || ! fgets(buf, 255, fp))
+ success = false;
+ if (pclose(fp) != 0)
+ success = false;
+ } else {
+ success = false;
+ }
+
+ if (success && buf[0]) {
+ char * p = strchr(buf, '\n');
+ if (p) *p = '\0';
+
+ DEBUG_("downloaded quote: " << buf);
+
+ price.parse(buf);
+ commodity.add_price(current_moment, price);
+
+ commodity.history->last_lookup = current_moment;
+ cache_dirty = true;
+
+ if (price && ! price_db.empty()) {
+#if defined(__GNUG__) && __GNUG__ < 3
+ ofstream database(price_db.c_str(), ios::out | ios::app);
+#else
+ ofstream database(price_db.c_str(), ios_base::out | ios_base::app);
+#endif
+ database << "P " << current_moment.to_string("%Y/%m/%d %H:%M:%S")
+ << " " << commodity.symbol << " " << price << endl;
+ }
+ } else {
+ throw_(std::runtime_error,
+ "Failed to download price for '" << commodity.symbol
+ << "' (command: \"getquote " << commodity.symbol << "\")");
+ }
+}
+#endif
+
+} // namespace ledger
diff --git a/src/quotes.h b/src/quotes.h
new file mode 100644
index 00000000..3ff70c68
--- /dev/null
+++ b/src/quotes.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _QUOTES_H
+#define _QUOTES_H
+
+#include "amount.h"
+
+namespace ledger {
+
+#if 0
+class quotes_by_script : public noncopyable, public commodity_t::base_t::updater_t
+{
+ string price_db;
+ unsigned long pricing_leeway;
+ bool& cache_dirty;
+
+ quotes_by_script();
+
+public:
+ quotes_by_script(path _price_db,
+ unsigned long _pricing_leeway,
+ bool& _cache_dirty)
+ : price_db(_price_db), pricing_leeway(_pricing_leeway),
+ cache_dirty(_cache_dirty) {
+ TRACE_CTOR(quotes_by_script, "path, unsigned long, bool&");
+ }
+ ~quotes_by_script() throw() {
+ TRACE_DTOR(quotes_by_script);
+ }
+
+ virtual void operator()(commodity_base_t& commodity,
+ const datetime_t& moment,
+ const datetime_t& date,
+ const datetime_t& last,
+ amount_t& price);
+};
+#endif
+
+} // namespace ledger
+
+#endif // _QUOTES_H
diff --git a/src/reconcile.cc b/src/reconcile.cc
new file mode 100644
index 00000000..aca1732e
--- /dev/null
+++ b/src/reconcile.cc
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "reconcile.h"
+
+namespace ledger {
+
+#define xact_next(x) reinterpret_cast<xact_t *>(x->xdata().ptr)
+#define xact_next_ptr(x) reinterpret_cast<xact_t **>(&x->xdata().ptr)
+
+static bool search_for_balance(amount_t& amount,
+ xact_t ** prev, xact_t * next)
+{
+ for (; next; next = xact_next(next)) {
+ xact_t * temp = *prev;
+ *prev = next;
+
+ amount -= next->amount;
+ if (! amount ||
+ search_for_balance(amount, xact_next_ptr(next), xact_next(next)))
+ return true;
+ amount += next->amount;
+
+ *prev = temp;
+ }
+ return false;
+}
+
+void reconcile_xacts::push_to_handler(xact_t * first)
+{
+ for (; first; first = xact_next(first))
+ item_handler<xact_t>::operator()(*first);
+
+ item_handler<xact_t>::flush();
+}
+
+void reconcile_xacts::flush()
+{
+ value_t cleared_balance;
+ value_t pending_balance;
+
+ xact_t * first = NULL;
+ xact_t ** last_ptr = &first;
+
+ foreach (xact_t * xact, xacts) {
+ if (! is_valid(cutoff) || xact->date() < cutoff) {
+ switch (xact->state) {
+ case xact_t::CLEARED:
+ cleared_balance += xact->amount;
+ break;
+ case xact_t::UNCLEARED:
+ case xact_t::PENDING:
+ pending_balance += xact->amount;
+ *last_ptr = xact;
+ last_ptr = xact_next_ptr(xact);
+ break;
+ }
+ }
+ }
+
+ if (cleared_balance.type() >= value_t::BALANCE)
+ throw std::runtime_error("Cannot reconcile accounts with multiple commodities");
+
+ cleared_balance.cast(value_t::AMOUNT);
+ balance.cast(value_t::AMOUNT);
+
+ commodity_t& cb_comm = cleared_balance.as_amount().commodity();
+ commodity_t& b_comm = balance.as_amount().commodity();
+
+ balance -= cleared_balance;
+ if (balance.type() >= value_t::BALANCE)
+ throw_(std::runtime_error,
+ "Reconcile balance is not of the same commodity ('"
+ << b_comm.symbol() << "' != '" << cb_comm.symbol() << "')");
+
+ // If the amount to reconcile is the same as the pending balance,
+ // then assume an exact match and return the results right away.
+ amount_t& to_reconcile(balance.as_amount_lval());
+ pending_balance.cast(value_t::AMOUNT);
+ if (to_reconcile == pending_balance.as_amount() ||
+ search_for_balance(to_reconcile, &first, first)) {
+ push_to_handler(first);
+ } else {
+ throw std::runtime_error("Could not reconcile account!");
+ }
+}
+
+} // namespace ledger
diff --git a/src/reconcile.h b/src/reconcile.h
new file mode 100644
index 00000000..2e133087
--- /dev/null
+++ b/src/reconcile.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _RECONCILE_H
+#define _RECONCILE_H
+
+#include "value.h"
+#include "iterators.h"
+#include "filters.h"
+
+namespace ledger {
+
+class reconcile_xacts : public item_handler<xact_t>
+{
+ value_t balance;
+ date_t cutoff;
+ xacts_list xacts;
+
+ reconcile_xacts();
+
+public:
+ reconcile_xacts(xact_handler_ptr handler,
+ const value_t& _balance,
+ const date_t& _cutoff)
+ : item_handler<xact_t>(handler),
+ balance(_balance), cutoff(_cutoff) {
+ TRACE_CTOR(reconcile_xacts,
+ "xact_handler_ptr, const value_t&, const date_t&");
+ }
+ virtual ~reconcile_xacts() throw() {
+ TRACE_DTOR(reconcile_xacts);
+ }
+
+ void push_to_handler(xact_t * first);
+
+ virtual void flush();
+ virtual void operator()(xact_t& xact) {
+ xacts.push_back(&xact);
+ }
+};
+
+} // namespace ledger
+
+#endif // _RECONCILE_H
diff --git a/src/report.cc b/src/report.cc
new file mode 100644
index 00000000..92f20d4f
--- /dev/null
+++ b/src/report.cc
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "report.h"
+#include "reconcile.h"
+
+namespace ledger {
+
+#if 0
+void report_t::process_options(const std::string& command,
+ strings_list::iterator arg,
+ strings_list::iterator args_end)
+{
+ // Configure some other options depending on report type
+
+ if (command == "p" || command == "e" || command == "w") {
+ show_related =
+ show_all_related = true;
+ }
+ else if (command == "E") {
+ show_subtotal = true;
+ }
+ else if (show_related) {
+ if (command == "r") {
+ show_inverted = true;
+ } else {
+ show_subtotal = true;
+ show_all_related = true;
+ }
+ }
+
+ if (command != "b" && command != "r")
+ amount_t::keep_base = true;
+
+ // Process remaining command-line arguments
+
+ if (command != "e") {
+ // Treat the remaining command-line arguments as regular
+ // expressions, used for refining report results.
+
+ std::list<std::string>::iterator i = arg;
+ for (; i != args_end; i++)
+ if (*i == "--")
+ break;
+
+ if (i != arg)
+ regexps_to_predicate(command, arg, i, true,
+ (command == "b" && ! show_subtotal &&
+ display_predicate.empty()));
+ if (i != args_end && ++i != args_end)
+ regexps_to_predicate(command, i, args_end);
+ }
+
+ // Setup the default value for the display predicate
+
+ if (display_predicate.empty()) {
+ if (command == "b") {
+ if (! show_empty)
+ display_predicate = "T";
+ if (! show_subtotal) {
+ if (! display_predicate.empty())
+ display_predicate += "&";
+ display_predicate += "l<=1";
+ }
+ }
+ else if (command == "E") {
+ display_predicate = "t";
+ }
+ else if (command == "r" && ! show_empty) {
+ display_predicate = "a";
+ }
+ }
+
+ DEBUG_PRINT("ledger.config.predicates", "Predicate: " << predicate);
+ DEBUG_PRINT("ledger.config.predicates", "Display P: " << display_predicate);
+
+ // Setup the values of %t and %T, used in format strings
+
+ if (! amount_expr.empty())
+ ledger::amount_expr = amount_expr;
+ if (! total_expr.empty())
+ ledger::total_expr = total_expr;
+
+ // Now setup the various formatting strings
+
+ if (! date_output_format.empty())
+ date_t::output_format = date_output_format;
+
+ amount_t::keep_price = keep_price;
+ amount_t::keep_date = keep_date;
+ amount_t::keep_tag = keep_tag;
+
+ if (! report_period.empty() && ! sort_all)
+ entry_sort = true;
+}
+#endif
+
+xact_handler_ptr
+report_t::chain_xact_handlers(xact_handler_ptr base_handler,
+ const bool handle_individual_xacts)
+{
+ bool remember_components = false;
+
+ xact_handler_ptr handler(base_handler);
+
+ // format_xacts write each xact received to the
+ // output stream.
+ if (handle_individual_xacts) {
+ // truncate_entries cuts off a certain number of _entries_ from
+ // being displayed. It does not affect calculation.
+ if (head_entries || tail_entries)
+ handler.reset(new truncate_entries(handler, head_entries, tail_entries));
+
+ // filter_xacts will only pass through xacts
+ // matching the `display_predicate'.
+ if (! display_predicate.empty())
+ handler.reset(new filter_xacts(handler, display_predicate));
+
+ // calc_xacts computes the running total. When this
+ // appears will determine, for example, whether filtered
+ // xacts are included or excluded from the running total.
+ handler.reset(new calc_xacts(handler));
+
+ // component_xacts looks for reported xact that
+ // match the given `descend_expr', and then reports the
+ // xacts which made up the total for that reported
+ // xact.
+ if (! descend_expr.empty()) {
+ std::list<std::string> descend_exprs;
+
+ std::string::size_type beg = 0;
+ for (std::string::size_type pos = descend_expr.find(';');
+ pos != std::string::npos;
+ beg = pos + 1, pos = descend_expr.find(';', beg))
+ descend_exprs.push_back(std::string(descend_expr, beg, pos - beg));
+ descend_exprs.push_back(std::string(descend_expr, beg));
+
+ for (std::list<std::string>::reverse_iterator i =
+ descend_exprs.rbegin();
+ i != descend_exprs.rend();
+ i++)
+ handler.reset(new component_xacts(handler, *i));
+
+ remember_components = true;
+ }
+
+ // reconcile_xacts will pass through only those
+ // xacts which can be reconciled to a given balance
+ // (calculated against the xacts which it receives).
+ if (! reconcile_balance.empty()) {
+ date_t cutoff = current_date;
+ if (! reconcile_date.empty())
+ cutoff = parse_date(reconcile_date);
+ handler.reset(new reconcile_xacts
+ (handler, value_t(reconcile_balance), cutoff));
+ }
+
+ // filter_xacts will only pass through xacts
+ // matching the `secondary_predicate'.
+ if (! secondary_predicate.empty())
+ handler.reset(new filter_xacts(handler, secondary_predicate));
+
+ // sort_xacts will sort all the xacts it sees, based
+ // on the `sort_order' value expression.
+ if (! sort_string.empty()) {
+ if (entry_sort)
+ handler.reset(new sort_entries(handler, sort_string));
+ else
+ handler.reset(new sort_xacts(handler, sort_string));
+ }
+
+ // changed_value_xacts adds virtual xacts to the
+ // list to account for changes in market value of commodities,
+ // which otherwise would affect the running total unpredictably.
+ if (show_revalued)
+ handler.reset(new changed_value_xacts(handler, show_revalued_only));
+
+ // collapse_xacts causes entries with multiple xacts
+ // to appear as entries with a subtotaled xact for each
+ // commodity used.
+ if (show_collapsed)
+ handler.reset(new collapse_xacts(handler));
+
+ // subtotal_xacts combines all the xacts it receives
+ // into one subtotal entry, which has one xact for each
+ // commodity in each account.
+ //
+ // period_xacts is like subtotal_xacts, but it
+ // subtotals according to time periods rather than totalling
+ // everything.
+ //
+ // dow_xacts is like period_xacts, except that it
+ // reports all the xacts that fall on each subsequent day
+ // of the week.
+ if (show_subtotal)
+ handler.reset(new subtotal_xacts(handler, remember_components));
+
+ if (days_of_the_week)
+ handler.reset(new dow_xacts(handler, remember_components));
+ else if (by_payee)
+ handler.reset(new by_payee_xacts(handler, remember_components));
+
+ // interval_xacts groups xacts together based on a
+ // time period, such as weekly or monthly.
+ if (! report_period.empty()) {
+ handler.reset(new interval_xacts(handler, report_period,
+ remember_components));
+ handler.reset(new sort_xacts(handler, "d"));
+ }
+ }
+
+ // invert_xacts inverts the value of the xacts it
+ // receives.
+ if (show_inverted)
+ handler.reset(new invert_xacts(handler));
+
+ // related_xacts will pass along all xacts related
+ // to the xact received. If `show_all_related' is true,
+ // then all the entry's xacts are passed; meaning that if
+ // one xact of an entry is to be printed, all the
+ // xact for that entry will be printed.
+ if (show_related)
+ handler.reset(new related_xacts(handler, show_all_related));
+
+ // This filter_xacts will only pass through xacts
+ // matching the `predicate'.
+ if (! predicate.empty()) {
+ DEBUG("report.predicate",
+ "Report predicate expression = " << predicate);
+ handler.reset(new filter_xacts(handler, predicate));
+ }
+
+#if 0
+ // budget_xacts takes a set of xacts from a data
+ // file and uses them to generate "budget xacts" which
+ // balance against the reported xacts.
+ //
+ // forecast_xacts is a lot like budget_xacts, except
+ // that it adds entries only for the future, and does not balance
+ // them against anything but the future balance.
+
+ if (budget_flags) {
+ budget_xacts * budget_handler
+ = new budget_xacts(handler, budget_flags);
+ budget_handler->add_period_entries(journal->period_entries);
+ handler.reset(budget_handler);
+
+ // Apply this before the budget handler, so that only matching
+ // xacts are calculated toward the budget. The use of
+ // filter_xacts above will further clean the results so
+ // that no automated xacts that don't match the filter get
+ // reported.
+ if (! predicate.empty())
+ handler.reset(new filter_xacts(handler, predicate));
+ }
+ else if (! forecast_limit.empty()) {
+ forecast_xacts * forecast_handler
+ = new forecast_xacts(handler, forecast_limit);
+ forecast_handler->add_period_entries(journal->period_entries);
+ handler.reset(forecast_handler);
+
+ // See above, under budget_xacts.
+ if (! predicate.empty())
+ handler.reset(new filter_xacts(handler, predicate));
+ }
+#endif
+
+ if (comm_as_payee)
+ handler.reset(new set_comm_as_payee(handler));
+ else if (code_as_payee)
+ handler.reset(new set_code_as_payee(handler));
+
+ return handler;
+}
+
+void report_t::xacts_report(xact_handler_ptr handler)
+{
+ session_xacts_iterator walker(session);
+ pass_down_xacts(chain_xact_handlers(handler), walker);
+ handler->flush();
+
+ if (DO_VERIFY())
+ session.clean_xacts();
+}
+
+void report_t::entry_report(xact_handler_ptr handler, entry_t& entry)
+{
+ entry_xacts_iterator walker(entry);
+ pass_down_xacts(chain_xact_handlers(handler), walker);
+ handler->flush();
+
+ if (DO_VERIFY())
+ session.clean_xacts(entry);
+}
+
+void report_t::sum_all_accounts()
+{
+ session_xacts_iterator walker(session);
+ pass_down_xacts
+ (chain_xact_handlers(xact_handler_ptr(new set_account_value), false),
+ walker);
+ // no flush() needed with set_account_value
+ session.master->calculate_sums();
+}
+
+void report_t::accounts_report(acct_handler_ptr handler)
+{
+ sum_all_accounts();
+
+ if (sort_string.empty()) {
+ basic_accounts_iterator walker(*session.master);
+ pass_down_accounts(handler, walker);
+ } else {
+ sorted_accounts_iterator walker(*session.master, sort_string);
+ pass_down_accounts(handler, walker);
+ }
+ handler->flush();
+
+ if (DO_VERIFY()) {
+ session.clean_xacts();
+ session.clean_accounts();
+ }
+}
+
+void report_t::commodities_report(const string& format)
+{
+}
+
+value_t report_t::get_amount_expr(call_scope_t& scope)
+{
+ return amount_expr.calc(scope);
+}
+
+value_t report_t::get_total_expr(call_scope_t& scope)
+{
+ return total_expr.calc(scope);
+}
+
+expr_t::ptr_op_t report_t::lookup(const string& name)
+{
+ const char * p = name.c_str();
+ switch (*p) {
+ case 'f':
+ if (std::strncmp(p, "fmt_", 4) == 0) {
+ p = p + 4;
+ switch (*p) {
+ case 't':
+ return MAKE_FUNCTOR(report_t::get_amount_expr);
+ case 'T':
+ return MAKE_FUNCTOR(report_t::get_total_expr);
+ }
+ }
+ break;
+
+ case 'o':
+ if (std::strncmp(p, "opt_", 4) == 0) {
+ p = p + 4;
+ switch (*p) {
+ case 'a':
+ if (std::strcmp(p, "amount_") == 0)
+ return MAKE_FUNCTOR(report_t::option_amount_);
+ break;
+
+ case 'f':
+ if (std::strcmp(p, "F_") == 0 ||
+ std::strcmp(p, "format_") == 0)
+ return MAKE_FUNCTOR(report_t::option_format_);
+ break;
+
+ case 'j':
+ if (! (*p + 1))
+ return MAKE_FUNCTOR(report_t::option_amount_data);
+ break;
+
+ case 'J':
+ if (! (*p + 1))
+ return MAKE_FUNCTOR(report_t::option_total_data);
+ break;
+
+ case 'l':
+ if (std::strcmp(p, "l_") || std::strcmp(p, "limit_"))
+ return MAKE_FUNCTOR(report_t::option_limit_);
+ break;
+
+ case 't':
+ if (std::strcmp(p, "t_"))
+ return MAKE_FUNCTOR(report_t::option_amount_);
+ else if (std::strcmp(p, "total_") == 0)
+ return MAKE_FUNCTOR(report_t::option_total_);
+ break;
+
+ case 'T':
+ if (std::strcmp(p, "T_"))
+ return MAKE_FUNCTOR(report_t::option_total_);
+ break;
+ }
+ }
+ break;
+ }
+
+ return session.lookup(name);
+}
+
+} // namespace ledger
diff --git a/src/report.h b/src/report.h
new file mode 100644
index 00000000..98b60ce1
--- /dev/null
+++ b/src/report.h
@@ -0,0 +1,757 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _REPORT_H
+#define _REPORT_H
+
+#include "session.h"
+#include "handler.h"
+
+namespace ledger {
+
+// These are the elements of any report:
+//
+// 1. Formatting string used for outputting the underlying ReportedType.
+//
+// 2. Handler object for the ReportedType. This is constructed using #1, or
+// else #1 is ignored completely. This handler object is also constructed
+// with the output stream that will be used during formatting.
+//
+// --- The details of #1 and #2 together represent the ItemHandler.
+//
+// 3. Mode of the report. Currently there are four modes:
+//
+// a. Transaction or commodity iteration. In this mode, all the journal's
+// entries, the transactions of a specific entry, or all the journal's
+// commodities are walked. In the first two cases, it's the underlying
+// transactions which are passed to #2; in the second case, each
+// commodity is passed to #2.
+//
+// b. Account iteration. This employs step 'a', but add a prologue and
+// epilogue to it. In the prologue it "sums" all account totals and
+// subtotals; in the epilogue it calls yet another handler whose job is
+// reporting (the handler used in 'a' is only for calculation).
+//
+// There is one variation on 'b' in which a "totals" line is also
+// displayed.
+//
+// c. Write journal. In this mode, a single function is called that output
+// the journal object as a textual file. #2 is used to print out each
+// transaction in the journal.
+//
+// d. Dump binary file. This is just like 'c', except that it dumps out a
+// binary file and #2 is completely ignored.
+//
+// 4. For 'a' and 'b' in #3, there is a different iteration function called,
+// depending on whether we're iterating:
+//
+// a. The transactions of an entry: walk_transactions.
+// b. The entries of a journal: walk_entries.
+// c. The commodities of a journal: walk_commodities.
+//
+// 5. Finally, for the 'a' and 'b' reporting modes, there is a variant which
+// says that the formatter should be "flushed" after the entities are
+// iterated. This does not happen for the commodities iteration, however.
+
+class report_t : public noncopyable, public scope_t
+{
+ report_t();
+
+public:
+ optional<path> output_file;
+ std::ostream * output_stream;
+
+ string format_string;
+ string date_output_format;
+ string predicate;
+ string secondary_predicate;
+ string display_predicate;
+ string report_period;
+ string report_period_sort;
+ string sort_string;
+ string descend_expr;
+ string forecast_limit;
+ string reconcile_balance;
+ string reconcile_date;
+
+ expr_t amount_expr;
+ expr_t total_expr;
+
+ unsigned long budget_flags;
+
+ int head_entries;
+ int tail_entries;
+
+ bool show_collapsed;
+ bool show_subtotal;
+ bool show_totals;
+ bool show_related;
+ bool show_all_related;
+ bool show_inverted;
+ bool show_empty;
+ bool days_of_the_week;
+ bool by_payee;
+ bool comm_as_payee;
+ bool code_as_payee;
+ bool show_revalued;
+ bool show_revalued_only;
+ bool keep_price;
+ bool keep_date;
+ bool keep_tag;
+ bool entry_sort;
+ bool sort_all;
+
+ string account;
+ optional<path> pager;
+
+ bool raw_mode;
+
+ session_t& session;
+
+ explicit report_t(session_t& _session)
+ : amount_expr("amount"),
+ total_expr("total"),
+
+ head_entries(0),
+ tail_entries(0),
+
+ show_collapsed(false),
+ show_subtotal(false),
+ show_totals(false),
+ show_related(false),
+ show_all_related(false),
+ show_inverted(false),
+ show_empty(false),
+ days_of_the_week(false),
+ by_payee(false),
+ comm_as_payee(false),
+ code_as_payee(false),
+ show_revalued(false),
+ show_revalued_only(false),
+ keep_price(false),
+ keep_date(false),
+ keep_tag(false),
+ entry_sort(false),
+ sort_all(false),
+
+ raw_mode(false),
+
+ session(_session)
+ {
+ TRACE_CTOR(report_t, "session_t&");
+ }
+
+ virtual ~report_t() {
+ TRACE_DTOR(report_t);
+ }
+
+ //
+ // Actual report generation; this is why we're here...
+ //
+
+ void xacts_report(xact_handler_ptr handler);
+ void entry_report(xact_handler_ptr handler, entry_t& entry);
+ void sum_all_accounts();
+ void accounts_report(acct_handler_ptr handler);
+ void commodities_report(const string& format);
+
+ xact_handler_ptr
+ chain_xact_handlers(xact_handler_ptr handler,
+ const bool handle_individual_transactions = true);
+
+#if 0
+ //////////////////////////////////////////////////////////////////////
+ //
+ // Basic options
+
+ value_t option_full_help(call_scope_t& args) { // H
+ option_full_help(std::cout);
+ throw 0;
+ }
+
+ value_t option_help(call_scope_t& args) { // h
+ option_help(std::cout);
+ throw 0;
+ }
+
+ value_t option_help_calc(call_scope_t& args) {
+ option_calc_help(std::cout);
+ throw 0;
+ }
+
+ value_t option_help_disp(call_scope_t& args) {
+ option_disp_help(std::cout);
+ throw 0;
+ }
+
+ value_t option_help_comm(call_scope_t& args) {
+ option_comm_help(std::cout);
+ throw 0;
+ }
+
+ value_t option_version(call_scope_t& args) { // v
+ show_version(std::cout);
+ throw 0;
+ }
+
+ value_t option_init_file(call_scope_t& args) { // i:
+ std::string path = resolve_path(optarg);
+ if (access(path.c_str(), R_OK) != -1)
+ config->init_file = path;
+ else
+ throw_(std::invalid_argument,
+ "The init file '" << path << "' does not exist or is not readable");
+ }
+
+ value_t option_file(call_scope_t& args) { // f:
+ if (std::string(optarg) == "-") {
+ config->data_file = optarg;
+ } else {
+ std::string path = resolve_path(optarg);
+ if (access(path.c_str(), R_OK) != -1)
+ config->data_file = path;
+ else
+ throw_(std::invalid_argument,
+ "The ledger file '" << path << "' does not exist or is not readable");
+ }
+ }
+
+ value_t option_cache(call_scope_t& args) { // :
+ config->cache_file = resolve_path(optarg);
+ }
+
+ value_t option_no_cache(call_scope_t& args) {
+ config->cache_file = "<none>";
+ }
+
+ value_t option_output(call_scope_t& args) { // o:
+ if (std::string(optarg) != "-") {
+ std::string path = resolve_path(optarg);
+ report->output_file = path;
+ }
+ }
+
+ value_t option_account(call_scope_t& args) { // a:
+ config->account = optarg;
+ }
+
+ value_t option_debug(call_scope_t& args) { // :
+ config->debug_mode = true;
+ ::setenv("DEBUG_CLASS", optarg, 1);
+ }
+
+ value_t option_verbose(call_scope_t& args) {
+ config->verbose_mode = true;
+ }
+
+ value_t option_trace(call_scope_t& args) {
+ config->trace_mode = true;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ //
+ // Report filtering
+
+ value_t option_effective(call_scope_t& args) {
+ xact_t::use_effective_date = true;
+ }
+
+ value_t option_begin(call_scope_t& args) { // b:
+ char buf[128];
+ interval_t interval(optarg);
+ if (! interval.begin)
+ throw_(std::invalid_argument,
+ "Could not determine beginning of period '" << optarg << "'");
+
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "d>=[";
+ report->predicate += interval.begin.to_string();
+ report->predicate += "]";
+ }
+
+ value_t option_end(call_scope_t& args) { // e:
+ char buf[128];
+ interval_t interval(optarg);
+ if (! interval.begin)
+ throw_(std::invalid_argument,
+ "Could not determine end of period '" << optarg << "'");
+
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "d<[";
+ report->predicate += interval.begin.to_string();
+ report->predicate += "]";
+
+ terminus = interval.begin;
+ }
+
+ value_t option_current(call_scope_t& args) { // c
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "d<=m";
+ }
+
+ value_t option_cleared(call_scope_t& args) { // C
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "X";
+ }
+
+ value_t option_uncleared(call_scope_t& args) { // U
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "!X";
+ }
+
+ value_t option_real(call_scope_t& args) { // R
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "R";
+ }
+
+ value_t option_actual(call_scope_t& args) { // L
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "L";
+ }
+
+ value_t option_lots(call_scope_t& args) {
+ report->keep_price =
+ report->keep_date =
+ report->keep_tag = true;
+ }
+
+ value_t option_lot_prices(call_scope_t& args) {
+ report->keep_price = true;
+ }
+
+ value_t option_lot_dates(call_scope_t& args) {
+ report->keep_date = true;
+ }
+
+ value_t option_lot_tags(call_scope_t& args) {
+ report->keep_tag = true;
+ }
+#endif
+
+ //////////////////////////////////////////////////////////////////////
+ //
+ // Output customization
+
+ value_t option_format_(call_scope_t& args) { // F:
+ format_string = args[0].as_string();
+ return true;
+ }
+
+#if 0
+ value_t option_date_format(call_scope_t& args) { // y:
+ report->date_output_format = optarg;
+ }
+
+ value_t option_input_date_format(call_scope_t& args) { // :
+ config->date_input_format = optarg;
+ }
+
+ value_t option_balance_format(call_scope_t& args) { // :
+ config->balance_format = optarg;
+ }
+
+ value_t option_register_format(call_scope_t& args) { // :
+ config->register_format = optarg;
+ }
+
+ value_t option_wide_register_format(call_scope_t& args) { // :
+ config->wide_register_format = optarg;
+ }
+
+ value_t option_plot_amount_format(call_scope_t& args) { // :
+ config->plot_amount_format = optarg;
+ }
+
+ value_t option_plot_total_format(call_scope_t& args) { // :
+ config->plot_total_format = optarg;
+ }
+
+ value_t option_print_format(call_scope_t& args) { // :
+ config->print_format = optarg;
+ }
+
+ value_t option_write_hdr_format(call_scope_t& args) { // :
+ config->write_hdr_format = optarg;
+ }
+
+ value_t option_write_xact_format(call_scope_t& args) { // :
+ config->write_xact_format = optarg;
+ }
+
+ value_t option_equity_format(call_scope_t& args) { // :
+ config->equity_format = optarg;
+ }
+
+ value_t option_prices_format(call_scope_t& args) { // :
+ config->prices_format = optarg;
+ }
+
+ value_t option_wide(call_scope_t& args) { // w
+ config->register_format = config->wide_register_format;
+ }
+
+ value_t option_head(call_scope_t& args) { // :
+ report->head_entries = std::atoi(optarg);
+ }
+
+ value_t option_tail(call_scope_t& args) { // :
+ report->tail_entries = std::atoi(optarg);
+ }
+
+ value_t option_pager(call_scope_t& args) { // :
+ config->pager = optarg;
+ }
+
+ value_t option_truncate(call_scope_t& args) { // :
+ std::string style(optarg);
+ if (style == "leading")
+ format_t::elision_style = format_t::TRUNCATE_LEADING;
+ else if (style == "middle")
+ format_t::elision_style = format_t::TRUNCATE_MIDDLE;
+ else if (style == "trailing")
+ format_t::elision_style = format_t::TRUNCATE_TRAILING;
+ else if (style == "abbrev")
+ format_t::elision_style = format_t::ABBREVIATE;
+ }
+
+ value_t option_abbrev_len(call_scope_t& args) { // :
+ format_t::abbrev_length = std::atoi(optarg);
+ }
+
+ value_t option_empty(call_scope_t& args) { // E
+ report->show_empty = true;
+ }
+
+ value_t option_collapse(call_scope_t& args) { // n
+ report->show_collapsed = true;
+ }
+
+ value_t option_subtotal(call_scope_t& args) { // s
+ report->show_subtotal = true;
+ }
+
+ value_t option_totals(call_scope_t& args) {
+ report->show_totals = true;
+ }
+
+ value_t option_sort(call_scope_t& args) { // S:
+ report->sort_string = optarg;
+ }
+
+ value_t option_sort_entries(call_scope_t& args) {
+ report->sort_string = optarg;
+ report->entry_sort = true;
+ }
+
+ value_t option_sort_all(call_scope_t& args) {
+ report->sort_string = optarg;
+ report->entry_sort = false;
+ report->sort_all = true;
+ }
+
+ value_t option_period_sort(call_scope_t& args) { // :
+ report->sort_string = optarg;
+ report->entry_sort = true;
+ }
+
+ value_t option_related(call_scope_t& args) { // r
+ report->show_related = true;
+ }
+
+ value_t option_descend(call_scope_t& args) {
+ std::string arg(optarg);
+ std::string::size_type beg = 0;
+ report->descend_expr = "";
+ for (std::string::size_type pos = arg.find(';');
+ pos != std::string::npos;
+ beg = pos + 1, pos = arg.find(';', beg))
+ report->descend_expr += (std::string("t=={") +
+ std::string(arg, beg, pos - beg) + "};");
+ report->descend_expr += (std::string("t=={") +
+ std::string(arg, beg) + "}");
+ }
+
+ value_t option_descend_if(call_scope_t& args) {
+ report->descend_expr = optarg;
+ }
+
+ value_t option_period(call_scope_t& args) { // p:
+ if (report->report_period.empty()) {
+ report->report_period = optarg;
+ } else {
+ report->report_period += " ";
+ report->report_period += optarg;
+ }
+
+ // If the period gives a beginning and/or ending date, make sure to
+ // modify the calculation predicate (via the --begin and --end
+ // options) to take this into account.
+
+ interval_t interval(report->report_period);
+
+ if (interval.begin) {
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "d>=[";
+ report->predicate += interval.begin.to_string();
+ report->predicate += "]";
+ }
+
+ if (interval.end) {
+ if (! report->predicate.empty())
+ report->predicate += "&";
+ report->predicate += "d<[";
+ report->predicate += interval.end.to_string();
+ report->predicate += "]";
+
+ terminus = interval.end;
+ }
+ }
+
+ value_t option_daily(call_scope_t& args) {
+ if (report->report_period.empty())
+ report->report_period = "daily";
+ else
+ report->report_period = std::string("daily ") + report->report_period;
+ }
+
+ value_t option_weekly(call_scope_t& args) { // W
+ if (report->report_period.empty())
+ report->report_period = "weekly";
+ else
+ report->report_period = std::string("weekly ") + report->report_period;
+ }
+
+ value_t option_monthly(call_scope_t& args) { // M
+ if (report->report_period.empty())
+ report->report_period = "monthly";
+ else
+ report->report_period = std::string("monthly ") + report->report_period;
+ }
+
+ value_t option_quarterly(call_scope_t& args) {
+ if (report->report_period.empty())
+ report->report_period = "quarterly";
+ else
+ report->report_period = std::string("quarterly ") + report->report_period;
+ }
+
+ value_t option_yearly(call_scope_t& args) { // Y
+ if (report->report_period.empty())
+ report->report_period = "yearly";
+ else
+ report->report_period = std::string("yearly ") + report->report_period;
+ }
+
+ value_t option_dow(call_scope_t& args) {
+ report->days_of_the_week = true;
+ }
+
+ value_t option_by_payee(call_scope_t& args) { // P
+ report->by_payee = true;
+ }
+
+ value_t option_comm_as_payee(call_scope_t& args) { // x
+ report->comm_as_payee = true;
+ }
+
+ value_t option_code_as_payee(call_scope_t& args) {
+ report->code_as_payee = true;
+ }
+
+ value_t option_budget(call_scope_t& args) {
+ report->budget_flags = BUDGET_BUDGETED;
+ }
+
+ value_t option_add_budget(call_scope_t& args) {
+ report->budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED;
+ }
+
+ value_t option_unbudgeted(call_scope_t& args) {
+ report->budget_flags = BUDGET_UNBUDGETED;
+ }
+
+ value_t option_forecast(call_scope_t& args) { // :
+ report->forecast_limit = optarg;
+ }
+
+ value_t option_reconcile(call_scope_t& args) { // :
+ report->reconcile_balance = optarg;
+ }
+
+ value_t option_reconcile_date(call_scope_t& args) { // :
+ report->reconcile_date = optarg;
+ }
+#endif
+
+ value_t option_limit_(call_scope_t& args) { // l:
+ if (! predicate.empty())
+ predicate += "&";
+ predicate += args[0].as_string();
+ return true;
+ }
+
+#if 0
+ value_t option_only(call_scope_t& args) { // :
+ if (! report->secondary_predicate.empty())
+ report->secondary_predicate += "&";
+ report->secondary_predicate += "(";
+ report->secondary_predicate += optarg;
+ report->secondary_predicate += ")";
+ }
+
+ value_t option_display(call_scope_t& args) { // d:
+ if (! report->display_predicate.empty())
+ report->display_predicate += "&";
+ report->display_predicate += "(";
+ report->display_predicate += optarg;
+ report->display_predicate += ")";
+ }
+#endif
+
+ value_t option_amount_(call_scope_t& args) { // t:
+ amount_expr = args[0].as_string();
+ return true;
+ }
+
+ value_t option_total_(call_scope_t& args) { // T:
+ total_expr = args[0].as_string();
+ return true;
+ }
+
+ value_t get_amount_expr(call_scope_t& scope);
+ value_t get_total_expr(call_scope_t& scope);
+
+ value_t option_amount_data(call_scope_t&) { // j
+ format_string = session.plot_amount_format;
+ return true;
+ }
+
+ value_t option_total_data(call_scope_t&) { // J
+ format_string = session.plot_total_format;
+ return true;
+ }
+
+#if 0
+ value_t option_ansi(call_scope_t& args) {
+ format_t::ansi_codes = true;
+ format_t::ansi_invert = false;
+ }
+
+ value_t option_ansi_invert(call_scope_t& args) {
+ format_t::ansi_codes =
+ format_t::ansi_invert = true;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ //
+ // Commodity reporting
+
+ value_t option_base(call_scope_t& args) { // :
+ amount_t::keep_base = true;
+ }
+
+ value_t option_price_db(call_scope_t& args) { // :
+ config->price_db = optarg;
+ }
+
+ value_t option_price_exp(call_scope_t& args) { // Z:
+ config->pricing_leeway = std::atol(optarg) * 60;
+ }
+
+ value_t option_download(call_scope_t& args) { // Q
+ config->download_quotes = true;
+ }
+
+ value_t option_quantity(call_scope_t& args) { // O
+ ledger::amount_expr = "amount";
+ ledger::total_expr = "total";
+ }
+
+ value_t option_basis(call_scope_t& args) { // B
+ ledger::amount_expr = "b";
+ ledger::total_expr = "B";
+ }
+
+ value_t option_price(call_scope_t& args) { // I
+ ledger::amount_expr = "i";
+ ledger::total_expr = "I";
+ }
+
+ value_t option_market(call_scope_t& args) { // V
+ report->show_revalued = true;
+
+ ledger::amount_expr = "v";
+ ledger::total_expr = "V";
+ }
+
+#if 0
+ namespace {
+ void parse_price_setting(const char * optarg)
+ {
+ char * equals = std::strchr(optarg, '=');
+ if (! equals)
+ return;
+
+ while (std::isspace(*optarg))
+ optarg++;
+ while (equals > optarg && std::isspace(*(equals - 1)))
+ equals--;
+
+ std::string symbol(optarg, 0, equals - optarg);
+ amount_t price(equals + 1);
+
+ if (commodity_t * commodity = commodity_t::find_or_create(symbol)) {
+ commodity->add_price(datetime_t::now, price);
+ commodity->history()->bogus_time = datetime_t::now;
+ }
+ }
+ }
+#endif
+#endif
+
+ //
+ // Scope members
+ //
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+};
+
+} // namespace ledger
+
+#endif // _REPORT_H
diff --git a/src/scope.cc b/src/scope.cc
new file mode 100644
index 00000000..7a349949
--- /dev/null
+++ b/src/scope.cc
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "scope.h"
+
+namespace ledger {
+
+void symbol_scope_t::define(const string& name, expr_t::ptr_op_t def)
+{
+ DEBUG("ledger.xpath.syms", "Defining '" << name << "' = " << def);
+
+ std::pair<symbol_map::iterator, bool> result
+ = symbols.insert(symbol_map::value_type(name, def));
+ if (! result.second) {
+ symbol_map::iterator i = symbols.find(name);
+ assert(i != symbols.end());
+ symbols.erase(i);
+
+ std::pair<symbol_map::iterator, bool> result2
+ = symbols.insert(symbol_map::value_type(name, def));
+ if (! result2.second)
+ throw_(compile_error,
+ "Redefinition of '" << name << "' in same scope");
+ }
+}
+
+expr_t::ptr_op_t symbol_scope_t::lookup(const string& name)
+{
+ symbol_map::const_iterator i = symbols.find(name);
+ if (i != symbols.end())
+ return (*i).second;
+
+ return child_scope_t::lookup(name);
+}
+
+#if 0
+namespace {
+ int count_leaves(expr_t::ptr_op_t expr)
+ {
+ int count = 0;
+ if (expr->kind != expr_t::op_t::O_COMMA) {
+ count = 1;
+ } else {
+ count += count_leaves(expr->left());
+ count += count_leaves(expr->right());
+ }
+ return count;
+ }
+
+ expr_t::ptr_op_t reduce_leaves(expr_t::ptr_op_t expr,
+ expr_t::ptr_op_t context)
+ {
+ if (! expr)
+ return NULL;
+
+ expr_t::ptr_op_t temp;
+
+ if (expr->kind != expr_t::op_t::O_COMMA) {
+ if (expr->kind < expr_t::op_t::TERMINALS) {
+ temp.reset(expr);
+ } else {
+ temp.reset(new op_t(expr_t::op_t::VALUE));
+ temp->set_value(NULL_VALUE);
+ expr->compute(temp->as_value_lval(), context);
+ }
+ } else {
+ temp.reset(new op_t(expr_t::op_t::O_COMMA));
+ temp->set_left(reduce_leaves(expr->left(), context));
+ temp->set_right(reduce_leaves(expr->right(), context));
+ }
+ return temp.release();
+ }
+
+ expr_t::ptr_op_t find_leaf(expr_t::ptr_op_t context, int goal, long& found)
+ {
+ if (! context)
+ return NULL;
+
+ if (context->kind != expr_t::op_t::O_COMMA) {
+ if (goal == found++)
+ return context;
+ } else {
+ expr_t::ptr_op_t expr = find_leaf(context->left(), goal, found);
+ if (expr)
+ return expr;
+ expr = find_leaf(context->right(), goal, found);
+ if (expr)
+ return expr;
+ }
+ return NULL;
+ }
+}
+#endif
+
+} // namespace ledger
diff --git a/src/scope.h b/src/scope.h
new file mode 100644
index 00000000..88b45d84
--- /dev/null
+++ b/src/scope.h
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SCOPE_H
+#define _SCOPE_H
+
+#include "expr.h"
+#include "op.h"
+
+namespace ledger {
+
+class scope_t
+{
+public:
+ explicit scope_t() {
+ TRACE_CTOR(scope_t, "");
+ }
+ virtual ~scope_t() {
+ TRACE_DTOR(scope_t);
+ }
+
+ virtual expr_t::ptr_op_t lookup(const string& name) = 0;
+
+ value_t resolve(const string& name) {
+ expr_t::ptr_op_t definition = lookup(name);
+ if (definition)
+ return definition->calc(*this);
+ else
+ return NULL_VALUE;
+ }
+
+ virtual optional<scope_t&> find_scope(const std::type_info&, bool = true) {
+ return none;
+ }
+};
+
+template <typename T>
+inline T& find_scope(scope_t& scope, bool skip_this = true) {
+ optional<scope_t&> found = scope.find_scope(typeid(T), skip_this);
+ assert(found);
+ return static_cast<T&>(*found);
+}
+
+template <typename T>
+inline optional<T&> maybe_find_scope(scope_t& scope, bool skip_this = true) {
+ optional<scope_t&> found = scope.find_scope(typeid(T), skip_this);
+ if (found)
+ return optional<T&>(static_cast<T&>(*found));
+ else
+ return none;
+}
+
+class child_scope_t : public noncopyable, public scope_t
+{
+public:
+ scope_t * parent;
+
+ explicit child_scope_t() : parent(NULL) {
+ TRACE_CTOR(child_scope_t, "");
+ }
+ explicit child_scope_t(scope_t& _parent)
+ : parent(&_parent) {
+ TRACE_CTOR(child_scope_t, "scope_t&");
+ }
+ virtual ~child_scope_t() {
+ TRACE_DTOR(child_scope_t);
+ }
+
+ virtual expr_t::ptr_op_t lookup(const string& name) {
+ if (parent)
+ return parent->lookup(name);
+ return expr_t::ptr_op_t();
+ }
+
+ virtual optional<scope_t&> find_scope(const std::type_info& type,
+ bool skip_this = true) {
+ for (scope_t * ptr = (skip_this ? parent : this); ptr; ) {
+ if (typeid(*ptr) == type)
+ return *ptr;
+ if (child_scope_t * scope = dynamic_cast<child_scope_t *>(ptr))
+ ptr = scope->parent;
+ else
+ ptr = NULL;
+ }
+ return none;
+ }
+};
+
+class symbol_scope_t : public child_scope_t
+{
+ typedef std::map<const string, expr_t::ptr_op_t> symbol_map;
+
+ symbol_map symbols;
+
+public:
+ explicit symbol_scope_t() {
+ TRACE_CTOR(symbol_scope_t, "");
+ }
+ explicit symbol_scope_t(scope_t& _parent) : child_scope_t(_parent) {
+ TRACE_CTOR(symbol_scope_t, "scope_t&");
+ }
+ virtual ~symbol_scope_t() {
+ TRACE_DTOR(symbol_scope_t);
+ }
+
+ void define(const string& name, const value_t& val) {
+ define(name, expr_t::op_t::wrap_value(val));
+ }
+ void define(const string& name, const function_t& func) {
+ define(name, expr_t::op_t::wrap_functor(func));
+ }
+ virtual void define(const string& name, expr_t::ptr_op_t def);
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+};
+
+class call_scope_t : public child_scope_t
+{
+ value_t args;
+
+ call_scope_t();
+
+public:
+ explicit call_scope_t(scope_t& _parent) : child_scope_t(_parent) {
+ TRACE_CTOR(call_scope_t, "scope_t&");
+ }
+ virtual ~call_scope_t() {
+ TRACE_DTOR(call_scope_t);
+ }
+
+ void set_args(const value_t& _args) {
+ args = _args;
+ }
+ value_t& value() {
+ return args;
+ }
+
+ value_t& operator[](const unsigned int index) {
+ // jww (2008-07-21): exception here if it's out of bounds
+ return args[index];
+ }
+ const value_t& operator[](const unsigned int index) const {
+ // jww (2008-07-21): exception here if it's out of bounds
+ return args[index];
+ }
+
+ void push_back(const value_t& val) {
+ args.push_back(val);
+ }
+ void pop_back() {
+ args.pop_back();
+ }
+
+ const std::size_t size() const {
+ return args.size();
+ }
+};
+
+template <typename T>
+class ptr_t : public noncopyable
+{
+ T * value;
+
+ ptr_t();
+
+public:
+ ptr_t(scope_t& scope, const string& name)
+ : value(scope.resolve(name).template as_pointer<T>()) {
+ TRACE_CTOR(ptr_t, "scope_t&, const string&");
+ }
+ ptr_t(call_scope_t& scope, const unsigned int idx)
+ : value(scope[idx].template as_pointer<T>()) {
+ TRACE_CTOR(ptr_t, "call_scope_t&, const unsigned int");
+ }
+ ~ptr_t() throw() {
+ TRACE_DTOR(ptr_t);
+ }
+
+ T& operator *() { return *value; }
+ T * operator->() { return value; }
+};
+
+template <typename T>
+class var_t : public noncopyable
+{
+ optional<value_t> value;
+
+ var_t();
+
+public:
+ var_t(scope_t& scope, const string& name)
+ {
+ TRACE_CTOR(var_t, "scope_t&, const string&");
+
+ try {
+ value = scope.resolve(name);
+ }
+ catch (...) {
+ DEBUG("scope.var_t", "Failed lookup var_t(\"" << name << "\")");
+ value = none;
+ }
+ }
+
+ var_t(call_scope_t& scope, const unsigned int idx)
+ {
+ TRACE_CTOR(var_t, "call_scope_t&, const unsigned int");
+
+ if (idx < scope.size())
+ value = scope[idx];
+ else
+ value = none;
+ }
+
+ ~var_t() throw() {
+ TRACE_DTOR(var_t);
+ }
+
+ operator bool() { return value; }
+
+ T& operator *();
+ const T& operator *() const;
+
+ T * operator->() {
+ return &**this;
+ }
+ const T * operator->() const {
+ return &**this;
+ }
+};
+
+template <>
+inline long& var_t<long>::operator *() {
+ return value->as_long_lval();
+}
+template <>
+inline const long& var_t<long>::operator *() const {
+ return value->as_long();
+}
+
+template <>
+inline string& var_t<string>::operator *() {
+ return value->as_string_lval();
+}
+template <>
+inline const string& var_t<string>::operator *() const {
+ return value->as_string();
+}
+
+} // namespace ledger
+
+#endif // _SCOPE_H
+
diff --git a/src/session.cc b/src/session.cc
new file mode 100644
index 00000000..06288444
--- /dev/null
+++ b/src/session.cc
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "session.h"
+#include "report.h"
+#include "handler.h"
+#include "iterators.h"
+#include "filters.h"
+
+namespace ledger {
+
+session_t * session_t::current = NULL;
+
+#if 0
+boost::mutex session_t::session_mutex;
+#endif
+
+void set_session_context(session_t * session)
+{
+#if 0
+ session_t::session_mutex.lock();
+#endif
+
+ if (session && ! session_t::current) {
+ session_t::initialize();
+ }
+ else if (! session && session_t::current) {
+ session_t::shutdown();
+#if 0
+ session_t::session_mutex.unlock();
+#endif
+ }
+
+ session_t::current = session;
+}
+
+void release_session_context()
+{
+#if 0
+ session_t::session_mutex.unlock();
+#endif
+}
+
+session_t::session_t()
+ : register_format
+ ("%-.10D %-.20P %-.22A %12.67t %!12.80T\n%/"
+ "%32|%-.22A %12.67t %!12.80T\n"),
+ wide_register_format
+ ("%-.10D %-.35P %-.38A %22.108t %!22.132T\n%/"
+ "%48|%-.38A %22.108t %!22.132T\n"),
+ print_format
+ ("\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"),
+ balance_format
+ ("%20T %2_%-a\n"),
+ equity_format
+ ("\n%D %Y%C%P\n%/ %-34W %12t\n"),
+ plot_amount_format
+ ("%D %(S(t))\n"),
+ plot_total_format
+ ("%D %(S(T))\n"),
+ write_hdr_format
+ ("%d %Y%C%P\n"),
+ write_xact_format
+ (" %-34W %12o%n\n"),
+ prices_format
+ ("%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"),
+ pricesdb_format
+ ("P %[%Y/%m/%d %H:%M:%S] %A %t\n"),
+
+ pricing_leeway(24 * 3600),
+
+ download_quotes(false),
+ use_cache(false),
+ cache_dirty(false),
+
+ now(now),
+
+#if 0
+ elision_style(ABBREVIATE),
+#endif
+ abbrev_length(2),
+
+ ansi_codes(false),
+ ansi_invert(false),
+
+ master(new account_t(NULL, ""))
+{
+ TRACE_CTOR(session_t, "");
+}
+
+session_t::~session_t()
+{
+ TRACE_DTOR(session_t);
+}
+
+std::size_t session_t::read_journal(journal_t& journal,
+ std::istream& in,
+ const path& pathname,
+ account_t * master)
+{
+ if (! master)
+ master = journal.master;
+
+ foreach (journal_t::parser_t& parser, parsers)
+ if (parser.test(in))
+ return parser.parse(in, *this, journal, master, &pathname);
+
+ return 0;
+}
+
+std::size_t session_t::read_journal(journal_t& journal,
+ const path& pathname,
+ account_t * master)
+{
+ journal.sources.push_back(pathname);
+
+ if (! exists(pathname))
+ throw_(std::logic_error, "Cannot read file" << pathname);
+
+ ifstream stream(pathname);
+ return read_journal(journal, stream, pathname, master);
+}
+
+void session_t::read_init()
+{
+ if (! init_file)
+ return;
+
+ if (! exists(*init_file))
+ throw_(std::logic_error, "Cannot read init file" << *init_file);
+
+ ifstream init(*init_file);
+
+ // jww (2006-09-15): Read initialization options here!
+}
+
+std::size_t session_t::read_data(journal_t& journal,
+ const string& master_account)
+{
+ if (data_file.empty())
+ throw_(parse_error, "No journal file was specified (please use -f)");
+
+ TRACE_START(parser, 1, "Parsing journal file");
+
+ std::size_t entry_count = 0;
+
+ DEBUG("ledger.cache", "3. use_cache = " << use_cache);
+
+ if (use_cache && cache_file) {
+ DEBUG("ledger.cache", "using_cache " << cache_file->string());
+ cache_dirty = true;
+ if (exists(*cache_file)) {
+ push_variable<optional<path> >
+ save_price_db(journal.price_db, price_db);
+
+ entry_count += read_journal(journal, *cache_file);
+ if (entry_count > 0)
+ cache_dirty = false;
+ }
+ }
+
+ if (entry_count == 0) {
+ account_t * acct = NULL;
+ if (! master_account.empty())
+ acct = journal.find_account(master_account);
+
+ journal.price_db = price_db;
+ if (journal.price_db && exists(*journal.price_db)) {
+ if (read_journal(journal, *journal.price_db)) {
+ throw_(parse_error, "Entries not allowed in price history file");
+ } else {
+ DEBUG("ledger.cache",
+ "read price database " << journal.price_db->string());
+ journal.sources.pop_back();
+ }
+ }
+
+ DEBUG("ledger.cache", "rejected cache, parsing " << data_file.string());
+ if (data_file == "-") {
+ use_cache = false;
+ journal.sources.push_back("/dev/stdin");
+ entry_count += read_journal(journal, std::cin, "/dev/stdin", acct);
+ }
+ else if (exists(data_file)) {
+ entry_count += read_journal(journal, data_file, acct);
+ if (journal.price_db)
+ journal.sources.push_back(*journal.price_db);
+ clean_accounts();
+ }
+ }
+
+ VERIFY(journal.valid());
+
+ TRACE_STOP(parser, 1);
+
+ return entry_count;
+}
+
+namespace {
+ account_t * find_account_re_(account_t * account, const mask_t& regexp)
+ {
+ if (regexp.match(account->fullname()))
+ return account;
+
+ foreach (accounts_map::value_type& pair, account->accounts)
+ if (account_t * a = find_account_re_(pair.second, regexp))
+ return a;
+
+ return NULL;
+ }
+}
+
+account_t * session_t::find_account_re(const string& regexp)
+{
+ return find_account_re_(master.get(), mask_t(regexp));
+}
+
+void session_t::clean_xacts()
+{
+ session_xacts_iterator walker(*this);
+ pass_down_xacts
+ (xact_handler_ptr(new clear_xact_xdata), walker);
+}
+
+void session_t::clean_xacts(entry_t& entry)
+{
+ entry_xacts_iterator walker(entry);
+ pass_down_xacts(xact_handler_ptr(new clear_xact_xdata), walker);
+}
+
+void session_t::clean_accounts()
+{
+ basic_accounts_iterator acct_walker(*master);
+ pass_down_accounts(acct_handler_ptr(new clear_account_xdata),
+ acct_walker);
+}
+
+#if 0
+value_t session_t::resolve(const string& name, expr_t::scope_t& locals)
+{
+ const char * p = name.c_str();
+ switch (*p) {
+ case 'd':
+#if 0
+ if (name == "date_format") {
+ // jww (2007-04-18): What to do here?
+ return string_value(moment_t::output_format);
+ }
+#endif
+ break;
+
+ case 'n':
+ switch (*++p) {
+ case 'o':
+ if (name == "now")
+ return value_t(now);
+ break;
+ }
+ break;
+
+ case 'r':
+ if (name == "register_format")
+ return string_value(register_format);
+ break;
+ }
+ return expr_t::scope_t::resolve(name, locals);
+}
+#endif
+
+expr_t::ptr_op_t session_t::lookup(const string& name)
+{
+ const char * p = name.c_str();
+ switch (*p) {
+ case 'b':
+ if (std::strcmp(p, "balance_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(balance_format));
+ break;
+
+ case 'e':
+ if (std::strcmp(p, "equity_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(equity_format));
+ break;
+
+ case 'p':
+ if (std::strcmp(p, "plot_amount_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(plot_amount_format));
+ else if (std::strcmp(p, "plot_total_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(plot_total_format));
+ else if (std::strcmp(p, "prices_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(prices_format));
+ else if (std::strcmp(p, "pricesdb_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(pricesdb_format));
+ else if (std::strcmp(p, "print_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(print_format));
+ break;
+
+ case 'r':
+ if (std::strcmp(p, "register_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(register_format));
+ break;
+
+ case 'w':
+ if (std::strcmp(p, "wide_register_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(wide_register_format));
+ else if (std::strcmp(p, "write_hdr_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(write_hdr_format));
+ else if (std::strcmp(p, "write_xact_format") == 0)
+ return expr_t::op_t::wrap_value(string_value(write_xact_format));
+ break;
+
+ case 'o':
+ if (std::strncmp(p, "opt_", 4) == 0) {
+ p = p + 4;
+ switch (*p) {
+ case 'd':
+ if (std::strcmp(p, "debug_") == 0)
+ return MAKE_FUNCTOR(session_t::option_debug_);
+ break;
+
+ case 'f':
+ if ((*(p + 1) == '_' && ! *(p + 2)) ||
+ std::strcmp(p, "file_") == 0)
+ return MAKE_FUNCTOR(session_t::option_file_);
+ break;
+
+ case 't':
+ if (std::strcmp(p, "trace_") == 0)
+ return MAKE_FUNCTOR(session_t::option_trace_);
+ break;
+
+ case 'v':
+ if (! *(p + 1) || std::strcmp(p, "verbose") == 0)
+ return MAKE_FUNCTOR(session_t::option_verbose);
+ else if (std::strcmp(p, "version") == 0)
+ return MAKE_FUNCTOR(session_t::option_version);
+ else if (std::strcmp(p, "verify") == 0)
+ return MAKE_FUNCTOR(session_t::option_verify);
+ break;
+ }
+ }
+ break;
+ }
+
+ return expr_t::ptr_op_t();
+}
+
+// jww (2007-04-26): All of Ledger should be accessed through a
+// session_t object
+void session_t::initialize()
+{
+ amount_t::initialize();
+ value_t::initialize();
+ expr_t::initialize();
+}
+
+void session_t::shutdown()
+{
+ expr_t::shutdown();
+ value_t::shutdown();
+ amount_t::shutdown();
+}
+
+} // namespace ledger
diff --git a/src/session.h b/src/session.h
new file mode 100644
index 00000000..36da0a53
--- /dev/null
+++ b/src/session.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SESSION_H
+#define _SESSION_H
+
+#include "scope.h"
+#include "journal.h"
+#include "account.h"
+#include "format.h"
+
+namespace ledger {
+
+class report_t;
+
+class session_t : public noncopyable, public scope_t
+{
+ static void initialize();
+ static void shutdown();
+
+ friend void set_session_context(session_t * session);
+ friend void release_session_context();
+
+public:
+ static session_t * current;
+
+ scoped_ptr<report_t> current_report;
+
+ path data_file;
+ optional<path> init_file;
+ optional<path> cache_file;
+ optional<path> price_db;
+
+ string register_format;
+ string wide_register_format;
+ string print_format;
+ string balance_format;
+ string equity_format;
+ string plot_amount_format;
+ string plot_total_format;
+ string write_hdr_format;
+ string write_xact_format;
+ string prices_format;
+ string pricesdb_format;
+
+ unsigned long pricing_leeway;
+
+ bool download_quotes;
+ bool use_cache;
+ bool cache_dirty;
+
+ datetime_t now;
+ date_t today;
+
+ format_t::elision_style_t elision_style;
+ int abbrev_length;
+
+ bool ansi_codes;
+ bool ansi_invert;
+
+ ptr_list<journal_t> journals;
+ ptr_list<journal_t::parser_t> parsers;
+ scoped_ptr<commodity_pool_t> commdity_pool;
+ scoped_ptr<account_t> master;
+ mutable accounts_map accounts_cache;
+
+ session_t();
+ virtual ~session_t();
+
+ journal_t * create_journal() {
+ journal_t * journal = new journal_t(this);
+ journals.push_back(journal);
+ return journal;
+ }
+ void close_journal(journal_t * journal) {
+ for (ptr_list<journal_t>::iterator i = journals.begin();
+ i != journals.end();
+ i++)
+ if (&*i == journal) {
+ journals.erase(i);
+ return;
+ }
+ assert(false);
+ checked_delete(journal);
+ }
+
+ std::size_t read_journal(journal_t& journal,
+ std::istream& in,
+ const path& pathname,
+ account_t * master = NULL);
+ std::size_t read_journal(journal_t& journal,
+ const path& pathname,
+ account_t * master = NULL);
+
+ void read_init();
+
+ std::size_t read_data(journal_t& journal,
+ const string& master_account = "");
+
+ void register_parser(journal_t::parser_t * parser) {
+ parsers.push_back(parser);
+ }
+ void unregister_parser(journal_t::parser_t * parser) {
+ for (ptr_list<journal_t::parser_t>::iterator i = parsers.begin();
+ i != parsers.end();
+ i++)
+ if (&*i == parser) {
+ parsers.erase(i);
+ return;
+ }
+ assert(false);
+ checked_delete(parser);
+ }
+
+ //
+ // Dealing with accounts
+ //
+
+ void add_account(account_t * acct) {
+ master->add_account(acct);
+ }
+ bool remove_account(account_t * acct) {
+ return master->remove_account(acct);
+ }
+
+ account_t * find_account(const string& name, bool auto_create = true) {
+ accounts_map::iterator c = accounts_cache.find(name);
+ if (c != accounts_cache.end())
+ return (*c).second;
+
+ account_t * account = master->find_account(name, auto_create);
+ accounts_cache.insert(accounts_map::value_type(name, account));
+ return account;
+ }
+ account_t * find_account_re(const string& regexp);
+
+ void clean_accounts();
+
+ void clean_xacts();
+ void clean_xacts(entry_t& entry);
+
+ //
+ // Scope members
+ //
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+
+ //
+ // Help options
+ //
+
+ value_t option_version(scope_t&) {
+ std::cout << "Ledger " << ledger::version << ", the command-line accounting tool";
+ std::cout << "\n\nCopyright (c) 2003-2008, John Wiegley. All rights reserved.\n\n\
+This program is made available under the terms of the BSD Public License.\n\
+See LICENSE file included with the distribution for details and disclaimer.\n";
+ std::cout << "\n(modules: gmp, pcre";
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+ std::cout << ", xml";
+#endif
+#ifdef HAVE_LIBOFX
+ std::cout << ", ofx";
+#endif
+ std::cout << ")\n";
+ return NULL_VALUE;
+ }
+
+ //
+ // Debug options
+ //
+
+ value_t option_trace_(scope_t&) {
+ return NULL_VALUE;
+ }
+ value_t option_debug_(scope_t&) {
+ return NULL_VALUE;
+ }
+ value_t option_verify(scope_t&) {
+ return NULL_VALUE;
+ }
+
+ value_t option_verbose(scope_t&) {
+#if defined(LOGGING_ON)
+ if (_log_level < LOG_INFO)
+ _log_level = LOG_INFO;
+#endif
+ return NULL_VALUE;
+ }
+
+ //
+ // Option handlers
+ //
+
+ value_t option_file_(call_scope_t& args) {
+ assert(args.size() == 1);
+ data_file = args[0].as_string();
+ return NULL_VALUE;
+ }
+};
+
+/**
+ * This sets the current session context, transferring all static
+ * globals to point at the data structures related to this session.
+ * Although Ledger itself is not thread-safe, by locking, switching
+ * session context, then unlocking after the operation is done,
+ * multiple threads can sequentially make use of the library. Thus, a
+ * session_t maintains all of the information relating to a single
+ * usage of the Ledger library.
+ */
+void set_session_context(session_t * session = NULL);
+
+} // namespace ledger
+
+#endif // _SESSION_H
diff --git a/src/system.hh b/src/system.hh
new file mode 100644
index 00000000..221b81ef
--- /dev/null
+++ b/src/system.hh
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SYSTEM_HH
+#define _SYSTEM_HH
+
+/**
+ * @file system.hh
+ * @author John Wiegley
+ * @date Mon Apr 23 03:43:05 2007
+ *
+ * @brief All system headers needed by Ledger.
+ *
+ * These are collected here so that a pre-compiled header can be made.
+ * None of these header files (with the exception of acconf.h, when
+ * configure is re-run) are expected to change.
+ */
+
+#include "acconf.h"
+
+#if defined(__GNUG__) && __GNUG__ < 3
+#define _XOPEN_SOURCE
+#endif
+
+#include <algorithm>
+#include <exception>
+#include <typeinfo>
+#include <stdexcept>
+#include <iostream>
+#include <streambuf>
+#include <iomanip>
+#include <fstream>
+#include <sstream>
+#include <iterator>
+#include <list>
+#include <map>
+#include <memory>
+#include <new>
+#include <stack>
+#include <string>
+#include <vector>
+
+#if defined(__GNUG__) && __GNUG__ < 3
+
+namespace std {
+ inline ostream & right (ostream & i) {
+ i.setf(i.right, i.adjustfield);
+ return i;
+ }
+ inline ostream & left (ostream & i) {
+ i.setf(i.left, i.adjustfield);
+ return i;
+ }
+}
+
+typedef unsigned long istream_pos_type;
+typedef unsigned long ostream_pos_type;
+
+#else // ! (defined(__GNUG__) && __GNUG__ < 3)
+
+typedef std::istream::pos_type istream_pos_type;
+typedef std::ostream::pos_type ostream_pos_type;
+
+#endif
+
+#include <cassert>
+#include <cctype>
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+
+#if defined __FreeBSD__ && __FreeBSD__ <= 4
+// FreeBSD has a broken isspace macro, so don't use it
+#undef isspace(c)
+#endif
+
+#include <sys/stat.h>
+
+#ifdef WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM)
+#include <pwd.h>
+#endif
+
+#if defined(HAVE_NL_LANGINFO)
+#include <langinfo.h>
+#endif
+
+#include <gmp.h>
+
+extern "C" {
+#if defined(HAVE_EXPAT)
+#include <expat.h> // expat XML parser
+#elif defined(HAVE_XMLPARSE)
+#include <xmlparse.h> // expat XML parser
+#endif
+}
+
+#if defined(HAVE_LIBOFX)
+#include <libofx.h>
+#endif
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/any.hpp>
+#include <boost/cast.hpp>
+#include <boost/current_function.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/filesystem/convenience.hpp>
+#include <boost/filesystem/exception.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <boost/filesystem/path.hpp>
+#include <boost/foreach.hpp>
+#include <boost/function.hpp>
+#include <boost/intrusive_ptr.hpp>
+#include <boost/lambda/bind.hpp>
+#include <boost/lambda/lambda.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/random_access_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/operators.hpp>
+#include <boost/optional.hpp>
+#include <boost/ptr_container/ptr_list.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <boost/regex.hpp>
+#include <boost/variant.hpp>
+
+#endif // _SYSTEM_HH
diff --git a/src/textual.cc b/src/textual.cc
new file mode 100644
index 00000000..f10125ea
--- /dev/null
+++ b/src/textual.cc
@@ -0,0 +1,1124 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(__GNUG__) && __GNUG__ < 3
+#define _XOPEN_SOURCE
+#endif
+
+#include "textual.h"
+#include "expr.h"
+#include "parser.h"
+#include "session.h"
+#include "option.h"
+#include "acconf.h"
+
+#define TIMELOG_SUPPORT 1
+
+namespace ledger {
+
+#define MAX_LINE 1024
+
+static path pathname;
+static unsigned int linenum;
+static unsigned int src_idx;
+static accounts_map account_aliases;
+
+static std::list<std::pair<path, int> > include_stack;
+
+#ifdef TIMELOG_SUPPORT
+struct time_entry_t
+{
+ datetime_t checkin;
+ account_t * account;
+ string desc;
+
+ time_entry_t() : account(NULL) {
+ TRACE_CTOR(time_entry_t, "");
+ }
+ time_entry_t(const datetime_t& _checkin,
+ account_t * _account = NULL,
+ const string& _desc = "")
+ : checkin(_checkin), account(_account), desc(_desc) {
+ TRACE_CTOR(time_entry_t, "const datetime_t&, account_t *, const string&");
+ }
+ time_entry_t(const time_entry_t& entry)
+ : checkin(entry.checkin), account(entry.account),
+ desc(entry.desc) {
+ TRACE_CTOR(time_entry_t, "copy");
+ }
+ ~time_entry_t() throw() {
+ TRACE_DTOR(time_entry_t);
+ }
+};
+#endif
+
+namespace {
+ optional<expr_t> parse_amount_expr(std::istream& in,
+ amount_t& amount,
+ xact_t * xact,
+ unsigned short flags = 0)
+ {
+ expr_t expr(in, flags | EXPR_PARSE_PARTIAL);
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed an amount expression");
+
+#ifdef DEBUG_ENABLED
+ DEBUG_IF("ledger.textual.parse") {
+ if (_debug_stream) {
+ ledger::dump_value_expr(*_debug_stream, expr);
+ *_debug_stream << std::endl;
+ }
+ }
+#endif
+
+ if (expr) {
+ amount = expr.calc(*xact).as_amount();
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "The transaction amount is " << amount);
+ return expr;
+ }
+ return none;
+ }
+}
+
+xact_t * parse_xact(char * line, account_t * account, entry_t * entry = NULL)
+{
+ std::istringstream in(line);
+
+ string err_desc;
+ try {
+
+ // The account will be determined later...
+ std::auto_ptr<xact_t> xact(new xact_t(NULL));
+ if (entry)
+ xact->entry = entry;
+
+ // Parse the state flag
+
+ char p = peek_next_nonws(in);
+ switch (p) {
+ case '*':
+ xact->state = xact_t::CLEARED;
+ in.get(p);
+ p = peek_next_nonws(in);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed the CLEARED flag");
+ break;
+ case '!':
+ xact->state = xact_t::PENDING;
+ in.get(p);
+ p = peek_next_nonws(in);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed the PENDING flag");
+ break;
+ }
+
+ // Parse the account name
+
+ unsigned long account_beg = static_cast<unsigned long>(in.tellg());
+ unsigned long account_end = account_beg;
+ while (! in.eof()) {
+ in.get(p);
+ if (in.eof() || (std::isspace(p) &&
+ (p == '\t' || in.peek() == EOF ||
+ std::isspace(in.peek()))))
+ break;
+ account_end++;
+ }
+
+ if (account_beg == account_end)
+ throw parse_error("No account was specified");
+
+ char * b = &line[account_beg];
+ char * e = &line[account_end];
+ if ((*b == '[' && *(e - 1) == ']') ||
+ (*b == '(' && *(e - 1) == ')')) {
+ xact->add_flags(XACT_VIRTUAL);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a virtual account name");
+ if (*b == '[') {
+ xact->add_flags(XACT_BALANCE);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a balanced virtual account name");
+ }
+ b++; e--;
+ }
+
+ string name(b, e - b);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed account name " << name);
+ if (account_aliases.size() > 0) {
+ accounts_map::const_iterator i = account_aliases.find(name);
+ if (i != account_aliases.end())
+ xact->account = (*i).second;
+ }
+ if (! xact->account)
+ xact->account = account->find_account(name);
+
+ // Parse the optional amount
+
+ bool saw_amount = false;
+
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (in.eof())
+ goto finished;
+ if (p == ';')
+ goto parse_note;
+ if (p == '=' && entry)
+ goto parse_assign;
+
+ try {
+ unsigned long beg = static_cast<unsigned long>(in.tellg());
+
+ xact->amount_expr =
+ parse_amount_expr(in, xact->amount, xact.get(),
+ EXPR_PARSE_NO_REDUCE | EXPR_PARSE_NO_ASSIGN);
+ saw_amount = true;
+
+ if (! xact->amount.is_null()) {
+ xact->amount.reduce();
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Reduced amount is " << xact->amount);
+ }
+
+ // We don't need to store the actual expression that resulted in the
+ // amount if it's constant
+ if (xact->amount_expr) {
+ if (xact->amount_expr->is_constant())
+ xact->amount_expr = expr_t();
+
+ unsigned long end = static_cast<unsigned long>(in.tellg());
+ xact->amount_expr->set_text(string(line, beg, end - beg));
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing transaction amount:\n");
+ throw err;
+ }
+ }
+
+ // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST)
+
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (p == '@') {
+ if (! saw_amount)
+ throw parse_error
+ ("Transaction cannot have a cost expression with an amount");
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Found a price indicator");
+ bool per_unit = true;
+ in.get(p);
+ if (in.peek() == '@') {
+ in.get(p);
+ per_unit = false;
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "And it's for a total price");
+ }
+
+ if (in.good() && ! in.eof()) {
+ xact->cost = amount_t();
+
+ try {
+ unsigned long beg = static_cast<unsigned long>(in.tellg());
+
+ xact->cost_expr =
+ parse_amount_expr(in, *xact->cost, xact.get(),
+ EXPR_PARSE_NO_MIGRATE |
+ EXPR_PARSE_NO_ASSIGN);
+
+ if (xact->cost_expr) {
+ unsigned long end = static_cast<unsigned long>(in.tellg());
+ if (per_unit)
+ xact->cost_expr->set_text(string("@") +
+ string(line, beg, end - beg));
+ else
+ xact->cost_expr->set_text(string("@@") +
+ string(line, beg, end - beg));
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing transaction cost:\n");
+ throw err;
+ }
+
+ if (xact->cost->sign() < 0)
+ throw parse_error("A transaction's cost may not be negative");
+
+ amount_t per_unit_cost(*xact->cost);
+ if (per_unit)
+ *xact->cost *= xact->amount;
+ else
+ per_unit_cost /= xact->amount;
+
+ if (xact->amount.commodity() &&
+ ! xact->amount.commodity().annotated) {
+ if (xact->entry)
+ xact->amount.annotate(annotation_t(per_unit_cost,
+ xact->entry->actual_date(),
+ xact->entry->code));
+ else
+ xact->amount.annotate(annotation_t(per_unit_cost));
+ }
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Total cost is " << *xact->cost);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Per-unit cost is " << per_unit_cost);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Annotated amount is " << xact->amount);
+ }
+ }
+ }
+
+ parse_assign:
+ if (entry != NULL) {
+ // Parse the optional assigned (= AMOUNT)
+
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (p == '=') {
+ in.get(p);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Found a balance assignment indicator");
+ if (in.good() && ! in.eof()) {
+ amount_t amt;
+
+ try {
+ unsigned long beg = static_cast<unsigned long>(in.tellg());
+
+ optional<expr_t> total_expr =
+ parse_amount_expr(in, amt, xact.get(), EXPR_PARSE_NO_MIGRATE);
+
+ if (amt.is_null())
+ throw parse_error
+ ("An assigned balance must evaluate to a constant value");
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "XACT assign: parsed amt = " << amt);
+
+ if (total_expr) {
+ unsigned long end = static_cast<unsigned long>(in.tellg());
+ total_expr->set_text(string("=") +
+ string(line, beg, end - beg));
+ }
+
+ // jww (2008-08-02): Save total_expr somewhere!
+
+ account_t::xdata_t& xdata(xact->account->xdata());
+
+ DEBUG("ledger.xact.assign", "account balance = " << xdata.value);
+ DEBUG("ledger.xact.assign", "xact amount = " << amt);
+
+ amount_t diff;
+ if (xdata.value.is_amount()) {
+ diff = amt - xdata.value.as_amount();
+ }
+ else if (xdata.value.is_balance()) {
+ optional<amount_t> comm_bal =
+ xdata.value.as_balance().commodity_amount(amt.commodity());
+ diff = amt - (comm_bal ? *comm_bal : amount_t(0L));
+ }
+ else if (xdata.value.is_balance_pair()) {
+ optional<amount_t> comm_bal =
+ xdata.value.as_balance_pair().commodity_amount(amt.commodity());
+ diff = amt - (comm_bal ? *comm_bal : amount_t(0L));
+ }
+ else {
+ diff = amt;
+ }
+
+ DEBUG("ledger.xact.assign", "diff = " << diff);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "XACT assign: diff = " << diff);
+
+ if (! diff.is_realzero()) {
+ if (! xact->amount.is_null()) {
+ xact_t * temp =
+ new xact_t(xact->account, diff,
+ XACT_GENERATED | XACT_CALCULATED);
+ entry->add_xact(temp);
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Created balancing transaction");
+ } else {
+ xact->amount = diff;
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Overwrite null transaction");
+ }
+ xdata.value = amt;
+ }
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing assigned balance:\n");
+ throw err;
+ }
+ }
+ }
+ }
+ }
+
+ // Parse the optional note
+
+ parse_note:
+ if (in.good() && ! in.eof()) {
+ p = peek_next_nonws(in);
+ if (p == ';') {
+ in.get(p);
+ p = peek_next_nonws(in);
+ xact->note = &line[static_cast<unsigned long>(in.tellg())];
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a note '" << *xact->note << "'");
+
+ if (char * b = std::strchr(xact->note->c_str(), '['))
+ if (char * e = std::strchr(xact->note->c_str(), ']')) {
+ char buf[256];
+ std::strncpy(buf, b + 1, e - b - 1);
+ buf[e - b - 1] = '\0';
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a transaction date " << buf);
+
+ if (char * p = std::strchr(buf, '=')) {
+ *p++ = '\0';
+ xact->_date_eff = parse_date(p);
+ }
+ if (buf[0])
+ xact->_date = parse_date(buf);
+ }
+ }
+ }
+
+ finished:
+ return xact.release();
+
+ }
+ catch (const std::exception& err) {
+ add_error_context("While parsing transaction:\n");
+ add_error_context(line_context(line, static_cast<unsigned long>(in.tellg()) - 1));
+ throw err;
+ }
+}
+
+bool parse_xacts(std::istream& in,
+ account_t * account,
+ entry_base_t& entry,
+ const string& kind,
+ unsigned long beg_pos)
+{
+ TRACE_START(entry_xacts, 1, "Time spent parsing transactions:");
+
+ static char line[MAX_LINE + 1];
+ bool added = false;
+
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
+ in.getline(line, MAX_LINE);
+ if (in.eof())
+ break;
+
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ beg_pos += len + 1;
+ linenum++;
+
+ if (line[0] == ' ' || line[0] == '\t') {
+ char * p = skip_ws(line);
+ if (! *p)
+ break;
+ }
+ if (xact_t * xact = parse_xact(line, account)) {
+ entry.add_xact(xact);
+ added = true;
+ }
+ }
+
+ TRACE_STOP(entry_xacts, 1);
+
+ return added;
+}
+
+entry_t * parse_entry(std::istream& in, char * line, account_t * master,
+ textual_parser_t& parser, unsigned long& pos)
+{
+ TRACE_START(entry_text, 1, "Time spent preparing entry text:");
+
+ std::auto_ptr<entry_t> curr(new entry_t);
+
+ // Parse the date
+
+ char * next = next_element(line);
+
+ if (char * p = std::strchr(line, '=')) {
+ *p++ = '\0';
+ curr->_date_eff = parse_date(p);
+ }
+ curr->_date = parse_date(line);
+
+ // Parse the optional cleared flag: *
+
+ xact_t::state_t state = xact_t::UNCLEARED;
+ if (next) {
+ switch (*next) {
+ case '*':
+ state = xact_t::CLEARED;
+ next = skip_ws(++next);
+ break;
+ case '!':
+ state = xact_t::PENDING;
+ next = skip_ws(++next);
+ break;
+ }
+ }
+
+ // Parse the optional code: (TEXT)
+
+ if (next && *next == '(') {
+ if (char * p = std::strchr(next++, ')')) {
+ *p++ = '\0';
+ curr->code = next;
+ next = skip_ws(p);
+ }
+ }
+
+ // Parse the description text
+
+ curr->payee = next ? next : "<Unspecified payee>";
+
+ TRACE_STOP(entry_text, 1);
+
+ // Parse all of the xacts associated with this entry
+
+ TRACE_START(entry_details, 1, "Time spent parsing entry details:");
+
+ unsigned long end_pos;
+ unsigned long beg_line = linenum;
+
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
+ unsigned long beg_pos = static_cast<unsigned long>(in.tellg());
+
+ line[0] = '\0';
+ in.getline(line, MAX_LINE);
+ if (in.eof() && line[0] == '\0')
+ break;
+
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ end_pos = beg_pos + len + 1;
+ linenum++;
+
+ if (line[0] == ' ' || line[0] == '\t') {
+ char * p = skip_ws(line);
+ if (! *p)
+ break;
+ }
+
+ if (xact_t * xact = parse_xact(line, master, curr.get())) {
+ if (state != xact_t::UNCLEARED &&
+ xact->state == xact_t::UNCLEARED)
+ xact->state = state;
+
+ xact->beg_pos = beg_pos;
+ xact->beg_line = beg_line;
+ xact->end_pos = end_pos;
+ xact->end_line = linenum;
+ pos = end_pos;
+
+ curr->add_xact(xact);
+ }
+
+ if (in.eof())
+ break;
+ }
+
+ TRACE_STOP(entry_details, 1);
+
+ return curr.release();
+}
+
+static inline void parse_symbol(char *& p, string& symbol)
+{
+ if (*p == '"') {
+ char * q = std::strchr(p + 1, '"');
+ if (! q)
+ throw parse_error("Quoted commodity symbol lacks closing quote");
+ symbol = string(p + 1, 0, q - p - 1);
+ p = q + 2;
+ } else {
+ char * q = next_element(p);
+ symbol = p;
+ if (q)
+ p = q;
+ else
+ p += symbol.length();
+ }
+ if (symbol.empty())
+ throw parse_error("Failed to parse commodity");
+}
+
+bool textual_parser_t::test(std::istream& in) const
+{
+ char buf[5];
+
+ in.read(buf, 5);
+ if (std::strncmp(buf, "<?xml", 5) == 0) {
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+ throw parse_error("Ledger file contains XML data, but format was not recognized");
+#else
+ throw parse_error("Ledger file contains XML data, but no XML support present");
+#endif
+ }
+
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ assert(in.good());
+ return true;
+}
+
+static void clock_out_from_timelog(std::list<time_entry_t>& time_entries,
+ const datetime_t& when,
+ account_t * account,
+ const char * desc,
+ journal_t& journal)
+{
+ time_entry_t event;
+
+ if (time_entries.size() == 1) {
+ event = time_entries.back();
+ time_entries.clear();
+ }
+ else if (time_entries.empty()) {
+ throw parse_error("Timelog check-out event without a check-in");
+ }
+ else if (! account) {
+ throw parse_error
+ ("When multiple check-ins are active, checking out requires an account");
+ }
+ else {
+ bool found = false;
+
+ for (std::list<time_entry_t>::iterator i = time_entries.begin();
+ i != time_entries.end();
+ i++)
+ if (account == (*i).account) {
+ event = *i;
+ found = true;
+ time_entries.erase(i);
+ break;
+ }
+
+ if (! found)
+ throw parse_error
+ ("Timelog check-out event does not match any current check-ins");
+ }
+
+ if (desc && event.desc.empty()) {
+ event.desc = desc;
+ desc = NULL;
+ }
+
+ std::auto_ptr<entry_t> curr(new entry_t);
+ curr->_date = when.date();
+ curr->code = desc ? desc : "";
+ curr->payee = event.desc;
+
+ if (when < event.checkin)
+ throw parse_error
+ ("Timelog check-out date less than corresponding check-in");
+
+ char buf[32];
+ std::sprintf(buf, "%lds", long((when - event.checkin).seconds()));
+ amount_t amt;
+ amt.parse(buf);
+ assert(amt.valid());
+
+ xact_t * xact
+ = new xact_t(event.account, amt, XACT_VIRTUAL);
+ xact->state = xact_t::CLEARED;
+ curr->add_xact(xact);
+
+ if (! journal.add_entry(curr.get()))
+ throw parse_error("Failed to record 'out' timelog entry");
+ else
+ curr.release();
+}
+
+unsigned int textual_parser_t::parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
+{
+ TRACE_START(parsing_total, 1, "Total time spent parsing text:");
+
+ static bool added_auto_entry_hook = false;
+ static char line[MAX_LINE + 1];
+ unsigned int count = 0;
+ unsigned int errors = 0;
+
+ std::list<account_t *> account_stack;
+ auto_entry_finalizer_t auto_entry_finalizer(&journal);
+ std::list<time_entry_t> time_entries;
+
+ if (! master)
+ master = journal.master;
+
+ account_stack.push_front(master);
+
+ pathname = journal.sources.back();
+ src_idx = journal.sources.size() - 1;
+ linenum = 1;
+
+ INFO("Parsing file '" << pathname.string() << "'");
+
+ unsigned long beg_pos = static_cast<unsigned long>(in.tellg());
+ unsigned long end_pos;
+ unsigned long beg_line = linenum;
+
+ while (in.good() && ! in.eof()) {
+ try {
+ in.getline(line, MAX_LINE);
+ if (in.eof())
+ break;
+
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[--len] = '\0';
+
+ end_pos = beg_pos + len + 1;
+ linenum++;
+
+ switch (line[0]) {
+ case '\0':
+ break;
+
+ case ' ':
+ case '\t': {
+ char * p = skip_ws(line);
+ if (*p)
+ throw parse_error("Line begins with whitespace");
+ break;
+ }
+
+#ifdef TIMELOG_SUPPORT
+ case 'i':
+ case 'I': {
+ string date(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+
+ time_entry_t event(parse_datetime(date),
+ account_stack.front()->find_account(p), n ? n : "");
+
+ if (! time_entries.empty())
+ foreach (time_entry_t& time_entry, time_entries)
+ if (event.account == time_entry.account)
+ throw parse_error("Cannot double check-in to the same account");
+
+ time_entries.push_back(event);
+ break;
+ }
+
+ case 'o':
+ case 'O':
+ if (time_entries.empty()) {
+ throw parse_error("Timelog check-out event without a check-in");
+ } else {
+ string date(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+
+ clock_out_from_timelog
+ (time_entries, parse_datetime(date),
+ p ? account_stack.front()->find_account(p) : NULL, n, journal);
+ count++;
+ }
+ break;
+#endif // TIMELOG_SUPPORT
+
+ case 'D': { // a default commodity for "entry"
+ amount_t amt(skip_ws(line + 1));
+ assert(amt.valid());
+ amount_t::current_pool->default_commodity = &amt.commodity();
+ break;
+ }
+
+ case 'A': // a default account for unbalanced xacts
+ journal.basket =
+ account_stack.front()->find_account(skip_ws(line + 1));
+ break;
+
+ case 'C': // a set of conversions
+ if (char * p = std::strchr(line + 1, '=')) {
+ *p++ = '\0';
+ // jww (2008-04-22): NYI!
+#if 0
+ parse_conversion(line + 1, p);
+#endif
+ }
+ break;
+
+ case 'P': { // a pricing entry
+ char * date_field_ptr = skip_ws(line + 1);
+ char * time_field_ptr = next_element(date_field_ptr);
+ if (! time_field_ptr) break;
+ string date_field = date_field_ptr;
+
+ char * symbol_and_price;
+ datetime_t datetime;
+
+ if (std::isdigit(time_field_ptr[0])) {
+ symbol_and_price = next_element(time_field_ptr);
+ if (! symbol_and_price) break;
+ datetime = parse_datetime(date_field + " " + time_field_ptr);
+ } else {
+ symbol_and_price = time_field_ptr;
+ datetime = parse_datetime(date_field);
+ }
+
+ string symbol;
+ parse_symbol(symbol_and_price, symbol);
+ amount_t price(symbol_and_price);
+ assert(price.valid());
+
+ if (commodity_t * commodity =
+ amount_t::current_pool->find_or_create(symbol))
+ commodity->add_price(datetime, price);
+ break;
+ }
+
+ case 'N': { // don't download prices
+ char * p = skip_ws(line + 1);
+ string symbol;
+ parse_symbol(p, symbol);
+
+ if (commodity_t * commodity =
+ amount_t::current_pool->find_or_create(symbol))
+ commodity->add_flags(COMMODITY_STYLE_NOMARKET);
+ break;
+ }
+
+ case 'Y': // set the current year
+ current_year = std::atoi(skip_ws(line + 1));
+ break;
+
+#ifdef TIMELOG_SUPPORT
+ case 'h':
+ case 'b':
+#endif
+ case '*': // comment line
+ case ';': // comment line
+ break;
+
+ case '-': { // option setting
+ char * p = next_element(line);
+ if (! p) {
+ p = std::strchr(line, '=');
+ if (p)
+ *p++ = '\0';
+ }
+ process_option(line + 2, session, p);
+ break;
+ }
+
+ case '=': { // automated entry
+ if (! added_auto_entry_hook) {
+ journal.add_entry_finalizer(&auto_entry_finalizer);
+ added_auto_entry_hook = true;
+ }
+
+ auto_entry_t * ae = new auto_entry_t(skip_ws(line + 1));
+ if (parse_xacts(in, account_stack.front(), *ae,
+ "automated", end_pos)) {
+ journal.auto_entries.push_back(ae);
+ ae->src_idx = src_idx;
+ ae->beg_pos = beg_pos;
+ ae->beg_line = beg_line;
+ ae->end_pos = end_pos;
+ ae->end_line = linenum;
+ }
+ break;
+ }
+
+ case '~': { // period entry
+ period_entry_t * pe = new period_entry_t(skip_ws(line + 1));
+ if (! pe->period)
+ throw_(parse_error, "Parsing time period '" << line << "'");
+
+ if (parse_xacts(in, account_stack.front(), *pe,
+ "period", end_pos)) {
+ if (pe->finalize()) {
+ extend_entry_base(&journal, *pe, true);
+ journal.period_entries.push_back(pe);
+ pe->src_idx = src_idx;
+ pe->beg_pos = beg_pos;
+ pe->beg_line = beg_line;
+ pe->end_pos = end_pos;
+ pe->end_line = linenum;
+ } else {
+ throw new parse_error("Period entry failed to balance");
+ }
+ }
+ break;
+ }
+
+ case '@':
+ case '!': { // directive
+ char * p = next_element(line);
+ string word(line + 1);
+ if (word == "include") {
+ push_variable<path> save_pathname(pathname);
+ push_variable<unsigned int> save_src_idx(src_idx);
+ push_variable<unsigned long> save_beg_pos(beg_pos);
+ push_variable<unsigned long> save_end_pos(end_pos);
+ push_variable<unsigned int> save_linenum(linenum);
+
+ pathname = p;
+#if 0
+ if (pathname[0] != '/' && pathname[0] != '\\' && pathname[0] != '~') {
+ string::size_type pos = save_pathname.prev.rfind('/');
+ if (pos == string::npos)
+ pos = save_pathname.prev.rfind('\\');
+ if (pos != string::npos)
+ pathname = string(save_pathname.prev, 0, pos + 1) + pathname;
+ }
+ pathname = resolve_path(pathname);
+
+ DEBUG("ledger.textual.include", "line " << linenum << ": " <<
+ "Including path '" << pathname << "'");
+
+ include_stack.push_back(std::pair<path, int>
+ (journal.sources.back(), linenum - 1));
+ count += parse_journal_file(pathname, config, journal,
+ account_stack.front());
+ include_stack.pop_back();
+#endif
+ }
+ else if (word == "account") {
+ account_t * acct;
+ acct = account_stack.front()->find_account(p);
+ account_stack.push_front(acct);
+ }
+ else if (word == "end") {
+ account_stack.pop_front();
+ }
+ else if (word == "alias") {
+ char * b = p;
+ if (char * e = std::strchr(b, '=')) {
+ char * z = e - 1;
+ while (std::isspace(*z))
+ *z-- = '\0';
+ *e++ = '\0';
+ e = skip_ws(e);
+
+ // Once we have an alias name (b) and the target account
+ // name (e), add a reference to the account in the
+ // `account_aliases' map, which is used by the xact
+ // parser to resolve alias references.
+ account_t * acct = account_stack.front()->find_account(e);
+ std::pair<accounts_map::iterator, bool> result
+ = account_aliases.insert(accounts_map::value_type(b, acct));
+ assert(result.second);
+ }
+ }
+ else if (word == "def") {
+ expr_t def(p);
+ def.compile(session); // causes definitions to be established
+ }
+ break;
+ }
+
+ default: {
+ unsigned long pos = beg_pos;
+ TRACE_START(entries, 1, "Time spent handling entries:");
+ if (entry_t * entry =
+ parse_entry(in, line, account_stack.front(), *this, pos)) {
+ if (journal.add_entry(entry)) {
+ entry->src_idx = src_idx;
+ entry->beg_pos = beg_pos;
+ entry->beg_line = beg_line;
+ entry->end_pos = pos;
+ entry->end_line = linenum;
+ count++;
+ } else {
+ checked_delete(entry);
+ throw parse_error("Entry does not balance");
+ }
+ } else {
+ throw parse_error("Failed to parse entry");
+ }
+ end_pos = pos;
+ TRACE_STOP(entries, 1);
+ break;
+ }
+ }
+ }
+ catch (const std::exception& err) {
+ for (std::list<std::pair<path, int> >::reverse_iterator i =
+ include_stack.rbegin();
+ i != include_stack.rend();
+ i++) {
+ add_error_context("In file included from ");
+#if 0
+ add_error_context(include_context((*i).first, (*i).second));
+#endif
+ }
+ add_error_context(file_context(pathname, linenum - 1));
+
+ std::cout.flush();
+ std::cerr << "Error: " << error_context() << err.what()
+ << std::endl;
+ errors++;
+ }
+ beg_pos = end_pos;
+ }
+
+ if (! time_entries.empty()) {
+ std::list<account_t *> accounts;
+
+ foreach (time_entry_t& time_entry, time_entries)
+ accounts.push_back(time_entry.account);
+
+ foreach (account_t * account, accounts)
+ clock_out_from_timelog(time_entries, current_time, account, NULL,
+ journal);
+
+ assert(time_entries.empty());
+ }
+
+ if (added_auto_entry_hook)
+ journal.remove_entry_finalizer(&auto_entry_finalizer);
+
+ if (errors > 0)
+ throw static_cast<int>(errors);
+
+ TRACE_STOP(parsing_total, 1);
+
+ return count;
+}
+
+void write_textual_journal(journal_t& journal,
+ const path& pathname,
+ xact_handler_ptr formatter,
+ const string& write_hdr_format,
+ std::ostream& out)
+{
+ unsigned long index = 0;
+ path found;
+
+ if (pathname.empty()) {
+ if (! journal.sources.empty())
+ found = *journal.sources.begin();
+ } else {
+#ifdef HAVE_REALPATH
+ char buf1[PATH_MAX];
+ char buf2[PATH_MAX];
+
+ ::realpath(pathname.string().c_str(), buf1);
+
+ foreach (const path& path, journal.sources) {
+ ::realpath(path.string().c_str(), buf2);
+ if (std::strcmp(buf1, buf2) == 0) {
+ found = path;
+ break;
+ }
+ index++;
+ }
+#else
+ foreach (const path& path, journal.sources) {
+ if (pathname == path) {
+ found = path;
+ break;
+ }
+ index++;
+ }
+#endif
+ }
+
+ if (found.empty())
+ throw_(std::runtime_error,
+ "Journal does not refer to file '" << pathname << "'");
+
+ entries_list::iterator el = journal.entries.begin();
+ auto_entries_list::iterator al = journal.auto_entries.begin();
+ period_entries_list::iterator pl = journal.period_entries.begin();
+
+ unsigned long pos = 0;
+
+ format_t hdr_fmt(write_hdr_format);
+ boost::filesystem::ifstream in(found);
+
+ while (! in.eof()) {
+ entry_base_t * base = NULL;
+ if (el != journal.entries.end() && pos == (*el)->beg_pos) {
+ hdr_fmt.format(out, **el);
+ base = *el++;
+ }
+ else if (al != journal.auto_entries.end() && pos == (*al)->beg_pos) {
+ out << "= " << (*al)->predicate.predicate.text() << '\n';
+ base = *al++;
+ }
+ else if (pl != journal.period_entries.end() && pos == (*pl)->beg_pos) {
+ out << "~ " << (*pl)->period_string << '\n';
+ base = *pl++;
+ }
+
+ char c;
+ if (base) {
+ foreach (xact_t * xact, base->xacts) {
+ if (! xact->has_flags(XACT_AUTO)) {
+ xact->xdata().add_flags(XACT_EXT_TO_DISPLAY);
+ (*formatter)(*xact);
+ }
+ }
+ formatter->flush();
+
+ while (pos < base->end_pos) {
+ in.get(c);
+ pos = static_cast<unsigned long>(in.tellg()); // pos++;
+ }
+ } else {
+ in.get(c);
+ pos = static_cast<unsigned long>(in.tellg()); // pos++;
+ out.put(c);
+ }
+ }
+}
+
+} // namespace ledger
diff --git a/src/textual.h b/src/textual.h
new file mode 100644
index 00000000..8064d0db
--- /dev/null
+++ b/src/textual.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TEXTUAL_H
+#define _TEXTUAL_H
+
+#include "journal.h"
+#include "handler.h"
+
+namespace ledger {
+
+class textual_parser_t : public journal_t::parser_t
+{
+public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+};
+
+xact_t * parse_xact_text(char * line, account_t * account);
+xact_t * parse_xact(std::istream& in, account_t * account);
+
+void write_textual_journal(journal_t& journal,
+ const path& pathname,
+ xact_handler_ptr& formatter,
+ const string& write_hdr_format,
+ std::ostream& out);
+
+#if 0
+class include_context : public file_context
+{
+ public:
+ include_context(const path& file, unsigned long line,
+ const string& desc = "") throw()
+ : file_context(file, line, desc) {}
+ virtual ~include_context() throw() {}
+
+ virtual void describe(std::ostream& out) const throw() {
+ if (! desc.empty())
+ out << desc << ": ";
+ out << "\"" << file.string() << "\", line " << line << ":"
+ << std::endl;
+ }
+};
+#endif
+
+} // namespace ledger
+
+#endif // _TEXTUAL_H
diff --git a/src/times.cc b/src/times.cc
new file mode 100644
index 00000000..97c0c242
--- /dev/null
+++ b/src/times.cc
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "utils.h" // this brings in times.h
+
+namespace ledger {
+
+namespace {
+#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK
+ const ptime time_now = boost::posix_time::microsec_clock::universal_time();
+#else
+ const ptime time_now = boost::posix_time::second_clock::universal_time();
+#endif
+ const date date_now = boost::gregorian::day_clock::universal_day();
+}
+
+const datetime_t& current_time(time_now);
+const date_t& current_date(date_now);
+ int current_year(current_date.year());
+
+namespace {
+ const char * formats[] = {
+ "%y/%m/%d",
+ "%Y/%m/%d",
+ "%m/%d",
+ "%y.%m.%d",
+ "%Y.%m.%d",
+ "%m.%d",
+ "%y-%m-%d",
+ "%Y-%m-%d",
+ "%m-%d",
+ "%a",
+ "%A",
+ "%b",
+ "%B",
+ "%Y",
+ NULL
+ };
+}
+
+optional<string> input_date_format;
+string output_date_format = "%Y/%m/%d";
+
+namespace {
+ bool parse_date_mask(const char * date_str, std::tm& result)
+ {
+ if (input_date_format) {
+ std::memset(&result, -1, sizeof(std::tm));
+ if (strptime(date_str, input_date_format->c_str(), &result))
+ return true;
+ }
+ for (const char ** f = formats; *f; f++) {
+ std::memset(&result, -1, sizeof(std::tm));
+ if (strptime(date_str, *f, &result))
+ return true;
+ }
+ return false;
+ }
+
+ bool parse_date(const char * date_str, std::tm& result, const int year)
+ {
+ if (! parse_date_mask(date_str, result))
+ return false;
+
+ result.tm_hour = 0;
+ result.tm_min = 0;
+ result.tm_sec = 0;
+
+ if (result.tm_year == -1)
+ result.tm_year = ((year == -1) ? current_year : year) - 1900;
+
+ if (result.tm_mon == -1)
+ result.tm_mon = 0;
+
+ if (result.tm_mday == -1)
+ result.tm_mday = 1;
+
+ return true;
+ }
+
+ bool quick_parse_date(const char * date_str, std::tm& result)
+ {
+ return parse_date(date_str, result, current_year);
+ }
+}
+
+datetime_t parse_datetime(const char * str)
+{
+ std::tm when;
+ // jww (2008-08-01): This needs to look for HH:MM:SS as well.
+ quick_parse_date(str, when);
+ return posix_time::ptime_from_tm(when);
+}
+
+date_t parse_date(const char * str)
+{
+ std::tm when;
+ quick_parse_date(str, when);
+ return gregorian::date_from_tm(when);
+}
+
+date_t interval_t::first(const optional<date_t>& moment) const
+{
+ if (! is_valid(begin))
+ throw_(date_error,
+ "Use of interval_t::first() with specifying a range start");
+
+ date_t quant(begin);
+
+ if (! advanced)
+ advanced = true;
+
+ if (moment && *moment > quant) {
+ // Find an efficient starting point for the upcoming while loop.
+ // We want a date early enough that the range will be correct, but
+ // late enough that we don't spend hundreds of thousands of loops
+ // skipping through time.
+
+ date_t quant(moment->year(), gregorian::Jan, 1);
+ date_t temp;
+ while (*moment >= (temp = increment(quant))) {
+ if (quant == temp)
+ break;
+ quant = temp;
+ }
+ }
+ return quant;
+}
+
+date_t interval_t::increment(const date_t& moment) const
+{
+ date_t future(moment);
+
+ if (years) future += gregorian::years(years);
+ if (months) future += gregorian::months(months);
+ if (days) future += gregorian::days(days);
+
+ return future;
+}
+
+namespace {
+ void parse_inclusion_specifier(const string& word,
+ date_t * begin, date_t * end)
+ {
+ struct std::tm when;
+
+ if (! parse_date_mask(word.c_str(), when))
+ throw_(date_error, "Could not parse date mask: " << word);
+
+ when.tm_hour = 0;
+ when.tm_min = 0;
+ when.tm_sec = 0;
+ when.tm_isdst = -1;
+
+ bool saw_year = true;
+ bool saw_mon = true;
+ bool saw_day = true;
+
+ if (when.tm_year == -1) {
+ when.tm_year = current_year - 1900;
+ saw_year = false;
+ }
+ if (when.tm_mon == -1) {
+ when.tm_mon = 0;
+ saw_mon = false;
+ } else {
+ saw_year = false; // don't increment by year if month used
+ }
+ if (when.tm_mday == -1) {
+ when.tm_mday = 1;
+ saw_day = false;
+ } else {
+ saw_mon = false; // don't increment by month if day used
+ saw_year = false; // don't increment by year if day used
+ }
+
+ if (begin) {
+ *begin = gregorian::date_from_tm(when);
+ if (end)
+ *end = interval_t(saw_day ? 1 : 0, saw_mon ? 1 : 0,
+ saw_year ? 1 : 0).increment(*begin);
+ }
+ else if (end) {
+ *end = gregorian::date_from_tm(when);
+ }
+ }
+
+ inline void read_lower_word(std::istream& in, string& word) {
+ in >> word;
+ for (int i = 0, l = word.length(); i < l; i++)
+ word[i] = std::tolower(word[i]);
+ }
+
+ void parse_date_words(std::istream& in, string& word,
+ date_t * begin, date_t * end)
+ {
+ string type;
+
+ bool mon_spec = false;
+ char buf[32];
+
+ if (word == "this" || word == "last" || word == "next") {
+ type = word;
+ if (! in.eof())
+ read_lower_word(in, word);
+ else
+ word = "month";
+ } else {
+ type = "this";
+ }
+
+ if (word == "month") {
+ time_t now = to_time_t(current_time);
+ std::strftime(buf, 31, "%B", localtime(&now));
+ word = buf;
+ mon_spec = true;
+ }
+ else if (word == "year") {
+ std::sprintf(buf, "%04d", current_year);
+ word = buf;
+ }
+
+ parse_inclusion_specifier(word, begin, end);
+
+ if (type == "last") {
+ if (mon_spec) {
+ if (begin)
+ *begin = interval_t(0, -1, 0).increment(*begin);
+ if (end)
+ *end = interval_t(0, -1, 0).increment(*end);
+ } else {
+ if (begin)
+ *begin = interval_t(0, 0, -1).increment(*begin);
+ if (end)
+ *end = interval_t(0, 0, -1).increment(*end);
+ }
+ }
+ else if (type == "next") {
+ if (mon_spec) {
+ if (begin)
+ *begin = interval_t(0, 1, 0).increment(*begin);
+ if (end)
+ *end = interval_t(0, 1, 0).increment(*end);
+ } else {
+ if (begin)
+ *begin = interval_t(0, 0, 1).increment(*begin);
+ if (end)
+ *end = interval_t(0, 0, 1).increment(*end);
+ }
+ }
+ }
+}
+
+void interval_t::parse(std::istream& in)
+{
+ string word;
+
+ while (! in.eof()) {
+ read_lower_word(in, word);
+ if (word == "every") {
+ read_lower_word(in, word);
+ if (std::isdigit(word[0])) {
+ int quantity = std::atol(word.c_str());
+ read_lower_word(in, word);
+ if (word == "days")
+ days = quantity;
+ else if (word == "weeks")
+ days = 7 * quantity;
+ else if (word == "months")
+ months = quantity;
+ else if (word == "quarters")
+ months = 3 * quantity;
+ else if (word == "years")
+ years = quantity;
+ }
+ else if (word == "day")
+ days = 1;
+ else if (word == "week")
+ days = 7;
+ else if (word == "month")
+ months = 1;
+ else if (word == "quarter")
+ months = 3;
+ else if (word == "year")
+ years = 1;
+ }
+ else if (word == "daily")
+ days = 1;
+ else if (word == "weekly")
+ days = 7;
+ else if (word == "biweekly")
+ days = 14;
+ else if (word == "monthly")
+ months = 1;
+ else if (word == "bimonthly")
+ months = 2;
+ else if (word == "quarterly")
+ months = 3;
+ else if (word == "yearly")
+ years = 1;
+ else if (word == "this" || word == "last" || word == "next") {
+ parse_date_words(in, word, &begin, &end);
+ }
+ else if (word == "in") {
+ read_lower_word(in, word);
+ parse_date_words(in, word, &begin, &end);
+ }
+ else if (word == "from" || word == "since") {
+ read_lower_word(in, word);
+ parse_date_words(in, word, &begin, NULL);
+ }
+ else if (word == "to" || word == "until") {
+ read_lower_word(in, word);
+ parse_date_words(in, word, NULL, &end);
+ }
+ else {
+ parse_inclusion_specifier(word, &begin, &end);
+ }
+ }
+}
+
+} // namespace ledger
diff --git a/src/times.h b/src/times.h
new file mode 100644
index 00000000..306477f5
--- /dev/null
+++ b/src/times.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TIMES_H
+#define _TIMES_H
+
+namespace ledger {
+
+DECLARE_EXCEPTION(datetime_error, std::runtime_error);
+DECLARE_EXCEPTION(date_error, std::runtime_error);
+
+typedef boost::posix_time::ptime datetime_t;
+typedef datetime_t::time_duration_type time_duration_t;
+
+inline bool is_valid(const datetime_t& moment) {
+ return ! moment.is_not_a_date_time();
+}
+
+typedef boost::gregorian::date date_t;
+typedef boost::gregorian::date_duration date_duration_t;
+
+inline bool is_valid(const date_t& moment) {
+ return ! moment.is_not_a_date();
+}
+
+extern const datetime_t& current_time;
+extern const date_t& current_date;
+extern int current_year;
+extern optional<string> input_date_format;
+extern string output_date_format;
+
+inline datetime_t parse_datetime(const string& str) {
+ return parse_datetime(str.c_str());
+}
+datetime_t parse_datetime(const char * str);
+
+inline date_t parse_date(const string& str) {
+ return parse_date(str.c_str());
+}
+date_t parse_date(const char * str);
+
+inline std::time_t to_time_t(const ptime& t)
+{
+ if( t == posix_time::neg_infin )
+ return 0;
+ else if( t == posix_time::pos_infin )
+ return LONG_MAX;
+ ptime start(date(1970,1,1));
+ return (t-start).total_seconds();
+}
+
+inline string format_datetime(const datetime_t& when)
+{
+ char buf[256];
+ time_t moment = to_time_t(when);
+ std::strftime(buf, 255, (output_date_format + " %H:%M:%S").c_str(),
+ std::localtime(&moment));
+ return buf;
+}
+
+inline string format_date(const date_t& when,
+ const optional<string>& format = none)
+{
+ if (format) {
+ char buf[256];
+ std::tm moment = gregorian::to_tm(when);
+ std::strftime(buf, 255, format->c_str(), &moment);
+ return buf;
+ } else {
+ return to_iso_extended_string(when);
+ }
+}
+
+struct interval_t
+{
+ int years;
+ int months;
+ int days;
+ date_t begin;
+ date_t end;
+
+ mutable bool advanced;
+
+ interval_t(int _days = 0, int _months = 0, int _years = 0,
+ const date_t& _begin = date_t(),
+ const date_t& _end = date_t())
+ : years(_years), months(_months), days(_days),
+ begin(_begin), end(_end), advanced(false) {
+ TRACE_CTOR(interval_t, "int, int, int, const date_t&, const date_t&");
+ }
+ interval_t(const interval_t& other)
+ : years(other.years),
+ months(other.months),
+ days(other.days),
+ begin(other.begin),
+ end(other.end),
+ advanced(other.advanced) {
+ TRACE_CTOR(interval_t, "copy");
+ }
+ interval_t(const string& desc)
+ : years(0), months(0), days(0), begin(), end(), advanced(false) {
+ TRACE_CTOR(interval_t, "const string&");
+ std::istringstream stream(desc);
+ parse(stream);
+ }
+
+ ~interval_t() throw() {
+ TRACE_DTOR(interval_t);
+ }
+
+ operator bool() const {
+ return years != 0 || months != 0 || days != 0;
+ }
+
+ void start(const date_t& moment) {
+ begin = first(moment);
+ }
+ date_t first(const optional<date_t>& moment = none) const;
+ date_t increment(const date_t&) const;
+
+ void parse(std::istream& in);
+};
+
+} // namespace ledger
+
+#endif // _TIMES_H
diff --git a/src/token.cc b/src/token.cc
new file mode 100644
index 00000000..d1d70a38
--- /dev/null
+++ b/src/token.cc
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "token.h"
+#include "parser.h"
+
+namespace ledger {
+
+void expr_t::token_t::parse_ident(std::istream& in)
+{
+ if (in.eof()) {
+ kind = TOK_EOF;
+ return;
+ }
+ assert(in.good());
+
+ char c = peek_next_nonws(in);
+
+ if (in.eof()) {
+ kind = TOK_EOF;
+ return;
+ }
+ assert(in.good());
+
+ kind = IDENT;
+ length = 0;
+
+ char buf[256];
+ READ_INTO_(in, buf, 255, c, length,
+ std::isalnum(c) || c == '_' || c == '.' || c == '-');
+
+ switch (buf[0]) {
+#if 0
+ case 'a':
+ if (std::strcmp(buf, "and") == 0)
+ kind = KW_AND;
+ break;
+ case 'd':
+ if (std::strcmp(buf, "div") == 0)
+ kind = KW_DIV;
+ break;
+ case 'e':
+ if (std::strcmp(buf, "eq") == 0)
+ kind = EQUAL;
+ break;
+#endif
+ case 'f':
+ if (std::strcmp(buf, "false") == 0) {
+ kind = VALUE;
+ value = false;
+ }
+ break;
+#if 0
+ case 'g':
+ if (std::strcmp(buf, "gt") == 0)
+ kind = GREATER;
+ else if (std::strcmp(buf, "ge") == 0)
+ kind = GREATEREQ;
+ break;
+ case 'i':
+ if (std::strcmp(buf, "is") == 0)
+ kind = EQUAL;
+ break;
+ case 'l':
+ if (std::strcmp(buf, "lt") == 0)
+ kind = LESS;
+ else if (std::strcmp(buf, "le") == 0)
+ kind = LESSEQ;
+ break;
+ case 'm':
+ if (std::strcmp(buf, "mod") == 0)
+ kind = KW_MOD;
+ break;
+ case 'n':
+ if (std::strcmp(buf, "ne") == 0)
+ kind = NEQUAL;
+ break;
+ case 'o':
+ if (std::strcmp(buf, "or") == 0)
+ kind = KW_OR;
+ break;
+#endif
+ case 't':
+ if (std::strcmp(buf, "true") == 0) {
+ kind = VALUE;
+ value = true;
+ }
+ break;
+ }
+
+ if (kind == IDENT)
+ value.set_string(buf);
+}
+
+void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
+{
+ if (in.eof()) {
+ kind = TOK_EOF;
+ return;
+ }
+ assert(in.good());
+
+ char c = peek_next_nonws(in);
+
+ if (in.eof()) {
+ kind = TOK_EOF;
+ return;
+ }
+ assert(in.good());
+
+ symbol[0] = c;
+ symbol[1] = '\0';
+
+ length = 1;
+
+ switch (c) {
+ case '&':
+ in.get(c);
+ kind = KW_AND;
+ break;
+ case '|':
+ in.get(c);
+ kind = KW_OR;
+ break;
+
+ case '(':
+ in.get(c);
+ kind = LPAREN;
+ break;
+ case ')':
+ in.get(c);
+ kind = RPAREN;
+ break;
+
+ case '[': {
+ in.get(c);
+
+ char buf[256];
+ READ_INTO_(in, buf, 255, c, length, c != ']');
+ if (c != ']')
+ expected(']', c);
+
+ in.get(c);
+ length++;
+
+ interval_t timespan(buf);
+ kind = VALUE;
+ value = timespan.first();
+ break;
+ }
+
+ case '\'':
+ case '"': {
+ char delim;
+ in.get(delim);
+ char buf[4096];
+ READ_INTO_(in, buf, 4095, c, length, c != delim);
+ if (c != delim)
+ expected(delim, c);
+ in.get(c);
+ length++;
+ kind = VALUE;
+ value.set_string(buf);
+ break;
+ }
+
+ case '{': {
+ in.get(c);
+ amount_t temp;
+ temp.parse(in, AMOUNT_PARSE_NO_MIGRATE);
+ in.get(c);
+ if (c != '}')
+ expected('}', c);
+ length++;
+ kind = VALUE;
+ value = temp;
+ break;
+ }
+
+ case '!':
+ in.get(c);
+ c = in.peek();
+ if (c == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = NEQUAL;
+ length = 2;
+ break;
+ }
+ kind = EXCLAM;
+ break;
+
+ case '-':
+ in.get(c);
+ kind = MINUS;
+ break;
+ case '+':
+ in.get(c);
+ kind = PLUS;
+ break;
+
+ case '*':
+ in.get(c);
+ kind = STAR;
+ break;
+
+ case '?':
+ in.get(c);
+ kind = QUERY;
+ break;
+
+ case ':':
+ in.get(c);
+ kind = COLON;
+ break;
+
+ case '/': {
+ in.get(c);
+
+ // Read in the regexp
+ char buf[256];
+ READ_INTO_(in, buf, 255, c, length, c != '/');
+ if (c != '/')
+ expected('/', c);
+ in.get(c);
+ length++;
+
+ kind = MASK;
+ value.set_string(buf);
+ break;
+ }
+
+ case '=':
+ in.get(c);
+ c = in.peek();
+ if (c == '~') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = MATCH;
+ length = 2;
+ break;
+ }
+ kind = EQUAL;
+ break;
+
+ case '<':
+ in.get(c);
+ if (in.peek() == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = LESSEQ;
+ length = 2;
+ break;
+ }
+ kind = LESS;
+ break;
+
+ case '>':
+ in.get(c);
+ if (in.peek() == '=') {
+ in.get(c);
+ symbol[1] = c;
+ symbol[2] = '\0';
+ kind = GREATEREQ;
+ length = 2;
+ break;
+ }
+ kind = GREATER;
+ break;
+
+ case ',':
+ in.get(c);
+ kind = COMMA;
+ break;
+
+ default: {
+ amount_t temp;
+ unsigned long pos = 0;
+
+ // When in relaxed parsing mode, we want to migrate commodity
+ // flags so that any precision specified by the user updates the
+ // current maximum displayed precision.
+ pos = static_cast<unsigned long>(in.tellg());
+
+ amount_t::flags_t parse_flags = 0;
+ if (pflags & EXPR_PARSE_NO_MIGRATE)
+ parse_flags |= AMOUNT_PARSE_NO_MIGRATE;
+ if (pflags & EXPR_PARSE_NO_REDUCE)
+ parse_flags |= AMOUNT_PARSE_NO_REDUCE;
+
+ if (! temp.parse(in, parse_flags | AMOUNT_PARSE_SOFT_FAIL)) {
+ // If the amount had no commodity, it must be an unambiguous
+ // variable reference
+
+ in.clear();
+ in.seekg(pos, std::ios::beg);
+
+ c = in.peek();
+ assert(! (std::isdigit(c) || c == '.'));
+ parse_ident(in);
+ } else {
+ kind = VALUE;
+ value = temp;
+ }
+ break;
+ }
+ }
+}
+
+void expr_t::token_t::rewind(std::istream& in)
+{
+ for (unsigned int i = 0; i < length; i++)
+ in.unget();
+}
+
+
+void expr_t::token_t::unexpected()
+{
+ switch (kind) {
+ case TOK_EOF:
+ throw_(parse_error, "Unexpected end of expression");
+ case IDENT:
+ throw_(parse_error, "Unexpected symbol '" << value << "'");
+ case VALUE:
+ throw_(parse_error, "Unexpected value '" << value << "'");
+ default:
+ throw_(parse_error, "Unexpected operator '" << symbol << "'");
+ }
+}
+
+void expr_t::token_t::expected(char wanted, char c)
+{
+ if (c == '\0') {
+ if (wanted)
+ throw_(parse_error, "Missing '" << wanted << "'");
+ else
+ throw_(parse_error, "Unexpected end");
+ } else {
+ if (wanted)
+ throw_(parse_error, "Invalid char '" << c
+ << "' (wanted '" << wanted << "')");
+ else
+ throw_(parse_error, "Invalid char '" << c << "'");
+ }
+}
+
+} // namespace ledger
diff --git a/src/token.h b/src/token.h
new file mode 100644
index 00000000..04ca39c1
--- /dev/null
+++ b/src/token.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TOKEN_H
+#define _TOKEN_H
+
+#include "expr.h"
+
+namespace ledger {
+
+struct expr_t::token_t : public noncopyable
+{
+ enum kind_t {
+ VALUE, // any kind of literal value
+ IDENT, // [A-Za-z_][-A-Za-z0-9_:]*
+ MASK, // /regexp/
+
+ LPAREN, // (
+ RPAREN, // )
+
+ EQUAL, // ==
+ NEQUAL, // !=
+ LESS, // <
+ LESSEQ, // <=
+ GREATER, // >
+ GREATEREQ, // >=
+
+ ASSIGN, // =
+ MATCH, // =~
+ MINUS, // -
+ PLUS, // +
+ STAR, // *
+ KW_DIV, // /
+
+ EXCLAM, // !
+ KW_AND, // &
+ KW_OR, // |
+ KW_MOD, // %
+
+ QUERY, // ?
+ COLON, // :
+
+ COMMA, // ,
+
+ TOK_EOF,
+ UNKNOWN
+
+ } kind;
+
+ char symbol[3];
+ value_t value;
+ std::size_t length;
+
+ explicit token_t() : kind(UNKNOWN), length(0) {
+ TRACE_CTOR(token_t, "");
+ }
+ ~token_t() throw() {
+ TRACE_DTOR(token_t);
+ }
+
+ token_t& operator=(const token_t& other) {
+ if (&other == this)
+ return *this;
+ assert(false);
+ return *this;
+ }
+
+ void clear() {
+ kind = UNKNOWN;
+ length = 0;
+ value = NULL_VALUE;
+
+ symbol[0] = '\0';
+ symbol[1] = '\0';
+ symbol[2] = '\0';
+ }
+
+ void parse_ident(std::istream& in);
+ void next(std::istream& in, const uint_least8_t flags);
+ void rewind(std::istream& in);
+ void unexpected();
+
+ static void expected(char wanted, char c = '\0');
+};
+
+} // namespace ledger
+
+#endif // _TOKEN_H
diff --git a/src/utils.cc b/src/utils.cc
new file mode 100644
index 00000000..5ac5cef0
--- /dev/null
+++ b/src/utils.cc
@@ -0,0 +1,716 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "utils.h"
+
+/**********************************************************************
+ *
+ * Assertions
+ */
+
+#if defined(ASSERTS_ON)
+
+namespace ledger {
+
+DECLARE_EXCEPTION(assertion_failed, std::logic_error);
+
+void debug_assert(const string& reason,
+ const string& func,
+ const string& file,
+ unsigned long line)
+{
+ std::ostringstream buf;
+ buf << "Assertion failed in \"" << file << "\", line " << line
+ << ": " << func << ": " << reason;
+ throw assertion_failed(buf.str());
+}
+
+} // namespace ledger
+
+#endif
+
+/**********************************************************************
+ *
+ * Verification (basically, very slow asserts)
+ */
+
+#if defined(VERIFY_ON)
+
+namespace ledger {
+
+bool verify_enabled = false;
+
+typedef std::pair<std::string, std::size_t> allocation_pair;
+typedef std::map<void *, allocation_pair> live_memory_map;
+typedef std::multimap<void *, allocation_pair> live_objects_map;
+
+typedef std::pair<unsigned int, std::size_t> count_size_pair;
+typedef std::map<std::string, count_size_pair> object_count_map;
+
+static live_memory_map * live_memory = NULL;
+static object_count_map * live_memory_count = NULL;
+static object_count_map * total_memory_count = NULL;
+
+static bool memory_tracing_active = false;
+
+static live_objects_map * live_objects = NULL;
+static object_count_map * live_object_count = NULL;
+static object_count_map * total_object_count = NULL;
+static object_count_map * total_ctor_count = NULL;
+
+void initialize_memory_tracing()
+{
+ memory_tracing_active = false;
+
+ live_memory = new live_memory_map;
+ live_memory_count = new object_count_map;
+ total_memory_count = new object_count_map;
+
+ live_objects = new live_objects_map;
+ live_object_count = new object_count_map;
+ total_object_count = new object_count_map;
+ total_ctor_count = new object_count_map;
+
+ memory_tracing_active = true;
+}
+
+void shutdown_memory_tracing()
+{
+ memory_tracing_active = false;
+
+ if (live_objects) {
+ IF_DEBUG("memory.counts")
+ report_memory(std::cerr, true);
+ else IF_DEBUG("memory.counts.live")
+ report_memory(std::cerr);
+ else if (live_objects->size() > 0)
+ report_memory(std::cerr);
+ }
+
+ checked_delete(live_memory); live_memory = NULL;
+ checked_delete(live_memory_count); live_memory_count = NULL;
+ checked_delete(total_memory_count); total_memory_count = NULL;
+
+ checked_delete(live_objects); live_objects = NULL;
+ checked_delete(live_object_count); live_object_count = NULL;
+ checked_delete(total_object_count); total_object_count = NULL;
+ checked_delete(total_ctor_count); total_ctor_count = NULL;
+}
+
+inline void add_to_count_map(object_count_map& the_map,
+ const char * name, std::size_t size)
+{
+ object_count_map::iterator k = the_map.find(name);
+ if (k != the_map.end()) {
+ (*k).second.first++;
+ (*k).second.second += size;
+ } else {
+ std::pair<object_count_map::iterator, bool> result =
+ the_map.insert(object_count_map::value_type(name, count_size_pair(1, size)));
+ VERIFY(result.second);
+ }
+}
+
+std::size_t current_memory_size()
+{
+ std::size_t memory_size = 0;
+
+ foreach (const object_count_map::value_type& pair, *live_memory_count)
+ memory_size += pair.second.second;
+
+ return memory_size;
+}
+
+static void trace_new_func(void * ptr, const char * which, std::size_t size)
+{
+ memory_tracing_active = false;
+
+ if (! live_memory) return;
+
+ live_memory->insert
+ (live_memory_map::value_type(ptr, allocation_pair(which, size)));
+
+ add_to_count_map(*live_memory_count, which, size);
+ add_to_count_map(*total_memory_count, which, size);
+ add_to_count_map(*total_memory_count, "__ALL__", size);
+
+ memory_tracing_active = true;
+}
+
+static void trace_delete_func(void * ptr, const char * which)
+{
+ memory_tracing_active = false;
+
+ if (! live_memory) return;
+
+ // Ignore deletions of memory not tracked, since it's possible that
+ // a user (like boost) allocated a block of memory before memory
+ // tracking began, and then deleted it before memory tracking ended.
+ // If it really is a double-delete, the malloc library on OS/X will
+ // notify me.
+
+ live_memory_map::iterator i = live_memory->find(ptr);
+ if (i == live_memory->end())
+ return;
+
+ std::size_t size = (*i).second.second;
+ VERIFY((*i).second.first == which);
+
+ live_memory->erase(i);
+
+ object_count_map::iterator j = live_memory_count->find(which);
+ VERIFY(j != live_memory_count->end());
+
+ (*j).second.second -= size;
+ if (--(*j).second.first == 0)
+ live_memory_count->erase(j);
+
+ memory_tracing_active = true;
+}
+
+} // namespace ledger
+
+void * operator new(std::size_t size) throw (std::bad_alloc) {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new", size);
+ return ptr;
+}
+void * operator new(std::size_t size, const std::nothrow_t&) throw() {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new", size);
+ return ptr;
+}
+void * operator new[](std::size_t size) throw (std::bad_alloc) {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new[]", size);
+ return ptr;
+}
+void * operator new[](std::size_t size, const std::nothrow_t&) throw() {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new[]", size);
+ return ptr;
+}
+void operator delete(void * ptr) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new");
+ std::free(ptr);
+}
+void operator delete(void * ptr, const std::nothrow_t&) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new");
+ std::free(ptr);
+}
+void operator delete[](void * ptr) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new[]");
+ std::free(ptr);
+}
+void operator delete[](void * ptr, const std::nothrow_t&) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new[]");
+ std::free(ptr);
+}
+
+namespace ledger {
+
+inline void report_count_map(std::ostream& out, object_count_map& the_map)
+{
+ foreach (object_count_map::value_type& pair, the_map)
+ out << " " << std::right << std::setw(12) << pair.second.first
+ << " " << std::right << std::setw(7) << pair.second.second
+ << " " << std::left << pair.first
+ << std::endl;
+}
+
+std::size_t current_objects_size()
+{
+ std::size_t objects_size = 0;
+
+ foreach (const object_count_map::value_type& pair, *live_object_count)
+ objects_size += pair.second.second;
+
+ return objects_size;
+}
+
+void trace_ctor_func(void * ptr, const char * cls_name, const char * args,
+ std::size_t cls_size)
+{
+ memory_tracing_active = false;
+
+ if (! live_objects) return;
+
+ static char name[1024];
+ std::strcpy(name, cls_name);
+ std::strcat(name, "(");
+ std::strcat(name, args);
+ std::strcat(name, ")");
+
+ DEBUG("memory.debug", "TRACE_CTOR " << ptr << " " << name);
+
+ live_objects->insert
+ (live_objects_map::value_type(ptr, allocation_pair(cls_name, cls_size)));
+
+ add_to_count_map(*live_object_count, cls_name, cls_size);
+ add_to_count_map(*total_object_count, cls_name, cls_size);
+ add_to_count_map(*total_object_count, "__ALL__", cls_size);
+ add_to_count_map(*total_ctor_count, name, cls_size);
+
+ memory_tracing_active = true;
+}
+
+void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size)
+{
+ memory_tracing_active = false;
+
+ if (! live_objects) return;
+
+ DEBUG("memory.debug", "TRACE_DTOR " << ptr << " " << cls_name);
+
+ live_objects_map::iterator i = live_objects->find(ptr);
+ if (i == live_objects->end()) {
+ std::cerr << "Attempting to delete " << ptr << " a non-living " << cls_name
+ << std::endl;
+ assert(false);
+ }
+
+ int ptr_count = live_objects->count(ptr);
+ for (int x = 0; x < ptr_count; x++, i++) {
+ if ((*i).second.first == cls_name) {
+ live_objects->erase(i);
+ break;
+ }
+ }
+
+ object_count_map::iterator k = live_object_count->find(cls_name);
+ VERIFY(k != live_object_count->end());
+
+ (*k).second.second -= cls_size;
+ if (--(*k).second.first == 0)
+ live_object_count->erase(k);
+
+ memory_tracing_active = true;
+}
+
+void report_memory(std::ostream& out, bool report_all)
+{
+ if (! live_memory) return;
+
+ if (live_memory_count->size() > 0) {
+ out << "NOTE: There may be memory held by Boost "
+ << "and libstdc++ after ledger::shutdown()" << std::endl;
+ out << "Live memory count:" << std::endl;
+ report_count_map(out, *live_memory_count);
+ }
+
+ if (live_memory->size() > 0) {
+ out << "Live memory:" << std::endl;
+
+ foreach (const live_memory_map::value_type& pair, *live_memory)
+ out << " " << std::right << std::setw(12) << pair.first
+ << " " << std::right << std::setw(7) << pair.second.second
+ << " " << std::left << pair.second.first
+ << std::endl;
+ }
+
+ if (report_all && total_memory_count->size() > 0) {
+ out << "Total memory counts:" << std::endl;
+ report_count_map(out, *total_memory_count);
+ }
+
+ if (live_object_count->size() > 0) {
+ out << "Live object count:" << std::endl;
+ report_count_map(out, *live_object_count);
+ }
+
+ if (live_objects->size() > 0) {
+ out << "Live objects:" << std::endl;
+
+ foreach (const live_objects_map::value_type& pair, *live_objects)
+ out << " " << std::right << std::setw(12) << pair.first
+ << " " << std::right << std::setw(7) << pair.second.second
+ << " " << std::left << pair.second.first
+ << std::endl;
+ }
+
+ if (report_all) {
+ if (total_object_count->size() > 0) {
+ out << "Total object counts:" << std::endl;
+ report_count_map(out, *total_object_count);
+ }
+
+ if (total_ctor_count->size() > 0) {
+ out << "Total constructor counts:" << std::endl;
+ report_count_map(out, *total_ctor_count);
+ }
+ }
+}
+
+
+string::string() : std::string() {
+ TRACE_CTOR(string, "");
+}
+string::string(const string& str) : std::string(str) {
+ TRACE_CTOR(string, "copy");
+}
+string::string(const std::string& str) : std::string(str) {
+ TRACE_CTOR(string, "const std::string&");
+}
+string::string(const int len, char x) : std::string(len, x) {
+ TRACE_CTOR(string, "const int, char");
+}
+string::string(const char * str) : std::string(str) {
+ TRACE_CTOR(string, "const char *");
+}
+string::string(const char * str, const char * end) : std::string(str, end) {
+ TRACE_CTOR(string, "const char *, const char *");
+}
+string::string(const string& str, int x) : std::string(str, x) {
+ TRACE_CTOR(string, "const string&, int");
+}
+string::string(const string& str, int x, int y) : std::string(str, x, y) {
+ TRACE_CTOR(string, "const string&, int, int");
+}
+string::string(const char * str, int x) : std::string(str, x) {
+ TRACE_CTOR(string, "const char *, int");
+}
+string::string(const char * str, int x, int y) : std::string(str, x, y) {
+ TRACE_CTOR(string, "const char *, int, int");
+}
+string::~string() throw() {
+ TRACE_DTOR(string);
+}
+
+} // namespace ledger
+
+#endif // VERIFY_ON
+
+ledger::string empty_string("");
+
+/**********************************************************************
+ *
+ * Logging
+ */
+
+#if defined(LOGGING_ON)
+
+namespace ledger {
+
+log_level_t _log_level = LOG_WARN;
+std::ostream * _log_stream = &std::cerr;
+std::ostringstream _log_buffer;
+
+#if defined(TRACING_ON)
+unsigned int _trace_level;
+#endif
+
+#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK
+#define CURRENT_TIME() boost::posix_time::microsec_clock::universal_time()
+#else
+#define CURRENT_TIME() boost::posix_time::second_clock::universal_time()
+#endif
+
+static inline void stream_memory_size(std::ostream& out, std::size_t size)
+{
+ if (size < 1024)
+ out << size << 'b';
+ else if (size < (1024 * 1024))
+ out << (double(size) / 1024.0) << 'K';
+ else if (size < (1024 * 1024 * 1024))
+ out << (double(size) / (1024.0 * 1024.0)) << 'M';
+ else
+ out << (double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G';
+}
+
+static bool logger_has_run = false;
+static ptime logger_start;
+
+bool logger_func(log_level_t level)
+{
+ unsigned long appender = 0;
+
+ if (! logger_has_run) {
+ logger_has_run = true;
+ logger_start = CURRENT_TIME();
+
+ IF_VERIFY()
+ *_log_stream << " TIME OBJSZ MEMSZ" << std::endl;
+
+ appender = (logger_start - current_time).total_milliseconds();
+ }
+
+ *_log_stream << std::right << std::setw(5)
+ << (CURRENT_TIME() - logger_start).total_milliseconds();
+
+ IF_VERIFY() {
+ *_log_stream << std::right << std::setw(6) << std::setprecision(3);
+ stream_memory_size(*_log_stream, current_objects_size());
+ *_log_stream << std::right << std::setw(6) << std::setprecision(3);
+ stream_memory_size(*_log_stream, current_memory_size());
+ }
+
+ *_log_stream << " " << std::left << std::setw(7);
+
+ switch (level) {
+ case LOG_CRIT: *_log_stream << "[CRIT]"; break;
+ case LOG_FATAL: *_log_stream << "[FATAL]"; break;
+ case LOG_ASSERT: *_log_stream << "[ASSRT]"; break;
+ case LOG_ERROR: *_log_stream << "[ERROR]"; break;
+ case LOG_VERIFY: *_log_stream << "[VERFY]"; break;
+ case LOG_WARN: *_log_stream << "[WARN]"; break;
+ case LOG_INFO: *_log_stream << "[INFO]"; break;
+ case LOG_EXCEPT: *_log_stream << "[EXCPT]"; break;
+ case LOG_DEBUG: *_log_stream << "[DEBUG]"; break;
+ case LOG_TRACE: *_log_stream << "[TRACE]"; break;
+
+ case LOG_OFF:
+ case LOG_ALL:
+ assert(false);
+ break;
+ }
+
+ *_log_stream << ' ' << _log_buffer.str();
+
+ if (appender)
+ *_log_stream << " (" << appender << "ms startup)";
+
+ *_log_stream << std::endl;
+
+ _log_buffer.str("");
+
+ return true;
+}
+
+} // namespace ledger
+
+#if defined(DEBUG_ON)
+
+namespace ledger {
+
+optional<std::string> _log_category;
+
+} // namespace ledger
+
+#endif // DEBUG_ON
+#endif // LOGGING_ON
+
+/**********************************************************************
+ *
+ * Timers (allows log entries to specify cumulative time spent)
+ */
+
+#if defined(LOGGING_ON) && defined(TIMERS_ON)
+
+namespace ledger {
+
+struct timer_t
+{
+ log_level_t level;
+ ptime begin;
+ time_duration spent;
+ std::string description;
+ bool active;
+
+ timer_t(log_level_t _level, std::string _description)
+ : level(_level), begin(CURRENT_TIME()),
+ spent(time_duration(0, 0, 0, 0)),
+ description(_description), active(true) {}
+};
+
+typedef std::map<std::string, timer_t> timer_map;
+
+static timer_map timers;
+
+void start_timer(const char * name, log_level_t lvl)
+{
+#if defined(VERIFY_ON)
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ if (i == timers.end()) {
+ timers.insert(timer_map::value_type(name, timer_t(lvl, _log_buffer.str())));
+ } else {
+ assert((*i).second.description == _log_buffer.str());
+ (*i).second.begin = CURRENT_TIME();
+ (*i).second.active = true;
+ }
+ _log_buffer.str("");
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = true;
+#endif
+}
+
+void stop_timer(const char * name)
+{
+#if defined(VERIFY_ON)
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ assert(i != timers.end());
+
+ (*i).second.spent += CURRENT_TIME() - (*i).second.begin;
+ (*i).second.active = false;
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = true;
+#endif
+}
+
+void finish_timer(const char * name)
+{
+#if defined(VERIFY_ON)
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ if (i == timers.end())
+ return;
+
+ time_duration spent = (*i).second.spent;
+ if ((*i).second.active) {
+ spent = CURRENT_TIME() - (*i).second.begin;
+ (*i).second.active = false;
+ }
+
+ _log_buffer << (*i).second.description << ' ';
+
+ bool need_paren =
+ (*i).second.description[(*i).second.description.size() - 1] != ':';
+
+ if (need_paren)
+ _log_buffer << '(';
+
+ _log_buffer << spent.total_milliseconds() << "ms";
+
+ if (need_paren)
+ _log_buffer << ')';
+
+ logger_func((*i).second.level);
+
+ timers.erase(i);
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = true;
+#endif
+}
+
+} // namespace ledger
+
+#endif // LOGGING_ON && TIMERS_ON
+
+/**********************************************************************
+ *
+ * Exception handling
+ */
+
+namespace ledger {
+
+std::ostringstream _desc_buffer;
+std::ostringstream _ctxt_buffer;
+
+} // namespace ledger
+
+/**********************************************************************
+ *
+ * General utility functions
+ */
+
+namespace ledger {
+
+path expand_path(const path& pathname)
+{
+ if (pathname.empty())
+ return pathname;
+
+#if 0
+ // jww (2007-04-30): I need to port this code to use
+ // boost::filesystem::path
+ const char * pfx = NULL;
+ string::size_type pos = pathname.find_first_of('/');
+
+ if (pathname.length() == 1 || pos == 1) {
+ pfx = std::getenv("HOME");
+#ifdef HAVE_GETPWUID
+ if (! pfx) {
+ // Punt. We're trying to expand ~/, but HOME isn't set
+ struct passwd * pw = getpwuid(getuid());
+ if (pw)
+ pfx = pw->pw_dir;
+ }
+#endif
+ }
+#ifdef HAVE_GETPWNAM
+ else {
+ string user(pathname, 1, pos == string::npos ?
+ string::npos : pos - 1);
+ struct passwd * pw = getpwnam(user.c_str());
+ if (pw)
+ pfx = pw->pw_dir;
+ }
+#endif
+
+ // if we failed to find an expansion, return the path unchanged.
+
+ if (! pfx)
+ return pathname;
+
+ string result(pfx);
+
+ if (pos == string::npos)
+ return result;
+
+ if (result.length() == 0 || result[result.length() - 1] != '/')
+ result += '/';
+
+ result += pathname.substr(pos + 1);
+
+ return result;
+#else
+ return pathname;
+#endif
+}
+
+path resolve_path(const path& pathname)
+{
+ path temp = pathname;
+ if (temp.string()[0] == '~')
+ temp = expand_path(temp);
+ temp.normalize();
+ return temp;
+}
+
+} // namespace ledger
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 00000000..60d47ec1
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,578 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file utils.h
+ * @author John Wiegley
+ * @date Sun May 6 21:20:00 2007
+ *
+ * @brief This file contains general utility facilities used by Ledger.
+ *
+ * Ledger has need of the following utility code, which this file
+ * provides or includes in:
+ *
+ * - system headers
+ * - asserts
+ * - verification (basically, "heavy asserts")
+ * - tracing code
+ * - debug logging code
+ * - timing code
+ * - current error context
+ * - exception framework
+ * - date/time type
+ * - supports_flags<> for objects that use flags
+ * - push_variable<> for restoring variable values
+ */
+
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#if defined(DEBUG_MODE)
+#define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE 1
+#define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING 1
+#endif
+
+#include <system.hh>
+
+/**********************************************************************
+ *
+ * Default values
+ */
+
+#if defined(DEBUG_MODE)
+#define VERIFY_ON 1
+#define TRACING_ON 1
+#define DEBUG_ON 1
+#define TIMERS_ON 1
+#elif defined(NDEBUG)
+#define NO_ASSERTS 1
+#define NO_LOGGING 1
+#else
+#define VERIFY_ON 1 // compiled in, use --verify to enable
+#define TRACING_ON 1 // use --trace X to enable
+#define TIMERS_ON 1
+#endif
+
+/**********************************************************************
+ *
+ * Forward declarations
+ */
+
+namespace ledger {
+ using namespace boost;
+
+#if defined(VERIFY_ON)
+ class string;
+#else
+ typedef std::string string;
+#endif
+
+ typedef std::list<string> strings_list;
+
+ typedef posix_time::ptime ptime;
+ typedef ptime::time_duration_type time_duration;
+ typedef gregorian::date date;
+ typedef gregorian::date_duration date_duration;
+ typedef posix_time::seconds seconds;
+
+ typedef boost::filesystem::path path;
+ typedef boost::filesystem::ifstream ifstream;
+ typedef boost::filesystem::ofstream ofstream;
+ typedef boost::filesystem::filesystem_error filesystem_error;
+}
+
+/**********************************************************************
+ *
+ * Assertions
+ */
+
+#ifdef assert
+#undef assert
+#endif
+
+#if ! defined(NO_ASSERTS)
+#define ASSERTS_ON 1
+#endif
+#if defined(ASSERTS_ON)
+
+namespace ledger {
+ void debug_assert(const string& reason, const string& func,
+ const string& file, unsigned long line);
+}
+
+#define assert(x) \
+ ((x) ? ((void)0) : debug_assert(#x, BOOST_CURRENT_FUNCTION, \
+ __FILE__, __LINE__))
+
+#else // ! ASSERTS_ON
+
+#define assert(x)
+
+#endif // ASSERTS_ON
+
+/**********************************************************************
+ *
+ * Verification (basically, very slow asserts)
+ */
+
+#if defined(VERIFY_ON)
+
+namespace ledger {
+
+extern bool verify_enabled;
+
+#define VERIFY(x) (ledger::verify_enabled ? assert(x) : ((void)0))
+#define DO_VERIFY() ledger::verify_enabled
+
+void initialize_memory_tracing();
+void shutdown_memory_tracing();
+
+std::size_t current_memory_size();
+std::size_t current_objects_size();
+
+void trace_ctor_func(void * ptr, const char * cls_name, const char * args,
+ std::size_t cls_size);
+void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size);
+
+#define TRACE_CTOR(cls, args) \
+ (DO_VERIFY() ? \
+ ledger::trace_ctor_func(this, #cls, args, sizeof(cls)) : ((void)0))
+#define TRACE_DTOR(cls) \
+ (DO_VERIFY() ? \
+ ledger::trace_dtor_func(this, #cls, sizeof(cls)) : ((void)0))
+
+void report_memory(std::ostream& out, bool report_all = false);
+
+/**
+ * This string type is a wrapper around std::string that allows us to
+ * trace constructor and destructor calls.
+ */
+class string : public std::string
+{
+public:
+ string();
+ string(const string& str);
+ string(const std::string& str);
+ string(const int len, char x);
+ string(const char * str);
+ string(const char * str, const char * end);
+ string(const string& str, int x);
+ string(const string& str, int x, int y);
+ string(const char * str, int x);
+ string(const char * str, int x, int y);
+ ~string() throw();
+};
+
+inline string operator+(const string& __lhs, const string& __rhs)
+{
+ string __str(__lhs);
+ __str.append(__rhs);
+ return __str;
+}
+
+string operator+(const char* __lhs, const string& __rhs);
+string operator+(char __lhs, const string& __rhs);
+
+inline string operator+(const string& __lhs, const char* __rhs)
+{
+ string __str(__lhs);
+ __str.append(__rhs);
+ return __str;
+}
+
+inline string operator+(const string& __lhs, char __rhs)
+{
+ typedef string __string_type;
+ typedef string::size_type __size_type;
+ __string_type __str(__lhs);
+ __str.append(__size_type(1), __rhs);
+ return __str;
+}
+
+inline bool operator==(const string& __lhs, const string& __rhs)
+{ return __lhs.compare(__rhs) == 0; }
+
+inline bool operator==(const char* __lhs, const string& __rhs)
+{ return __rhs.compare(__lhs) == 0; }
+
+inline bool operator==(const string& __lhs, const char* __rhs)
+{ return __lhs.compare(__rhs) == 0; }
+
+inline bool operator!=(const string& __lhs, const string& __rhs)
+{ return __rhs.compare(__lhs) != 0; }
+
+inline bool operator!=(const char* __lhs, const string& __rhs)
+{ return __rhs.compare(__lhs) != 0; }
+
+inline bool operator!=(const string& __lhs, const char* __rhs)
+{ return __lhs.compare(__rhs) != 0; }
+
+} // namespace ledger
+
+#else // ! VERIFY_ON
+
+#define VERIFY(x)
+#define DO_VERIFY() true
+#define TRACE_CTOR(cls, args)
+#define TRACE_DTOR(cls)
+
+#endif // VERIFY_ON
+
+extern ledger::string empty_string;
+
+#define IF_VERIFY() if (DO_VERIFY())
+
+/**********************************************************************
+ *
+ * Logging
+ */
+
+#if ! defined(NO_LOGGING)
+#define LOGGING_ON 1
+#endif
+#if defined(LOGGING_ON)
+
+namespace ledger {
+
+enum log_level_t {
+ LOG_OFF = 0,
+ LOG_CRIT,
+ LOG_FATAL,
+ LOG_ASSERT,
+ LOG_ERROR,
+ LOG_VERIFY,
+ LOG_WARN,
+ LOG_INFO,
+ LOG_EXCEPT,
+ LOG_DEBUG,
+ LOG_TRACE,
+ LOG_ALL
+};
+
+extern log_level_t _log_level;
+extern std::ostream * _log_stream;
+extern std::ostringstream _log_buffer;
+
+bool logger_func(log_level_t level);
+
+#define LOGGER(cat) \
+ static const char * const _this_category = cat
+
+#if defined(TRACING_ON)
+
+extern unsigned int _trace_level;
+
+#define SHOW_TRACE(lvl) \
+ (ledger::_log_level >= ledger::LOG_TRACE && lvl <= ledger::_trace_level)
+#define TRACE(lvl, msg) \
+ (SHOW_TRACE(lvl) ? \
+ ((ledger::_log_buffer << msg), \
+ ledger::logger_func(ledger::LOG_TRACE)) : false)
+
+#else // TRACING_ON
+
+#define SHOW_TRACE(lvl) false
+#define TRACE(lvl, msg)
+
+#endif // TRACING_ON
+
+#if defined(DEBUG_ON)
+
+extern optional<std::string> _log_category;
+
+inline bool category_matches(const char * cat) {
+ return _log_category && starts_with(cat, *_log_category);
+}
+
+#define SHOW_DEBUG(cat) \
+ (ledger::_log_level >= ledger::LOG_DEBUG && ledger::category_matches(cat))
+#define SHOW_DEBUG_() SHOW_DEBUG(_this_category)
+
+#define DEBUG(cat, msg) \
+ (SHOW_DEBUG(cat) ? \
+ ((ledger::_log_buffer << msg), \
+ ledger::logger_func(ledger::LOG_DEBUG)) : false)
+#define DEBUG_(msg) DEBUG(_this_category, msg)
+
+#else // DEBUG_ON
+
+#define SHOW_DEBUG(cat) false
+#define SHOW_DEBUG_() false
+#define DEBUG(cat, msg)
+#define DEBUG_(msg)
+
+#endif // DEBUG_ON
+
+#define LOG_MACRO(level, msg) \
+ (ledger::_log_level >= level ? \
+ ((ledger::_log_buffer << msg), ledger::logger_func(level)) : false)
+
+#define SHOW_INFO() (ledger::_log_level >= ledger::LOG_INFO)
+#define SHOW_WARN() (ledger::_log_level >= ledger::LOG_WARN)
+#define SHOW_ERROR() (ledger::_log_level >= ledger::LOG_ERROR)
+#define SHOW_FATAL() (ledger::_log_level >= ledger::LOG_FATAL)
+#define SHOW_CRITICAL() (ledger::_log_level >= ledger::LOG_CRIT)
+
+#define INFO(msg) LOG_MACRO(ledger::LOG_INFO, msg)
+#define WARN(msg) LOG_MACRO(ledger::LOG_WARN, msg)
+#define ERROR(msg) LOG_MACRO(ledger::LOG_ERROR, msg)
+#define FATAL(msg) LOG_MACRO(ledger::LOG_FATAL, msg)
+#define CRITICAL(msg) LOG_MACRO(ledger::LOG_CRIT, msg)
+#define EXCEPTION(msg) LOG_MACRO(ledger::LOG_EXCEPT, msg)
+
+} // namespace ledger
+
+#else // ! LOGGING_ON
+
+#define LOGGER(cat)
+
+#define SHOW_TRACE(lvl) false
+#define SHOW_DEBUG(cat) false
+#define SHOW_DEBUG_() false
+#define SHOW_INFO() false
+#define SHOW_WARN() false
+#define SHOW_ERROR() false
+#define SHOW_FATAL() false
+#define SHOW_CRITICAL() false
+
+#define TRACE(lvl, msg)
+#define DEBUG(cat, msg)
+#define DEBUG_(msg)
+#define INFO(msg)
+#define WARN(msg)
+#define ERROR(msg)
+#define FATAL(msg)
+#define CRITICAL(msg)
+
+#endif // LOGGING_ON
+
+#define IF_TRACE(lvl) if (SHOW_TRACE(lvl))
+#define IF_DEBUG(cat) if (SHOW_DEBUG(cat))
+#define IF_DEBUG_() if (SHOW_DEBUG_())
+#define IF_INFO() if (SHOW_INFO())
+#define IF_WARN() if (SHOW_WARN())
+#define IF_ERROR() if (SHOW_ERROR())
+#define IF_FATAL() if (SHOW_FATAL())
+#define IF_CRITICAL() if (SHOW_CRITICAL())
+
+/**********************************************************************
+ *
+ * Timers (allows log entries to specify cumulative time spent)
+ */
+
+#if defined(LOGGING_ON) && defined(TIMERS_ON)
+
+namespace ledger {
+
+void start_timer(const char * name, log_level_t lvl);
+void stop_timer(const char * name);
+void finish_timer(const char * name);
+
+#if defined(TRACING_ON)
+#define TRACE_START(name, lvl, msg) \
+ (SHOW_TRACE(lvl) ? \
+ ((ledger::_log_buffer << msg), \
+ ledger::start_timer(#name, ledger::LOG_TRACE)) : ((void)0))
+#define TRACE_STOP(name, lvl) \
+ (SHOW_TRACE(lvl) ? ledger::stop_timer(#name) : ((void)0))
+#define TRACE_FINISH(name, lvl) \
+ (SHOW_TRACE(lvl) ? ledger::finish_timer(#name) : ((void)0))
+#else
+#define TRACE_START(name, lvl, msg)
+#define TRACE_STOP(name)
+#define TRACE_FINISH(name)
+#endif
+
+#if defined(DEBUG_ON)
+#define DEBUG_START(name, cat, msg) \
+ (SHOW_DEBUG(cat) ? \
+ ((ledger::_log_buffer << msg), \
+ ledger::start_timer(#name, ledger::LOG_DEBUG)) : ((void)0))
+#define DEBUG_START_(name, msg) \
+ DEBUG_START_(name, _this_category, msg)
+#define DEBUG_STOP(name, cat) \
+ (SHOW_DEBUG(cat) ? ledger::stop_timer(#name) : ((void)0))
+#define DEBUG_STOP_(name) \
+ DEBUG_STOP_(name, _this_category)
+#define DEBUG_FINISH(name, cat) \
+ (SHOW_DEBUG(cat) ? ledger::finish_timer(#name) : ((void)0))
+#define DEBUG_FINISH_(name) \
+ DEBUG_FINISH_(name, _this_category)
+#else
+#define DEBUG_START(name, cat, msg)
+#define DEBUG_START_(name, msg)
+#define DEBUG_STOP(name)
+#define DEBUG_FINISH(name)
+#endif
+
+#define INFO_START(name, msg) \
+ (SHOW_INFO() ? \
+ ((ledger::_log_buffer << msg), \
+ ledger::start_timer(#name, ledger::LOG_INFO)) : ((void)0))
+#define INFO_STOP(name) \
+ (SHOW_INFO() ? stop_timer(#name) : ((void)0))
+#define INFO_FINISH(name) \
+ (SHOW_INFO() ? finish_timer(#name) : ((void)0))
+
+} // namespace ledger
+
+#else // ! (LOGGING_ON && TIMERS_ON)
+
+#define TRACE_START(lvl, msg, name)
+#define TRACE_STOP(name)
+#define TRACE_FINISH(name)
+
+#define DEBUG_START(name, msg)
+#define DEBUG_START_(name, cat, msg)
+#define DEBUG_STOP(name)
+#define DEBUG_FINISH(name)
+
+#define INFO_START(name, msg)
+#define INFO_STOP(name)
+#define INFO_FINISH(name)
+
+#endif // TIMERS_ON
+
+/**********************************************************************
+ *
+ * - Exception handling helpers
+ * - Date/time support classes
+ * - General support for objects with "flags"
+ * - Support for scoped execution and variable restoration
+ */
+
+#include "error.h"
+#include "times.h"
+#include "flags.h"
+#include "pushvar.h"
+
+/**********************************************************************
+ *
+ * General utility functions
+ */
+
+#define foreach BOOST_FOREACH
+
+namespace ledger {
+
+template <typename T, typename U>
+inline T& downcast(U& object) {
+ return *polymorphic_downcast<T *>(&object);
+}
+
+path resolve_path(const path& pathname);
+
+#ifdef HAVE_REALPATH
+extern "C" char * realpath(const char *, char resolved_path[]);
+#endif
+
+inline const string& either_or(const string& first,
+ const string& second) {
+ return first.empty() ? second : first;
+}
+
+inline char * skip_ws(char * ptr) {
+ while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
+ ptr++;
+ return ptr;
+}
+
+inline char * next_element(char * buf, bool variable = false) {
+ for (char * p = buf; *p; p++) {
+ if (! (*p == ' ' || *p == '\t'))
+ continue;
+
+ if (! variable) {
+ *p = '\0';
+ return skip_ws(p + 1);
+ }
+ else if (*p == '\t') {
+ *p = '\0';
+ return skip_ws(p + 1);
+ }
+ else if (*(p + 1) == ' ') {
+ *p = '\0';
+ return skip_ws(p + 2);
+ }
+ }
+ return NULL;
+}
+
+inline char peek_next_nonws(std::istream& in) {
+ char c = in.peek();
+ while (! in.eof() && std::isspace(c)) {
+ in.get(c);
+ c = in.peek();
+ }
+ return c;
+}
+
+#define READ_INTO(str, targ, size, var, cond) { \
+ char * _p = targ; \
+ var = str.peek(); \
+ while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \
+ str.get(var); \
+ if (str.eof()) \
+ break; \
+ if (var == '\\') { \
+ str.get(var); \
+ if (in.eof()) \
+ break; \
+ } \
+ *_p++ = var; \
+ var = str.peek(); \
+ } \
+ *_p = '\0'; \
+}
+
+#define READ_INTO_(str, targ, size, var, idx, cond) { \
+ char * _p = targ; \
+ var = str.peek(); \
+ while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \
+ str.get(var); \
+ if (str.eof()) \
+ break; \
+ idx++; \
+ if (var == '\\') { \
+ str.get(var); \
+ if (in.eof()) \
+ break; \
+ idx++; \
+ } \
+ *_p++ = var; \
+ var = str.peek(); \
+ } \
+ *_p = '\0'; \
+}
+
+} // namespace ledger
+
+#endif // _UTILS_H
diff --git a/src/value.cc b/src/value.cc
new file mode 100644
index 00000000..16d114ca
--- /dev/null
+++ b/src/value.cc
@@ -0,0 +1,1777 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "value.h"
+#include "binary.h"
+
+namespace ledger {
+
+intrusive_ptr<value_t::storage_t> value_t::true_value;
+intrusive_ptr<value_t::storage_t> value_t::false_value;
+
+value_t::storage_t& value_t::storage_t::operator=(const value_t::storage_t& rhs)
+{
+ type = rhs.type;
+
+ switch (type) {
+ case DATETIME:
+ new(reinterpret_cast<datetime_t *>(data))
+ datetime_t(*reinterpret_cast<datetime_t *>
+ (const_cast<char *>(rhs.data)));
+ break;
+
+ case DATE:
+ new(reinterpret_cast<date_t *>(data))
+ date_t(*reinterpret_cast<date_t *>(const_cast<char *>(rhs.data)));
+ break;
+
+ case AMOUNT:
+ new(reinterpret_cast<amount_t *>(data))
+ amount_t(*reinterpret_cast<amount_t *>
+ (const_cast<char *>(rhs.data)));
+ break;
+
+ case BALANCE:
+ *reinterpret_cast<balance_t **>(data) =
+ new balance_t(**reinterpret_cast<balance_t **>
+ (const_cast<char *>(rhs.data)));
+ break;
+
+ case BALANCE_PAIR:
+ *reinterpret_cast<balance_pair_t **>(data) =
+ new balance_pair_t(**reinterpret_cast<balance_pair_t **>
+ (const_cast<char *>(rhs.data)));
+ break;
+
+ case STRING:
+ new(reinterpret_cast<string *>(data))
+ string(*reinterpret_cast<string *>(const_cast<char *>(rhs.data)));
+ break;
+
+ case SEQUENCE:
+ *reinterpret_cast<sequence_t **>(data) =
+ new sequence_t(**reinterpret_cast<sequence_t **>
+ (const_cast<char *>(rhs.data)));
+ break;
+
+ default:
+ // The rest are fundamental types, which can be copied using
+ // std::memcpy
+ std::memcpy(data, rhs.data, sizeof(data));
+ break;
+ }
+
+ return *this;
+}
+
+void value_t::storage_t::destroy()
+{
+ switch (type) {
+ case AMOUNT:
+ reinterpret_cast<amount_t *>(data)->~amount_t();
+ break;
+ case BALANCE:
+ checked_delete(*reinterpret_cast<balance_t **>(data));
+ break;
+ case BALANCE_PAIR:
+ checked_delete(*reinterpret_cast<balance_pair_t **>(data));
+ break;
+ case STRING:
+ reinterpret_cast<string *>(data)->~string();
+ break;
+ case SEQUENCE:
+ checked_delete(*reinterpret_cast<sequence_t **>(data));
+ break;
+ case POINTER:
+ reinterpret_cast<boost::any *>(data)->~any();
+ break;
+
+ default:
+ break;
+ }
+ type = VOID;
+}
+
+void value_t::initialize()
+{
+#if defined(DEBUG_ON)
+ LOGGER("value.initialize");
+#endif
+
+ true_value = new storage_t;
+ true_value->type = BOOLEAN;
+ *reinterpret_cast<bool *>(true_value->data) = true;
+
+ false_value = new storage_t;
+ false_value->type = BOOLEAN;
+ *reinterpret_cast<bool *>(false_value->data) = false;
+
+#if 0
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(bool));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(datetime_t));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(date_t));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(long));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(amount_t));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_t *));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(balance_pair_t *));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(string));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(sequence_t *));
+ BOOST_STATIC_ASSERT(sizeof(amount_t) >= sizeof(boost::any));
+#endif
+
+ DEBUG_(std::setw(3) << std::right << sizeof(bool)
+ << " sizeof(bool)");
+ DEBUG_(std::setw(3) << std::right << sizeof(datetime_t)
+ << " sizeof(datetime_t)");
+ DEBUG_(std::setw(3) << std::right << sizeof(date_t)
+ << " sizeof(date_t)");
+ DEBUG_(std::setw(3) << std::right << sizeof(long)
+ << " sizeof(long)");
+ DEBUG_(std::setw(3) << std::right << sizeof(amount_t)
+ << " sizeof(amount_t)");
+ DEBUG_(std::setw(3) << std::right << sizeof(balance_t *)
+ << " sizeof(balance_t *)");
+ DEBUG_(std::setw(3) << std::right << sizeof(balance_pair_t *)
+ << " sizeof(balance_pair_t *)");
+ DEBUG_(std::setw(3) << std::right << sizeof(string)
+ << " sizeof(string)");
+ DEBUG_(std::setw(3) << std::right << sizeof(sequence_t *)
+ << " sizeof(sequence_t *)");
+ DEBUG_(std::setw(3) << std::right << sizeof(boost::any)
+ << " sizeof(boost::any)");
+}
+
+void value_t::shutdown()
+{
+ true_value = intrusive_ptr<storage_t>();
+ false_value = intrusive_ptr<storage_t>();
+}
+
+void value_t::_dup()
+{
+ assert(storage);
+ if (storage->refc > 1)
+ storage = new storage_t(*storage.get());
+}
+
+value_t::operator bool() const
+{
+ switch (type()) {
+ case BOOLEAN:
+ return as_boolean();
+ case DATETIME:
+ return is_valid(as_datetime());
+ case DATE:
+ return is_valid(as_date());
+ case INTEGER:
+ return as_long();
+ case AMOUNT:
+ return as_amount();
+ case BALANCE:
+ return as_balance();
+ case BALANCE_PAIR:
+ return as_balance_pair();
+ case STRING:
+ return ! as_string().empty();
+ case SEQUENCE:
+ return ! as_sequence().empty();
+ case POINTER:
+ return ! as_any_pointer().empty();
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return 0;
+}
+
+bool value_t::to_boolean() const
+{
+ if (is_boolean()) {
+ return as_boolean();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(BOOLEAN);
+ return temp.as_boolean();
+ }
+}
+
+datetime_t value_t::to_datetime() const
+{
+ if (is_datetime()) {
+ return as_datetime();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(DATETIME);
+ return temp.as_datetime();
+ }
+}
+
+date_t value_t::to_date() const
+{
+ if (is_date()) {
+ return as_date();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(DATE);
+ return temp.as_date();
+ }
+}
+
+long value_t::to_long() const
+{
+ if (is_long()) {
+ return as_long();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(INTEGER);
+ return temp.as_long();
+ }
+}
+
+amount_t value_t::to_amount() const
+{
+ if (is_amount()) {
+ return as_amount();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(AMOUNT);
+ return temp.as_amount();
+ }
+}
+
+balance_t value_t::to_balance() const
+{
+ if (is_balance()) {
+ return as_balance();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(BALANCE);
+ return temp.as_balance();
+ }
+}
+
+balance_pair_t value_t::to_balance_pair() const
+{
+ if (is_balance_pair()) {
+ return as_balance_pair();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(BALANCE_PAIR);
+ return temp.as_balance_pair();
+ }
+}
+
+string value_t::to_string() const
+{
+ if (is_string()) {
+ return as_string();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(STRING);
+ return temp.as_string();
+ }
+}
+
+value_t::sequence_t value_t::to_sequence() const
+{
+ if (is_sequence()) {
+ return as_sequence();
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(SEQUENCE);
+ return temp.as_sequence();
+ }
+}
+
+
+void value_t::in_place_simplify()
+{
+#if defined(DEBUG_ON)
+ LOGGER("amounts.values.simplify");
+#endif
+
+ if (is_realzero()) {
+ DEBUG_("Zeroing type " << static_cast<int>(type()));
+ set_long(0L);
+ return;
+ }
+
+ if (is_balance_pair() &&
+ (! as_balance_pair().cost || as_balance_pair().cost->is_realzero())) {
+ DEBUG_("Reducing balance pair to balance");
+ in_place_cast(BALANCE);
+ }
+
+ if (is_balance() && as_balance().amounts.size() == 1) {
+ DEBUG_("Reducing balance to amount");
+ DEBUG("ledger.value.reduce", "as a balance it looks like: " << *this);
+ in_place_cast(AMOUNT);
+ DEBUG("ledger.value.reduce", "as an amount it looks like: " << *this);
+ }
+
+#if 0
+ if (is_amount() && ! as_amount().has_commodity() &&
+ as_amount().fits_in_long()) {
+ DEBUG_("Reducing amount to integer");
+ in_place_cast(INTEGER);
+ }
+#endif
+}
+
+value_t& value_t::operator+=(const value_t& val)
+{
+ if (is_string()) {
+ if (val.is_string())
+ as_string_lval() += val.as_string();
+ else
+ as_string_lval() += val.to_string();
+ return *this;
+ }
+ else if (is_sequence()) {
+ if (val.is_sequence()) {
+ sequence_t& seq(as_sequence_lval());
+ seq.insert(seq.end(), val.as_sequence().begin(),
+ val.as_sequence().end());
+ } else {
+ as_sequence_lval().push_back(val);
+ }
+ return *this;
+ }
+
+ switch (type()) {
+ case DATETIME:
+ switch (val.type()) {
+ case INTEGER:
+ as_datetime_lval() += date_duration(val.as_long());
+ return *this;
+ case AMOUNT:
+ as_datetime_lval() += date_duration(val.as_amount().to_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case DATE:
+ switch (val.type()) {
+ case INTEGER:
+ as_date_lval() += date_duration_t(val.as_long());
+ return *this;
+ case AMOUNT:
+ as_date_lval() += date_duration_t(val.as_amount().to_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ as_long_lval() += val.as_long();
+ return *this;
+ case AMOUNT:
+ in_place_cast(AMOUNT);
+ as_amount_lval() += val.as_amount();
+ return *this;
+ case BALANCE:
+ in_place_cast(BALANCE);
+ as_balance_lval() += val.as_balance();
+ return *this;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() += val.as_balance_pair();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ if (as_amount().has_commodity()) {
+ in_place_cast(BALANCE);
+ return *this += val;
+ } else {
+ as_amount_lval() += val.as_long();
+ return *this;
+ }
+ break;
+
+ case AMOUNT:
+ if (as_amount().commodity() != val.as_amount().commodity()) {
+ in_place_cast(BALANCE);
+ return *this += val;
+ } else {
+ as_amount_lval() += val.as_amount();
+ return *this;
+ }
+ break;
+
+ case BALANCE:
+ in_place_cast(BALANCE);
+ as_balance_lval() += val.as_balance();
+ return *this;
+
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() += val.as_balance_pair();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_lval() += val.to_amount();
+ return *this;
+ case AMOUNT:
+ as_balance_lval() += val.as_amount();
+ return *this;
+ case BALANCE:
+ as_balance_lval() += val.as_balance();
+ return *this;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() += val.as_balance_pair();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_pair_lval() += val.to_amount();
+ return *this;
+ case AMOUNT:
+ as_balance_pair_lval() += val.as_amount();
+ return *this;
+ case BALANCE:
+ as_balance_pair_lval() += val.as_balance();
+ return *this;
+ case BALANCE_PAIR:
+ as_balance_pair_lval() += val.as_balance_pair();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot add " << val.label() << " to " << label());
+ return *this;
+}
+
+value_t& value_t::operator-=(const value_t& val)
+{
+ if (is_sequence()) {
+ sequence_t& seq(as_sequence_lval());
+
+ if (val.is_sequence()) {
+ foreach (const value_t& v, val.as_sequence()) {
+ sequence_t::iterator j = std::find(seq.begin(), seq.end(), v);
+ if (j != seq.end())
+ seq.erase(j);
+ }
+ } else {
+ sequence_t::iterator i = std::find(seq.begin(), seq.end(), val);
+ if (i != seq.end())
+ seq.erase(i);
+ }
+ return *this;
+ }
+
+ switch (type()) {
+ case DATETIME:
+ switch (val.type()) {
+ case INTEGER:
+ as_datetime_lval() -= date_duration(val.as_long());
+ return *this;
+ case AMOUNT:
+ as_datetime_lval() -= date_duration(val.as_amount().to_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case DATE:
+ switch (val.type()) {
+ case INTEGER:
+ as_date_lval() -= date_duration_t(val.as_long());
+ return *this;
+ case AMOUNT:
+ as_date_lval() -= date_duration_t(val.as_amount().to_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ as_long_lval() -= val.as_long();
+ return *this;
+ case AMOUNT:
+ in_place_cast(AMOUNT);
+ as_amount_lval() -= val.as_amount();
+ in_place_simplify();
+ return *this;
+ case BALANCE:
+ in_place_cast(BALANCE);
+ as_balance_lval() -= val.as_balance();
+ in_place_simplify();
+ return *this;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() -= val.as_balance_pair();
+ in_place_simplify();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ if (as_amount().has_commodity()) {
+ in_place_cast(BALANCE);
+ *this -= val;
+ in_place_simplify();
+ return *this;
+ } else {
+ as_amount_lval() -= val.as_long();
+ in_place_simplify();
+ return *this;
+ }
+ break;
+
+ case AMOUNT:
+ if (as_amount().commodity() != val.as_amount().commodity()) {
+ in_place_cast(BALANCE);
+ *this -= val;
+ in_place_simplify();
+ return *this;
+ } else {
+ as_amount_lval() -= val.as_amount();
+ in_place_simplify();
+ return *this;
+ }
+ break;
+
+ case BALANCE:
+ in_place_cast(BALANCE);
+ as_balance_lval() -= val.as_balance();
+ in_place_simplify();
+ return *this;
+
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() -= val.as_balance_pair();
+ in_place_simplify();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_lval() -= val.to_amount();
+ in_place_simplify();
+ return *this;
+ case AMOUNT:
+ as_balance_lval() -= val.as_amount();
+ in_place_simplify();
+ return *this;
+ case BALANCE:
+ as_balance_lval() -= val.as_balance();
+ in_place_simplify();
+ return *this;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ as_balance_pair_lval() -= val.as_balance_pair();
+ in_place_simplify();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_pair_lval() -= val.to_amount();
+ in_place_simplify();
+ return *this;
+ case AMOUNT:
+ as_balance_pair_lval() -= val.as_amount();
+ in_place_simplify();
+ return *this;
+ case BALANCE:
+ as_balance_pair_lval() -= val.as_balance();
+ in_place_simplify();
+ return *this;
+ case BALANCE_PAIR:
+ as_balance_pair_lval() -= val.as_balance_pair();
+ in_place_simplify();
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot subtract " << val.label() << " from " << label());
+
+ return *this;
+}
+
+value_t& value_t::operator*=(const value_t& val)
+{
+ if (is_string()) {
+ string temp;
+ long count = val.to_long();
+ for (long i = 0; i < count; i++)
+ temp += as_string();
+ set_string(temp);
+ return *this;
+ }
+ else if (is_sequence()) {
+ value_t temp;
+ long count = val.to_long();
+ for (long i = 0; i < count; i++)
+ temp += as_sequence();
+ return *this = temp;
+ }
+
+ switch (type()) {
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ as_long_lval() *= val.as_long();
+ return *this;
+ case AMOUNT:
+ set_amount(val.as_amount() * as_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ as_amount_lval() *= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (as_amount().commodity() == val.as_amount().commodity() ||
+ ! val.as_amount().has_commodity()) {
+ as_amount_lval() *= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_lval() *= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (! val.as_amount().has_commodity()) {
+ as_balance_lval() *= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_pair_lval() *= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (! val.as_amount().has_commodity()) {
+ as_balance_pair_lval() *= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot multiply " << label() << " with " << val.label());
+
+ return *this;
+}
+
+value_t& value_t::operator/=(const value_t& val)
+{
+ switch (type()) {
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ as_long_lval() /= val.as_long();
+ return *this;
+ case AMOUNT:
+ set_amount(val.as_amount() / as_long());
+ return *this;
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ as_amount_lval() /= val.as_long();
+ return *this;
+
+ case AMOUNT:
+ if (as_amount().commodity() == val.as_amount().commodity() ||
+ ! val.as_amount().has_commodity()) {
+ as_amount_lval() /= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_lval() /= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (! val.as_amount().has_commodity()) {
+ as_balance_lval() /= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type()) {
+ case INTEGER:
+ as_balance_pair_lval() /= val.as_long();
+ return *this;
+ case AMOUNT:
+ if (! val.as_amount().has_commodity()) {
+ as_balance_pair_lval() /= val.as_amount();
+ return *this;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot divide " << label() << " by " << val.label());
+
+ return *this;
+}
+
+
+bool value_t::operator==(const value_t& val) const
+{
+ switch (type()) {
+ case VOID:
+ return val.type() == VOID;
+
+ case BOOLEAN:
+ if (val.is_boolean())
+ return as_boolean() == val.as_boolean();
+ break;
+
+ case DATETIME:
+ if (val.is_datetime())
+ return as_datetime() == val.as_datetime();
+ break;
+
+ case DATE:
+ if (val.is_date())
+ return as_date() == val.as_date();
+ break;
+
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ return as_long() == val.as_long();
+ case AMOUNT:
+ return val.as_amount() == to_amount();
+ case BALANCE:
+ return val.as_balance() == to_amount();
+ case BALANCE_PAIR:
+ return val.as_balance_pair() == to_amount();
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ return as_amount() == val.as_long();
+ case AMOUNT:
+ return as_amount() == val.as_amount();
+ case BALANCE:
+ return val.as_balance() == as_amount();
+ case BALANCE_PAIR:
+ return val.as_balance_pair() == as_amount();
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type()) {
+ case INTEGER:
+ return as_balance() == val.to_amount();
+ case AMOUNT:
+ return as_balance() == val.as_amount();
+ case BALANCE:
+ return as_balance() == val.as_balance();
+ case BALANCE_PAIR:
+ return val.as_balance_pair() == as_balance();
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type()) {
+ case INTEGER:
+ return as_balance_pair() == val.to_amount();
+ case AMOUNT:
+ return as_balance_pair() == val.as_amount();
+ case BALANCE:
+ return as_balance_pair() == val.as_balance();
+ case BALANCE_PAIR:
+ return as_balance_pair() == val.as_balance_pair();
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ if (val.is_string())
+ return as_string() == val.as_string();
+ break;
+
+ case SEQUENCE:
+ if (val.is_sequence())
+ return as_sequence() == val.as_sequence();
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot compare " << label() << " to " << val.label());
+
+ return *this;
+}
+
+bool value_t::operator<(const value_t& val) const
+{
+ switch (type()) {
+ case DATETIME:
+ if (val.is_datetime())
+ return as_datetime() < val.as_datetime();
+ break;
+
+ case DATE:
+ if (val.is_date())
+ return as_date() < val.as_date();
+ break;
+
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ return as_long() < val.as_long();
+ case AMOUNT:
+ return val.as_amount() < as_long();
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ return as_amount() < val.as_long();
+ case AMOUNT:
+ return as_amount() < val.as_amount();
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ if (val.is_string())
+ return as_string() < val.as_string();
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot compare " << label() << " to " << val.label());
+
+ return *this;
+}
+
+#if 0
+bool value_t::operator>(const value_t& val) const
+{
+ switch (type()) {
+ case DATETIME:
+ if (val.is_datetime())
+ return as_datetime() > val.as_datetime();
+ break;
+
+ case DATE:
+ if (val.is_date())
+ return as_date() > val.as_date();
+ break;
+
+ case INTEGER:
+ switch (val.type()) {
+ case INTEGER:
+ return as_long() > val.as_long();
+ case AMOUNT:
+ return val.as_amount() > as_long();
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type()) {
+ case INTEGER:
+ return as_amount() > val.as_long();
+ case AMOUNT:
+ return as_amount() > val.as_amount();
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ if (val.is_string())
+ return as_string() > val.as_string();
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot compare " << label() << " to " << val.label());
+
+ return *this;
+}
+#endif
+
+void value_t::in_place_cast(type_t cast_type)
+{
+ if (type() == cast_type)
+ return;
+
+ if (cast_type == BOOLEAN) {
+ set_boolean(bool(*this));
+ return;
+ }
+ else if (cast_type == SEQUENCE) {
+ sequence_t temp;
+ if (! is_null())
+ temp.push_back(*this);
+ set_sequence(temp);
+ return;
+ }
+
+ switch (type()) {
+ case BOOLEAN:
+ switch (cast_type) {
+ case STRING:
+ set_string(as_boolean() ? "true" : "false");
+ return;
+ default:
+ break;
+ }
+ break;
+
+ case INTEGER:
+ switch (cast_type) {
+ case AMOUNT:
+ set_amount(as_long());
+ return;
+ case BALANCE:
+ set_balance(to_amount());
+ return;
+ case BALANCE_PAIR:
+ set_balance_pair(to_amount());
+ return;
+ case STRING:
+ set_string(lexical_cast<string>(as_long()));
+ return;
+ default:
+ break;
+ }
+ break;
+
+ case AMOUNT: {
+ const amount_t& amt(as_amount());
+ switch (cast_type) {
+ case INTEGER:
+ if (amt.is_null())
+ set_long(0L);
+ else
+ set_long(as_amount().to_long());
+ return;
+ case BALANCE:
+ if (amt.is_null())
+ set_balance(balance_t());
+ else
+ set_balance(as_amount()); // creates temporary
+ return;
+ case BALANCE_PAIR:
+ if (amt.is_null())
+ set_balance_pair(balance_pair_t());
+ else
+ set_balance_pair(as_amount()); // creates temporary
+ return;
+ case STRING:
+ if (amt.is_null())
+ set_string("");
+ else
+ set_string(as_amount().to_string());
+ return;
+ default:
+ break;
+ }
+ break;
+ }
+
+ case BALANCE:
+ switch (cast_type) {
+ case AMOUNT: {
+ const balance_t& temp(as_balance());
+ if (temp.amounts.size() == 1) {
+ // Because we are changing the current balance value to an amount
+ // value, and because set_amount takes a reference (and that memory is
+ // about to be repurposed), we must pass in a copy.
+ set_amount(amount_t((*temp.amounts.begin()).second));
+ return;
+ }
+ else if (temp.amounts.size() == 0) {
+ set_amount(0L);
+ return;
+ }
+ else {
+ throw_(value_error, "Cannot convert " << label() <<
+ " with multiple commodities to " << label(cast_type));
+ }
+ break;
+ }
+ case BALANCE_PAIR:
+ set_balance_pair(as_balance());
+ return;
+ default:
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (cast_type) {
+ case AMOUNT: {
+ const balance_t& temp(as_balance_pair().quantity());
+ if (temp.amounts.size() == 1) {
+ set_amount(amount_t((*temp.amounts.begin()).second));
+ return;
+ }
+ else if (temp.amounts.size() == 0) {
+ set_amount(0L);
+ return;
+ }
+ else {
+ throw_(value_error, "Cannot convert " << label() <<
+ " with multiple commodities to " << label(cast_type));
+ }
+ break;
+ }
+ case BALANCE:
+ // A temporary is required, becaues set_balance is going to wipe us out
+ // before assigned the value passed in.
+ set_balance(balance_t(as_balance_pair().quantity()));
+ return;
+ default:
+ break;
+ }
+ break;
+
+ case STRING:
+ switch (cast_type) {
+ case INTEGER: {
+ if (all(as_string(), is_digit())) {
+ set_long(lexical_cast<long>(as_string()));
+ return;
+ } else {
+ throw_(value_error,
+ "Cannot convert string '" << *this << "' to an integer");
+ }
+ break;
+ }
+ case AMOUNT:
+ set_amount(amount_t(as_string()));
+ return;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ throw_(value_error,
+ "Cannot convert " << label() << " to " << label(cast_type));
+}
+
+void value_t::in_place_negate()
+{
+ switch (type()) {
+ case BOOLEAN:
+ set_boolean(! as_boolean());
+ return;
+ case INTEGER:
+ case DATETIME:
+ set_long(- as_long());
+ return;
+ case DATE:
+ set_long(- as_long());
+ return;
+ case AMOUNT:
+ as_amount_lval().in_place_negate();
+ return;
+ case BALANCE:
+ as_balance_lval().in_place_negate();
+ return;
+ case BALANCE_PAIR:
+ as_balance_pair_lval().in_place_negate();
+ return;
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot negate " << label());
+}
+
+bool value_t::is_realzero() const
+{
+ switch (type()) {
+ case BOOLEAN:
+ return ! as_boolean();
+ case INTEGER:
+ return as_long() == 0;
+ case DATETIME:
+ return ! is_valid(as_datetime());
+ case DATE:
+ return ! is_valid(as_date());
+ case AMOUNT:
+ return as_amount().is_realzero();
+ case BALANCE:
+ return as_balance().is_realzero();
+ case BALANCE_PAIR:
+ return as_balance_pair().is_realzero();
+ case STRING:
+ return as_string().empty();
+ case SEQUENCE:
+ return as_sequence().empty();
+
+ case POINTER:
+ return as_any_pointer().empty();
+
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return true;
+}
+
+bool value_t::is_zero() const
+{
+ switch (type()) {
+ case BOOLEAN:
+ return ! as_boolean();
+ case INTEGER:
+ return as_long() == 0;
+ case DATETIME:
+ return ! is_valid(as_datetime());
+ case DATE:
+ return ! is_valid(as_date());
+ case AMOUNT:
+ return as_amount().is_zero();
+ case BALANCE:
+ return as_balance().is_zero();
+ case BALANCE_PAIR:
+ return as_balance_pair().is_zero();
+ case STRING:
+ return as_string().empty();
+ case SEQUENCE:
+ return as_sequence().empty();
+
+ case POINTER:
+ return as_any_pointer().empty();
+
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return true;
+}
+
+value_t value_t::value(const optional<datetime_t>& moment) const
+{
+ switch (type()) {
+ case INTEGER:
+ return *this;
+
+ case AMOUNT: {
+ if (optional<amount_t> val = as_amount().value(moment))
+ return *val;
+ return false;
+ }
+ case BALANCE: {
+ if (optional<balance_t> bal = as_balance().value(moment))
+ return *bal;
+ return false;
+ }
+ case BALANCE_PAIR: {
+ if (optional<balance_t> bal_pair =
+ as_balance_pair().quantity().value(moment))
+ return *bal_pair;
+ return false;
+ }
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot find the value of " << label());
+ return NULL_VALUE;
+}
+
+void value_t::in_place_reduce()
+{
+ switch (type()) {
+ case INTEGER:
+ return;
+ case AMOUNT:
+ as_amount_lval().in_place_reduce();
+ return;
+ case BALANCE:
+ as_balance_lval().in_place_reduce();
+ return;
+ case BALANCE_PAIR:
+ as_balance_pair_lval().in_place_reduce();
+ return;
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot reduce " << label());
+}
+
+value_t value_t::abs() const
+{
+ switch (type()) {
+ case INTEGER: {
+ long val = as_long();
+ if (val < 0)
+ return - val;
+ return val;
+ }
+ case AMOUNT:
+ return as_amount().abs();
+ case BALANCE:
+ return as_balance().abs();
+ case BALANCE_PAIR:
+ return as_balance_pair().abs();
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot abs " << label());
+ return NULL_VALUE;
+}
+
+value_t value_t::round() const
+{
+ switch (type()) {
+ case INTEGER:
+ return *this;
+ case AMOUNT:
+ return as_amount().round();
+ case BALANCE:
+ return as_balance().round();
+ case BALANCE_PAIR:
+ return as_balance_pair().round();
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot round " << label());
+ return NULL_VALUE;
+}
+
+value_t value_t::unround() const
+{
+ switch (type()) {
+ case INTEGER:
+ return *this;
+ case AMOUNT:
+ return as_amount().unround();
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot unround " << label());
+ return NULL_VALUE;
+}
+
+#if 0
+value_t value_t::annotated_price() const
+{
+ switch (type()) {
+ case AMOUNT: {
+ optional<amount_t> temp = as_amount().annotation_details().price;
+ if (! temp)
+ return false;
+ return *temp;
+ }
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot find the annotated price of " << label());
+ return NULL_VALUE;
+}
+
+value_t value_t::annotated_date() const
+{
+ switch (type()) {
+ case DATETIME:
+ return *this;
+ case DATE:
+ return *this;
+
+ case AMOUNT: {
+ optional<datetime_t> temp = as_amount().annotation_details().date;
+ if (! temp)
+ return false;
+ return *temp;
+ }
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot find the annotated date of " << label());
+ return NULL_VALUE;
+}
+
+value_t value_t::annotated_tag() const
+{
+ switch (type()) {
+ case AMOUNT: {
+ optional<string> temp = as_amount().annotation_details().tag;
+ if (! temp)
+ return false;
+ return string_value(*temp);
+ }
+
+ case STRING:
+ return *this;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot find the annotated tag of " << label());
+ return NULL_VALUE;
+}
+#endif
+
+value_t value_t::strip_annotations(const bool keep_price,
+ const bool keep_date,
+ const bool keep_tag) const
+{
+ switch (type()) {
+ case VOID:
+ case BOOLEAN:
+ case INTEGER:
+ case DATETIME:
+ case DATE:
+ case STRING:
+ case POINTER:
+ return *this;
+
+ case SEQUENCE: {
+ sequence_t temp;
+ foreach (const value_t& value, as_sequence())
+ temp.push_back(value.strip_annotations(keep_price, keep_date, keep_tag));
+ return temp;
+ }
+
+ case AMOUNT:
+ return as_amount().strip_annotations(keep_price, keep_date, keep_tag);
+ case BALANCE:
+ return as_balance().strip_annotations(keep_price, keep_date, keep_tag);
+ case BALANCE_PAIR:
+ return as_balance_pair().quantity().strip_annotations(keep_price, keep_date,
+ keep_tag);
+
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return NULL_VALUE;
+}
+
+value_t value_t::cost() const
+{
+ switch (type()) {
+ case INTEGER:
+ case AMOUNT:
+ case BALANCE:
+ return *this;
+
+ case BALANCE_PAIR:
+ assert(as_balance_pair().cost);
+ if (as_balance_pair().cost)
+ return *(as_balance_pair().cost);
+ else
+ return as_balance_pair().quantity();
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot find the cost of " << label());
+ return NULL_VALUE;
+}
+
+value_t& value_t::add(const amount_t& amount, const optional<amount_t>& tcost)
+{
+ switch (type()) {
+ case INTEGER:
+ case AMOUNT:
+ if (tcost) {
+ in_place_cast(BALANCE_PAIR);
+ return add(amount, tcost);
+ }
+ else if ((is_amount() &&
+ as_amount().commodity() != amount.commodity()) ||
+ (! is_amount() && amount.commodity())) {
+ in_place_cast(BALANCE);
+ return add(amount, tcost);
+ }
+ else if (! is_amount()) {
+ in_place_cast(AMOUNT);
+ }
+ return *this += amount;
+
+ case BALANCE:
+ if (tcost) {
+ in_place_cast(BALANCE_PAIR);
+ return add(amount, tcost);
+ }
+ return *this += amount;
+
+ case BALANCE_PAIR:
+ as_balance_pair_lval().add(amount, tcost);
+ return *this;
+
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot add an amount to " << label());
+ return *this;
+}
+
+void value_t::dump(std::ostream& out, const int first_width,
+ const int latter_width) const
+{
+ switch (type()) {
+ case VOID:
+ out << "VOID";
+ break;
+
+ case BOOLEAN:
+ out << as_boolean();
+ break;
+
+ case DATETIME:
+ out << format_datetime(as_datetime());
+ break;
+
+ case DATE:
+ out << format_date(as_date());
+ break;
+
+ case INTEGER:
+ out << as_long();
+ break;
+
+ case AMOUNT:
+ out << as_amount();
+ break;
+
+ case STRING:
+ out << as_string();
+ break;
+
+ case POINTER:
+ out << boost::unsafe_any_cast<const void *>(&as_any_pointer());
+ break;
+
+ case SEQUENCE: {
+ out << '(';
+ bool first = true;
+ foreach (const value_t& value, as_sequence()) {
+ if (first)
+ first = false;
+ else
+ out << ", ";
+
+ value.dump(out, first_width, latter_width);
+ }
+ out << ')';
+ break;
+ }
+
+ case BALANCE:
+ as_balance().print(out, first_width, latter_width);
+ break;
+ case BALANCE_PAIR:
+ as_balance_pair().print(out, first_width, latter_width);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+}
+
+void value_t::print(std::ostream& out, const bool relaxed) const
+{
+ switch (type()) {
+ case VOID:
+ out << "";
+ break;
+
+ case BOOLEAN:
+ if (as_boolean())
+ out << "true";
+ else
+ out << "false";
+ break;
+
+ case INTEGER:
+ out << as_long();
+ break;
+
+ case AMOUNT:
+ if (! relaxed)
+ out << '{';
+ out << as_amount();
+ if (! relaxed)
+ out << '}';
+ break;
+
+ case BALANCE:
+ case BALANCE_PAIR:
+ assert(false);
+ break;
+
+ case DATETIME:
+ assert(false);
+ break;
+ case DATE:
+ out << '[' << format_date(as_date()) << ']';
+ break;
+
+ case STRING:
+ out << '"' << as_string() << '"';
+ break;
+
+ case POINTER:
+ assert(false);
+ break;
+
+ case SEQUENCE: {
+ out << '(';
+ bool first = true;
+ foreach (const value_t& value, as_sequence()) {
+ if (first)
+ first = false;
+ else
+ out << ", ";
+
+ value.print(out, relaxed);
+ }
+ out << ')';
+ break;
+ }
+ }
+}
+
+void value_t::read(const char *& data)
+{
+ switch (static_cast<value_t::type_t>(binary::read_long<int>(data))) {
+ case BOOLEAN:
+ set_boolean(binary::read_bool(data));
+ break;
+ case INTEGER:
+ set_long(binary::read_long<unsigned long>(data));
+ break;
+ case DATETIME:
+#if 0
+ // jww (2008-04-22): I need to record and read a datetime_t directly
+ set_datetime(read_long<unsigned long>(data));
+#endif
+ break;
+ case DATE:
+#if 0
+ // jww (2008-04-22): I need to record and read a date_t directly
+ set_date(read_long<unsigned long>(data));
+#endif
+ break;
+ case AMOUNT: {
+ amount_t temp;
+ temp.read(data);
+ set_amount(temp);
+ break;
+ }
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot read " << label() << " from a stream");
+}
+
+void value_t::write(std::ostream& out) const
+{
+ binary::write_long(out, static_cast<int>(type()));
+
+ switch (type()) {
+ case BOOLEAN:
+ binary::write_bool(out, as_boolean());
+ break;
+ case INTEGER:
+ binary::write_long(out, as_long());
+ break;
+ case DATETIME:
+#if 0
+ binary::write_number(out, as_datetime());
+#endif
+ break;
+ case DATE:
+#if 0
+ binary::write_number(out, as_date());
+#endif
+ break;
+ case AMOUNT:
+ as_amount().write(out);
+ break;
+ default:
+ break;
+ }
+
+ throw_(value_error, "Cannot read " << label() << " to a stream");
+}
+
+bool value_t::valid() const
+{
+ switch (type()) {
+ case AMOUNT:
+ return as_amount().valid();
+ case BALANCE:
+ return as_balance().valid();
+ case BALANCE_PAIR:
+ return as_balance_pair().valid();
+ default:
+ break;
+ }
+ return true;
+}
+
+} // namespace ledger
diff --git a/src/value.h b/src/value.h
new file mode 100644
index 00000000..7c82efaf
--- /dev/null
+++ b/src/value.h
@@ -0,0 +1,928 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup numerics Core numerics
+ */
+
+/**
+ * @file value.h
+ * @author John Wiegley
+ * @date Thu Jun 14 21:54:00 2007
+ *
+ * @brief Abstract dynamic type representing various numeric types.
+ *
+ * @ingroup numerics
+ *
+ * A value_t object can be one of many types, and changes its type
+ * dynamically based on how it is used. For example, if you assign
+ * the number 10 to a value object, it's internal type will be
+ * INTEGER.
+ */
+#ifndef _VALUE_H
+#define _VALUE_H
+
+#include "balpair.h" // pulls in balance.h and amount.h
+
+namespace ledger {
+
+DECLARE_EXCEPTION(value_error, std::runtime_error);
+
+/**
+ * @class value_t
+ *
+ * @brief Dynamic type representing various numeric types.
+ *
+ * The following type is a polymorphous value type used solely for
+ * performance reasons. The alternative is to compute value
+ * expressions (valexpr.cc) in terms of the largest data type,
+ * balance_t. This was found to be prohibitively expensive, especially
+ * when large logic chains were involved, since many temporary
+ * allocations would occur for every operator. With value_t, and the
+ * fact that logic chains only need boolean values to continue, no
+ * memory allocations need to take place at all.
+ */
+class value_t
+ : public ordered_field_operators<value_t,
+ equality_comparable<value_t, balance_pair_t,
+ equality_comparable<value_t, balance_t,
+ additive<value_t, balance_pair_t,
+ additive<value_t, balance_t,
+ multiplicative<value_t, balance_pair_t,
+ multiplicative<value_t, balance_t,
+ ordered_field_operators<value_t, amount_t,
+#ifdef HAVE_GDTOA
+ ordered_field_operators<value_t, double,
+#endif
+ ordered_field_operators<value_t, unsigned long,
+ ordered_field_operators<value_t, long> > > > > > > > > >
+#ifdef HAVE_GDTOA
+ >
+#endif
+{
+public:
+ /**
+ * The sequence_t member type abstracts the type used to represent a
+ * resizable "array" of value_t objects.
+ */
+ typedef std::vector<value_t> sequence_t;
+
+ typedef sequence_t::iterator iterator;
+ typedef sequence_t::const_iterator const_iterator;
+ typedef sequence_t::difference_type difference_type;
+
+ /**
+ * type_t gives the type of the data contained or referenced by a
+ * value_t object. Use the type() method to get a value of type
+ * type_t.
+ */
+ enum type_t {
+ VOID, // a null value (i.e., uninitialized)
+ BOOLEAN, // a boolean
+ DATETIME, // a date and time (Boost posix_time)
+ DATE, // a date (Boost gregorian::date)
+ INTEGER, // a signed integer value
+ AMOUNT, // a ledger::amount_t
+ BALANCE, // a ledger::balance_t
+ BALANCE_PAIR, // a ledger::balance_pair_t
+ STRING, // a string object
+ SEQUENCE, // a vector of value_t objects
+ POINTER // an opaque pointer of any type
+ };
+
+private:
+ class storage_t
+ {
+ friend class value_t;
+
+ /**
+ * The `data' member holds the actual bytes relating to whatever
+ * has been stuffed into this storage object. There is a set of
+ * asserts in value.cc to guarantee that the sizeof expression
+ * used here is indeed at least as big as the largest object that
+ * will ever be copied into `data'.
+ *
+ * The `type' member holds the value_t::type_t value representing
+ * the type of the object stored.
+ */
+ char data[sizeof(amount_t)];
+ type_t type;
+
+ /**
+ * `refc' holds the current reference count for each storage_t
+ * object.
+ */
+ mutable int refc;
+
+ /**
+ * Constructor. Since all storage object are assigned to after
+ * construction, the only constructors allowed are explicit, and
+ * copy (see below). The default starting type is VOID, which
+ * should rarely ever be seen in practice, since the first thing
+ * that value_t typically does is to assign a valid value.
+ */
+ explicit storage_t() : type(VOID), refc(0) {
+ TRACE_CTOR(value_t::storage_t, "");
+ }
+
+ public: // so `checked_delete' can access it
+ /**
+ * Destructor. Must only be called when the reference count has
+ * reached zero. The `destroy' method is used to do the actual
+ * cleanup of the data, since it's quite possible for `destroy' to
+ * be called while the object is still active -- to clear the
+ * stored data for subsequent reuse of the storage_t object.
+ */
+ ~storage_t() {
+ TRACE_DTOR(value_t::storage_t);
+ DEBUG("value.storage.refcount", "Destroying " << this);
+ assert(refc == 0);
+ destroy();
+ }
+
+ void destroy();
+
+ private:
+ /**
+ * Assignment and copy operators. These are called when making a
+ * new copy of a storage object in order to modify the copy.
+ */
+ explicit storage_t(const storage_t& rhs)
+ : type(rhs.type), refc(0) {
+ TRACE_CTOR(value_t::storage_t, "copy");
+ *this = rhs;
+ }
+ storage_t& operator=(const storage_t& rhs);
+
+ /**
+ * Reference counting methods. The intrusive_ptr_* methods are
+ * used by boost::intrusive_ptr to manage the calls to acquire and
+ * release.
+ */
+ void acquire() const {
+ DEBUG("value.storage.refcount",
+ "Acquiring " << this << ", refc now " << refc + 1);
+ assert(refc >= 0);
+ refc++;
+ }
+ void release() const {
+ DEBUG("value.storage.refcount",
+ "Releasing " << this << ", refc now " << refc - 1);
+ assert(refc > 0);
+ if (--refc == 0)
+ checked_delete(this);
+ }
+
+ friend inline void intrusive_ptr_add_ref(value_t::storage_t * storage) {
+ storage->acquire();
+ }
+ friend inline void intrusive_ptr_release(value_t::storage_t * storage) {
+ storage->release();
+ }
+ };
+
+ /**
+ * The actual data for each value_t is kept in the `storage' member.
+ * Data is modified using a copy-on-write policy.
+ */
+ intrusive_ptr<storage_t> storage;
+
+ /**
+ * _dup() makes a private copy of the current value (if necessary)
+ * so it can subsequently be modified.
+ *
+ * _clear() removes our pointer to the current value and initializes
+ * a new storage bin for things to be stored in.
+ *
+ * _reset() makes the current object appear as if it were
+ * uninitialized.
+ */
+ void _dup();
+ void _clear() {
+ if (! storage || storage->refc > 1)
+ storage = new storage_t;
+ else
+ storage->destroy();
+ }
+ void _reset() {
+ if (storage)
+ storage = intrusive_ptr<storage_t>();
+ }
+
+ /**
+ * Because boolean "true" and "false" are so common, a pair of
+ * static references are kept to prevent the creation of throwaway
+ * storage_t objects just to represent these two common values.
+ */
+ static intrusive_ptr<storage_t> true_value;
+ static intrusive_ptr<storage_t> false_value;
+
+public:
+ // jww (2007-05-03): Make these private, and make ledger::initialize
+ // a member function of session_t.
+ static void initialize();
+ static void shutdown();
+
+public:
+ /**
+ * Constructors. value_t objects may be constructed from almost any
+ * value type that they can contain, including variations on those
+ * types (such as long, unsigned long, etc). The ordering of the
+ * methods here reflects the ordering of the constants in type_t
+ * above.
+ *
+ * One constructor of special note is that taking a string or
+ * character pointer as an argument. Because value_t("$100") is
+ * interpreted as a commoditized amount, the form value_t("$100",
+ * true) is required to represent the literal string "$100", and not
+ * the amount "one hundred dollars".
+ */
+ value_t() {
+ TRACE_CTOR(value_t, "");
+ }
+
+ value_t(const bool val) {
+ TRACE_CTOR(value_t, "const bool");
+ set_boolean(val);
+ }
+
+ value_t(const datetime_t& val) {
+ TRACE_CTOR(value_t, "const datetime_t&");
+ set_datetime(val);
+ }
+ value_t(const date_t& val) {
+ TRACE_CTOR(value_t, "const date_t&");
+ set_date(val);
+ }
+
+ value_t(const long val) {
+ TRACE_CTOR(value_t, "const long");
+ set_long(val);
+ }
+ value_t(const unsigned long val) {
+ TRACE_CTOR(value_t, "const unsigned long");
+ set_amount(val);
+ }
+#ifdef HAVE_GDTOA
+ value_t(const double val) {
+ TRACE_CTOR(value_t, "const double");
+ set_amount(val);
+ }
+#endif
+ value_t(const amount_t& val) {
+ TRACE_CTOR(value_t, "const amount_t&");
+ set_amount(val);
+ }
+ value_t(const balance_t& val) {
+ TRACE_CTOR(value_t, "const balance_t&");
+ set_balance(val);
+ }
+ value_t(const balance_pair_t& val) {
+ TRACE_CTOR(value_t, "const balance_pair_t&");
+ set_balance_pair(val);
+ }
+
+ explicit value_t(const string& val, bool literal = false) {
+ TRACE_CTOR(value_t, "const string&, bool");
+ if (literal)
+ set_string(val);
+ else
+ set_amount(amount_t(val));
+ }
+ explicit value_t(const char * val, bool literal = false) {
+ TRACE_CTOR(value_t, "const char *");
+ if (literal)
+ set_string(val);
+ else
+ set_amount(amount_t(val));
+ }
+
+ value_t(const sequence_t& val) {
+ TRACE_CTOR(value_t, "const sequence_t&");
+ set_sequence(val);
+ }
+
+ template <typename T>
+ explicit value_t(T * item) {
+ TRACE_CTOR(value_t, "T *");
+ set_pointer(item);
+ }
+
+ /**
+ * Destructor. This does not do anything, because the intrusive_ptr
+ * that refers to our storage object will decrease its reference
+ * count itself upon destruction.
+ */
+ ~value_t() {
+ TRACE_DTOR(value_t);
+ }
+
+ /**
+ * Assignment and copy operators. Values are cheaply copied by
+ * simply creating another reference to the other value's storage
+ * object. A true copy is only ever made prior to modification.
+ */
+ value_t(const value_t& val) {
+ TRACE_CTOR(value_t, "copy");
+ *this = val;
+ }
+ value_t& operator=(const value_t& val) {
+ if (! (this == &val || storage == val.storage))
+ storage = val.storage;
+ return *this;
+ }
+
+ /**
+ * Comparison operators. Values can be compared to other values
+ */
+ bool operator==(const value_t& val) const;
+ bool operator<(const value_t& val) const;
+
+ template <typename T>
+ bool operator==(const T& amt) const {
+ return *this == value_t(amt);
+ }
+ template <typename T>
+ bool operator<(const T& amt) const {
+ return *this < value_t(amt);
+ }
+
+ /**
+ * Binary arithmetic operators.
+ *
+ * add(amount_t, optional<amount_t>) allows for the possibility of
+ * adding both an amount and its cost in a single operation.
+ * Otherwise, there is no way to separately represent the "cost"
+ * part of an amount addition statement.
+ */
+ value_t& operator+=(const value_t& val);
+ value_t& operator-=(const value_t& val);
+ value_t& operator*=(const value_t& val);
+ value_t& operator/=(const value_t& val);
+
+ // This special form of add is use to produce a balance pair by
+ // simultaneously adding both an amount and its cost.
+ value_t& add(const amount_t& amount,
+ const optional<amount_t>& cost = none);
+
+ /**
+ * Unary arithmetic operators.
+ */
+ value_t negate() const {
+ value_t temp = *this;
+ temp.in_place_negate();
+ return temp;
+ }
+ void in_place_negate(); // exists for efficiency's sake
+
+ value_t operator-() const {
+ return negate();
+ }
+
+ value_t abs() const;
+ value_t round() const;
+ value_t unround() const;
+
+ value_t reduce() const {
+ value_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+ void in_place_reduce(); // exists for efficiency's sake
+
+ // Return the "market value" of a given value at a specific time.
+ value_t value(const optional<datetime_t>& moment = none) const;
+ value_t cost() const;
+
+
+ /**
+ * Truth tests.
+ */
+ operator bool() const;
+
+ bool is_realzero() const;
+ bool is_zero() const;
+ bool is_null() const {
+ if (! storage) {
+ assert(is_type(VOID));
+ return true;
+ } else {
+ assert(! is_type(VOID));
+ return false;
+ }
+ }
+
+ type_t type() const {
+ type_t result = storage ? storage->type : VOID;
+ assert(result >= VOID && result <= POINTER);
+ return result;
+ }
+ bool is_type(type_t _type) const {
+ return type() == _type;
+ }
+
+private:
+ void set_type(type_t new_type) {
+ assert(new_type >= VOID && new_type <= POINTER);
+ if (new_type == VOID) {
+ _reset();
+ assert(is_null());
+ } else {
+ _clear();
+ storage->type = new_type;
+ assert(is_type(new_type));
+ }
+ }
+
+public:
+ /**
+ * Data manipulation methods. A value object may be truth tested for the
+ * existence of every type it can contain:
+ *
+ * is_boolean()
+ * is_long()
+ * is_datetime()
+ * is_date()
+ * is_amount()
+ * is_balance()
+ * is_balance_pair()
+ * is_string()
+ * is_sequence()
+ * is_pointer()
+ *
+ * There are corresponding as_*() methods that represent a value as a
+ * reference to its underlying type. For example, as_long() returns a
+ * reference to a "const long".
+ *
+ * There are also as_*_lval() methods, which represent the underlying data
+ * as a reference to a non-const type. The difference here is that an
+ * _lval() call causes the underlying data to be fully copied before the
+ * resulting reference is returned.
+ *
+ * Lastly, there are corresponding set_*(data) methods for directly
+ * assigning data of a particular type, rather than using the regular
+ * assignment operator (whose implementation simply calls the various set_
+ * methods).
+ */
+ bool is_boolean() const {
+ return is_type(BOOLEAN);
+ }
+ bool& as_boolean_lval() {
+ assert(is_boolean());
+ _dup();
+ return *reinterpret_cast<bool *>(storage->data);
+ }
+ const bool& as_boolean() const {
+ assert(is_boolean());
+ return *reinterpret_cast<bool *>(storage->data);
+ }
+ void set_boolean(const bool val) {
+ set_type(BOOLEAN);
+ storage = val ? true_value : false_value;
+ }
+
+ bool is_datetime() const {
+ return is_type(DATETIME);
+ }
+ datetime_t& as_datetime_lval() {
+ assert(is_datetime());
+ _dup();
+ return *reinterpret_cast<datetime_t *>(storage->data);
+ }
+ const datetime_t& as_datetime() const {
+ assert(is_datetime());
+ return *reinterpret_cast<datetime_t *>(storage->data);
+ }
+ void set_datetime(const datetime_t& val) {
+ set_type(DATETIME);
+ new(reinterpret_cast<datetime_t *>(storage->data)) datetime_t(val);
+ }
+
+ bool is_date() const {
+ return is_type(DATE);
+ }
+ date_t& as_date_lval() {
+ assert(is_date());
+ _dup();
+ return *reinterpret_cast<date_t *>(storage->data);
+ }
+ const date_t& as_date() const {
+ assert(is_date());
+ return *reinterpret_cast<date_t *>(storage->data);
+ }
+ void set_date(const date_t& val) {
+ set_type(DATE);
+ new(reinterpret_cast<date_t *>(storage->data)) date_t(val);
+ }
+
+ bool is_long() const {
+ return is_type(INTEGER);
+ }
+ long& as_long_lval() {
+ assert(is_long());
+ _dup();
+ return *reinterpret_cast<long *>(storage->data);
+ }
+ const long& as_long() const {
+ assert(is_long());
+ return *reinterpret_cast<long *>(storage->data);
+ }
+ void set_long(const long val) {
+ set_type(INTEGER);
+ *reinterpret_cast<long *>(storage->data) = val;
+ }
+
+ bool is_amount() const {
+ return is_type(AMOUNT);
+ }
+ amount_t& as_amount_lval() {
+ assert(is_amount());
+ _dup();
+ amount_t& amt(*reinterpret_cast<amount_t *>(storage->data));
+ assert(amt.valid());
+ return amt;
+ }
+ const amount_t& as_amount() const {
+ assert(is_amount());
+ amount_t& amt(*reinterpret_cast<amount_t *>(storage->data));
+ assert(amt.valid());
+ return amt;
+ }
+ void set_amount(const amount_t& val) {
+ assert(val.valid());
+ set_type(AMOUNT);
+ new(reinterpret_cast<amount_t *>(storage->data)) amount_t(val);
+ }
+
+ bool is_balance() const {
+ return is_type(BALANCE);
+ }
+ balance_t& as_balance_lval() {
+ assert(is_balance());
+ _dup();
+ balance_t& bal(**reinterpret_cast<balance_t **>(storage->data));
+ assert(bal.valid());
+ return bal;
+ }
+ const balance_t& as_balance() const {
+ assert(is_balance());
+ balance_t& bal(**reinterpret_cast<balance_t **>(storage->data));
+ assert(bal.valid());
+ return bal;
+ }
+ void set_balance(const balance_t& val) {
+ assert(val.valid());
+ set_type(BALANCE);
+ *reinterpret_cast<balance_t **>(storage->data) = new balance_t(val);
+ }
+
+ bool is_balance_pair() const {
+ return is_type(BALANCE_PAIR);
+ }
+ balance_pair_t& as_balance_pair_lval() {
+ assert(is_balance_pair());
+ _dup();
+ balance_pair_t& bal_pair(**reinterpret_cast<balance_pair_t **>(storage->data));
+ assert(bal_pair.valid());
+ return bal_pair;
+ }
+ const balance_pair_t& as_balance_pair() const {
+ assert(is_balance_pair());
+ balance_pair_t& bal_pair(**reinterpret_cast<balance_pair_t **>(storage->data));
+ assert(bal_pair.valid());
+ return bal_pair;
+ }
+ void set_balance_pair(const balance_pair_t& val) {
+ assert(val.valid());
+ set_type(BALANCE_PAIR);
+ *reinterpret_cast<balance_pair_t **>(storage->data) = new balance_pair_t(val);
+ }
+
+ bool is_string() const {
+ return is_type(STRING);
+ }
+ string& as_string_lval() {
+ assert(is_string());
+ _dup();
+ return *reinterpret_cast<string *>(storage->data);
+ }
+ const string& as_string() const {
+ assert(is_string());
+ return *reinterpret_cast<string *>(storage->data);
+ }
+ void set_string(const string& val = "") {
+ set_type(STRING);
+ new(reinterpret_cast<string *>(storage->data)) string(val);
+ }
+ void set_string(const char * val = "") {
+ set_type(STRING);
+ new(reinterpret_cast<string *>(storage->data)) string(val);
+ }
+
+ bool is_sequence() const {
+ return is_type(SEQUENCE);
+ }
+ sequence_t& as_sequence_lval() {
+ assert(is_sequence());
+ _dup();
+ return **reinterpret_cast<sequence_t **>(storage->data);
+ }
+ const sequence_t& as_sequence() const {
+ assert(is_sequence());
+ return **reinterpret_cast<sequence_t **>(storage->data);
+ }
+ void set_sequence(const sequence_t& val) {
+ set_type(SEQUENCE);
+ *reinterpret_cast<sequence_t **>(storage->data) = new sequence_t(val);
+ }
+
+ /**
+ * Dealing with pointers is bit involved because we actually deal
+ * with typed pointers. For example, if you call as_pointer it
+ * returns a boost::any object, but if you use as_pointer<void>,
+ * then it returns a void *. The latter form only succeeds if the
+ * stored pointers was assigned to the value as a void*, otherwise
+ * it throws an exception.
+ */
+ bool is_pointer() const {
+ return is_type(POINTER);
+ }
+ boost::any& as_any_pointer_lval() {
+ assert(is_pointer());
+ _dup();
+ return *reinterpret_cast<boost::any *>(storage->data);
+ }
+ template <typename T>
+ T * as_pointer_lval() {
+ assert(is_pointer());
+ _dup();
+ return any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data));
+ }
+ template <typename T>
+ T& as_ref_lval() {
+ assert(is_pointer());
+ _dup();
+ return *any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data));
+ }
+ const boost::any& as_any_pointer() const {
+ assert(is_pointer());
+ return *reinterpret_cast<boost::any *>(storage->data);
+ }
+ template <typename T>
+ T * as_pointer() const {
+ assert(is_pointer());
+ return any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data));
+ }
+ template <typename T>
+ T& as_ref() const {
+ assert(is_pointer());
+ return *any_cast<T *>(*reinterpret_cast<boost::any *>(storage->data));
+ }
+ void set_any_pointer(const boost::any& val) {
+ set_type(POINTER);
+ new(reinterpret_cast<boost::any *>(storage->data)) boost::any(val);
+ }
+ template <typename T>
+ void set_pointer(T * val) {
+ set_type(POINTER);
+ new(reinterpret_cast<boost::any *>(storage->data)) boost::any(val);
+ }
+
+ /**
+ * Data conversion methods. These methods convert a value object to
+ * its underlying type, where possible. If not possible, an
+ * exception is thrown.
+ */
+ bool to_boolean() const;
+ long to_long() const;
+ datetime_t to_datetime() const;
+ date_t to_date() const;
+ amount_t to_amount() const;
+ balance_t to_balance() const;
+ balance_pair_t to_balance_pair() const;
+ string to_string() const;
+ sequence_t to_sequence() const;
+
+ /**
+ * Dynamic typing conversion methods.
+ *
+ * `cast(type_t)' returns a new value whose type has been cast to
+ * the given type, but whose value is based on the original value.
+ * For example, the uncommoditized AMOUNT "100.00" could be cast to
+ * an INTEGER value. If a cast would lose information or is not
+ * meaningful, an exception is thrown.
+ *
+ * `simplify()' is an automatic cast to the simplest type that can
+ * still represent the original value.
+ *
+ * There are also "in-place" versions of these two methods:
+ * in_place_cast
+ * in_place_simplify
+ */
+ value_t cast(type_t cast_type) const {
+ value_t temp(*this);
+ temp.in_place_cast(cast_type);
+ return temp;
+ }
+ void in_place_cast(type_t cast_type);
+
+ value_t simplify() const {
+ value_t temp = *this;
+ temp.in_place_simplify();
+ return temp;
+ }
+ void in_place_simplify();
+
+ /**
+ * Annotated commodity methods.
+ */
+#if 0
+ // These helper methods only apply to AMOUNT values.
+ value_t annotated_price() const;
+ value_t annotated_date() const;
+ value_t annotated_tag() const;
+#endif
+
+ value_t strip_annotations(const bool keep_price = amount_t::keep_price,
+ const bool keep_date = amount_t::keep_date,
+ const bool keep_tag = amount_t::keep_tag) const;
+
+ /**
+ * Collection-style access methods for SEQUENCE values.
+ */
+ value_t& operator[](const int index) {
+ assert(! is_null());
+ if (is_sequence())
+ return as_sequence_lval()[index];
+ else if (index == 0)
+ return *this;
+
+ assert(false);
+ static value_t null;
+ return null;
+ }
+ const value_t& operator[](const int index) const {
+ assert(! is_null());
+ if (is_sequence())
+ return as_sequence()[index];
+ else if (index == 0)
+ return *this;
+
+ assert(false);
+ static value_t null;
+ return null;
+ }
+
+ void push_back(const value_t& val) {
+ if (! val.is_null()) {
+ if (is_null()) {
+ *this = val;
+ } else {
+ if (! is_sequence())
+ in_place_cast(SEQUENCE);
+
+ if (! val.is_sequence()) {
+ if (! val.is_null())
+ as_sequence_lval().push_back(val);
+ } else {
+ const value_t::sequence_t& val_seq(val.as_sequence());
+ std::copy(val_seq.begin(), val_seq.end(),
+ back_inserter(as_sequence_lval()));
+ }
+ }
+ }
+ }
+
+ void pop_back() {
+ assert(! is_null());
+
+ if (! is_sequence()) {
+ _reset();
+ } else {
+ as_sequence_lval().pop_back();
+
+ const value_t::sequence_t& seq(as_sequence());
+ std::size_t new_size = seq.size();
+ if (new_size == 0)
+ _reset();
+ else if (new_size == 1)
+ *this = seq.front();
+ }
+ }
+
+ const std::size_t size() const {
+ if (is_null())
+ return 0;
+ else if (is_sequence())
+ return as_sequence().size();
+ else
+ return 1;
+ }
+
+ /**
+ * Informational methods.
+ */
+ string label(optional<type_t> the_type = none) const {
+ switch (the_type ? *the_type : type()) {
+ case VOID:
+ return "an uninitialized value";
+ case BOOLEAN:
+ return "a boolean";
+ case DATETIME:
+ return "a date/time";
+ case DATE:
+ return "a date";
+ case INTEGER:
+ return "an integer";
+ case AMOUNT:
+ return "an amount";
+ case BALANCE:
+ return "a balance";
+ case BALANCE_PAIR:
+ return "a balance pair";
+ case STRING:
+ return "a string";
+ case SEQUENCE:
+ return "a sequence";
+ case POINTER:
+ return "a pointer";
+ default:
+ assert(false);
+ break;
+ }
+ assert(false);
+ return "<invalid>";
+ }
+
+ /**
+ * Printing methods.
+ */
+ void dump(std::ostream& out, const int first_width,
+ const int latter_width = -1) const;
+ void print(std::ostream& out, const bool relaxed = true) const;
+
+ /**
+ * Serialization methods. A value may be deserialized from an input
+ * stream or a character pointer, and it may be serialized to an
+ * output stream. The methods used are:
+ */
+ void read(const char *& data);
+ void write(std::ostream& out) const;
+
+ /**
+ * Debugging methods.
+ */
+ bool valid() const;
+};
+
+#define NULL_VALUE (value_t())
+
+inline value_t string_value(const string& str) {
+ return value_t(str, true);
+}
+
+inline std::ostream& operator<<(std::ostream& out, const value_t& val) {
+ val.print(out, 12);
+ return out;
+}
+
+inline string value_context(const value_t& val) {
+ std::ostringstream buf;
+ buf << std::right;
+ buf.width(20);
+ val.print(buf);
+ buf << std::endl;
+ return buf.str();
+}
+
+} // namespace ledger
+
+#endif // _VALUE_H
diff --git a/src/xact.cc b/src/xact.cc
new file mode 100644
index 00000000..ce4da0d7
--- /dev/null
+++ b/src/xact.cc
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "xact.h"
+#include "journal.h"
+#include "account.h"
+#include "format.h"
+
+namespace ledger {
+
+bool xact_t::use_effective_date = false;
+
+xact_t::~xact_t()
+{
+ TRACE_DTOR(xact_t);
+}
+
+date_t xact_t::actual_date() const
+{
+ if (! _date && entry)
+ return entry->actual_date();
+ return *_date;
+}
+
+date_t xact_t::effective_date() const
+{
+ if (! _date_eff && entry)
+ return entry->effective_date();
+ return *_date_eff;
+}
+
+namespace {
+ value_t get_state(xact_t& xact) {
+ return long(xact.state);
+ }
+
+ value_t state_uncleared(call_scope_t&) {
+ return 0L;
+ }
+
+ value_t state_cleared(call_scope_t&) {
+ return 1L;
+ }
+
+ value_t state_pending(call_scope_t&) {
+ return 2L;
+ }
+
+ value_t get_date(xact_t& xact) {
+ return xact.date();
+ }
+
+ value_t get_payee(xact_t& xact) {
+ return string_value(xact.entry->payee);
+ }
+
+ value_t get_amount(xact_t& xact) {
+ return xact.amount;
+ }
+
+ value_t get_total(xact_t& xact) {
+ if (xact.xdata_)
+ return xact.xdata_->total;
+ else
+ return xact.amount;
+ }
+
+ value_t get_cost(xact_t& xact) {
+ return xact.cost ? *xact.cost : xact.amount;
+ }
+
+ value_t get_note(xact_t& xact) {
+ return string_value(xact.note ? *xact.note : ":NOTELESS:");
+ }
+
+ value_t get_account(call_scope_t& scope)
+ {
+ xact_t& xact(downcast<xact_t>(*scope.parent));
+
+ var_t<long> max_width(scope, 0);
+
+ string name = xact.reported_account()->fullname();
+
+ if (max_width && *max_width > 2)
+ name = format_t::truncate(name, *max_width - 2, true);
+
+ if (xact.has_flags(XACT_VIRTUAL)) {
+ if (xact.must_balance())
+ name = string("[") + name + "]";
+ else
+ name = string("(") + name + ")";
+ }
+ return string_value(name);
+ }
+
+ value_t get_account_base(xact_t& xact) {
+ return string_value(xact.reported_account()->name);
+ }
+
+ value_t get_beg_pos(xact_t& xact) {
+ return long(xact.beg_pos);
+ }
+
+ value_t get_beg_line(xact_t& xact) {
+ return long(xact.beg_line);
+ }
+
+ value_t get_end_pos(xact_t& xact) {
+ return long(xact.end_pos);
+ }
+
+ value_t get_end_line(xact_t& xact) {
+ return long(xact.end_line);
+ }
+
+ template <value_t (*Func)(xact_t&)>
+ value_t get_wrapper(call_scope_t& scope) {
+ return (*Func)(find_scope<xact_t>(scope));
+ }
+}
+
+expr_t::ptr_op_t xact_t::lookup(const string& name)
+{
+ switch (name[0]) {
+ case 'a':
+ if (name[1] == '\0' || name == "amount")
+ return WRAP_FUNCTOR(get_wrapper<&get_amount>);
+ else if (name == "account")
+ return WRAP_FUNCTOR(get_account);
+ else if (name == "account_base")
+ return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
+ break;
+
+ case 'c':
+ if (name == "cleared")
+ return expr_t::op_t::wrap_value(0L);
+ break;
+
+ case 'd':
+ if (name[1] == '\0' || name == "date")
+ return WRAP_FUNCTOR(get_wrapper<&get_date>);
+ break;
+
+ case 'f':
+ if (name.find("fmt_") == 0) {
+ switch (name[4]) {
+ case 'A':
+ return WRAP_FUNCTOR(get_account);
+ case 'D':
+ return WRAP_FUNCTOR(get_wrapper<&get_date>);
+ case 'P':
+ return WRAP_FUNCTOR(get_wrapper<&get_payee>);
+ }
+ }
+ break;
+
+ case 'p':
+ if (name == "pending")
+ return expr_t::op_t::wrap_value(2L);
+ else if (name == "payee")
+ return WRAP_FUNCTOR(get_wrapper<&get_payee>);
+ break;
+
+ case 't':
+ if (name[1] == '\0' || name == "total")
+ return WRAP_FUNCTOR(get_wrapper<&get_total>);
+ break;
+
+ case 'u':
+ if (name == "uncleared")
+ return expr_t::op_t::wrap_value(1L);
+ break;
+ }
+ return entry->lookup(name);
+}
+
+bool xact_t::valid() const
+{
+ if (! entry) {
+ DEBUG("ledger.validate", "xact_t: ! entry");
+ return false;
+ }
+
+ if (state != UNCLEARED && state != CLEARED && state != PENDING) {
+ DEBUG("ledger.validate", "xact_t: state is bad");
+ return false;
+ }
+
+ xacts_list::const_iterator i =
+ std::find(entry->xacts.begin(),
+ entry->xacts.end(), this);
+ if (i == entry->xacts.end()) {
+ DEBUG("ledger.validate", "xact_t: ! found");
+ return false;
+ }
+
+ if (! account) {
+ DEBUG("ledger.validate", "xact_t: ! account");
+ return false;
+ }
+
+ if (! amount.valid()) {
+ DEBUG("ledger.validate", "xact_t: ! amount.valid()");
+ return false;
+ }
+
+ if (cost && ! cost->valid()) {
+ DEBUG("ledger.validate", "xact_t: cost && ! cost->valid()");
+ return false;
+ }
+
+ if (flags() & ~0x003f) {
+ DEBUG("ledger.validate", "xact_t: flags are bad");
+ return false;
+ }
+
+ return true;
+}
+
+#if 0
+xact_context::xact_context(const xact_t& _xact, const string& desc) throw()
+ : file_context("", 0, desc), xact(_xact)
+{
+ const paths_list& sources(xact.entry->journal->sources);
+ unsigned int x = 0;
+ foreach (const path& path, sources)
+ if (x == xact.entry->src_idx) {
+ file = path;
+ break;
+ }
+ line = xact.beg_line;
+}
+#endif
+
+void xact_t::add_to_value(value_t& value)
+{
+ if (xdata_ && xdata_->has_flags(XACT_EXT_COMPOUND)) {
+ value += xdata_->value;
+ }
+ else if (cost || (! value.is_null() && ! value.is_realzero())) {
+ if (value.is_null())
+ value = amount_t();
+ value.add(amount, cost);
+ }
+ else {
+ value = amount;
+ }
+}
+
+} // namespace ledger
diff --git a/src/xact.h b/src/xact.h
new file mode 100644
index 00000000..5d4950f9
--- /dev/null
+++ b/src/xact.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _XACT_H
+#define _XACT_H
+
+#include "utils.h"
+#include "scope.h"
+
+namespace ledger {
+
+class entry_t;
+class account_t;
+
+class xact_t;
+typedef std::list<xact_t *> xacts_list;
+
+class xact_t : public supports_flags<>, public scope_t
+{
+public:
+#define XACT_NORMAL 0x0000 // no flags at all, a basic transaction
+#define XACT_VIRTUAL 0x0001 // the account was specified with (parens)
+#define XACT_BALANCE 0x0002 // the account was specified with [brackets]
+#define XACT_AUTO 0x0004 // transaction created by automated entry
+#define XACT_IN_CACHE 0x0008 // transaction allocated by the binary cache
+#define XACT_CALCULATED 0x0010 // transaction's amount was auto-calculated
+#define XACT_GENERATED 0x0020 // transaction was not found in a journal
+#define XACT_TEMP 0x0040 // transaction is a temporary object
+
+ enum state_t { UNCLEARED, CLEARED, PENDING };
+
+ entry_t * entry;
+ account_t * account;
+ state_t state;
+
+ optional<date_t> _date;
+ optional<date_t> _date_eff;
+ optional<string> note;
+
+ amount_t amount;
+ optional<expr_t> amount_expr;
+ optional<amount_t> cost;
+ optional<expr_t> cost_expr;
+
+ istream_pos_type beg_pos;
+ unsigned long beg_line;
+ istream_pos_type end_pos;
+ unsigned long end_line;
+
+ static bool use_effective_date;
+
+ xact_t(account_t * _account = NULL,
+ flags_t _flags = XACT_NORMAL)
+ : supports_flags<>(_flags), entry(NULL), account(_account),
+ state(UNCLEARED), beg_pos(0), beg_line(0), end_pos(0), end_line(0)
+ {
+ TRACE_CTOR(xact_t, "account_t *, flags_t");
+ }
+ xact_t(account_t * _account,
+ const amount_t& _amount,
+ flags_t _flags = XACT_NORMAL,
+ const optional<string>& _note = none)
+ : supports_flags<>(_flags), entry(NULL), account(_account),
+ state(UNCLEARED), note(_note), amount(_amount),
+ beg_pos(0), beg_line(0), end_pos(0), end_line(0)
+ {
+ TRACE_CTOR(xact_t,
+ "account_t *, const amount_t&, flags_t, const string&");
+ }
+ xact_t(const xact_t& xact)
+ : supports_flags<>(xact),
+ scope_t(),
+ entry(xact.entry),
+ account(xact.account),
+ state(xact.state),
+ _date(xact._date),
+ _date_eff(xact._date_eff),
+ note(xact.note),
+ amount(xact.amount),
+ cost(xact.cost),
+ beg_pos(xact.beg_pos),
+ beg_line(xact.beg_line),
+ end_pos(xact.end_pos),
+ end_line(xact.end_line),
+ xdata_(xact.xdata_) // jww (2008-07-19): What are the copy semantics?
+ {
+ TRACE_CTOR(xact_t, "copy");
+ }
+ ~xact_t();
+
+ date_t actual_date() const;
+ date_t effective_date() const;
+ date_t date() const {
+ if (use_effective_date)
+ return effective_date();
+ else
+ return actual_date();
+ }
+
+ bool must_balance() const {
+ return ! has_flags(XACT_VIRTUAL) || has_flags(XACT_BALANCE);
+ }
+
+ virtual expr_t::ptr_op_t lookup(const string& name);
+
+ bool valid() const;
+
+ struct xdata_t : public supports_flags<>
+ {
+#define XACT_EXT_RECEIVED 0x01
+#define XACT_EXT_HANDLED 0x02
+#define XACT_EXT_TO_DISPLAY 0x04
+#define XACT_EXT_DISPLAYED 0x08
+#define XACT_EXT_NO_TOTAL 0x10
+#define XACT_EXT_SORT_CALC 0x20
+#define XACT_EXT_COMPOUND 0x40
+#define XACT_EXT_MATCHES 0x80
+
+ value_t total;
+ value_t sort_value;
+ value_t value;
+ unsigned int index;
+ date_t date;
+ account_t * account;
+ void * ptr;
+
+ optional<xacts_list> component_xacts;
+
+ xdata_t() : supports_flags<>(), index(0), account(NULL), ptr(NULL) {
+ TRACE_CTOR(xdata_t, "");
+ }
+ ~xdata_t() throw() {
+ TRACE_DTOR(xdata_t);
+ }
+
+ void remember_xact(xact_t& xact) {
+ if (! component_xacts)
+ component_xacts = xacts_list();
+ component_xacts->push_back(&xact);
+ }
+
+ bool has_component_xacts() const {
+ return component_xacts && ! component_xacts->empty();
+ }
+
+ void copy_component_xacts(xacts_list& xacts) {
+ foreach (xact_t * xact, xacts)
+ remember_xact(*xact);
+ }
+
+#if 0
+ void walk_component_xacts(item_handler<xact_t>& handler) const {
+ foreach (xact_t * xact, *component_xacts)
+ handler(*xact);
+ }
+#endif
+ };
+
+ // This variable holds optional "extended data" which is usually produced
+ // only during reporting, and only for the transaction set being reported.
+ // It's a memory-saving measure to delay allocation until the last possible
+ // moment.
+ mutable optional<xdata_t> xdata_;
+
+ bool has_xdata() const {
+ return xdata_;
+ }
+ void clear_xdata() {
+ xdata_ = none;
+ }
+ xdata_t& xdata() {
+ if (! xdata_)
+ xdata_ = xdata_t();
+ return *xdata_;
+ }
+
+ void add_to_value(value_t& value);
+
+ date_t reported_date() const {
+ if (xdata_ && is_valid(xdata_->date))
+ return xdata_->date;
+ return
+ date();
+ }
+
+ account_t * reported_account() {
+ if (xdata_)
+ if (account_t * acct = xdata_->account)
+ return acct;
+ return account;
+ }
+
+ const account_t * reported_account() const {
+ return const_cast<xact_t *>(this)->reported_account();
+ }
+};
+
+} // namespace ledger
+
+#endif // _XACT_H
diff --git a/xml.cc b/src/xml.cc
index 418c6bf7..715965cb 100644
--- a/xml.cc
+++ b/src/xml.cc
@@ -1,19 +1,37 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
#include "xml.h"
#include "journal.h"
-#include "datetime.h"
-#include "error.h"
-
-#include <iostream>
-#include <sstream>
-#include <cstring>
-
-extern "C" {
-#if defined(HAVE_EXPAT)
-#include <expat.h> // expat XML parser
-#elif defined(HAVE_XMLPARSE)
-#include <xmlparse.h> // expat XML parser
-#endif
-}
+#include "utils.h"
namespace ledger {
@@ -25,13 +43,13 @@ static unsigned int count;
static journal_t * curr_journal;
static entry_t * curr_entry;
static commodity_t * curr_comm;
-static std::string comm_flags;
+static string comm_flags;
-static transaction_t::state_t curr_state;
+static xact_t::state_t curr_state;
-static std::string data;
-static bool ignore;
-static std::string have_error;
+static string data;
+static bool ignore;
+static string have_error;
static void startElement(void *userData, const char *name, const char **attrs)
{
@@ -41,16 +59,16 @@ static void startElement(void *userData, const char *name, const char **attrs)
if (std::strcmp(name, "entry") == 0) {
assert(! curr_entry);
curr_entry = new entry_t;
- curr_state = transaction_t::UNCLEARED;
+ curr_state = xact_t::UNCLEARED;
}
- else if (std::strcmp(name, "transaction") == 0) {
+ else if (std::strcmp(name, "xact") == 0) {
assert(curr_entry);
- curr_entry->add_transaction(new transaction_t);
- if (curr_state != transaction_t::UNCLEARED)
- curr_entry->transactions.back()->state = curr_state;
+ curr_entry->add_xact(new xact_t);
+ if (curr_state != xact_t::UNCLEARED)
+ curr_entry->xacts.back()->state = curr_state;
}
else if (std::strcmp(name, "commodity") == 0) {
- if (std::string(attrs[0]) == "flags")
+ if (string(attrs[0]) == "flags")
comm_flags = attrs[1];
}
else if (std::strcmp(name, "total") == 0) {
@@ -72,56 +90,56 @@ static void endElement(void *userData, const char *name)
count++;
} else {
account_t * acct = curr_journal->find_account("<Unknown>");
- curr_entry->add_transaction(new transaction_t(acct));
+ curr_entry->add_xact(new xact_t(acct));
if (curr_journal->add_entry(curr_entry)) {
count++;
} else {
- delete curr_entry;
+ checked_delete(curr_entry);
have_error = "Entry cannot be balanced";
}
}
curr_entry = NULL;
}
else if (std::strcmp(name, "en:date") == 0) {
- curr_entry->_date = data;
+ curr_entry->_date = parse_date(data);
}
else if (std::strcmp(name, "en:date_eff") == 0) {
- curr_entry->_date_eff = data;
+ curr_entry->_date_eff = parse_date(data);
}
else if (std::strcmp(name, "en:code") == 0) {
curr_entry->code = data;
}
else if (std::strcmp(name, "en:cleared") == 0) {
- curr_state = transaction_t::CLEARED;
+ curr_state = xact_t::CLEARED;
}
else if (std::strcmp(name, "en:pending") == 0) {
- curr_state = transaction_t::PENDING;
+ curr_state = xact_t::PENDING;
}
else if (std::strcmp(name, "en:payee") == 0) {
curr_entry->payee = data;
}
else if (std::strcmp(name, "tr:account") == 0) {
- curr_entry->transactions.back()->account = curr_journal->find_account(data);
+ curr_entry->xacts.back()->account = curr_journal->find_account(data);
}
else if (std::strcmp(name, "tr:cleared") == 0) {
- curr_entry->transactions.back()->state = transaction_t::CLEARED;
+ curr_entry->xacts.back()->state = xact_t::CLEARED;
}
else if (std::strcmp(name, "tr:pending") == 0) {
- curr_entry->transactions.back()->state = transaction_t::PENDING;
+ curr_entry->xacts.back()->state = xact_t::PENDING;
}
else if (std::strcmp(name, "tr:virtual") == 0) {
- curr_entry->transactions.back()->flags |= TRANSACTION_VIRTUAL;
+ curr_entry->xacts.back()->add_flags(XACT_VIRTUAL);
}
else if (std::strcmp(name, "tr:generated") == 0) {
- curr_entry->transactions.back()->flags |= TRANSACTION_AUTO;
+ curr_entry->xacts.back()->add_flags(XACT_AUTO);
}
else if (std::strcmp(name, "symbol") == 0) {
assert(! curr_comm);
- curr_comm = commodity_t::find_or_create(data);
+ curr_comm = amount_t::current_pool->find_or_create(data);
assert(curr_comm);
curr_comm->add_flags(COMMODITY_STYLE_SUFFIXED);
if (! comm_flags.empty()) {
- for (std::string::size_type i = 0, l = comm_flags.length(); i < l; i++) {
+ for (string::size_type i = 0, l = comm_flags.length(); i < l; i++) {
switch (comm_flags[i]) {
case 'P': curr_comm->drop_flags(COMMODITY_STYLE_SUFFIXED); break;
case 'S': curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); break;
@@ -146,15 +164,15 @@ static void endElement(void *userData, const char *name)
}
#endif
else if (std::strcmp(name, "quantity") == 0) {
- curr_entry->transactions.back()->amount.parse(data);
+ curr_entry->xacts.back()->amount.parse(data);
if (curr_comm) {
- std::string::size_type i = data.find('.');
- if (i != std::string::npos) {
+ string::size_type i = data.find('.');
+ if (i != string::npos) {
int precision = data.length() - i - 1;
if (precision > curr_comm->precision())
curr_comm->set_precision(precision);
}
- curr_entry->transactions.back()->amount.set_commodity(*curr_comm);
+ curr_entry->xacts.back()->amount.set_commodity(*curr_comm);
curr_comm = NULL;
}
}
@@ -166,7 +184,7 @@ static void endElement(void *userData, const char *name)
static void dataHandler(void *userData, const char *s, int len)
{
if (! ignore)
- data = std::string(s, len);
+ data = string(s, len);
}
bool xml_parser_t::test(std::istream& in) const
@@ -192,21 +210,20 @@ bool xml_parser_t::test(std::istream& in) const
return true;
}
-unsigned int xml_parser_t::parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master,
- const std::string * original_file)
+unsigned int xml_parser_t::parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master,
+ const path * original_file)
{
char buf[BUFSIZ];
count = 0;
- curr_journal = journal;
+ curr_journal = &journal;
curr_entry = NULL;
curr_comm = NULL;
ignore = false;
- unsigned int offset = 2;
XML_Parser parser = XML_ParserCreate(NULL);
current_parser = parser;
@@ -221,23 +238,23 @@ unsigned int xml_parser_t::parse(std::istream& in,
result = XML_Parse(parser, buf, std::strlen(buf), in.eof());
}
catch (const std::exception& err) {
- unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
XML_ParserFree(parser);
- throw new parse_error(err.what());
+ throw parse_error(err.what());
}
if (! have_error.empty()) {
- unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
parse_error err(have_error);
std::cerr << "Error: " << err.what() << std::endl;
have_error = "";
}
if (! result) {
- unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
const char * err = XML_ErrorString(XML_GetErrorCode(parser));
XML_ParserFree(parser);
- throw new parse_error(err);
+ throw parse_error(err);
}
}
@@ -290,56 +307,57 @@ void xml_write_amount(std::ostream& out, const amount_t& amount,
void xml_write_value(std::ostream& out, const value_t& value,
const int depth = 0)
{
- balance_t * bal = NULL;
+ const balance_t * bal = NULL;
for (int i = 0; i < depth; i++) out << ' ';
out << "<value type=\"";
- switch (value.type) {
+ switch (value.type()) {
case value_t::BOOLEAN: out << "boolean"; break;
case value_t::INTEGER: out << "integer"; break;
case value_t::AMOUNT: out << "amount"; break;
case value_t::BALANCE:
case value_t::BALANCE_PAIR: out << "balance"; break;
+ default:
+ assert(false);
+ break;
}
out << "\">\n";
- switch (value.type) {
+ switch (value.type()) {
case value_t::BOOLEAN:
for (int i = 0; i < depth + 2; i++) out << ' ';
- out << "<boolean>" << *((bool *) value.data) << "</boolean>\n";
+ out << "<boolean>" << value.as_boolean() << "</boolean>\n";
break;
case value_t::INTEGER:
for (int i = 0; i < depth + 2; i++) out << ' ';
- out << "<integer>" << *((long *) value.data) << "</integer>\n";
+ out << "<integer>" << value.as_long() << "</integer>\n";
break;
case value_t::AMOUNT:
- xml_write_amount(out, *((amount_t *) value.data), depth + 2);
+ xml_write_amount(out, value.as_amount(), depth + 2);
break;
case value_t::BALANCE:
- bal = (balance_t *) value.data;
+ bal = &(value.as_balance());
// fall through...
case value_t::BALANCE_PAIR:
if (! bal)
- bal = &((balance_pair_t *) value.data)->quantity;
+ bal = &(value.as_balance_pair().quantity());
for (int i = 0; i < depth + 2; i++) out << ' ';
out << "<balance>\n";
- for (amounts_map::const_iterator i = bal->amounts.begin();
- i != bal->amounts.end();
- i++)
- xml_write_amount(out, (*i).second, depth + 4);
+ foreach (const balance_t::amounts_map::value_type& pair, bal->amounts)
+ xml_write_amount(out, pair.second, depth + 4);
for (int i = 0; i < depth + 2; i++) out << ' ';
out << "</balance>\n";
break;
default:
- assert(0);
+ assert(false);
break;
}
@@ -347,7 +365,7 @@ void xml_write_value(std::ostream& out, const value_t& value,
out << "</value>\n";
}
-void output_xml_string(std::ostream& out, const std::string& str)
+void output_xml_string(std::ostream& out, const string& str)
{
for (const char * s = str.c_str(); *s; s++) {
switch (*s) {
@@ -369,108 +387,113 @@ void output_xml_string(std::ostream& out, const std::string& str)
void format_xml_entries::format_last_entry()
{
- output_stream << " <entry>\n"
- << " <en:date>" << last_entry->_date.to_string("%Y/%m/%d")
- << "</en:date>\n";
-
- if (last_entry->_date_eff)
- output_stream << " <en:date_eff>"
- << last_entry->_date_eff.to_string("%Y/%m/%d")
- << "</en:date_eff>\n";
-
- if (! last_entry->code.empty()) {
- output_stream << " <en:code>";
- output_xml_string(output_stream, last_entry->code);
- output_stream << "</en:code>\n";
+ std::ostream& out(*report.output_stream);
+
+#if 0
+ // jww (2008-05-08): Need to format these dates
+ out << " <entry>\n"
+ << " <en:date>" << last_entry->_date.to_string("%Y/%m/%d")
+ << "</en:date>\n";
+
+ if (is_valid(last_entry->_date_eff))
+ out << " <en:date_eff>"
+ << last_entry->_date_eff.to_string("%Y/%m/%d")
+ << "</en:date_eff>\n";
+#endif
+
+ if (last_entry->code) {
+ out << " <en:code>";
+ output_xml_string(out, *last_entry->code);
+ out << "</en:code>\n";
}
if (! last_entry->payee.empty()) {
- output_stream << " <en:payee>";
- output_xml_string(output_stream, last_entry->payee);
- output_stream << "</en:payee>\n";
+ out << " <en:payee>";
+ output_xml_string(out, last_entry->payee);
+ out << "</en:payee>\n";
}
bool first = true;
- for (transactions_list::const_iterator i = last_entry->transactions.begin();
- i != last_entry->transactions.end();
- i++) {
- if (transaction_has_xdata(**i) &&
- transaction_xdata_(**i).dflags & TRANSACTION_TO_DISPLAY) {
+ foreach (xact_t * xact, last_entry->xacts) {
+ if (xact->has_xdata() &&
+ xact->xdata().has_flags(XACT_EXT_TO_DISPLAY)) {
if (first) {
- output_stream << " <en:transactions>\n";
+ out << " <en:xacts>\n";
first = false;
}
- output_stream << " <transaction>\n";
+ out << " <xact>\n";
- if ((*i)->_date)
- output_stream << " <tr:date>"
- << (*i)->_date.to_string("%Y/%m/%d")
- << "</tr:date>\n";
-
- if ((*i)->_date_eff)
- output_stream << " <tr:date_eff>"
- << (*i)->_date_eff.to_string("%Y/%m/%d")
- << "</tr:date_eff>\n";
+#if 0
+ // jww (2008-05-08): Need to format these
+ if (xact->_date)
+ out << " <tr:date>"
+ << xact->_date.to_string("%Y/%m/%d")
+ << "</tr:date>\n";
+
+ if (is_valid(xact->_date_eff))
+ out << " <tr:date_eff>"
+ << xact->_date_eff.to_string("%Y/%m/%d")
+ << "</tr:date_eff>\n";
+#endif
- if ((*i)->state == transaction_t::CLEARED)
- output_stream << " <tr:cleared/>\n";
- else if ((*i)->state == transaction_t::PENDING)
- output_stream << " <tr:pending/>\n";
+ if (xact->state == xact_t::CLEARED)
+ out << " <tr:cleared/>\n";
+ else if (xact->state == xact_t::PENDING)
+ out << " <tr:pending/>\n";
- if ((*i)->flags & TRANSACTION_VIRTUAL)
- output_stream << " <tr:virtual/>\n";
- if ((*i)->flags & TRANSACTION_AUTO)
- output_stream << " <tr:generated/>\n";
+ if (xact->has_flags(XACT_VIRTUAL))
+ out << " <tr:virtual/>\n";
+ if (xact->has_flags(XACT_AUTO))
+ out << " <tr:generated/>\n";
- if ((*i)->account) {
- std::string name = (*i)->account->fullname();
+ if (xact->account) {
+ string name = xact->account->fullname();
if (name == "<Total>")
name = "[TOTAL]";
else if (name == "<Unknown>")
name = "[UNKNOWN]";
- output_stream << " <tr:account>";
- output_xml_string(output_stream, name);
- output_stream << "</tr:account>\n";
+ out << " <tr:account>";
+ output_xml_string(out, name);
+ out << "</tr:account>\n";
}
- output_stream << " <tr:amount>\n";
- if (transaction_xdata_(**i).dflags & TRANSACTION_COMPOUND)
- xml_write_value(output_stream,
- transaction_xdata_(**i).value, 10);
+ out << " <tr:amount>\n";
+ if (xact->xdata().has_flags(XACT_EXT_COMPOUND))
+ xml_write_value(out, xact->xdata().value, 10);
else
- xml_write_value(output_stream, value_t((*i)->amount), 10);
- output_stream << " </tr:amount>\n";
+ xml_write_value(out, value_t(xact->amount), 10);
+ out << " </tr:amount>\n";
- if ((*i)->cost) {
- output_stream << " <tr:cost>\n";
- xml_write_value(output_stream, value_t(*(*i)->cost), 10);
- output_stream << " </tr:cost>\n";
+ if (xact->cost) {
+ out << " <tr:cost>\n";
+ xml_write_value(out, value_t(*xact->cost), 10);
+ out << " </tr:cost>\n";
}
- if (! (*i)->note.empty()) {
- output_stream << " <tr:note>";
- output_xml_string(output_stream, (*i)->note);
- output_stream << "</tr:note>\n";
+ if (xact->note) {
+ out << " <tr:note>";
+ output_xml_string(out, *xact->note);
+ out << "</tr:note>\n";
}
if (show_totals) {
- output_stream << " <total>\n";
- xml_write_value(output_stream, transaction_xdata_(**i).total, 10);
- output_stream << " </total>\n";
+ out << " <total>\n";
+ xml_write_value(out, xact->xdata().total, 10);
+ out << " </total>\n";
}
- output_stream << " </transaction>\n";
+ out << " </xact>\n";
- transaction_xdata_(**i).dflags |= TRANSACTION_DISPLAYED;
+ xact->xdata().add_flags(XACT_EXT_DISPLAYED);
}
}
if (! first)
- output_stream << " </en:transactions>\n";
+ out << " </en:xacts>\n";
- output_stream << " </entry>\n";
+ out << " </entry>\n";
}
} // namespace ledger
diff --git a/src/xml.h b/src/xml.h
new file mode 100644
index 00000000..38768cff
--- /dev/null
+++ b/src/xml.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2003-2008, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _XML_H
+#define _XML_H
+
+#include "journal.h"
+#include "report.h"
+#include "output.h"
+
+namespace ledger {
+
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+
+class xml_parser_t : public journal_t::parser_t
+{
+ public:
+ virtual bool test(std::istream& in) const;
+
+ virtual unsigned int parse(std::istream& in,
+ session_t& session,
+ journal_t& journal,
+ account_t * master = NULL,
+ const path * original_file = NULL);
+};
+
+#endif
+
+class format_xml_entries : public format_entries
+{
+ bool show_totals;
+
+ format_xml_entries();
+
+public:
+ format_xml_entries(report_t& _report,
+ const bool _show_totals = false)
+ : format_entries(_report, ""), show_totals(_show_totals) {
+ TRACE_CTOR(format_xml_entries, "std::ostream&, const bool");
+ *report.output_stream << "<?xml version=\"1.0\"?>\n"
+ << "<ledger version=\"2.5\">\n";
+ }
+ virtual ~format_xml_entries() throw() {
+ TRACE_DTOR(format_xml_entries);
+ }
+
+ virtual void flush() {
+ format_entries::flush();
+ *report.output_stream << "</ledger>" << std::endl;
+ }
+
+ virtual void format_last_entry();
+};
+
+} // namespace ledger
+
+#endif // _XML_H
diff --git a/startup.cc b/startup.cc
deleted file mode 100644
index 9e35adfb..00000000
--- a/startup.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-#include "ledger.h"
-
-using namespace ledger;
-
-namespace ledger {
- parser_t * binary_parser_ptr = NULL;
- parser_t * xml_parser_ptr = NULL;
- parser_t * gnucash_parser_ptr = NULL;
- parser_t * ofx_parser_ptr = NULL;
- parser_t * qif_parser_ptr = NULL;
- parser_t * textual_parser_ptr = NULL;
-}
-
-namespace {
- binary_parser_t binary_parser;
-#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
- xml_parser_t xml_parser;
- gnucash_parser_t gnucash_parser;
-#endif
-#ifdef HAVE_LIBOFX
- ofx_parser_t ofx_parser;
-#endif
- qif_parser_t qif_parser;
- textual_parser_t textual_parser;
-
- static class startup {
- public:
- startup();
- ~startup();
- } _startup;
-
- startup::startup()
- {
- std::ios::sync_with_stdio(false);
-
- initialize_parser_support();
-
- register_parser(&binary_parser); binary_parser_ptr = &binary_parser;
-#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
- register_parser(&xml_parser); xml_parser_ptr = &xml_parser;
- register_parser(&gnucash_parser); gnucash_parser_ptr = &gnucash_parser;
-#endif
-#ifdef HAVE_LIBOFX
- register_parser(&ofx_parser); ofx_parser_ptr = &ofx_parser;
-#endif
- register_parser(&qif_parser); qif_parser_ptr = &qif_parser;
- register_parser(&textual_parser); textual_parser_ptr = &textual_parser;
- }
-
- startup::~startup()
- {
- if (! ledger::do_cleanup)
- return;
- shutdown_parser_support();
- }
-}
diff --git a/test.py b/test.py
deleted file mode 100755
index 49276de0..00000000
--- a/test.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env python
-
-from amounts import *
-
-amt_ncd = Amount("100")
-print amt_ncd
-amt_nc = Amount("100.00")
-print amt_nc
-amt = Amount("$100.00")
-print amt
-namt_ncd = Amount("-100")
-print namt_ncd
-namt_nc = Amount("-100.00")
-print namt_nc
-namt = Amount("$-100.00")
-print namt
-namt2 = Amount("-$100.00")
-print namt2
-
-val = Value("$100.00")
-
-print val
diff --git a/test/PyUnitTests.py b/test/PyUnitTests.py
new file mode 100755
index 00000000..52671fbd
--- /dev/null
+++ b/test/PyUnitTests.py
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+PYTHONPATH="%builddir%":"%srcdir%":$PYTHONPATH \
+DYLD_LIBRARY_PATH="%builddir%/.libs":$DYLD_LIBRARY_PATH \
+ python "%srcdir%"/test/UnitTests.py
diff --git a/test/UnitTests.cc b/test/UnitTests.cc
new file mode 100644
index 00000000..e176a055
--- /dev/null
+++ b/test/UnitTests.cc
@@ -0,0 +1,112 @@
+#include <cppunit/CompilerOutputter.h>
+#include <cppunit/TestResult.h>
+#include <cppunit/TestResultCollector.h>
+#include <cppunit/TestRunner.h>
+#include <cppunit/TextTestProgressListener.h>
+#include <cppunit/BriefTestProgressListener.h>
+#include <cppunit/XmlOutputter.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include "UnitTests.h"
+
+
+// Create the CppUnit registry
+
+CPPUNIT_REGISTRY_ADD_TO_DEFAULT("Framework");
+
+CPPUNIT_REGISTRY_ADD_TO_DEFAULT("numerics");
+
+// Create a sample test, which acts both as a template, and a
+// verification that the basic framework is functioning.
+
+class UnitTests : public CPPUNIT_NS::TestCase
+{
+ CPPUNIT_TEST_SUITE( UnitTests );
+ CPPUNIT_TEST( testInitialization );
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ UnitTests() {}
+ virtual ~UnitTests() {}
+
+ virtual void setUp() {}
+ virtual void tearDown() {}
+
+ void testInitialization() {
+ assertEqual(std::string("Hello, world!"),
+ std::string("Hello, world!"));
+ }
+
+private:
+ UnitTests( const UnitTests &copy );
+ void operator =( const UnitTests &copy );
+};
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(UnitTests, "framework");
+
+
+// Create the various runners and commence running the tests!
+
+int main(int argc, char* argv[])
+{
+ int index = 1;
+
+ if (argc > index && std::string(argv[index]) == "--verify") {
+ ledger::verify_enabled = true;
+ index++;
+ }
+
+ // Retreive test path from command line first argument. Default to
+ // "" which resolves to the top level suite.
+ std::string testPath = ((argc > index) ? std::string(argv[index]) :
+ std::string(""));
+
+ // Create the event manager and test controller
+ CPPUNIT_NS::TestResult controller;
+
+ // Add a listener that collects test results
+ CPPUNIT_NS::TestResultCollector result;
+ controller.addListener(&result);
+
+ // Add a listener that print dots as test run.
+#if 1
+ CPPUNIT_NS::TextTestProgressListener progress;
+#else
+ CPPUNIT_NS::BriefTestProgressListener progress;
+#endif
+ controller.addListener(&progress);
+
+ // Add the top suite to the test runner
+ CPPUNIT_NS::TestRunner runner;
+ runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest());
+ try {
+ IF_VERIFY()
+ initialize_memory_tracing();
+
+ runner.run(controller, testPath);
+
+ IF_VERIFY()
+ shutdown_memory_tracing();
+
+ // Print test in a compiler compatible format.
+ CPPUNIT_NS::CompilerOutputter outputter(&result, CPPUNIT_NS::stdCOut());
+ outputter.write();
+
+#if 0
+ // Uncomment this for XML output
+ std::ofstream file("tests.xml");
+ CPPUNIT_NS::XmlOutputter xml(&result, file);
+ xml.setStyleSheet("report.xsl");
+ xml.write();
+ file.close();
+#endif
+ }
+ catch (std::invalid_argument &e) { // Test path not resolved
+ CPPUNIT_NS::stdCOut() << "\n"
+ << "ERROR: " << e.what()
+ << "\n";
+ return 0;
+ }
+
+ return result.wasSuccessful() ? 0 : 1;
+}
diff --git a/test/UnitTests.h b/test/UnitTests.h
new file mode 100644
index 00000000..e7027cf4
--- /dev/null
+++ b/test/UnitTests.h
@@ -0,0 +1,24 @@
+#ifndef _UNITTESTS_H
+#define _UNITTESTS_H
+
+#include "ledger.h"
+
+using namespace ledger;
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/Exception.h>
+#include <cppunit/Portability.h>
+
+#define assertDoublesEqual(x,y,z,w) CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(x,y,z,w)
+#define assertEqual(x,y) CPPUNIT_ASSERT_EQUAL(x,y)
+#define assertNotEqual(x,y) CPPUNIT_ASSERT((x) != (y))
+#define assertTrue(x) CPPUNIT_ASSERT(x)
+#define assertFalse(x) CPPUNIT_ASSERT(! (x))
+#define assertValid(x) CPPUNIT_ASSERT((x).valid())
+#define assertEqualMessage(x,y,z) CPPUNIT_ASSERT_EQUAL_MESSAGE(x,y,z)
+#define assertMessage(x,y) CPPUNIT_ASSERT_MESSAGE(x,y)
+#define assertThrow(x,y) CPPUNIT_ASSERT_THROW(x,y)
+
+#define internalAmount(x) amount_t::exact(x)
+
+#endif /* _UNITTESTS_H */
diff --git a/test/UnitTests.py b/test/UnitTests.py
new file mode 100644
index 00000000..a51300a3
--- /dev/null
+++ b/test/UnitTests.py
@@ -0,0 +1,9 @@
+from unittest import TextTestRunner, TestSuite
+
+import test.python.numerics.t_amount as t_amount
+
+suites = [
+ t_amount.suite(),
+]
+
+TextTestRunner().run(TestSuite(suites))
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/test/__init__.py
diff --git a/test/python/__init__.py b/test/python/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/test/python/__init__.py
diff --git a/test/python/numerics/__init__.py b/test/python/numerics/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/test/python/numerics/__init__.py
diff --git a/test/python/numerics/t_amount.py b/test/python/numerics/t_amount.py
new file mode 100644
index 00000000..0aabbec4
--- /dev/null
+++ b/test/python/numerics/t_amount.py
@@ -0,0 +1,1469 @@
+# -*- coding: utf-8 -*-
+
+import unittest
+import exceptions
+import operator
+
+from ledger import *
+from StringIO import *
+
+internalAmount = amount.exact
+
+class t_amountTestCase(unittest.TestCase):
+ testSession = None
+
+ def assertValid(self, amt):
+ self.assertTrue(amt.valid())
+
+ def setUp(self):
+ #self.testSession = session()
+ #set_session_context(self.testSession)
+
+ # Cause the display precision for dollars to be initialized to 2.
+ x1 = amount("$1.00")
+ self.assertTrue(x1)
+
+ amount.stream_fullstrings = True # make reports from UnitTests accurate
+
+ def tearDown(self):
+ amount.stream_fullstrings = False
+
+ #set_session_context()
+ #self.testSession = None
+
+ def testParser(self):
+ x0 = amount()
+ x1 = amount()
+ x2 = amount()
+ x3 = amount()
+ x4 = amount("123.456")
+ x5 = amount(x4)
+ x6 = amount(x4)
+ x7 = amount(x4)
+ x8 = amount("$123.45")
+ x9 = amount(x8)
+ x10 = amount(x8)
+ x11 = amount(x8)
+ x12 = amount("$100")
+
+ self.assertEqual(2, x12.commodity.precision)
+
+ #buf = "$100..."
+ #input = StringIO(buf)
+ #x13 = amount()
+ #x13.parse(input)
+ #self.assertEqual(x12, x13)
+
+ x14 = amount()
+ self.assertRaises(exceptions.ArithmeticError, lambda: x14.parse("DM"))
+
+ x15 = amount("$1.000.000,00")
+
+ x16 = amount("$2000")
+ self.assertEqual("$2.000,00", x16.to_string())
+ x16.parse("$2000,00")
+ self.assertEqual("$2.000,00", x16.to_string())
+
+ # Since European-ness is an additive quality, we must switch back
+ # to American-ness manually
+ x15.commodity.drop_flags(COMMODITY_STYLE_EUROPEAN)
+
+ x17 = amount("$1,000,000.00")
+
+ x18 = amount("$2000")
+ self.assertEqual("$2,000.00", x18.to_string())
+ x18.parse("$2,000")
+ self.assertEqual("$2,000.00", x18.to_string())
+
+ self.assertEqual(x15, x17)
+
+ x19 = amount("EUR 1000")
+ x20 = amount("EUR 1000")
+
+ self.assertEqual("EUR 1000", x19.to_string())
+ self.assertEqual("EUR 1000", x20.to_string())
+
+ x1.parse("$100.0000", AMOUNT_PARSE_NO_MIGRATE)
+ self.assertEqual(2, x12.commodity.precision)
+ self.assertEqual(x1.commodity, x12.commodity)
+ self.assertEqual(x1, x12)
+
+ x0.parse("$100.0000")
+ self.assertEqual(4, x12.commodity.precision)
+ self.assertEqual(x0.commodity, x12.commodity)
+ self.assertEqual(x0, x12)
+
+ x2.parse("$100.00", AMOUNT_PARSE_NO_REDUCE)
+ self.assertEqual(x2, x12)
+ x3.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE)
+ self.assertEqual(x3, x12)
+
+ x4.parse("$100.00")
+ self.assertEqual(x4, x12)
+ x5.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE)
+ self.assertEqual(x5, x12)
+ x6.parse("$100.00", AMOUNT_PARSE_NO_REDUCE)
+ self.assertEqual(x6, x12)
+ x7.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE)
+ self.assertEqual(x7, x12)
+
+ x8.parse("$100.00")
+ self.assertEqual(x8, x12)
+ x9.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE)
+ self.assertEqual(x9, x12)
+ x10.parse("$100.00", AMOUNT_PARSE_NO_REDUCE)
+ self.assertEqual(x10, x12)
+ x11.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE)
+ self.assertEqual(x11, x12)
+
+ self.assertValid(x0)
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+ self.assertValid(x8)
+ self.assertValid(x9)
+ self.assertValid(x10)
+ self.assertValid(x11)
+ self.assertValid(x12)
+
+ def testConstructors(self):
+ x0 = amount()
+ x1 = amount(123456)
+ x2 = amount(123456L)
+ x3 = amount("123.456")
+ x5 = amount("123456")
+ x6 = amount("123.456")
+ x7 = amount("123456")
+ x8 = amount("123.456")
+ x9 = amount(x3)
+ x10 = amount(x6)
+ x11 = amount(x8)
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: amount(0) == x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: amount() == x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: amount("0") == x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: amount("0.0") == x0)
+ self.assertEqual(x2, x1)
+ self.assertEqual(x5, x1)
+ self.assertEqual(x7, x1)
+ self.assertEqual(x6, x3)
+ self.assertEqual(x8, x3)
+ self.assertEqual(x10, x3)
+ self.assertEqual(x10, x9)
+
+ self.assertValid(x0)
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+ self.assertValid(x8)
+ self.assertValid(x9)
+ self.assertValid(x10)
+ self.assertValid(x11)
+
+ def testCommodityConstructors(self):
+ x1 = amount("$123.45")
+ x2 = amount("-$123.45")
+ x3 = amount("$-123.45")
+ x4 = amount("DM 123.45")
+ x5 = amount("-DM 123.45")
+ x6 = amount("DM -123.45")
+ x7 = amount("123.45 euro")
+ x8 = amount("-123.45 euro")
+ x9 = amount("123.45€")
+ x10 = amount("-123.45€")
+
+ self.assertEqual(amount("$123.45"), x1)
+ self.assertEqual(amount("-$123.45"), x2)
+ self.assertEqual(amount("$-123.45"), x3)
+ self.assertEqual(amount("DM 123.45"), x4)
+ self.assertEqual(amount("-DM 123.45"), x5)
+ self.assertEqual(amount("DM -123.45"), x6)
+ self.assertEqual(amount("123.45 euro"), x7)
+ self.assertEqual(amount("-123.45 euro"), x8)
+ self.assertEqual(amount("123.45€"), x9)
+ self.assertEqual(amount("-123.45€"), x10)
+
+ self.assertEqual("$123.45", x1.to_string())
+ self.assertEqual("$-123.45", x2.to_string())
+ self.assertEqual("$-123.45", x3.to_string())
+ self.assertEqual("DM 123.45", x4.to_string())
+ self.assertEqual("DM -123.45", x5.to_string())
+ self.assertEqual("DM -123.45", x6.to_string())
+ self.assertEqual("123.45 euro", x7.to_string())
+ self.assertEqual("-123.45 euro", x8.to_string())
+ self.assertEqual("123.45€", x9.to_string())
+ self.assertEqual("-123.45€", x10.to_string())
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+ self.assertValid(x8)
+ self.assertValid(x9)
+ self.assertValid(x10)
+
+ def testAssignment(self):
+ x0 = amount()
+ x1 = amount(123456)
+ x2 = amount(123456L)
+ x3 = amount("123.456")
+ x5 = amount("123456")
+ x6 = amount("123.456")
+ x7 = "123456"
+ x8 = "123.456"
+ x9 = amount(x3)
+ x10 = amount(x6)
+
+ self.assertEqual(x2, x1)
+ self.assertEqual(x5, x1)
+ self.assertEqual(x7, x1)
+ self.assertEqual(x6, x3)
+ self.assertEqual(x8, x3)
+ self.assertEqual(x10, x3)
+ self.assertEqual(x10, x9)
+
+ x1 = amount(123456)
+ x2 = amount(123456L)
+ x3 = amount("123.456")
+ x5 = amount("123456")
+ x6 = amount("123.456")
+ x7 = amount("123456")
+ x8 = amount("123.456")
+ x9 = x3
+ x10 = amount(x6)
+
+ self.assertEqual(x2, x1)
+ self.assertEqual(x5, x1)
+ self.assertEqual(x7, x1)
+ self.assertEqual(x6, x3)
+ self.assertEqual(x8, x3)
+ self.assertEqual(x10, x3)
+ self.assertEqual(x10, x9)
+
+ self.assertFalse(x1.is_null())
+ x1 = x0 # sets x1 back to uninitialized state
+ self.assertTrue(x0.is_null())
+ self.assertTrue(x1.is_null())
+
+ self.assertValid(x0)
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+ self.assertValid(x8)
+ self.assertValid(x9)
+ self.assertValid(x10)
+
+ def testCommodityAssignment(self):
+ x1 = amount("$123.45")
+ x2 = amount("-$123.45")
+ x3 = amount("$-123.45")
+ x4 = amount("DM 123.45")
+ x5 = amount("-DM 123.45")
+ x6 = amount("DM -123.45")
+ x7 = amount("123.45 euro")
+ x8 = amount("-123.45 euro")
+ x9 = amount("123.45€")
+ x10 = amount("-123.45€")
+
+ self.assertEqual(amount("$123.45"), x1)
+ self.assertEqual(amount("-$123.45"), x2)
+ self.assertEqual(amount("$-123.45"), x3)
+ self.assertEqual(amount("DM 123.45"), x4)
+ self.assertEqual(amount("-DM 123.45"), x5)
+ self.assertEqual(amount("DM -123.45"), x6)
+ self.assertEqual(amount("123.45 euro"), x7)
+ self.assertEqual(amount("-123.45 euro"), x8)
+ self.assertEqual(amount("123.45€"), x9)
+ self.assertEqual(amount("-123.45€"), x10)
+
+ self.assertEqual("$123.45", x1.to_string())
+ self.assertEqual("$-123.45", x2.to_string())
+ self.assertEqual("$-123.45", x3.to_string())
+ self.assertEqual("DM 123.45", x4.to_string())
+ self.assertEqual("DM -123.45", x5.to_string())
+ self.assertEqual("DM -123.45", x6.to_string())
+ self.assertEqual("123.45 euro", x7.to_string())
+ self.assertEqual("-123.45 euro", x8.to_string())
+ self.assertEqual("123.45€", x9.to_string())
+ self.assertEqual("-123.45€", x10.to_string())
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+ self.assertValid(x8)
+ self.assertValid(x9)
+ self.assertValid(x10)
+
+ def testEquality(self):
+ x1 = amount(123456)
+ x2 = amount(456789)
+ x3 = amount(333333)
+ x4 = amount("123456.0")
+ x5 = amount("123456.0")
+ x6 = amount("123456.0")
+
+ self.assertTrue(x1 == 123456)
+ self.assertTrue(x1 != x2)
+ self.assertTrue(x1 == (x2 - x3))
+ self.assertTrue(x1 == x4)
+ self.assertTrue(x4 == x5)
+ self.assertTrue(x4 == x6)
+
+ self.assertTrue(x1 == 123456)
+ self.assertTrue(123456 == x1)
+ self.assertTrue(x1 == 123456L)
+ self.assertTrue(123456L == x1)
+ self.assertTrue(x1 == amount("123456.0"))
+ self.assertTrue(amount("123456.0") == x1)
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x6)
+
+ def testCommodityEquality(self):
+ x0 = amount()
+ x1 = amount("$123.45")
+ x2 = amount("-$123.45")
+ x3 = amount("$-123.45")
+ x4 = amount("DM 123.45")
+ x5 = amount("-DM 123.45")
+ x6 = amount("DM -123.45")
+ x7 = amount("123.45 euro")
+ x8 = amount("-123.45 euro")
+ x9 = amount("123.45€")
+ x10 = amount("-123.45€")
+
+ self.assertTrue(x0.is_null())
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.is_zero())
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.is_realzero())
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.sign() == 0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.compare(x1) < 0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.compare(x2) > 0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.compare(x0) == 0)
+
+ self.assertTrue(x1 != x2)
+ self.assertTrue(x1 != x4)
+ self.assertTrue(x1 != x7)
+ self.assertTrue(x1 != x9)
+ self.assertTrue(x2 == x3)
+ self.assertTrue(x4 != x5)
+ self.assertTrue(x5 == x6)
+ self.assertTrue(x7 == - x8)
+ self.assertTrue(x9 == - x10)
+
+ self.assertValid(x0)
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+ self.assertValid(x8)
+ self.assertValid(x9)
+ self.assertValid(x10)
+
+ def testComparisons(self):
+ x0 = amount()
+ x1 = amount(-123)
+ x2 = amount(123)
+ x3 = amount("-123.45")
+ x4 = amount("123.45")
+ x5 = amount("-123.45")
+ x6 = amount("123.45")
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 > x1)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 < x2)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 > x3)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 < x4)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 > x5)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 < x6)
+
+ self.assertTrue(x1 > x3)
+ self.assertTrue(x3 <= x5)
+ self.assertTrue(x3 >= x5)
+ self.assertTrue(x3 < x1)
+ self.assertTrue(x3 < x4)
+
+ self.assertTrue(x1 < 100)
+ self.assertTrue(100 > x1)
+ self.assertTrue(x1 < 100L)
+ self.assertTrue(100L > x1)
+ self.assertTrue(x1 < amount("100.0"))
+ self.assertTrue(amount("100.0") > x1)
+
+ self.assertValid(x0)
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x6)
+
+ def testCommodityComparisons(self):
+ x1 = amount("$-123")
+ x2 = amount("$123.00")
+ x3 = amount(internalAmount("$-123.4544"))
+ x4 = amount(internalAmount("$123.4544"))
+ x5 = amount("$-123.45")
+ x6 = amount("$123.45")
+ x7 = amount("DM 123.45")
+
+ self.assertTrue(x1 > x3)
+ self.assertTrue(x3 <= x5)
+ self.assertTrue(x3 < x5)
+ self.assertTrue(x3 <= x5)
+ self.assertFalse(x3 == x5)
+ self.assertTrue(x3 < x1)
+ self.assertTrue(x3 < x4)
+ self.assertFalse(x6 == x7)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x6 < x7)
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x6)
+
+ def testIntegerAddition(self):
+ x0 = amount()
+ x1 = amount(123)
+ y1 = amount(456)
+
+ self.assertEqual(amount(579), x1 + y1)
+ self.assertEqual(amount(579), x1 + 456)
+ self.assertEqual(amount(579), 456 + x1)
+
+ x1 += amount(456)
+ self.assertEqual(amount(579), x1)
+ x1 += 456
+ self.assertEqual(amount(1035), x1)
+
+ x4 = amount("123456789123456789123456789")
+
+ self.assertEqual(amount("246913578246913578246913578"), x4 + x4)
+
+ self.assertValid(x0)
+ self.assertValid(x1)
+ self.assertValid(y1)
+ self.assertValid(x4)
+
+ def testFractionalAddition(self):
+ x1 = amount("123.123")
+ y1 = amount("456.456")
+
+ self.assertEqual(amount("579.579"), x1 + y1)
+ self.assertEqual(amount("579.579"), x1 + amount("456.456"))
+ self.assertEqual(amount("579.579"), amount("456.456") + x1)
+
+ x1 += amount("456.456")
+ self.assertEqual(amount("579.579"), x1)
+ x1 += amount("456.456")
+ self.assertEqual(amount("1036.035"), x1)
+ x1 += 456
+ self.assertEqual(amount("1492.035"), x1)
+
+ x2 = amount("123456789123456789.123456789123456789")
+
+ self.assertEqual(amount("246913578246913578.246913578246913578"), x2 + x2)
+
+ self.assertValid(x1)
+ self.assertValid(y1)
+ self.assertValid(x2)
+
+ def testCommodityAddition(self):
+ x0 = amount()
+ x1 = amount("$123.45")
+ x2 = amount(internalAmount("$123.456789"))
+ x3 = amount("DM 123.45")
+ x4 = amount("123.45 euro")
+ x5 = amount("123.45€")
+ x6 = amount("123.45")
+
+ self.assertEqual(amount("$246.90"), x1 + x1)
+ self.assertNotEqual(amount("$246.91"), x1 + x2)
+ self.assertEqual(internalAmount("$246.906789"), x1 + x2)
+
+ # Converting to string drops internal precision
+ self.assertEqual("$246.90", (x1 + x1).to_string())
+ self.assertEqual("$246.91", (x1 + x2).to_string())
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 + x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 + x1)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 + x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 + x3)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 + x4)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 + x5)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 + x6)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 + amount("123.45"))
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 + 123)
+
+ self.assertEqual(amount("DM 246.90"), x3 + x3)
+ self.assertEqual(amount("246.90 euro"), x4 + x4)
+ self.assertEqual(amount("246.90€"), x5 + x5)
+
+ self.assertEqual("DM 246.90", (x3 + x3).to_string())
+ self.assertEqual("246.90 euro", (x4 + x4).to_string())
+ self.assertEqual("246.90€", (x5 + x5).to_string())
+
+ x1 += amount("$456.45")
+ self.assertEqual(amount("$579.90"), x1)
+ x1 += amount("$456.45")
+ self.assertEqual(amount("$1036.35"), x1)
+ x1 += amount("$456")
+ self.assertEqual(amount("$1492.35"), x1)
+
+ x7 = amount(internalAmount("$123456789123456789.123456789123456789"))
+
+ self.assertEqual(internalAmount("$246913578246913578.246913578246913578"), x7 + x7)
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+
+ def testIntegerSubtraction(self):
+ x1 = amount(123)
+ y1 = amount(456)
+
+ self.assertEqual(amount(333), y1 - x1)
+ self.assertEqual(amount(-333), x1 - y1)
+ self.assertEqual(amount(23), x1 - 100)
+ self.assertEqual(amount(-23), 100 - x1)
+
+ x1 -= amount(456)
+ self.assertEqual(amount(-333), x1)
+ x1 -= 456
+ self.assertEqual(amount(-789), x1)
+
+ x4 = amount("123456789123456789123456789")
+ y4 = amount("8238725986235986")
+
+ self.assertEqual(amount("123456789115218063137220803"), x4 - y4)
+ self.assertEqual(amount("-123456789115218063137220803"), y4 - x4)
+
+ self.assertValid(x1)
+ self.assertValid(y1)
+ self.assertValid(x4)
+ self.assertValid(y4)
+
+ def testFractionalSubtraction(self):
+ x1 = amount("123.123")
+ y1 = amount("456.456")
+
+ self.assertEqual(amount("-333.333"), x1 - y1)
+ self.assertEqual(amount("333.333"), y1 - x1)
+
+ x1 -= amount("456.456")
+ self.assertEqual(amount("-333.333"), x1)
+ x1 -= amount("456.456")
+ self.assertEqual(amount("-789.789"), x1)
+ x1 -= 456
+ self.assertEqual(amount("-1245.789"), x1)
+
+ x2 = amount("123456789123456789.123456789123456789")
+ y2 = amount("9872345982459.248974239578")
+
+ self.assertEqual(amount("123446916777474329.874482549545456789"), x2 - y2)
+ self.assertEqual(amount("-123446916777474329.874482549545456789"), y2 - x2)
+
+ self.assertValid(x1)
+ self.assertValid(y1)
+ self.assertValid(x2)
+ self.assertValid(y2)
+
+ def testCommoditySubtraction(self):
+ x0 = amount()
+ x1 = amount("$123.45")
+ x2 = amount(internalAmount("$123.456789"))
+ x3 = amount("DM 123.45")
+ x4 = amount("123.45 euro")
+ x5 = amount("123.45€")
+ x6 = amount("123.45")
+
+ self.assertNotEqual(amount(), x1 - x1)
+ self.assertEqual(amount("$0"), x1 - x1)
+ self.assertEqual(amount("$23.45"), x1 - amount("$100.00"))
+ self.assertEqual(amount("$-23.45"), amount("$100.00") - x1)
+ self.assertNotEqual(amount("$-0.01"), x1 - x2)
+ self.assertEqual(internalAmount("$-0.006789"), x1 - x2)
+
+ # Converting to string drops internal precision. If an amount is
+ # zero, it drops the commodity as well.
+ self.assertEqual("$0.00", (x1 - x1).to_string())
+ self.assertEqual("$-0.01", (x1 - x2).to_string())
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 - x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 - x1)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 - x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 - x3)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 - x4)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 - x5)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 - x6)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 - amount("123.45"))
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 - 123)
+
+ self.assertEqual(amount("DM 0.00"), x3 - x3)
+ self.assertEqual(amount("DM 23.45"), x3 - amount("DM 100.00"))
+ self.assertEqual(amount("DM -23.45"), amount("DM 100.00") - x3)
+ self.assertEqual(amount("0.00 euro"), x4 - x4)
+ self.assertEqual(amount("23.45 euro"), x4 - amount("100.00 euro"))
+ self.assertEqual(amount("-23.45 euro"), amount("100.00 euro") - x4)
+ self.assertEqual(amount("0.00€"), x5 - x5)
+ self.assertEqual(amount("23.45€"), x5 - amount("100.00€"))
+ self.assertEqual(amount("-23.45€"), amount("100.00€") - x5)
+
+ self.assertEqual("DM 0.00", (x3 - x3).to_string())
+ self.assertEqual("DM 23.45", (x3 - amount("DM 100.00")).to_string())
+ self.assertEqual("DM -23.45", (amount("DM 100.00") - x3).to_string())
+ self.assertEqual("0.00 euro", (x4 - x4).to_string())
+ self.assertEqual("23.45 euro", (x4 - amount("100.00 euro")).to_string())
+ self.assertEqual("-23.45 euro", (amount("100.00 euro") - x4).to_string())
+ self.assertEqual("0.00€", (x5 - x5).to_string())
+ self.assertEqual("23.45€", (x5 - amount("100.00€")).to_string())
+ self.assertEqual("-23.45€", (amount("100.00€") - x5).to_string())
+
+ x1 -= amount("$456.45")
+ self.assertEqual(amount("$-333.00"), x1)
+ x1 -= amount("$456.45")
+ self.assertEqual(amount("$-789.45"), x1)
+ x1 -= amount("$456")
+ self.assertEqual(amount("$-1245.45"), x1)
+
+ x7 = amount(internalAmount("$123456789123456789.123456789123456789"))
+ x8 = amount(internalAmount("$2354974984698.98459845984598"))
+
+ self.assertEqual(internalAmount("$123454434148472090.138858329277476789"), x7 - x8)
+ self.assertEqual("$123454434148472090.138858329277476789", (x7 - x8).to_string())
+ self.assertEqual("$123454434148472090.14",
+ (amount("$1.00") * (x7 - x8)).to_string())
+ self.assertEqual(internalAmount("$-123454434148472090.138858329277476789"), x8 - x7)
+ self.assertEqual("$-123454434148472090.138858329277476789", (x8 - x7).to_string())
+ self.assertEqual("$-123454434148472090.14",
+ (amount("$1.00") * (x8 - x7)).to_string())
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+ self.assertValid(x8)
+
+ def testIntegerMultiplication(self):
+ x1 = amount(123)
+ y1 = amount(456)
+
+ self.assertEqual(amount(0), x1 * 0)
+ self.assertEqual(amount(0), amount(0) * x1)
+ self.assertEqual(amount(0), 0 * x1)
+ self.assertEqual(x1, x1 * 1)
+ self.assertEqual(x1, amount(1) * x1)
+ self.assertEqual(x1, 1 * x1)
+ self.assertEqual(- x1, x1 * -1)
+ self.assertEqual(- x1, amount(-1) * x1)
+ self.assertEqual(- x1, -1 * x1)
+ self.assertEqual(amount(56088), x1 * y1)
+ self.assertEqual(amount(56088), y1 * x1)
+ self.assertEqual(amount(56088), x1 * 456)
+ self.assertEqual(amount(56088), amount(456) * x1)
+ self.assertEqual(amount(56088), 456 * x1)
+
+ x1 *= amount(123)
+ self.assertEqual(amount(15129), x1)
+ x1 *= 123
+ self.assertEqual(amount(1860867), x1)
+
+ x4 = amount("123456789123456789123456789")
+
+ self.assertEqual(amount("15241578780673678546105778281054720515622620750190521"),
+ x4 * x4)
+
+ self.assertValid(x1)
+ self.assertValid(y1)
+ self.assertValid(x4)
+
+ def testFractionalMultiplication(self):
+ x1 = amount("123.123")
+ y1 = amount("456.456")
+
+ self.assertEqual(amount(0), x1 * 0)
+ self.assertEqual(amount(0), amount(0) * x1)
+ self.assertEqual(amount(0), 0 * x1)
+ self.assertEqual(x1, x1 * 1)
+ self.assertEqual(x1, amount(1) * x1)
+ self.assertEqual(x1, 1 * x1)
+ self.assertEqual(- x1, x1 * -1)
+ self.assertEqual(- x1, amount(-1) * x1)
+ self.assertEqual(- x1, -1 * x1)
+ self.assertEqual(amount("56200.232088"), x1 * y1)
+ self.assertEqual(amount("56200.232088"), y1 * x1)
+ self.assertEqual(amount("56200.232088"), x1 * amount("456.456"))
+ self.assertEqual(amount("56200.232088"), amount("456.456") * x1)
+ self.assertEqual(amount("56200.232088"), amount("456.456") * x1)
+
+ x1 *= amount("123.123")
+ self.assertEqual(amount("15159.273129"), x1)
+ x1 *= amount("123.123")
+ self.assertEqual(amount("1866455.185461867"), x1)
+ x1 *= 123
+ self.assertEqual(amount("229573987.811809641"), x1)
+
+ x2 = amount("123456789123456789.123456789123456789")
+
+ self.assertEqual(amount("15241578780673678546105778311537878.046486820281054720515622620750190521"),
+ x2 * x2)
+
+ self.assertValid(x1)
+ self.assertValid(y1)
+ self.assertValid(x2)
+
+ def testCommodityMultiplication(self):
+ x0 = amount()
+ x1 = amount("$123.12")
+ y1 = amount("$456.45")
+ x2 = amount(internalAmount("$123.456789"))
+ x3 = amount("DM 123.45")
+ x4 = amount("123.45 euro")
+ x5 = amount("123.45€")
+
+ self.assertEqual(amount("$0.00"), x1 * 0)
+ self.assertEqual(amount("$0.00"), 0 * x1)
+ self.assertEqual(x1, x1 * 1)
+ self.assertEqual(x1, 1 * x1)
+ self.assertEqual(- x1, x1 * -1)
+ self.assertEqual(- x1, -1 * x1)
+ self.assertEqual(internalAmount("$56198.124"), x1 * y1)
+ self.assertEqual("$56198.12", (x1 * y1).to_string())
+ self.assertEqual(internalAmount("$56198.124"), y1 * x1)
+ self.assertEqual("$56198.12", (y1 * x1).to_string())
+
+ # Internal amounts retain their precision, even when being
+ # converted to strings
+ self.assertEqual(internalAmount("$15199.99986168"), x1 * x2)
+ self.assertEqual(internalAmount("$15199.99986168"), x2 * x1)
+ self.assertEqual("$15200.00", (x1 * x2).to_string())
+ self.assertEqual("$15199.99986168", (x2 * x1).to_string())
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 * x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 * x1)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 * x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 * x3)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 * x4)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 * x5)
+
+ x1 *= amount("123.12")
+ self.assertEqual(internalAmount("$15158.5344"), x1)
+ self.assertEqual("$15158.53", x1.to_string())
+ x1 *= amount("123.12")
+ self.assertEqual(internalAmount("$1866318.755328"), x1)
+ self.assertEqual("$1866318.76", x1.to_string())
+ x1 *= 123
+ self.assertEqual(internalAmount("$229557206.905344"), x1)
+ self.assertEqual("$229557206.91", x1.to_string())
+
+ x7 = amount(internalAmount("$123456789123456789.123456789123456789"))
+
+ self.assertEqual(internalAmount("$15241578780673678546105778311537878.046486820281054720515622620750190521"),
+ x7 * x7)
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x7)
+
+ def testIntegerDivision(self):
+ x1 = amount(123)
+ y1 = amount(456)
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 / 0)
+ self.assertEqual(amount(0), amount(0) / x1)
+ self.assertEqual(amount(0), 0 / x1)
+ self.assertEqual(x1, x1 / 1)
+ self.assertEqual(amount("0.008130"), amount(1) / x1)
+ self.assertEqual(amount("0.008130"), 1 / x1)
+ self.assertEqual(- x1, x1 / -1)
+ self.assertEqual(- amount("0.008130"), amount(-1) / x1)
+ self.assertEqual(- amount("0.008130"), -1 / x1)
+ self.assertEqual(amount("0.269737"), x1 / y1)
+ self.assertEqual(amount("3.707317"), y1 / x1)
+ self.assertEqual(amount("0.269737"), x1 / 456)
+ self.assertEqual(amount("3.707317"), amount(456) / x1)
+ self.assertEqual(amount("3.707317"), 456 / x1)
+
+ x1 /= amount(456)
+ self.assertEqual(amount("0.269737"), x1)
+ x1 /= 456
+ self.assertEqual(amount("0.00059152850877193"), x1)
+
+ x4 = amount("123456789123456789123456789")
+ y4 = amount("56")
+
+ self.assertEqual(amount(1), x4 / x4)
+ self.assertEqual(amount("2204585520061728377204585.517857"), x4 / y4)
+
+ self.assertValid(x1)
+ self.assertValid(y1)
+ self.assertValid(x4)
+ self.assertValid(y4)
+
+ def testFractionalDivision(self):
+ x1 = amount("123.123")
+ y1 = amount("456.456")
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 / 0)
+ self.assertEqual(amount("0.00812195934"), amount("1.0") / x1)
+ self.assertEqual(amount("0.00812195934"), amount("1.0") / x1)
+ self.assertEqual(x1, x1 / amount("1.0"))
+ self.assertEqual(amount("0.00812195934"), amount("1.0") / x1)
+ self.assertEqual(amount("0.00812195934"), amount("1.0") / x1)
+ self.assertEqual(- x1, x1 / amount("-1.0"))
+ self.assertEqual(- amount("0.00812195934"), amount("-1.0") / x1)
+ self.assertEqual(- amount("0.00812195934"), amount("-1.0") / x1)
+ self.assertEqual(amount("0.269736842105263"), x1 / y1)
+ self.assertEqual(amount("3.707317073170732"), y1 / x1)
+ self.assertEqual(amount("0.269736842105263"), x1 / amount("456.456"))
+ self.assertEqual(amount("3.707317073170732"), amount("456.456") / x1)
+ self.assertEqual(amount("3.707317073170732"), amount("456.456") / x1)
+
+ x1 /= amount("456.456")
+ self.assertEqual(amount("0.269736842105263"), x1)
+ x1 /= amount("456.456")
+ self.assertEqual(amount("0.000590937225286255411255411255411255411"), x1)
+ x1 /= 456
+ self.assertEqual(amount("0.000001295914967733016252753094858358016252192982456140350877192982456140350877192982"), x1)
+
+ x4 = amount("1234567891234567.89123456789")
+ y4 = amount("56.789")
+
+ self.assertEqual(amount("1.0"), x4 / x4)
+ self.assertEqual(amount("21739560323910.7554497273748437197344556164046"), x4 / y4)
+
+ self.assertValid(x1)
+ self.assertValid(y1)
+ self.assertValid(x4)
+ self.assertValid(y4)
+
+ def testCommodityDivision(self):
+ x0 = amount()
+ x1 = amount("$123.12")
+ y1 = amount("$456.45")
+ x2 = amount(internalAmount("$123.456789"))
+ x3 = amount("DM 123.45")
+ x4 = amount("123.45 euro")
+ x5 = amount("123.45€")
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 / 0)
+ self.assertEqual(amount("$0.00"), 0 / x1)
+ self.assertEqual(x1, x1 / 1)
+ self.assertEqual(internalAmount("$0.00812216"), 1 / x1)
+ self.assertEqual(- x1, x1 / -1)
+ self.assertEqual(internalAmount("$-0.00812216"), -1 / x1)
+ self.assertEqual(internalAmount("$0.26973382"), x1 / y1)
+ self.assertEqual("$0.27", (x1 / y1).to_string())
+ self.assertEqual(internalAmount("$3.70735867"), y1 / x1)
+ self.assertEqual("$3.71", (y1 / x1).to_string())
+
+ # Internal amounts retain their precision, even when being
+ # converted to strings
+ self.assertEqual(internalAmount("$0.99727201"), x1 / x2)
+ self.assertEqual(internalAmount("$1.00273545321637426901"), x2 / x1)
+ self.assertEqual("$1.00", (x1 / x2).to_string())
+ self.assertEqual("$1.00273545321637426901", (x2 / x1).to_string())
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1 / x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 / x1)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 / x0)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 / x3)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 / x4)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0 / x5)
+
+ x1 /= amount("123.12")
+ self.assertEqual(internalAmount("$1.00"), x1)
+ self.assertEqual("$1.00", x1.to_string())
+ x1 /= amount("123.12")
+ self.assertEqual(internalAmount("$0.00812216"), x1)
+ self.assertEqual("$0.01", x1.to_string())
+ x1 /= 123
+ self.assertEqual(internalAmount("$0.00006603"), x1)
+ self.assertEqual("$0.00", x1.to_string())
+
+ x6 = amount(internalAmount("$237235987235987.98723987235978"))
+ x7 = amount(internalAmount("$123456789123456789.123456789123456789"))
+
+ self.assertEqual(amount("$1"), x7 / x7)
+ self.assertEqual(internalAmount("$0.0019216115121765559608381226612019501046413574469262"),
+ x6 / x7)
+ self.assertEqual(internalAmount("$520.39654928343335571379527154924040947271699678158689736256"),
+ x7 / x6)
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+
+ def testNegation(self):
+ x0 = amount()
+ x1 = amount(-123456)
+ x3 = amount("-123.456")
+ x5 = amount("-123456")
+ x6 = amount("-123.456")
+ x7 = amount("-123456")
+ x8 = amount("-123.456")
+ x9 = amount(- x3)
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.negate())
+ self.assertEqual(x5, x1)
+ self.assertEqual(x7, x1)
+ self.assertEqual(x6, x3)
+ self.assertEqual(x8, x3)
+ self.assertEqual(- x6, x9)
+ self.assertEqual(x3.negate(), x9)
+
+ x10 = amount(x9.negate())
+
+ self.assertEqual(x3, x10)
+
+ self.assertValid(x1)
+ self.assertValid(x3)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+ self.assertValid(x8)
+ self.assertValid(x9)
+ self.assertValid(x10)
+
+ def testCommodityNegation(self):
+ x1 = amount("$123.45")
+ x2 = amount("-$123.45")
+ x3 = amount("$-123.45")
+ x4 = amount("DM 123.45")
+ x5 = amount("-DM 123.45")
+ x6 = amount("DM -123.45")
+ x7 = amount("123.45 euro")
+ x8 = amount("-123.45 euro")
+ x9 = amount("123.45€")
+ x10 = amount("-123.45€")
+
+ self.assertEqual(amount("$-123.45"), - x1)
+ self.assertEqual(amount("$123.45"), - x2)
+ self.assertEqual(amount("$123.45"), - x3)
+ self.assertEqual(amount("DM -123.45"), - x4)
+ self.assertEqual(amount("DM 123.45"), - x5)
+ self.assertEqual(amount("DM 123.45"), - x6)
+ self.assertEqual(amount("-123.45 euro"), - x7)
+ self.assertEqual(amount("123.45 euro"), - x8)
+ self.assertEqual(amount("-123.45€"), - x9)
+ self.assertEqual(amount("123.45€"), - x10)
+
+ self.assertEqual(amount("$-123.45"), x1.negate())
+ self.assertEqual(amount("$123.45"), x2.negate())
+ self.assertEqual(amount("$123.45"), x3.negate())
+
+ self.assertEqual("$-123.45", (- x1).to_string())
+ self.assertEqual("$123.45", (- x2).to_string())
+ self.assertEqual("$123.45", (- x3).to_string())
+ self.assertEqual("DM -123.45", (- x4).to_string())
+ self.assertEqual("DM 123.45", (- x5).to_string())
+ self.assertEqual("DM 123.45", (- x6).to_string())
+ self.assertEqual("-123.45 euro", (- x7).to_string())
+ self.assertEqual("123.45 euro", (- x8).to_string())
+ self.assertEqual("-123.45€", (- x9).to_string())
+ self.assertEqual("123.45€", (- x10).to_string())
+
+ self.assertEqual(amount("$-123.45"), x1.negate())
+ self.assertEqual(amount("$123.45"), x2.negate())
+ self.assertEqual(amount("$123.45"), x3.negate())
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+ self.assertValid(x6)
+ self.assertValid(x7)
+ self.assertValid(x8)
+ self.assertValid(x9)
+ self.assertValid(x10)
+
+ def testAbs(self):
+ x0 = amount()
+ x1 = amount(-1234)
+ x2 = amount(1234)
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.abs())
+ self.assertEqual(amount(1234), x1.abs())
+ self.assertEqual(amount(1234), x2.abs())
+
+ self.assertValid(x0)
+ self.assertValid(x1)
+ self.assertValid(x2)
+
+ def testCommodityAbs(self):
+ x1 = amount("$-1234.56")
+ x2 = amount("$1234.56")
+
+ self.assertEqual(amount("$1234.56"), x1.abs())
+ self.assertEqual(amount("$1234.56"), x2.abs())
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+
+ def testFractionalRound(self):
+ x0 = amount()
+ x1 = amount("1234.567890")
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.precision)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.round())
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.round(2))
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.unround())
+ self.assertEqual(6, x1.precision)
+
+ x1b = amount(x1.unround())
+
+ self.assertEqual(x1b.precision, x1b.unround().precision)
+
+ y7 = amount(x1.round(7))
+ y6 = amount(x1.round(6))
+ y5 = amount(x1.round(5))
+ y4 = amount(x1.round(4))
+ y3 = amount(x1.round(3))
+ y2 = amount(x1.round(2))
+ y1 = amount(x1.round(1))
+ y0 = amount(x1.round(0))
+
+ self.assertEqual(6, y7.precision)
+ self.assertEqual(6, y6.precision)
+ self.assertEqual(5, y5.precision)
+ self.assertEqual(4, y4.precision)
+ self.assertEqual(3, y3.precision)
+ self.assertEqual(2, y2.precision)
+ self.assertEqual(1, y1.precision)
+ self.assertEqual(0, y0.precision)
+
+ self.assertEqual(amount("1234.56789"), y7)
+ self.assertEqual(amount("1234.56789"), y6)
+ self.assertEqual(amount("1234.56789"), y5)
+ self.assertEqual(amount("1234.5679"), y4)
+ self.assertEqual(amount("1234.568"), y3)
+ self.assertEqual(amount("1234.57"), y2)
+ self.assertEqual(amount("1234.6"), y1)
+ self.assertEqual(amount("1235"), y0)
+
+ x2 = amount("9876.543210")
+
+ self.assertEqual(amount("9876.543210"), x2.round(6))
+ self.assertEqual(amount("9876.54321"), x2.round(5))
+ self.assertEqual(amount("9876.5432"), x2.round(4))
+ self.assertEqual(amount("9876.543"), x2.round(3))
+ self.assertEqual(amount("9876.54"), x2.round(2))
+ self.assertEqual(amount("9876.5"), x2.round(1))
+ self.assertEqual(amount("9877"), x2.round(0))
+
+ x3 = amount("-1234.567890")
+
+ self.assertEqual(amount("-1234.56789"), x3.round(6))
+ self.assertEqual(amount("-1234.56789"), x3.round(5))
+ self.assertEqual(amount("-1234.5679"), x3.round(4))
+ self.assertEqual(amount("-1234.568"), x3.round(3))
+ self.assertEqual(amount("-1234.57"), x3.round(2))
+ self.assertEqual(amount("-1234.6"), x3.round(1))
+ self.assertEqual(amount("-1235"), x3.round(0))
+
+ x4 = amount("-9876.543210")
+
+ self.assertEqual(amount("-9876.543210"), x4.round(6))
+ self.assertEqual(amount("-9876.54321"), x4.round(5))
+ self.assertEqual(amount("-9876.5432"), x4.round(4))
+ self.assertEqual(amount("-9876.543"), x4.round(3))
+ self.assertEqual(amount("-9876.54"), x4.round(2))
+ self.assertEqual(amount("-9876.5"), x4.round(1))
+ self.assertEqual(amount("-9877"), x4.round(0))
+
+ x5 = amount("0.0000000000000000000000000000000000001")
+
+ self.assertEqual(amount("0.0000000000000000000000000000000000001"),
+ x5.round(37))
+ self.assertEqual(amount(0), x5.round(36))
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+
+ def testCommodityRound(self):
+ x1 = amount(internalAmount("$1234.567890"))
+
+ self.assertEqual(internalAmount("$1234.56789"), x1.round(6))
+ self.assertEqual(internalAmount("$1234.56789"), x1.round(5))
+ self.assertEqual(internalAmount("$1234.5679"), x1.round(4))
+ self.assertEqual(internalAmount("$1234.568"), x1.round(3))
+ self.assertEqual(amount("$1234.57"), x1.round(2))
+ self.assertEqual(amount("$1234.6"), x1.round(1))
+ self.assertEqual(amount("$1235"), x1.round(0))
+
+ x2 = amount(internalAmount("$9876.543210"))
+
+ self.assertEqual(internalAmount("$9876.543210"), x2.round(6))
+ self.assertEqual(internalAmount("$9876.54321"), x2.round(5))
+ self.assertEqual(internalAmount("$9876.5432"), x2.round(4))
+ self.assertEqual(internalAmount("$9876.543"), x2.round(3))
+ self.assertEqual(amount("$9876.54"), x2.round(2))
+ self.assertEqual(amount("$9876.5"), x2.round(1))
+ self.assertEqual(amount("$9877"), x2.round(0))
+
+ x3 = amount(internalAmount("$-1234.567890"))
+
+ self.assertEqual(internalAmount("$-1234.56789"), x3.round(6))
+ self.assertEqual(internalAmount("$-1234.56789"), x3.round(5))
+ self.assertEqual(internalAmount("$-1234.5679"), x3.round(4))
+ self.assertEqual(internalAmount("$-1234.568"), x3.round(3))
+ self.assertEqual(amount("$-1234.57"), x3.round(2))
+ self.assertEqual(amount("$-1234.6"), x3.round(1))
+ self.assertEqual(amount("$-1235"), x3.round(0))
+
+ x4 = amount(internalAmount("$-9876.543210"))
+
+ self.assertEqual(internalAmount("$-9876.543210"), x4.round(6))
+ self.assertEqual(internalAmount("$-9876.54321"), x4.round(5))
+ self.assertEqual(internalAmount("$-9876.5432"), x4.round(4))
+ self.assertEqual(internalAmount("$-9876.543"), x4.round(3))
+ self.assertEqual(amount("$-9876.54"), x4.round(2))
+ self.assertEqual(amount("$-9876.5"), x4.round(1))
+ self.assertEqual(amount("$-9877"), x4.round(0))
+
+ x5 = amount("$123.45")
+
+ x5 *= amount("100.12")
+
+ self.assertEqual(internalAmount("$12359.814"), x5)
+ self.assertEqual("$12359.81", x5.to_string())
+ self.assertEqual("$12359.814", x5.to_fullstring())
+ self.assertEqual("$12359.814", x5.unround().to_string())
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+ self.assertValid(x5)
+
+ def testCommodityDisplayRound(self):
+ x1 = amount("$0.85")
+ x2 = amount("$0.1")
+
+ x1 *= amount("0.19")
+
+ self.assertNotEqual(amount("$0.16"), x1)
+ self.assertEqual(internalAmount("$0.1615"), x1)
+ self.assertEqual("$0.16", x1.to_string())
+
+ self.assertEqual(amount("$0.10"), x2)
+ self.assertNotEqual(internalAmount("$0.101"), x2)
+ self.assertEqual("$0.10", x2.to_string())
+
+ x1 *= 7
+
+ self.assertNotEqual(amount("$1.13"), x1)
+ self.assertEqual(internalAmount("$1.1305"), x1)
+ self.assertEqual("$1.13", x1.to_string())
+
+ def testReduction(self):
+ x0 = amount()
+ x1 = amount("60s")
+ x2 = amount("600s")
+ x3 = amount("6000s")
+ x4 = amount("360000s")
+ x5 = amount("10m")
+ x6 = amount("100m")
+ x7 = amount("1000m")
+ x8 = amount("10000m")
+ x9 = amount("10h")
+ x10 = amount("100h")
+ x11 = amount("1000h")
+ x12 = amount("10000h")
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.reduce())
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.unreduce())
+ self.assertEqual(x2, x5)
+ self.assertEqual(x3, x6)
+ self.assertEqual(x4, x10)
+ self.assertEqual("100.0h", x4.unreduce().to_string())
+
+ def testSign(self):
+ x0 = amount()
+ x1 = amount("0.0000000000000000000000000000000000001")
+ x2 = amount("-0.0000000000000000000000000000000000001")
+ x3 = amount("1")
+ x4 = amount("-1")
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.sign())
+ self.assertTrue(x1.sign() > 0)
+ self.assertTrue(x2.sign() < 0)
+ self.assertTrue(x3.sign() > 0)
+ self.assertTrue(x4.sign() < 0)
+
+ self.assertValid(x0)
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+
+ def testCommoditySign(self):
+ x1 = amount(internalAmount("$0.0000000000000000000000000000000000001"))
+ x2 = amount(internalAmount("$-0.0000000000000000000000000000000000001"))
+ x3 = amount("$1")
+ x4 = amount("$-1")
+
+ self.assertTrue(x1.sign() != 0)
+ self.assertTrue(x2.sign() != 0)
+ self.assertTrue(x3.sign() > 0)
+ self.assertTrue(x4.sign() < 0)
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+ self.assertValid(x3)
+ self.assertValid(x4)
+
+ def testTruth(self):
+ x0 = amount()
+ x1 = amount("1234")
+ x2 = amount("1234.56")
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: 1 if x0 else 0)
+
+ self.assertTrue(x1)
+ self.assertTrue(x2)
+
+ self.assertValid(x0)
+ self.assertValid(x1)
+ self.assertValid(x2)
+
+ def testCommodityTruth(self):
+ x1 = amount("$1234")
+ x2 = amount("$1234.56")
+
+ if x1:
+ self.assertTrue(True)
+ else:
+ self.assertTrue(False)
+
+ if x2:
+ self.assertTrue(True)
+ else:
+ self.assertTrue(False)
+
+ self.assertValid(x1)
+ self.assertValid(x2)
+
+ def testForZero(self):
+ x0 = amount()
+ x1 = amount("0.000000000000000000001")
+
+ self.assertTrue(x1)
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.is_zero())
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.is_realzero())
+ self.assertFalse(x1.is_zero())
+ self.assertFalse(x1.is_realzero())
+
+ self.assertValid(x0)
+ self.assertValid(x1)
+
+ def testCommodityForZero(self):
+ x1 = amount(internalAmount("$0.000000000000000000001"))
+
+ self.assertTrue(x1)
+ self.assertFalse(x1.is_zero())
+ self.assertFalse(x1.is_realzero())
+
+ self.assertValid(x1)
+
+ def testIntegerConversion(self):
+ x0 = amount()
+ x1 = amount(123456)
+ x2 = amount("12345682348723487324")
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x0.to_long())
+ #self.assertRaises(exceptions.ArithmeticError, lambda: x0.to_double())
+ self.assertFalse(x2.fits_in_long())
+ self.assertEqual(123456, x1.to_long())
+ #self.assertEqual(123456.0, x1.to_double())
+ self.assertEqual("123456", x1.to_string())
+ self.assertEqual("123456", x1.quantity_string)
+
+ self.assertValid(x1)
+
+ def testFractionalConversion(self):
+ x1 = amount("1234.56")
+ x2 = amount("1234.5683787634678348734")
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1.to_long()) # loses precision
+ #self.assertRaises(exceptions.ArithmeticError, lambda: x2.to_double()) # loses precision
+ #self.assertFalse(x2.fits_in_double())
+ self.assertEqual(1234, x1.to_long(True))
+ #self.assertEqual(1234.56, x1.to_double())
+ self.assertEqual("1234.56", x1.to_string())
+ self.assertEqual("1234.56", x1.quantity_string)
+
+ self.assertValid(x1)
+
+ def testCommodityConversion(self):
+ x1 = amount("$1234.56")
+
+ self.assertRaises(exceptions.ArithmeticError, lambda: x1.to_long()) # loses precision
+ self.assertEqual(1234, x1.to_long(True))
+ #self.assertEqual(1234.56, x1.to_double())
+ self.assertEqual("$1234.56", x1.to_string())
+ self.assertEqual("1234.56", x1.quantity_string)
+
+ self.assertValid(x1)
+
+ def testPrinting(self):
+ pass
+ #x0 = amount()
+ #x1 = amount("982340823.380238098235098235098235098")
+ #
+ #bufstr = StringIO()
+ #self.assertRaises(exceptions.ArithmeticError, lambda: bufstr.write(x0))
+ #
+ #bufstr = StringIO()
+ #bufstr.write(x1)
+ #
+ #self.assertEqual("982340823.380238098235098235098235098",
+ # bufstr.getvalue())
+ #
+ #self.assertValid(x0)
+ #self.assertValid(x1)
+
+ def testCommodityPrinting(self):
+ pass
+ #x1 = amount(internalAmount("$982340823.386238098235098235098235098"))
+ #x2 = amount("$982340823.38")
+ #
+ #bufstr = StringIO()
+ #bufstr.write(x1)
+ #
+ #self.assertEqual("$982340823.386238098235098235098235098",
+ # bufstr.getvalue())
+ #
+ #bufstr = StringIO()
+ #bufstr.write((x1 * x2).to_string())
+ #
+ #self.assertEqual("$964993493285024293.18099172508158508135413499124",
+ # bufstr.getvalue())
+ #
+ #bufstr = StringIO()
+ #bufstr.write((x2 * x1).to_string())
+ #
+ #self.assertEqual("$964993493285024293.18", bufstr.getvalue())
+ #
+ #self.assertValid(x1)
+ #self.assertValid(x2)
+
+ def testSerialization(self):
+ pass
+ #x0 = amount()
+ #x1 = amount("$8,192.34")
+ #x2 = amount("8192.34")
+ #x3 = amount("8192.34")
+ #x4 = amount("-8192.34")
+ #x5 = amount(x4)
+ #
+ ## Force x3's pointer to actually be set to null_commodity
+ ##x3.set_commodity(*x3.current_pool.null_commodity)
+ #
+ #buf = ""
+ #storage = StringIO()
+ #self.assertRaises(exceptions.ArithmeticError, lambda: x0.write(storage))
+ #x1.write(storage)
+ #x2.write(storage)
+ #x3.write(storage)
+ #x4.write(storage)
+ #x5.write(storage)
+ #buf = storage.getvalue()
+ #
+ #x1b = amount()
+ #x2b = amount()
+ #x3b = amount()
+ #x4b = amount()
+ #x5b = amount()
+ #
+ #storage = StringIO(buf)
+ #x1b.read(storage)
+ #x2b.read(storage)
+ #x3b.read(storage)
+ #x4b.read(storage)
+ #x5b.read(storage)
+ #
+ #self.assertEqual(x1, x1b)
+ #self.assertEqual(x2, x2b)
+ #self.assertEqual(x3, x3b)
+ #self.assertEqual(x4, x4b)
+ #
+ #ptr = buf.c_str()
+ #
+ #x1c = amount()
+ #x2c = amount()
+ #x3c = amount()
+ #x4c = amount()
+ #x5c = amount()
+ #
+ #x1c.read(ptr)
+ #x2c.read(ptr)
+ #x3c.read(ptr)
+ #x4c.read(ptr)
+ #x5c.read(ptr)
+ #
+ #self.assertEqual(x1, x1b)
+ #self.assertEqual(x2, x2b)
+ #self.assertEqual(x3, x3b)
+ #self.assertEqual(x4, x4b)
+ #
+ #self.assertValid(x1)
+ #self.assertValid(x2)
+ #self.assertValid(x3)
+ #self.assertValid(x4)
+ #self.assertValid(x1b)
+ #self.assertValid(x2b)
+ #self.assertValid(x3b)
+ #self.assertValid(x4b)
+ #self.assertValid(x1c)
+ #self.assertValid(x2c)
+ #self.assertValid(x3c)
+ #self.assertValid(x4c)
+
+
+def suite():
+ return unittest.TestLoader().loadTestsFromTestCase(t_amountTestCase)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/regress.py b/test/regress.py
new file mode 100755
index 00000000..aff17687
--- /dev/null
+++ b/test/regress.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import re
+import string
+import difflib
+import tempfile
+
+from subprocess import Popen, PIPE
+
+ledger = sys.argv[1]
+tests = sys.argv[2]
+
+if not os.path.isfile(ledger):
+ sys.exit(1)
+if not os.path.isdir(tests):
+ sys.exit(1)
+
+succeeded = 0
+failed = 0
+
+def test_regression(test_file):
+ global succeeded, failed
+
+ bug = open(test_file)
+ command = bug.readline()
+
+ line = bug.readline()
+ assert "<<<\n" == line
+ line = bug.readline()
+
+ data = []
+ while line != ">>>1\n":
+ data.append(line)
+ line = bug.readline()
+ line = bug.readline()
+
+ use_stdin = False
+ if command.startswith("-f - "):
+ use_stdin = True
+
+ command = ("%s" % ledger) + command
+ else:
+ tempdata = tempfile.mkstemp()
+
+ os.write(tempdata[0], string.join(data))
+ os.close(tempdata[0])
+
+ command = ("%s -f \"%s\" " % (ledger, tempdata[1])) + command
+
+ output = []
+ while line != ">>>2\n":
+ output.append(line)
+ line = bug.readline()
+ line = bug.readline()
+
+ error = []
+ while not line.startswith("==="):
+ error.append(line)
+ line = bug.readline()
+
+ match = re.match('=== ([0-9]+)', line)
+ assert match
+
+ exitcode = int(match.group(1))
+
+ p = Popen(command[:-1], shell=True,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE,
+ close_fds=True)
+
+ if use_stdin:
+ p.stdin.write(string.join(data))
+ p.stdin.close()
+
+ success = True
+
+ printed = False
+ index = 0
+ for line in difflib.unified_diff(output, p.stdout.readlines()):
+ index += 1
+ if index < 3:
+ continue
+ if not printed:
+ if success: print
+ print "Regression failure in output from %s:" \
+ % os.path.basename(test_file)
+ if success: failed += 1
+ success = False
+ printed = True
+ print " ", line,
+
+ printed = False
+ index = 0
+ for line in difflib.unified_diff(error, p.stderr.readlines()):
+ index += 1
+ if index < 3:
+ continue
+ if not printed:
+ if success: print
+ print "Regression failure in error output from %s:" \
+ % os.path.basename(test_file)
+ if success: failed += 1
+ success = False
+ printed = True
+ print " ", line,
+
+ if exitcode != p.wait():
+ if success: print
+ if success: failed += 1
+ success = False
+ print "Regression failure in exitcode from %s: %d (expected) != %d" \
+ % (os.path.basename(test_file), exitcode, p.returncode)
+
+ if success:
+ succeeded += 1
+ print ".",
+
+ if not use_stdin:
+ os.remove(tempdata[1])
+
+for test in os.listdir(tests):
+ if re.search('\.test$', test):
+ test_regression(os.path.join(tests, test))
+
+print
+if succeeded > 0:
+ print "OK (%d) " % succeeded,
+if failed > 0:
+ print "FAILED (%d)" % failed,
+print
+
+sys.exit(failed)
diff --git a/test/regress/205.test b/test/regress/205.test
new file mode 100644
index 00000000..0ad16aad
--- /dev/null
+++ b/test/regress/205.test
@@ -0,0 +1,13 @@
+bal
+<<<
+2007/02/02 RD VMMXX
+ Assets:Investments:Vanguard:VMMXX 0.350 VMMXX @ $1.00
+ Income:Dividends:Vanguard:VMMXX $-0.35
+>>>1
+ 0.350 VMMXX Assets
+ $-0.35 Income
+--------------------
+ $-0.35
+ 0.350 VMMXX
+>>>2
+=== 0
diff --git a/test/unit/t_amount.cc b/test/unit/t_amount.cc
new file mode 100644
index 00000000..69d97af3
--- /dev/null
+++ b/test/unit/t_amount.cc
@@ -0,0 +1,1576 @@
+#include "t_amount.h"
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AmountTestCase, "numerics");
+
+void AmountTestCase::setUp()
+{
+ ledger::set_session_context(&session);
+
+ // Cause the display precision for dollars to be initialized to 2.
+ amount_t x1("$1.00");
+ assertTrue(x1);
+
+ amount_t::stream_fullstrings = true; // make reports from UnitTests accurate
+}
+
+void AmountTestCase::tearDown()
+{
+ amount_t::stream_fullstrings = false;
+
+ ledger::set_session_context();
+}
+
+void AmountTestCase::testParser()
+{
+ amount_t x0;
+ amount_t x1;
+ amount_t x2;
+ amount_t x3;
+ amount_t x4("123.456");
+ amount_t x5(x4);
+ amount_t x6(x4);
+ amount_t x7(x4);
+ amount_t x8("$123.45");
+ amount_t x9(x8);
+ amount_t x10(x8);
+ amount_t x11(x8);
+ amount_t x12("$100");
+
+ assertEqual(amount_t::precision_t(2), x12.commodity().precision());
+
+ string buf("$100...");
+ std::istringstream input(buf);
+ amount_t x13;
+ x13.parse(input);
+ assertEqual(x12, x13);
+
+ amount_t x14;
+ assertThrow(x14.parse("DM"), amount_error);
+
+ amount_t x15("$1.000.000,00"); // parsing this switches us to European
+
+ amount_t x16("$2000");
+ assertEqual(string("$2.000,00"), x16.to_string());
+ x16.parse("$2000,00");
+ assertEqual(string("$2.000,00"), x16.to_string());
+
+ // Since European-ness is an additive quality, we must switch back
+ // to American-ness manually
+ x15.commodity().drop_flags(COMMODITY_STYLE_EUROPEAN);
+
+ amount_t x17("$1,000,000.00"); // parsing this switches back to American
+
+ amount_t x18("$2000");
+ assertEqual(string("$2,000.00"), x18.to_string());
+ x18.parse("$2,000");
+ assertEqual(string("$2,000.00"), x18.to_string());
+
+ assertEqual(x15, x17);
+
+ amount_t x19("EUR 1000");
+ amount_t x20("EUR 1000");
+
+ assertEqual(string("EUR 1000"), x19.to_string());
+ assertEqual(string("EUR 1000"), x20.to_string());
+
+ x1.parse("$100.0000", AMOUNT_PARSE_NO_MIGRATE);
+ assertEqual(amount_t::precision_t(2), x12.commodity().precision());
+ assertEqual(x1.commodity(), x12.commodity());
+ assertEqual(x1, x12);
+
+ x0.parse("$100.0000");
+ assertEqual(amount_t::precision_t(4), x12.commodity().precision());
+ assertEqual(x0.commodity(), x12.commodity());
+ assertEqual(x0, x12);
+
+ x2.parse("$100.00", AMOUNT_PARSE_NO_REDUCE);
+ assertEqual(x2, x12);
+ x3.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE);
+ assertEqual(x3, x12);
+
+ x4.parse("$100.00");
+ assertEqual(x4, x12);
+ x5.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE);
+ assertEqual(x5, x12);
+ x6.parse("$100.00", AMOUNT_PARSE_NO_REDUCE);
+ assertEqual(x6, x12);
+ x7.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE);
+ assertEqual(x7, x12);
+
+ x8.parse("$100.00");
+ assertEqual(x8, x12);
+ x9.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE);
+ assertEqual(x9, x12);
+ x10.parse("$100.00", AMOUNT_PARSE_NO_REDUCE);
+ assertEqual(x10, x12);
+ x11.parse("$100.00", AMOUNT_PARSE_NO_MIGRATE | AMOUNT_PARSE_NO_REDUCE);
+ assertEqual(x11, x12);
+
+ assertValid(x0);
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+ assertValid(x8);
+ assertValid(x9);
+ assertValid(x10);
+ assertValid(x11);
+ assertValid(x12);
+}
+
+void AmountTestCase::testConstructors()
+{
+ amount_t x0;
+ amount_t x1(123456L);
+ amount_t x2(123456UL);
+ amount_t x3("123.456");
+ amount_t x5("123456");
+ amount_t x6("123.456");
+ amount_t x7(string("123456"));
+ amount_t x8(string("123.456"));
+ amount_t x9(x3);
+ amount_t x10(x6);
+ amount_t x11(x8);
+
+ assertThrow(amount_t(0L) == x0, amount_error);
+ assertThrow(amount_t() == x0, amount_error);
+ assertThrow(amount_t("0") == x0, amount_error);
+ assertThrow(amount_t("0.0") == x0, amount_error);
+ assertEqual(x2, x1);
+ assertEqual(x5, x1);
+ assertEqual(x7, x1);
+ assertEqual(x6, x3);
+ assertEqual(x8, x3);
+ assertEqual(x10, x3);
+ assertEqual(x10, x9);
+
+ assertValid(x0);
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+ assertValid(x8);
+ assertValid(x9);
+ assertValid(x10);
+ assertValid(x11);
+}
+
+void AmountTestCase::testCommodityConstructors()
+{
+ amount_t x1("$123.45");
+ amount_t x2("-$123.45");
+ amount_t x3("$-123.45");
+ amount_t x4("DM 123.45");
+ amount_t x5("-DM 123.45");
+ amount_t x6("DM -123.45");
+ amount_t x7("123.45 euro");
+ amount_t x8("-123.45 euro");
+ amount_t x9("123.45€");
+ amount_t x10("-123.45€");
+
+ assertEqual(amount_t("$123.45"), x1);
+ assertEqual(amount_t("-$123.45"), x2);
+ assertEqual(amount_t("$-123.45"), x3);
+ assertEqual(amount_t("DM 123.45"), x4);
+ assertEqual(amount_t("-DM 123.45"), x5);
+ assertEqual(amount_t("DM -123.45"), x6);
+ assertEqual(amount_t("123.45 euro"), x7);
+ assertEqual(amount_t("-123.45 euro"), x8);
+ assertEqual(amount_t("123.45€"), x9);
+ assertEqual(amount_t("-123.45€"), x10);
+
+ assertEqual(string("$123.45"), x1.to_string());
+ assertEqual(string("$-123.45"), x2.to_string());
+ assertEqual(string("$-123.45"), x3.to_string());
+ assertEqual(string("DM 123.45"), x4.to_string());
+ assertEqual(string("DM -123.45"), x5.to_string());
+ assertEqual(string("DM -123.45"), x6.to_string());
+ assertEqual(string("123.45 euro"), x7.to_string());
+ assertEqual(string("-123.45 euro"), x8.to_string());
+ assertEqual(string("123.45€"), x9.to_string());
+ assertEqual(string("-123.45€"), x10.to_string());
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+ assertValid(x8);
+ assertValid(x9);
+ assertValid(x10);
+}
+
+void AmountTestCase::testAssignment()
+{
+ amount_t x0;
+ amount_t x1;
+ amount_t x2;
+ amount_t x3;
+ amount_t x5;
+ amount_t x6;
+ amount_t x7;
+ amount_t x8;
+ amount_t x9;
+ amount_t x10;
+
+ x1 = 123456L;
+ x2 = 123456UL;
+ x3 = "123.456";
+ x5 = "123456";
+ x6 = "123.456";
+ x7 = string("123456");
+ x8 = string("123.456");
+ x9 = x3;
+ x10 = amount_t(x6);
+
+ assertEqual(x2, x1);
+ assertEqual(x5, x1);
+ assertEqual(x7, x1);
+ assertEqual(x6, x3);
+ assertEqual(x8, x3);
+ assertEqual(x10, x3);
+ assertEqual(x10, x9);
+
+ assertFalse(x1.is_null());
+ x1 = x0; // sets x1 back to uninitialized state
+ assertTrue(x0.is_null());
+ assertTrue(x1.is_null());
+
+ assertValid(x0);
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+ assertValid(x8);
+ assertValid(x9);
+ assertValid(x10);
+}
+
+void AmountTestCase::testCommodityAssignment()
+{
+ amount_t x1;
+ amount_t x2;
+ amount_t x3;
+ amount_t x4;
+ amount_t x5;
+ amount_t x6;
+ amount_t x7;
+ amount_t x8;
+ amount_t x9;
+ amount_t x10;
+
+ x1 = "$123.45";
+ x2 = "-$123.45";
+ x3 = "$-123.45";
+ x4 = "DM 123.45";
+ x5 = "-DM 123.45";
+ x6 = "DM -123.45";
+ x7 = "123.45 euro";
+ x8 = "-123.45 euro";
+ x9 = "123.45€";
+ x10 = "-123.45€";
+
+ assertEqual(amount_t("$123.45"), x1);
+ assertEqual(amount_t("-$123.45"), x2);
+ assertEqual(amount_t("$-123.45"), x3);
+ assertEqual(amount_t("DM 123.45"), x4);
+ assertEqual(amount_t("-DM 123.45"), x5);
+ assertEqual(amount_t("DM -123.45"), x6);
+ assertEqual(amount_t("123.45 euro"), x7);
+ assertEqual(amount_t("-123.45 euro"), x8);
+ assertEqual(amount_t("123.45€"), x9);
+ assertEqual(amount_t("-123.45€"), x10);
+
+ assertEqual(string("$123.45"), x1.to_string());
+ assertEqual(string("$-123.45"), x2.to_string());
+ assertEqual(string("$-123.45"), x3.to_string());
+ assertEqual(string("DM 123.45"), x4.to_string());
+ assertEqual(string("DM -123.45"), x5.to_string());
+ assertEqual(string("DM -123.45"), x6.to_string());
+ assertEqual(string("123.45 euro"), x7.to_string());
+ assertEqual(string("-123.45 euro"), x8.to_string());
+ assertEqual(string("123.45€"), x9.to_string());
+ assertEqual(string("-123.45€"), x10.to_string());
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+ assertValid(x8);
+ assertValid(x9);
+ assertValid(x10);
+}
+
+void AmountTestCase::testEquality()
+{
+ amount_t x1(123456L);
+ amount_t x2(456789L);
+ amount_t x3(333333L);
+ amount_t x4("123456.0");
+ amount_t x5("123456.0");
+ amount_t x6("123456.0");
+
+ assertTrue(x1 == 123456L);
+ assertTrue(x1 != x2);
+ assertTrue(x1 == (x2 - x3));
+ assertTrue(x1 == x4);
+ assertTrue(x4 == x5);
+ assertTrue(x4 == x6);
+
+ assertTrue(x1 == 123456L);
+ assertTrue(123456L == x1);
+ assertTrue(x1 == 123456UL);
+ assertTrue(123456UL == x1);
+ assertTrue(x1 == amount_t("123456.0"));
+ assertTrue(amount_t("123456.0") == x1);
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x6);
+}
+
+void AmountTestCase::testCommodityEquality()
+{
+ amount_t x0;
+ amount_t x1;
+ amount_t x2;
+ amount_t x3;
+ amount_t x4;
+ amount_t x5;
+ amount_t x6;
+ amount_t x7;
+ amount_t x8;
+ amount_t x9;
+ amount_t x10;
+
+ x1 = "$123.45";
+ x2 = "-$123.45";
+ x3 = "$-123.45";
+ x4 = "DM 123.45";
+ x5 = "-DM 123.45";
+ x6 = "DM -123.45";
+ x7 = "123.45 euro";
+ x8 = "-123.45 euro";
+ x9 = "123.45€";
+ x10 = "-123.45€";
+
+ assertTrue(x0.is_null());
+ assertThrow(x0.is_zero(), amount_error);
+ assertThrow(x0.is_realzero(), amount_error);
+ assertThrow(assert(x0.sign() == 0), amount_error);
+ assertThrow(assert(x0.compare(x1) < 0), amount_error);
+ assertThrow(assert(x0.compare(x2) > 0), amount_error);
+ assertThrow(assert(x0.compare(x0) == 0), amount_error);
+
+ assertTrue(x1 != x2);
+ assertTrue(x1 != x4);
+ assertTrue(x1 != x7);
+ assertTrue(x1 != x9);
+ assertTrue(x2 == x3);
+ assertTrue(x4 != x5);
+ assertTrue(x5 == x6);
+ assertTrue(x7 == - x8);
+ assertTrue(x9 == - x10);
+
+ assertValid(x0);
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+ assertValid(x8);
+ assertValid(x9);
+ assertValid(x10);
+}
+
+void AmountTestCase::testComparisons()
+{
+ amount_t x0;
+ amount_t x1(-123L);
+ amount_t x2(123L);
+ amount_t x3("-123.45");
+ amount_t x4("123.45");
+ amount_t x5("-123.45");
+ amount_t x6("123.45");
+
+ assertThrow(x0 > x1, amount_error);
+ assertThrow(x0 < x2, amount_error);
+ assertThrow(x0 > x3, amount_error);
+ assertThrow(x0 < x4, amount_error);
+ assertThrow(x0 > x5, amount_error);
+ assertThrow(x0 < x6, amount_error);
+
+ assertTrue(x1 > x3);
+ assertTrue(x3 <= x5);
+ assertTrue(x3 >= x5);
+ assertTrue(x3 < x1);
+ assertTrue(x3 < x4);
+
+ assertTrue(x1 < 100L);
+ assertTrue(100L > x1);
+ assertTrue(x1 < 100UL);
+ assertTrue(100UL > x1);
+#ifdef HAVE_GDTOA
+ assertTrue(x1 < 100.0);
+ assertTrue(100.0 > x1);
+#endif
+
+ assertValid(x0);
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x6);
+}
+
+void AmountTestCase::testCommodityComparisons()
+{
+ amount_t x1("$-123");
+ amount_t x2("$123.00");
+ amount_t x3(internalAmount("$-123.4544"));
+ amount_t x4(internalAmount("$123.4544"));
+ amount_t x5("$-123.45");
+ amount_t x6("$123.45");
+ amount_t x7("DM 123.45");
+
+ assertTrue(x1 > x3);
+ assertTrue(x3 <= x5);
+ assertTrue(x3 < x5);
+ assertTrue(x3 <= x5);
+ assertFalse(x3 == x5);
+ assertTrue(x3 < x1);
+ assertTrue(x3 < x4);
+ assertFalse(x6 == x7);
+ assertThrow(x6 < x7, amount_error);
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x6);
+}
+
+void AmountTestCase::testIntegerAddition()
+{
+ amount_t x0;
+ amount_t x1(123L);
+ amount_t y1(456L);
+
+ assertEqual(amount_t(579L), x1 + y1);
+ assertEqual(amount_t(579L), x1 + 456L);
+ assertEqual(amount_t(579L), 456L + x1);
+
+ x1 += amount_t(456L);
+ assertEqual(amount_t(579L), x1);
+ x1 += 456L;
+ assertEqual(amount_t(1035L), x1);
+
+ amount_t x4("123456789123456789123456789");
+
+ assertEqual(amount_t("246913578246913578246913578"), x4 + x4);
+
+ assertValid(x0);
+ assertValid(x1);
+ assertValid(y1);
+ assertValid(x4);
+}
+
+void AmountTestCase::testFractionalAddition()
+{
+ amount_t x1("123.123");
+ amount_t y1("456.456");
+
+ assertEqual(amount_t("579.579"), x1 + y1);
+ assertEqual(amount_t("579.579"), x1 + amount_t("456.456"));
+ assertEqual(amount_t("579.579"), amount_t("456.456") + x1);
+
+ x1 += amount_t("456.456");
+ assertEqual(amount_t("579.579"), x1);
+ x1 += amount_t("456.456");
+ assertEqual(amount_t("1036.035"), x1);
+ x1 += 456L;
+ assertEqual(amount_t("1492.035"), x1);
+
+ amount_t x2("123456789123456789.123456789123456789");
+
+ assertEqual(amount_t("246913578246913578.246913578246913578"), x2 + x2);
+
+ assertValid(x1);
+ assertValid(y1);
+ assertValid(x2);
+}
+
+void AmountTestCase::testCommodityAddition()
+{
+ amount_t x0;
+ amount_t x1("$123.45");
+ amount_t x2(internalAmount("$123.456789"));
+ amount_t x3("DM 123.45");
+ amount_t x4("123.45 euro");
+ amount_t x5("123.45€");
+ amount_t x6("123.45");
+
+ assertEqual(amount_t("$246.90"), x1 + x1);
+ assertNotEqual(amount_t("$246.91"), x1 + x2);
+ assertEqual(internalAmount("$246.906789"), x1 + x2);
+
+ // Converting to string drops internal precision
+ assertEqual(string("$246.90"), (x1 + x1).to_string());
+ assertEqual(string("$246.91"), (x1 + x2).to_string());
+
+ assertThrow(x1 + x0, amount_error);
+ assertThrow(x0 + x1, amount_error);
+ assertThrow(x0 + x0, amount_error);
+ assertThrow(x1 + x3, amount_error);
+ assertThrow(x1 + x4, amount_error);
+ assertThrow(x1 + x5, amount_error);
+ assertThrow(x1 + x6, amount_error);
+#ifdef HAVE_GDTOA
+ assertThrow(x1 + 123.45, amount_error);
+#endif
+ assertThrow(x1 + 123L, amount_error);
+
+ assertEqual(amount_t("DM 246.90"), x3 + x3);
+ assertEqual(amount_t("246.90 euro"), x4 + x4);
+ assertEqual(amount_t("246.90€"), x5 + x5);
+
+ assertEqual(string("DM 246.90"), (x3 + x3).to_string());
+ assertEqual(string("246.90 euro"), (x4 + x4).to_string());
+ assertEqual(string("246.90€"), (x5 + x5).to_string());
+
+ x1 += amount_t("$456.45");
+ assertEqual(amount_t("$579.90"), x1);
+ x1 += amount_t("$456.45");
+ assertEqual(amount_t("$1036.35"), x1);
+ x1 += amount_t("$456");
+ assertEqual(amount_t("$1492.35"), x1);
+
+ amount_t x7(internalAmount("$123456789123456789.123456789123456789"));
+
+ assertEqual(internalAmount("$246913578246913578.246913578246913578"), x7 + x7);
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+}
+
+void AmountTestCase::testIntegerSubtraction()
+{
+ amount_t x1(123L);
+ amount_t y1(456L);
+
+ assertEqual(amount_t(333L), y1 - x1);
+ assertEqual(amount_t(-333L), x1 - y1);
+ assertEqual(amount_t(23L), x1 - 100L);
+ assertEqual(amount_t(-23L), 100L - x1);
+
+ x1 -= amount_t(456L);
+ assertEqual(amount_t(-333L), x1);
+ x1 -= 456L;
+ assertEqual(amount_t(-789L), x1);
+
+ amount_t x4("123456789123456789123456789");
+ amount_t y4("8238725986235986");
+
+ assertEqual(amount_t("123456789115218063137220803"), x4 - y4);
+ assertEqual(amount_t("-123456789115218063137220803"), y4 - x4);
+
+ assertValid(x1);
+ assertValid(y1);
+ assertValid(x4);
+ assertValid(y4);
+}
+
+void AmountTestCase::testFractionalSubtraction()
+{
+ amount_t x1("123.123");
+ amount_t y1("456.456");
+
+ assertEqual(amount_t("-333.333"), x1 - y1);
+ assertEqual(amount_t("333.333"), y1 - x1);
+
+ x1 -= amount_t("456.456");
+ assertEqual(amount_t("-333.333"), x1);
+ x1 -= amount_t("456.456");
+ assertEqual(amount_t("-789.789"), x1);
+ x1 -= 456L;
+ assertEqual(amount_t("-1245.789"), x1);
+
+ amount_t x2("123456789123456789.123456789123456789");
+ amount_t y2("9872345982459.248974239578");
+
+ assertEqual(amount_t("123446916777474329.874482549545456789"), x2 - y2);
+ assertEqual(amount_t("-123446916777474329.874482549545456789"), y2 - x2);
+
+ assertValid(x1);
+ assertValid(y1);
+ assertValid(x2);
+ assertValid(y2);
+}
+
+void AmountTestCase::testCommoditySubtraction()
+{
+ amount_t x0;
+ amount_t x1("$123.45");
+ amount_t x2(internalAmount("$123.456789"));
+ amount_t x3("DM 123.45");
+ amount_t x4("123.45 euro");
+ amount_t x5("123.45€");
+ amount_t x6("123.45");
+
+ assertNotEqual(amount_t(), x1 - x1);
+ assertEqual(amount_t("$0"), x1 - x1);
+ assertEqual(amount_t("$23.45"), x1 - amount_t("$100.00"));
+ assertEqual(amount_t("$-23.45"), amount_t("$100.00") - x1);
+ assertNotEqual(amount_t("$-0.01"), x1 - x2);
+ assertEqual(internalAmount("$-0.006789"), x1 - x2);
+
+ // Converting to string drops internal precision. If an amount is
+ // zero, it drops the commodity as well.
+ assertEqual(string("$0.00"), (x1 - x1).to_string());
+ assertEqual(string("$-0.01"), (x1 - x2).to_string());
+
+ assertThrow(x1 - x0, amount_error);
+ assertThrow(x0 - x1, amount_error);
+ assertThrow(x0 - x0, amount_error);
+ assertThrow(x1 - x3, amount_error);
+ assertThrow(x1 - x4, amount_error);
+ assertThrow(x1 - x5, amount_error);
+ assertThrow(x1 - x6, amount_error);
+#ifdef HAVE_GDTOA
+ assertThrow(x1 - 123.45, amount_error);
+#endif
+ assertThrow(x1 - 123L, amount_error);
+
+ assertEqual(amount_t("DM 0.00"), x3 - x3);
+ assertEqual(amount_t("DM 23.45"), x3 - amount_t("DM 100.00"));
+ assertEqual(amount_t("DM -23.45"), amount_t("DM 100.00") - x3);
+ assertEqual(amount_t("0.00 euro"), x4 - x4);
+ assertEqual(amount_t("23.45 euro"), x4 - amount_t("100.00 euro"));
+ assertEqual(amount_t("-23.45 euro"), amount_t("100.00 euro") - x4);
+ assertEqual(amount_t("0.00€"), x5 - x5);
+ assertEqual(amount_t("23.45€"), x5 - amount_t("100.00€"));
+ assertEqual(amount_t("-23.45€"), amount_t("100.00€") - x5);
+
+ assertEqual(string("DM 0.00"), (x3 - x3).to_string());
+ assertEqual(string("DM 23.45"), (x3 - amount_t("DM 100.00")).to_string());
+ assertEqual(string("DM -23.45"), (amount_t("DM 100.00") - x3).to_string());
+ assertEqual(string("0.00 euro"), (x4 - x4).to_string());
+ assertEqual(string("23.45 euro"), (x4 - amount_t("100.00 euro")).to_string());
+ assertEqual(string("-23.45 euro"), (amount_t("100.00 euro") - x4).to_string());
+ assertEqual(string("0.00€"), (x5 - x5).to_string());
+ assertEqual(string("23.45€"), (x5 - amount_t("100.00€")).to_string());
+ assertEqual(string("-23.45€"), (amount_t("100.00€") - x5).to_string());
+
+ x1 -= amount_t("$456.45");
+ assertEqual(amount_t("$-333.00"), x1);
+ x1 -= amount_t("$456.45");
+ assertEqual(amount_t("$-789.45"), x1);
+ x1 -= amount_t("$456");
+ assertEqual(amount_t("$-1245.45"), x1);
+
+ amount_t x7(internalAmount("$123456789123456789.123456789123456789"));
+ amount_t x8(internalAmount("$2354974984698.98459845984598"));
+
+ assertEqual(internalAmount("$123454434148472090.138858329277476789"), x7 - x8);
+ assertEqual(string("$123454434148472090.138858329277476789"), (x7 - x8).to_string());
+ assertEqual(string("$123454434148472090.14"),
+ (amount_t("$1.00") * (x7 - x8)).to_string());
+ assertEqual(internalAmount("$-123454434148472090.138858329277476789"), x8 - x7);
+ assertEqual(string("$-123454434148472090.138858329277476789"), (x8 - x7).to_string());
+ assertEqual(string("$-123454434148472090.14"),
+ (amount_t("$1.00") * (x8 - x7)).to_string());
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+ assertValid(x8);
+}
+
+void AmountTestCase::testIntegerMultiplication()
+{
+ amount_t x1(123L);
+ amount_t y1(456L);
+
+ assertEqual(amount_t(0L), x1 * 0L);
+ assertEqual(amount_t(0L), amount_t(0L) * x1);
+ assertEqual(amount_t(0L), 0L * x1);
+ assertEqual(x1, x1 * 1L);
+ assertEqual(x1, amount_t(1L) * x1);
+ assertEqual(x1, 1L * x1);
+ assertEqual(- x1, x1 * -1L);
+ assertEqual(- x1, amount_t(-1L) * x1);
+ assertEqual(- x1, -1L * x1);
+ assertEqual(amount_t(56088L), x1 * y1);
+ assertEqual(amount_t(56088L), y1 * x1);
+ assertEqual(amount_t(56088L), x1 * 456L);
+ assertEqual(amount_t(56088L), amount_t(456L) * x1);
+ assertEqual(amount_t(56088L), 456L * x1);
+
+ x1 *= amount_t(123L);
+ assertEqual(amount_t(15129L), x1);
+ x1 *= 123L;
+ assertEqual(amount_t(1860867L), x1);
+
+ amount_t x4("123456789123456789123456789");
+
+ assertEqual(amount_t("15241578780673678546105778281054720515622620750190521"),
+ x4 * x4);
+
+ assertValid(x1);
+ assertValid(y1);
+ assertValid(x4);
+}
+
+void AmountTestCase::testFractionalMultiplication()
+{
+ amount_t x1("123.123");
+ amount_t y1("456.456");
+
+ assertEqual(amount_t(0L), x1 * 0L);
+ assertEqual(amount_t(0L), amount_t(0L) * x1);
+ assertEqual(amount_t(0L), 0L * x1);
+ assertEqual(x1, x1 * 1L);
+ assertEqual(x1, amount_t(1L) * x1);
+ assertEqual(x1, 1L * x1);
+ assertEqual(- x1, x1 * -1L);
+ assertEqual(- x1, amount_t(-1L) * x1);
+ assertEqual(- x1, -1L * x1);
+ assertEqual(amount_t("56200.232088"), x1 * y1);
+ assertEqual(amount_t("56200.232088"), y1 * x1);
+ assertEqual(amount_t("56200.232088"), x1 * amount_t("456.456"));
+ assertEqual(amount_t("56200.232088"), amount_t("456.456") * x1);
+ assertEqual(amount_t("56200.232088"), amount_t("456.456") * x1);
+
+ x1 *= amount_t("123.123");
+ assertEqual(amount_t("15159.273129"), x1);
+ x1 *= amount_t("123.123");
+ assertEqual(amount_t("1866455.185461867"), x1);
+ x1 *= 123L;
+ assertEqual(amount_t("229573987.811809641"), x1);
+
+ amount_t x2("123456789123456789.123456789123456789");
+
+ assertEqual(amount_t("15241578780673678546105778311537878.046486820281054720515622620750190521"),
+ x2 * x2);
+
+ assertValid(x1);
+ assertValid(y1);
+ assertValid(x2);
+}
+
+void AmountTestCase::testCommodityMultiplication()
+{
+ amount_t x0;
+ amount_t x1("$123.12");
+ amount_t y1("$456.45");
+ amount_t x2(internalAmount("$123.456789"));
+ amount_t x3("DM 123.45");
+ amount_t x4("123.45 euro");
+ amount_t x5("123.45€");
+
+ assertEqual(amount_t("$0.00"), x1 * 0L);
+ assertEqual(amount_t("$0.00"), 0L * x1);
+ assertEqual(x1, x1 * 1L);
+ assertEqual(x1, 1L * x1);
+ assertEqual(- x1, x1 * -1L);
+ assertEqual(- x1, -1L * x1);
+ assertEqual(internalAmount("$56198.124"), x1 * y1);
+ assertEqual(string("$56198.12"), (x1 * y1).to_string());
+ assertEqual(internalAmount("$56198.124"), y1 * x1);
+ assertEqual(string("$56198.12"), (y1 * x1).to_string());
+
+ // Internal amounts retain their precision, even when being
+ // converted to strings
+ assertEqual(internalAmount("$15199.99986168"), x1 * x2);
+ assertEqual(internalAmount("$15199.99986168"), x2 * x1);
+ assertEqual(string("$15200.00"), (x1 * x2).to_string());
+ assertEqual(string("$15199.99986168"), (x2 * x1).to_string());
+
+ assertThrow(x1 * x0, amount_error);
+ assertThrow(x0 * x1, amount_error);
+ assertThrow(x0 * x0, amount_error);
+ //assertThrow(x1 * x3, amount_error);
+ //assertThrow(x1 * x4, amount_error);
+ //assertThrow(x1 * x5, amount_error);
+
+ x1 *= amount_t("123.12");
+ assertEqual(internalAmount("$15158.5344"), x1);
+ assertEqual(string("$15158.53"), x1.to_string());
+ x1 *= amount_t("123.12");
+ assertEqual(internalAmount("$1866318.755328"), x1);
+ assertEqual(string("$1866318.76"), x1.to_string());
+ x1 *= 123L;
+ assertEqual(internalAmount("$229557206.905344"), x1);
+ assertEqual(string("$229557206.91"), x1.to_string());
+
+ amount_t x7(internalAmount("$123456789123456789.123456789123456789"));
+
+ assertEqual(internalAmount("$15241578780673678546105778311537878.046486820281054720515622620750190521"),
+ x7 * x7);
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x7);
+}
+
+void AmountTestCase::testIntegerDivision()
+{
+ amount_t x1(123L);
+ amount_t y1(456L);
+
+ assertThrow(x1 / 0L, amount_error);
+ assertEqual(amount_t(0L), amount_t(0L) / x1);
+ assertEqual(amount_t(0L), 0L / x1);
+ assertEqual(x1, x1 / 1L);
+ assertEqual(amount_t("0.008130"), amount_t(1L) / x1);
+ assertEqual(amount_t("0.008130"), 1L / x1);
+ assertEqual(- x1, x1 / -1L);
+ assertEqual(- amount_t("0.008130"), amount_t(-1L) / x1);
+ assertEqual(- amount_t("0.008130"), -1L / x1);
+ assertEqual(amount_t("0.269737"), x1 / y1);
+ assertEqual(amount_t("3.707317"), y1 / x1);
+ assertEqual(amount_t("0.269737"), x1 / 456L);
+ assertEqual(amount_t("3.707317"), amount_t(456L) / x1);
+ assertEqual(amount_t("3.707317"), 456L / x1);
+
+ x1 /= amount_t(456L);
+ assertEqual(amount_t("0.269737"), x1);
+ x1 /= 456L;
+ assertEqual(amount_t("0.00059152850877193"), x1);
+
+ amount_t x4("123456789123456789123456789");
+ amount_t y4("56");
+
+ assertEqual(amount_t(1L), x4 / x4);
+ assertEqual(amount_t("2204585520061728377204585.517857"), x4 / y4);
+
+ assertValid(x1);
+ assertValid(y1);
+ assertValid(x4);
+ assertValid(y4);
+}
+
+void AmountTestCase::testFractionalDivision()
+{
+ amount_t x1("123.123");
+ amount_t y1("456.456");
+
+ assertThrow(x1 / 0L, amount_error);
+ assertEqual(amount_t("0.00812195934"), amount_t("1.0") / x1);
+ assertEqual(amount_t("0.00812195934"), amount_t("1.0") / x1);
+ assertEqual(x1, x1 / amount_t("1.0"));
+ assertEqual(amount_t("0.00812195934"), amount_t("1.0") / x1);
+ assertEqual(amount_t("0.00812195934"), amount_t("1.0") / x1);
+ assertEqual(- x1, x1 / amount_t("-1.0"));
+ assertEqual(- amount_t("0.00812195934"), amount_t("-1.0") / x1);
+ assertEqual(- amount_t("0.00812195934"), amount_t("-1.0") / x1);
+ assertEqual(amount_t("0.269736842105263"), x1 / y1);
+ assertEqual(amount_t("3.707317073170732"), y1 / x1);
+ assertEqual(amount_t("0.269736842105263"), x1 / amount_t("456.456"));
+ assertEqual(amount_t("3.707317073170732"), amount_t("456.456") / x1);
+ assertEqual(amount_t("3.707317073170732"), amount_t("456.456") / x1);
+
+ x1 /= amount_t("456.456");
+ assertEqual(amount_t("0.269736842105263"), x1);
+ x1 /= amount_t("456.456");
+ assertEqual(amount_t("0.000590937225286255411255411255411255411"), x1);
+ x1 /= 456L;
+ assertEqual(amount_t("0.000001295914967733016252753094858358016252192982456140350877192982456140350877192982"), x1);
+
+ amount_t x4("1234567891234567.89123456789");
+ amount_t y4("56.789");
+
+ assertEqual(amount_t("1.0"), x4 / x4);
+ assertEqual(amount_t("21739560323910.7554497273748437197344556164046"), x4 / y4);
+
+ assertValid(x1);
+ assertValid(y1);
+ assertValid(x4);
+ assertValid(y4);
+}
+
+void AmountTestCase::testCommodityDivision()
+{
+ amount_t x0;
+ amount_t x1("$123.12");
+ amount_t y1("$456.45");
+ amount_t x2(internalAmount("$123.456789"));
+ amount_t x3("DM 123.45");
+ amount_t x4("123.45 euro");
+ amount_t x5("123.45€");
+
+ assertThrow(x1 / 0L, amount_error);
+ assertEqual(amount_t("$0.00"), 0L / x1);
+ assertEqual(x1, x1 / 1L);
+ assertEqual(internalAmount("$0.00812216"), 1L / x1);
+ assertEqual(- x1, x1 / -1L);
+ assertEqual(internalAmount("$-0.00812216"), -1L / x1);
+ assertEqual(internalAmount("$0.26973382"), x1 / y1);
+ assertEqual(string("$0.27"), (x1 / y1).to_string());
+ assertEqual(internalAmount("$3.70735867"), y1 / x1);
+ assertEqual(string("$3.71"), (y1 / x1).to_string());
+
+ // Internal amounts retain their precision, even when being
+ // converted to strings
+ assertEqual(internalAmount("$0.99727201"), x1 / x2);
+ assertEqual(internalAmount("$1.00273545321637426901"), x2 / x1);
+ assertEqual(string("$1.00"), (x1 / x2).to_string());
+ assertEqual(string("$1.00273545321637426901"), (x2 / x1).to_string());
+
+ assertThrow(x1 / x0, amount_error);
+ assertThrow(x0 / x1, amount_error);
+ assertThrow(x0 / x0, amount_error);
+ //assertThrow(x1 / x3, amount_error);
+ //assertThrow(x1 / x4, amount_error);
+ //assertThrow(x1 / x5, amount_error);
+
+ x1 /= amount_t("123.12");
+ assertEqual(internalAmount("$1.00"), x1);
+ assertEqual(string("$1.00"), x1.to_string());
+ x1 /= amount_t("123.12");
+ assertEqual(internalAmount("$0.00812216"), x1);
+ assertEqual(string("$0.01"), x1.to_string());
+ x1 /= 123L;
+ assertEqual(internalAmount("$0.00006603"), x1);
+ assertEqual(string("$0.00"), x1.to_string());
+
+ amount_t x6(internalAmount("$237235987235987.98723987235978"));
+ amount_t x7(internalAmount("$123456789123456789.123456789123456789"));
+
+ assertEqual(amount_t("$1"), x7 / x7);
+ assertEqual(internalAmount("$0.0019216115121765559608381226612019501046413574469262"),
+ x6 / x7);
+ assertEqual(internalAmount("$520.39654928343335571379527154924040947271699678158689736256"),
+ x7 / x6);
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+}
+
+void AmountTestCase::testNegation()
+{
+ amount_t x0;
+ amount_t x1(-123456L);
+ amount_t x3("-123.456");
+ amount_t x5("-123456");
+ amount_t x6("-123.456");
+ amount_t x7(string("-123456"));
+ amount_t x8(string("-123.456"));
+ amount_t x9(- x3);
+
+ assertThrow(x0.negate(), amount_error);
+ assertEqual(x5, x1);
+ assertEqual(x7, x1);
+ assertEqual(x6, x3);
+ assertEqual(x8, x3);
+ assertEqual(- x6, x9);
+ assertEqual(x3.negate(), x9);
+
+ amount_t x10(x9.negate());
+
+ assertEqual(x3, x10);
+
+ assertValid(x1);
+ assertValid(x3);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+ assertValid(x8);
+ assertValid(x9);
+ assertValid(x10);
+}
+
+void AmountTestCase::testCommodityNegation()
+{
+ amount_t x1("$123.45");
+ amount_t x2("-$123.45");
+ amount_t x3("$-123.45");
+ amount_t x4("DM 123.45");
+ amount_t x5("-DM 123.45");
+ amount_t x6("DM -123.45");
+ amount_t x7("123.45 euro");
+ amount_t x8("-123.45 euro");
+ amount_t x9("123.45€");
+ amount_t x10("-123.45€");
+
+ assertEqual(amount_t("$-123.45"), - x1);
+ assertEqual(amount_t("$123.45"), - x2);
+ assertEqual(amount_t("$123.45"), - x3);
+ assertEqual(amount_t("DM -123.45"), - x4);
+ assertEqual(amount_t("DM 123.45"), - x5);
+ assertEqual(amount_t("DM 123.45"), - x6);
+ assertEqual(amount_t("-123.45 euro"), - x7);
+ assertEqual(amount_t("123.45 euro"), - x8);
+ assertEqual(amount_t("-123.45€"), - x9);
+ assertEqual(amount_t("123.45€"), - x10);
+
+ assertEqual(amount_t("$-123.45"), x1.negate());
+ assertEqual(amount_t("$123.45"), x2.negate());
+ assertEqual(amount_t("$123.45"), x3.negate());
+
+ assertEqual(string("$-123.45"), (- x1).to_string());
+ assertEqual(string("$123.45"), (- x2).to_string());
+ assertEqual(string("$123.45"), (- x3).to_string());
+ assertEqual(string("DM -123.45"), (- x4).to_string());
+ assertEqual(string("DM 123.45"), (- x5).to_string());
+ assertEqual(string("DM 123.45"), (- x6).to_string());
+ assertEqual(string("-123.45 euro"), (- x7).to_string());
+ assertEqual(string("123.45 euro"), (- x8).to_string());
+ assertEqual(string("-123.45€"), (- x9).to_string());
+ assertEqual(string("123.45€"), (- x10).to_string());
+
+ assertEqual(amount_t("$-123.45"), x1.negate());
+ assertEqual(amount_t("$123.45"), x2.negate());
+ assertEqual(amount_t("$123.45"), x3.negate());
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+ assertValid(x6);
+ assertValid(x7);
+ assertValid(x8);
+ assertValid(x9);
+ assertValid(x10);
+}
+
+void AmountTestCase::testAbs()
+{
+ amount_t x0;
+ amount_t x1(-1234L);
+ amount_t x2(1234L);
+
+ assertThrow(x0.abs(), amount_error);
+ assertEqual(amount_t(1234L), x1.abs());
+ assertEqual(amount_t(1234L), x2.abs());
+
+ assertValid(x0);
+ assertValid(x1);
+ assertValid(x2);
+}
+
+void AmountTestCase::testCommodityAbs()
+{
+ amount_t x1("$-1234.56");
+ amount_t x2("$1234.56");
+
+ assertEqual(amount_t("$1234.56"), x1.abs());
+ assertEqual(amount_t("$1234.56"), x2.abs());
+
+ assertValid(x1);
+ assertValid(x2);
+}
+
+void AmountTestCase::testFractionalRound()
+{
+ amount_t x0;
+ amount_t x1("1234.567890");
+
+ assertThrow(x0.precision(), amount_error);
+ assertThrow(x0.round(), amount_error);
+ assertThrow(x0.round(2), amount_error);
+ assertThrow(x0.unround(), amount_error);
+ assertEqual(amount_t::precision_t(6), x1.precision());
+
+ amount_t x1b(x1.unround());
+
+ assertEqual(x1b.precision(), x1b.unround().precision());
+
+ amount_t y7(x1.round(7));
+ amount_t y6(x1.round(6));
+ amount_t y5(x1.round(5));
+ amount_t y4(x1.round(4));
+ amount_t y3(x1.round(3));
+ amount_t y2(x1.round(2));
+ amount_t y1(x1.round(1));
+ amount_t y0(x1.round(0));
+
+ assertEqual(amount_t::precision_t(6), y7.precision());
+ assertEqual(amount_t::precision_t(6), y6.precision());
+ assertEqual(amount_t::precision_t(5), y5.precision());
+ assertEqual(amount_t::precision_t(4), y4.precision());
+ assertEqual(amount_t::precision_t(3), y3.precision());
+ assertEqual(amount_t::precision_t(2), y2.precision());
+ assertEqual(amount_t::precision_t(1), y1.precision());
+ assertEqual(amount_t::precision_t(0), y0.precision());
+
+ assertEqual(amount_t("1234.56789"), y7);
+ assertEqual(amount_t("1234.56789"), y6);
+ assertEqual(amount_t("1234.56789"), y5);
+ assertEqual(amount_t("1234.5679"), y4);
+ assertEqual(amount_t("1234.568"), y3);
+ assertEqual(amount_t("1234.57"), y2);
+ assertEqual(amount_t("1234.6"), y1);
+ assertEqual(amount_t("1235"), y0);
+
+ amount_t x2("9876.543210");
+
+ assertEqual(amount_t("9876.543210"), x2.round(6));
+ assertEqual(amount_t("9876.54321"), x2.round(5));
+ assertEqual(amount_t("9876.5432"), x2.round(4));
+ assertEqual(amount_t("9876.543"), x2.round(3));
+ assertEqual(amount_t("9876.54"), x2.round(2));
+ assertEqual(amount_t("9876.5"), x2.round(1));
+ assertEqual(amount_t("9877"), x2.round(0));
+
+ amount_t x3("-1234.567890");
+
+ assertEqual(amount_t("-1234.56789"), x3.round(6));
+ assertEqual(amount_t("-1234.56789"), x3.round(5));
+ assertEqual(amount_t("-1234.5679"), x3.round(4));
+ assertEqual(amount_t("-1234.568"), x3.round(3));
+ assertEqual(amount_t("-1234.57"), x3.round(2));
+ assertEqual(amount_t("-1234.6"), x3.round(1));
+ assertEqual(amount_t("-1235"), x3.round(0));
+
+ amount_t x4("-9876.543210");
+
+ assertEqual(amount_t("-9876.543210"), x4.round(6));
+ assertEqual(amount_t("-9876.54321"), x4.round(5));
+ assertEqual(amount_t("-9876.5432"), x4.round(4));
+ assertEqual(amount_t("-9876.543"), x4.round(3));
+ assertEqual(amount_t("-9876.54"), x4.round(2));
+ assertEqual(amount_t("-9876.5"), x4.round(1));
+ assertEqual(amount_t("-9877"), x4.round(0));
+
+ amount_t x5("0.0000000000000000000000000000000000001");
+
+ assertEqual(amount_t("0.0000000000000000000000000000000000001"),
+ x5.round(37));
+ assertEqual(amount_t(0L), x5.round(36));
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+}
+
+void AmountTestCase::testCommodityRound()
+{
+ amount_t x1(internalAmount("$1234.567890"));
+
+ assertEqual(internalAmount("$1234.56789"), x1.round(6));
+ assertEqual(internalAmount("$1234.56789"), x1.round(5));
+ assertEqual(internalAmount("$1234.5679"), x1.round(4));
+ assertEqual(internalAmount("$1234.568"), x1.round(3));
+ assertEqual(amount_t("$1234.57"), x1.round(2));
+ assertEqual(amount_t("$1234.6"), x1.round(1));
+ assertEqual(amount_t("$1235"), x1.round(0));
+
+ amount_t x2(internalAmount("$9876.543210"));
+
+ assertEqual(internalAmount("$9876.543210"), x2.round(6));
+ assertEqual(internalAmount("$9876.54321"), x2.round(5));
+ assertEqual(internalAmount("$9876.5432"), x2.round(4));
+ assertEqual(internalAmount("$9876.543"), x2.round(3));
+ assertEqual(amount_t("$9876.54"), x2.round(2));
+ assertEqual(amount_t("$9876.5"), x2.round(1));
+ assertEqual(amount_t("$9877"), x2.round(0));
+
+ amount_t x3(internalAmount("$-1234.567890"));
+
+ assertEqual(internalAmount("$-1234.56789"), x3.round(6));
+ assertEqual(internalAmount("$-1234.56789"), x3.round(5));
+ assertEqual(internalAmount("$-1234.5679"), x3.round(4));
+ assertEqual(internalAmount("$-1234.568"), x3.round(3));
+ assertEqual(amount_t("$-1234.57"), x3.round(2));
+ assertEqual(amount_t("$-1234.6"), x3.round(1));
+ assertEqual(amount_t("$-1235"), x3.round(0));
+
+ amount_t x4(internalAmount("$-9876.543210"));
+
+ assertEqual(internalAmount("$-9876.543210"), x4.round(6));
+ assertEqual(internalAmount("$-9876.54321"), x4.round(5));
+ assertEqual(internalAmount("$-9876.5432"), x4.round(4));
+ assertEqual(internalAmount("$-9876.543"), x4.round(3));
+ assertEqual(amount_t("$-9876.54"), x4.round(2));
+ assertEqual(amount_t("$-9876.5"), x4.round(1));
+ assertEqual(amount_t("$-9877"), x4.round(0));
+
+ amount_t x5("$123.45");
+
+ x5 *= amount_t("100.12");
+
+ assertEqual(internalAmount("$12359.814"), x5);
+ assertEqual(string("$12359.81"), x5.to_string());
+ assertEqual(string("$12359.814"), x5.to_fullstring());
+ assertEqual(string("$12359.814"), x5.unround().to_string());
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x5);
+}
+
+void AmountTestCase::testCommodityDisplayRound()
+{
+ amount_t x1("$0.85");
+ amount_t x2("$0.1");
+
+ x1 *= amount_t("0.19");
+
+ assertNotEqual(amount_t("$0.16"), x1);
+ assertEqual(internalAmount("$0.1615"), x1);
+ assertEqual(string("$0.16"), x1.to_string());
+
+ assertEqual(amount_t("$0.10"), x2);
+ assertNotEqual(internalAmount("$0.101"), x2);
+ assertEqual(string("$0.10"), x2.to_string());
+
+ x1 *= 7L;
+
+ assertNotEqual(amount_t("$1.13"), x1);
+ assertEqual(internalAmount("$1.1305"), x1);
+ assertEqual(string("$1.13"), x1.to_string());
+}
+
+void AmountTestCase::testReduction()
+{
+ amount_t x0;
+ amount_t x1("60s");
+ amount_t x2("600s");
+ amount_t x3("6000s");
+ amount_t x4("360000s");
+ amount_t x5("10m"); // 600s
+ amount_t x6("100m"); // 6000s
+ amount_t x7("1000m"); // 60000s
+ amount_t x8("10000m"); // 600000s
+ amount_t x9("10h"); // 36000s
+ amount_t x10("100h"); // 360000s
+ amount_t x11("1000h"); // 3600000s
+ amount_t x12("10000h"); // 36000000s
+
+ assertThrow(x0.reduce(), amount_error);
+ assertThrow(x0.unreduce(), amount_error);
+ assertEqual(x2, x5);
+ assertEqual(x3, x6);
+ assertEqual(x4, x10);
+ assertEqual(string("100.0h"), x4.unreduce().to_string());
+}
+
+void AmountTestCase::testSign()
+{
+ amount_t x0;
+ amount_t x1("0.0000000000000000000000000000000000001");
+ amount_t x2("-0.0000000000000000000000000000000000001");
+ amount_t x3("1");
+ amount_t x4("-1");
+
+ assertThrow(x0.sign(), amount_error);
+ assertTrue(x1.sign() > 0);
+ assertTrue(x2.sign() < 0);
+ assertTrue(x3.sign() > 0);
+ assertTrue(x4.sign() < 0);
+
+ assertValid(x0);
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+}
+
+void AmountTestCase::testCommoditySign()
+{
+ amount_t x1(internalAmount("$0.0000000000000000000000000000000000001"));
+ amount_t x2(internalAmount("$-0.0000000000000000000000000000000000001"));
+ amount_t x3("$1");
+ amount_t x4("$-1");
+
+ assertTrue(x1.sign() != 0);
+ assertTrue(x2.sign() != 0);
+ assertTrue(x3.sign() > 0);
+ assertTrue(x4.sign() < 0);
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+}
+
+void AmountTestCase::testTruth()
+{
+ amount_t x0;
+ amount_t x1("1234");
+ amount_t x2("1234.56");
+
+ assertThrow(assert(x0 ? 1 : 0), amount_error);
+
+ assertTrue(x1);
+ assertTrue(x2);
+
+ assertValid(x0);
+ assertValid(x1);
+ assertValid(x2);
+}
+
+void AmountTestCase::testCommodityTruth()
+{
+ amount_t x1("$1234");
+ amount_t x2("$1234.56");
+
+ if (x1)
+ assertTrue(true);
+ else
+ assertTrue(false);
+
+ if (x2)
+ assertTrue(true);
+ else
+ assertTrue(false);
+
+ assertValid(x1);
+ assertValid(x2);
+}
+
+void AmountTestCase::testForZero()
+{
+ amount_t x0;
+ amount_t x1("0.000000000000000000001");
+
+ assertTrue(x1);
+ assertThrow(x0.is_zero(), amount_error);
+ assertThrow(x0.is_realzero(), amount_error);
+ assertFalse(x1.is_zero());
+ assertFalse(x1.is_realzero());
+
+ assertValid(x0);
+ assertValid(x1);
+}
+
+void AmountTestCase::testCommodityForZero()
+{
+ amount_t x1(internalAmount("$0.000000000000000000001"));
+
+ assertTrue(x1); // an internal amount never betrays its precision
+ assertFalse(x1.is_zero());
+ assertFalse(x1.is_realzero());
+
+ assertValid(x1);
+}
+
+void AmountTestCase::testIntegerConversion()
+{
+ amount_t x0;
+ amount_t x1(123456L);
+ amount_t x2("12345682348723487324");
+
+ assertThrow(x0.to_long(), amount_error);
+#ifdef HAVE_GDTOA
+ assertThrow(x0.to_double(), amount_error);
+#endif
+ assertFalse(x2.fits_in_long());
+ assertEqual(123456L, x1.to_long());
+#ifdef HAVE_GDTOA
+ assertEqual(123456.0, x1.to_double());
+#endif
+ assertEqual(string("123456"), x1.to_string());
+ assertEqual(string("123456"), x1.quantity_string());
+
+ assertValid(x1);
+}
+
+void AmountTestCase::testFractionalConversion()
+{
+ amount_t x1("1234.56");
+ amount_t x2("1234.5683787634678348734");
+
+ assertThrow(x1.to_long(), amount_error); // loses precision
+#ifdef HAVE_GDTOA
+ assertThrow(x2.to_double(), amount_error); // loses precision
+ assertFalse(x2.fits_in_double());
+#endif
+ assertEqual(1234L, x1.to_long(true));
+#ifdef HAVE_GDTOA
+ assertEqual(1234.56, x1.to_double());
+#endif
+ assertEqual(string("1234.56"), x1.to_string());
+ assertEqual(string("1234.56"), x1.quantity_string());
+
+ assertValid(x1);
+}
+
+void AmountTestCase::testCommodityConversion()
+{
+ amount_t x1("$1234.56");
+
+ assertThrow(x1.to_long(), amount_error); // loses precision
+ assertEqual(1234L, x1.to_long(true));
+#ifdef HAVE_GDTOA
+ assertEqual(1234.56, x1.to_double());
+#endif
+ assertEqual(string("$1234.56"), x1.to_string());
+ assertEqual(string("1234.56"), x1.quantity_string());
+
+ assertValid(x1);
+}
+
+void AmountTestCase::testPrinting()
+{
+ amount_t x0;
+ amount_t x1("982340823.380238098235098235098235098");
+
+#if 0
+ {
+ std::ostringstream bufstr;
+ assertThrow(bufstr << x0, amount_error);
+ }
+#endif
+
+ {
+ std::ostringstream bufstr;
+ bufstr << x1;
+
+ assertEqual(std::string("982340823.380238098235098235098235098"),
+ bufstr.str());
+ }
+
+ assertValid(x0);
+ assertValid(x1);
+}
+
+void AmountTestCase::testCommodityPrinting()
+{
+ amount_t x1(internalAmount("$982340823.386238098235098235098235098"));
+ amount_t x2("$982340823.38");
+
+ {
+ std::ostringstream bufstr;
+ bufstr << x1;
+
+ assertEqual(std::string("$982340823.386238098235098235098235098"),
+ bufstr.str());
+ }
+
+ {
+ std::ostringstream bufstr;
+ bufstr << (x1 * x2).to_string();
+
+ assertEqual(std::string("$964993493285024293.18099172508158508135413499124"),
+ bufstr.str());
+ }
+
+ {
+ std::ostringstream bufstr;
+ bufstr << (x2 * x1).to_string();
+
+ assertEqual(std::string("$964993493285024293.18"), bufstr.str());
+ }
+
+ assertValid(x1);
+ assertValid(x2);
+}
+
+void AmountTestCase::testSerialization()
+{
+ amount_t x0;
+ amount_t x1("$8,192.34");
+ amount_t x2("8192.34");
+ amount_t x3("8192.34");
+ amount_t x4("-8192.34");
+ amount_t x5(x4);
+
+ // Force x3's pointer to actually be set to null_commodity
+ x3.set_commodity(*x3.current_pool->null_commodity);
+
+ std::string buf;
+ {
+ std::ostringstream storage;
+ assertThrow(x0.write(storage), amount_error);
+ x1.write(storage);
+ x2.write(storage);
+ x3.write(storage);
+ x4.write(storage);
+ x5.write(storage);
+ buf = storage.str();
+ }
+
+ amount_t x1b;
+ amount_t x2b;
+ amount_t x3b;
+ amount_t x4b;
+ amount_t x5b;
+ {
+ std::istringstream storage(buf);
+ x1b.read(storage);
+ x2b.read(storage);
+ x3b.read(storage);
+ x4b.read(storage);
+ x5b.read(storage);
+ }
+
+ assertEqual(x1, x1b);
+ assertEqual(x2, x2b);
+ assertEqual(x3, x3b);
+ assertEqual(x4, x4b);
+
+ const char * ptr = buf.c_str();
+
+ amount_t x1c;
+ amount_t x2c;
+ amount_t x3c;
+ amount_t x4c;
+ amount_t x5c;
+ {
+ x1c.read(ptr);
+ x2c.read(ptr);
+ x3c.read(ptr);
+ x4c.read(ptr);
+ x5c.read(ptr);
+ }
+
+ assertEqual(x1, x1b);
+ assertEqual(x2, x2b);
+ assertEqual(x3, x3b);
+ assertEqual(x4, x4b);
+
+ assertValid(x1);
+ assertValid(x2);
+ assertValid(x3);
+ assertValid(x4);
+ assertValid(x1b);
+ assertValid(x2b);
+ assertValid(x3b);
+ assertValid(x4b);
+ assertValid(x1c);
+ assertValid(x2c);
+ assertValid(x3c);
+ assertValid(x4c);
+}
diff --git a/test/unit/t_amount.h b/test/unit/t_amount.h
new file mode 100644
index 00000000..2d5a327a
--- /dev/null
+++ b/test/unit/t_amount.h
@@ -0,0 +1,110 @@
+#ifndef _T_AMOUNT_H
+#define _T_AMOUNT_H
+
+#include "UnitTests.h"
+
+class AmountTestCase : public CPPUNIT_NS::TestCase
+{
+ CPPUNIT_TEST_SUITE(AmountTestCase);
+
+ CPPUNIT_TEST(testConstructors);
+ CPPUNIT_TEST(testCommodityConstructors);
+ CPPUNIT_TEST(testParser);
+ CPPUNIT_TEST(testAssignment);
+ CPPUNIT_TEST(testCommodityAssignment);
+ CPPUNIT_TEST(testEquality);
+ CPPUNIT_TEST(testCommodityEquality);
+ CPPUNIT_TEST(testComparisons);
+ CPPUNIT_TEST(testCommodityComparisons);
+ CPPUNIT_TEST(testIntegerAddition);
+ CPPUNIT_TEST(testFractionalAddition);
+ CPPUNIT_TEST(testCommodityAddition);
+ CPPUNIT_TEST(testIntegerSubtraction);
+ CPPUNIT_TEST(testFractionalSubtraction);
+ CPPUNIT_TEST(testCommoditySubtraction);
+ CPPUNIT_TEST(testIntegerMultiplication);
+ CPPUNIT_TEST(testFractionalMultiplication);
+ CPPUNIT_TEST(testCommodityMultiplication);
+ CPPUNIT_TEST(testIntegerDivision);
+ CPPUNIT_TEST(testFractionalDivision);
+ CPPUNIT_TEST(testCommodityDivision);
+ CPPUNIT_TEST(testNegation);
+ CPPUNIT_TEST(testCommodityNegation);
+ CPPUNIT_TEST(testAbs);
+ CPPUNIT_TEST(testCommodityAbs);
+ CPPUNIT_TEST(testFractionalRound);
+ CPPUNIT_TEST(testCommodityRound);
+ CPPUNIT_TEST(testCommodityDisplayRound);
+ CPPUNIT_TEST(testReduction);
+ CPPUNIT_TEST(testSign);
+ CPPUNIT_TEST(testCommoditySign);
+ CPPUNIT_TEST(testTruth);
+ CPPUNIT_TEST(testCommodityTruth);
+ CPPUNIT_TEST(testForZero);
+ CPPUNIT_TEST(testCommodityForZero);
+ CPPUNIT_TEST(testIntegerConversion);
+ CPPUNIT_TEST(testFractionalConversion);
+ CPPUNIT_TEST(testCommodityConversion);
+ CPPUNIT_TEST(testPrinting);
+ CPPUNIT_TEST(testCommodityPrinting);
+ CPPUNIT_TEST(testSerialization);
+
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ ledger::session_t session;
+
+ AmountTestCase() {}
+ virtual ~AmountTestCase() {}
+
+ virtual void setUp();
+ virtual void tearDown();
+
+ void testConstructors();
+ void testCommodityConstructors();
+ void testParser();
+ void testAssignment();
+ void testCommodityAssignment();
+ void testEquality();
+ void testCommodityEquality();
+ void testComparisons();
+ void testCommodityComparisons();
+ void testIntegerAddition();
+ void testFractionalAddition();
+ void testCommodityAddition();
+ void testIntegerSubtraction();
+ void testFractionalSubtraction();
+ void testCommoditySubtraction();
+ void testIntegerMultiplication();
+ void testFractionalMultiplication();
+ void testCommodityMultiplication();
+ void testIntegerDivision();
+ void testFractionalDivision();
+ void testCommodityDivision();
+ void testNegation();
+ void testCommodityNegation();
+ void testAbs();
+ void testCommodityAbs();
+ void testFractionalRound();
+ void testCommodityRound();
+ void testCommodityDisplayRound();
+ void testReduction();
+ void testSign();
+ void testCommoditySign();
+ void testTruth();
+ void testCommodityTruth();
+ void testForZero();
+ void testCommodityForZero();
+ void testIntegerConversion();
+ void testFractionalConversion();
+ void testCommodityConversion();
+ void testPrinting();
+ void testCommodityPrinting();
+ void testSerialization();
+
+private:
+ AmountTestCase(const AmountTestCase &copy);
+ void operator=(const AmountTestCase &copy);
+};
+
+#endif // _T_AMOUNT_H
diff --git a/test/unit/t_balance.cc b/test/unit/t_balance.cc
new file mode 100644
index 00000000..ca759836
--- /dev/null
+++ b/test/unit/t_balance.cc
@@ -0,0 +1,25 @@
+#include "t_balance.h"
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(BalanceTestCase, "numerics");
+
+void BalanceTestCase::setUp()
+{
+ ledger::set_session_context(&session);
+
+ // Cause the display precision for dollars to be initialized to 2.
+ amount_t x1("$1.00");
+ assertTrue(x1);
+
+ amount_t::stream_fullstrings = true; // make reports from UnitTests accurate
+}
+
+void BalanceTestCase::tearDown()
+{
+ amount_t::stream_fullstrings = false;
+
+ ledger::set_session_context();
+}
+
+void BalanceTestCase::testConstructors()
+{
+}
diff --git a/test/unit/t_balance.h b/test/unit/t_balance.h
new file mode 100644
index 00000000..7c27f7e8
--- /dev/null
+++ b/test/unit/t_balance.h
@@ -0,0 +1,30 @@
+#ifndef _T_BALANCE_H
+#define _T_BALANCE_H
+
+#include "UnitTests.h"
+
+class BalanceTestCase : public CPPUNIT_NS::TestCase
+{
+ CPPUNIT_TEST_SUITE(BalanceTestCase);
+
+ CPPUNIT_TEST(testConstructors);
+
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ ledger::session_t session;
+
+ BalanceTestCase() {}
+ virtual ~BalanceTestCase() {}
+
+ virtual void setUp();
+ virtual void tearDown();
+
+ void testConstructors();
+
+private:
+ BalanceTestCase(const BalanceTestCase &copy);
+ void operator=(const BalanceTestCase &copy);
+};
+
+#endif // _T_BALANCE_H
diff --git a/test/unit/t_commodity.cc b/test/unit/t_commodity.cc
new file mode 100644
index 00000000..a96bed72
--- /dev/null
+++ b/test/unit/t_commodity.cc
@@ -0,0 +1,64 @@
+#include "t_commodity.h"
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CommodityTestCase, "numerics");
+
+void CommodityTestCase::setUp() {
+ ledger::set_session_context(&session);
+}
+void CommodityTestCase::tearDown() {
+ ledger::set_session_context();
+}
+
+void CommodityTestCase::testPriceHistory()
+{
+ datetime_t jan17_07 = parse_datetime("2007/01/17 00:00:00");
+ datetime_t feb27_07 = parse_datetime("2007/02/27 18:00:00");
+ datetime_t feb28_07 = parse_datetime("2007/02/28 06:00:00");
+ datetime_t feb28_07sbm = parse_datetime("2007/02/28 11:59:59");
+ datetime_t mar01_07 = parse_datetime("2007/03/01 00:00:00");
+ datetime_t apr15_07 = parse_datetime("2007/04/15 13:00:00");
+
+ // jww (2007-04-17): tbd
+ amount_t x0;
+ amount_t x1("100.10 AAPL");
+
+ assertThrow(x0.value(), amount_error);
+ assertFalse(x1.value());
+
+ // Commodities cannot be constructed by themselves, since a great
+ // deal of their state depends on how they were seen to be used.
+ commodity_t& aapl(x1.commodity());
+
+ aapl.add_price(jan17_07, amount_t("$10.20"));
+ aapl.add_price(feb27_07, amount_t("$13.40"));
+ aapl.add_price(feb28_07, amount_t("$18.33"));
+ aapl.add_price(feb28_07sbm, amount_t("$18.30"));
+ aapl.add_price(mar01_07, amount_t("$19.50"));
+ aapl.add_price(apr15_07, amount_t("$21.22"));
+
+ optional<amount_t> amt1 = x1.value(feb28_07sbm);
+ assertTrue(amt1);
+ assertEqual(amount_t("$1831.83"), *amt1);
+
+ optional<amount_t> amt2 = x1.value(current_time);
+ assertTrue(amt2);
+ assertEqual(amount_t("$2124.12"), *amt2);
+
+ assertValid(x1);
+}
+
+void CommodityTestCase::testLots()
+{
+ // jww (2007-04-17): tbd
+}
+
+void CommodityTestCase::testScalingBase()
+{
+ // jww (2007-04-17): tbd
+}
+
+void CommodityTestCase::testReduction()
+{
+ // jww (2007-04-17): tbd
+}
+
diff --git a/test/unit/t_commodity.h b/test/unit/t_commodity.h
new file mode 100644
index 00000000..ed739751
--- /dev/null
+++ b/test/unit/t_commodity.h
@@ -0,0 +1,36 @@
+#ifndef _T_COMMMODITY_H
+#define _T_COMMMODITY_H
+
+#include "UnitTests.h"
+
+class CommodityTestCase : public CPPUNIT_NS::TestCase
+{
+ CPPUNIT_TEST_SUITE(CommodityTestCase);
+
+ CPPUNIT_TEST(testPriceHistory);
+ CPPUNIT_TEST(testLots);
+ CPPUNIT_TEST(testScalingBase);
+ CPPUNIT_TEST(testReduction);
+
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ ledger::session_t session;
+
+ CommodityTestCase() {}
+ virtual ~CommodityTestCase() {}
+
+ virtual void setUp();
+ virtual void tearDown();
+
+ void testPriceHistory();
+ void testLots();
+ void testScalingBase();
+ void testReduction();
+
+private:
+ CommodityTestCase(const CommodityTestCase &copy);
+ void operator=(const CommodityTestCase &copy);
+};
+
+#endif // _T_COMMMODITY_H
diff --git a/test/unit/t_expr.cc b/test/unit/t_expr.cc
new file mode 100644
index 00000000..58a60e23
--- /dev/null
+++ b/test/unit/t_expr.cc
@@ -0,0 +1,25 @@
+#include "t_expr.h"
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ValueExprTestCase, "numerics");
+
+void ValueExprTestCase::setUp()
+{
+ ledger::set_session_context(&session);
+
+ // Cause the display precision for dollars to be initialized to 2.
+ amount_t x1("$1.00");
+ assertTrue(x1);
+
+ amount_t::stream_fullstrings = true; // make reports from UnitTests accurate
+}
+
+void ValueExprTestCase::tearDown()
+{
+ amount_t::stream_fullstrings = false;
+
+ ledger::set_session_context();
+}
+
+void ValueExprTestCase::testConstructors()
+{
+}
diff --git a/test/unit/t_expr.h b/test/unit/t_expr.h
new file mode 100644
index 00000000..bb03ba56
--- /dev/null
+++ b/test/unit/t_expr.h
@@ -0,0 +1,30 @@
+#ifndef _T_EXPR_H
+#define _T_EXPR_H
+
+#include "UnitTests.h"
+
+class ValueExprTestCase : public CPPUNIT_NS::TestCase
+{
+ CPPUNIT_TEST_SUITE(ValueExprTestCase);
+
+ CPPUNIT_TEST(testConstructors);
+
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ ledger::session_t session;
+
+ ValueExprTestCase() {}
+ virtual ~ValueExprTestCase() {}
+
+ virtual void setUp();
+ virtual void tearDown();
+
+ void testConstructors();
+
+private:
+ ValueExprTestCase(const ValueExprTestCase &copy);
+ void operator=(const ValueExprTestCase &copy);
+};
+
+#endif // _T_EXPR_H
diff --git a/test/unit/t_times.cc b/test/unit/t_times.cc
new file mode 100644
index 00000000..05fd34ce
--- /dev/null
+++ b/test/unit/t_times.cc
@@ -0,0 +1,81 @@
+#include "t_times.h"
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(DateTimeTestCase, "utility");
+
+void DateTimeTestCase::setUp() {}
+void DateTimeTestCase::tearDown() {}
+
+void DateTimeTestCase::testConstructors()
+{
+#if 0
+ std::time_t time_t_now = std::time(NULL);
+ struct tm * moment = std::localtime(&time_t_now);
+
+ std::time_t localMoment = std::mktime(moment);
+
+ ptime d0;
+ ptime d1(parse_datetime("1990/01/01"));
+ ptime d3(boost::posix_time::from_time_t(localMoment));
+ ptime d4(parse_datetime("2006/12/25"));
+ //ptime d5(parse_datetime("12/25"));
+ ptime d6(parse_datetime("2006.12.25"));
+ //ptime d7(parse_datetime("12.25"));
+ ptime d8(parse_datetime("2006-12-25"));
+ //ptime d9(parse_datetime("12-25"));
+#if 0
+ ptime d10(parse_datetime("tue"));
+ ptime d11(parse_datetime("tuesday"));
+ ptime d12(parse_datetime("feb"));
+ ptime d13(parse_datetime("february"));
+ ptime d14(parse_datetime("2006"));
+#endif
+ ptime d15(d3);
+
+ assertTrue(d0.is_not_a_date_time());
+ assertFalse(d1.is_not_a_date_time());
+ assertFalse(d4.is_not_a_date_time());
+
+ assertTrue(now > d1);
+ //assertTrue(now <= d3);
+ assertTrue(now > d4);
+
+ assertEqual(d3, d15);
+ assertEqual(d4, d6);
+ assertEqual(d4, d8);
+ //assertEqual(d5, d7);
+ //assertEqual(d5, d9);
+#if 0
+ assertEqual(d10, d11);
+ assertEqual(d12, d13);
+#endif
+
+#if 0
+ assertThrow(parse_datetime("2007/02/29"), datetime_error *);
+ assertThrow(parse_datetime("2007/13/01"), datetime_error *);
+ assertThrow(parse_datetime("2007/00/01"), datetime_error *);
+ assertThrow(parse_datetime("2007/01/00"), datetime_error *);
+ assertThrow(parse_datetime("2007/00/00"), datetime_error *);
+ assertThrow(parse_datetime("2007/05/32"), datetime_error *);
+
+ assertThrow(parse_datetime("2006x/12/25"), datetime_error *);
+ assertThrow(parse_datetime("2006/12x/25"), datetime_error *);
+ //assertThrow(parse_datetime("2006/12/25x"), datetime_error *);
+
+ assertThrow(parse_datetime("feb/12/25"), datetime_error *);
+ assertThrow(parse_datetime("2006/mon/25"), datetime_error *);
+ assertThrow(parse_datetime("2006/12/web"), datetime_error *);
+
+ assertThrow(parse_datetime("12*25"), datetime_error *);
+
+ assertThrow(parse_datetime("tuf"), datetime_error *);
+ assertThrow(parse_datetime("tufsday"), datetime_error *);
+ assertThrow(parse_datetime("fec"), datetime_error *);
+ assertThrow(parse_datetime("fecruary"), datetime_error *);
+ assertThrow(parse_datetime("207x"), datetime_error *);
+ assertThrow(parse_datetime("hello"), datetime_error *);
+
+ interval_t i1;
+ interval_t i2;
+#endif
+#endif
+}
diff --git a/test/unit/t_times.h b/test/unit/t_times.h
new file mode 100644
index 00000000..5bbadf21
--- /dev/null
+++ b/test/unit/t_times.h
@@ -0,0 +1,28 @@
+#ifndef _T_TIMES_H
+#define _T_TIMES_H
+
+#include "UnitTests.h"
+
+class DateTimeTestCase : public CPPUNIT_NS::TestCase
+{
+ CPPUNIT_TEST_SUITE(DateTimeTestCase);
+
+ CPPUNIT_TEST(testConstructors);
+
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ DateTimeTestCase() {}
+ virtual ~DateTimeTestCase() {}
+
+ virtual void setUp();
+ virtual void tearDown();
+
+ void testConstructors();
+
+private:
+ DateTimeTestCase(const DateTimeTestCase &copy);
+ void operator=(const DateTimeTestCase &copy);
+};
+
+#endif /* _T_TIMES_H */
diff --git a/test/unit/t_utils.cc b/test/unit/t_utils.cc
new file mode 100644
index 00000000..eda84a3a
--- /dev/null
+++ b/test/unit/t_utils.cc
@@ -0,0 +1,10 @@
+#include "t_utils.h"
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(UtilitiesTestCase, "utility");
+
+void UtilitiesTestCase::setUp() {}
+void UtilitiesTestCase::tearDown() {}
+
+void UtilitiesTestCase::testConstructors()
+{
+}
diff --git a/test/unit/t_utils.h b/test/unit/t_utils.h
new file mode 100644
index 00000000..97154bae
--- /dev/null
+++ b/test/unit/t_utils.h
@@ -0,0 +1,28 @@
+#ifndef _T_UTILS_H
+#define _T_UTILS_H
+
+#include "UnitTests.h"
+
+class UtilitiesTestCase : public CPPUNIT_NS::TestCase
+{
+ CPPUNIT_TEST_SUITE(UtilitiesTestCase);
+
+ CPPUNIT_TEST(testConstructors);
+
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ UtilitiesTestCase() {}
+ virtual ~UtilitiesTestCase() {}
+
+ virtual void setUp();
+ virtual void tearDown();
+
+ void testConstructors();
+
+private:
+ UtilitiesTestCase(const UtilitiesTestCase &copy);
+ void operator=(const UtilitiesTestCase &copy);
+};
+
+#endif /* _T_UTILS_H */
diff --git a/test/unit/t_valexpr.cc b/test/unit/t_valexpr.cc
new file mode 100644
index 00000000..026b4eec
--- /dev/null
+++ b/test/unit/t_valexpr.cc
@@ -0,0 +1,25 @@
+#include "t_valexpr.h"
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ValueExprTestCase, "numerics");
+
+void ValueExprTestCase::setUp()
+{
+ ledger::set_session_context(&session);
+
+ // Cause the display precision for dollars to be initialized to 2.
+ amount_t x1("$1.00");
+ assertTrue(x1);
+
+ amount_t::stream_fullstrings = true; // make reports from UnitTests accurate
+}
+
+void ValueExprTestCase::tearDown()
+{
+ amount_t::stream_fullstrings = false;
+
+ ledger::set_session_context();
+}
+
+void ValueExprTestCase::testConstructors()
+{
+}
diff --git a/test/unit/t_valexpr.h b/test/unit/t_valexpr.h
new file mode 100644
index 00000000..3cac4ed6
--- /dev/null
+++ b/test/unit/t_valexpr.h
@@ -0,0 +1,30 @@
+#ifndef _T_VALEXPR_H
+#define _T_VALEXPR_H
+
+#include "UnitTests.h"
+
+class ValueExprTestCase : public CPPUNIT_NS::TestCase
+{
+ CPPUNIT_TEST_SUITE(ValueExprTestCase);
+
+ CPPUNIT_TEST(testConstructors);
+
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ ledger::session_t session;
+
+ ValueExprTestCase() {}
+ virtual ~ValueExprTestCase() {}
+
+ virtual void setUp();
+ virtual void tearDown();
+
+ void testConstructors();
+
+private:
+ ValueExprTestCase(const ValueExprTestCase &copy);
+ void operator=(const ValueExprTestCase &copy);
+};
+
+#endif // _T_VALEXPR_H
diff --git a/tests/amounts.h b/tests/amounts.h
deleted file mode 100644
index a72e57d2..00000000
--- a/tests/amounts.h
+++ /dev/null
@@ -1,169 +0,0 @@
-#ifndef __TESTAMOUNT_H
-#define __TESTAMOUNT_H
-
-#include <cxxtest/TestSuite.h>
-
-#include <amount.h>
-
-using namespace ledger;
-
-class TestAmount : public CxxTest::TestSuite
-{
-public:
- void testCreateAmountWithoutCommodityFromInteger()
- {
- amount_t a((long int)42);
- TS_ASSERT_EQUALS("", a.commodity().symbol());
- TS_ASSERT_EQUALS("", a.commodity().base_symbol());
- TS_ASSERT_EQUALS("42", a.quantity_string());
- }
-
- void testCreateAmountWithoutCommodity()
- {
- amount_t a("42");
- TS_ASSERT_EQUALS("", a.commodity().symbol());
- TS_ASSERT_EQUALS("", a.commodity().base_symbol());
- TS_ASSERT_EQUALS("42", a.quantity_string());
- }
-
- void testCreateAmountWithPrefixCommodity()
- {
- amount_t *a;
- a = new amount_t("$ 42");
- TS_ASSERT_EQUALS("$", a->commodity().symbol());
- TS_ASSERT_EQUALS("$", a->commodity().base_symbol());
- TS_ASSERT_EQUALS("42", a->quantity_string());
- }
-
- void testCreateAmountWithPostfixCommodity()
- {
- amount_t *a;
- a = new amount_t("42 GLD");
- TS_ASSERT_EQUALS("GLD", a->commodity().symbol());
- TS_ASSERT_EQUALS("GLD", a->commodity().base_symbol());
- TS_ASSERT_EQUALS("42", a->quantity_string());
- }
-
- void testCreateAmountWithPrefixCommodityContainingSpace()
- {
- amount_t *a;
- a = new amount_t("\"one dollar\" 42");
- TS_ASSERT_EQUALS("\"one dollar\"", a->commodity().symbol());
- TS_ASSERT_EQUALS("one dollar", a->commodity().base_symbol());
- TS_ASSERT_EQUALS("42", a->quantity_string());
- }
-
- void testCreateAmountWithPostfixCommodityContainingSpace()
- {
- amount_t *a;
- a = new amount_t("42 \"swedish crowns\"");
- TS_ASSERT_EQUALS("\"swedish crowns\"", a->commodity().symbol());
- TS_ASSERT_EQUALS("swedish crowns", a->commodity().base_symbol());
- TS_ASSERT_EQUALS("42", a->quantity_string());
- }
-
- void testCreateAmountWithDirectPrefixCommodity()
- {
- amount_t *a;
- a = new amount_t("$42");
- TS_ASSERT_EQUALS("$", a->commodity().symbol());
- TS_ASSERT_EQUALS("$", a->commodity().base_symbol());
- TS_ASSERT_EQUALS("42", a->quantity_string());
- }
-
- void testCreateAmountWithDirectPostfixCommodity()
- {
- amount_t *a;
- a = new amount_t("42GLD");
- TS_ASSERT_EQUALS("GLD", a->commodity().symbol());
- TS_ASSERT_EQUALS("GLD", a->commodity().base_symbol());
- TS_ASSERT_EQUALS("42", a->quantity_string());
- }
-
- void testCannotCreateAmountWithoutQuantity()
- {
- TS_ASSERT_THROWS(new amount_t("$"), amount_error*);
- }
-
- void testCreateBiiigIntegerAmount()
- {
- amount_t a("12345678901234567890123456789012345678901234567890");
- TS_ASSERT_EQUALS("12345678901234567890123456789012345678901234567890",
- a.quantity_string());
- }
-
- void testCreateBiiigDecimalAmount()
- {
- amount_t a("12345678.901234567890123456789012345678901234567890");
- TS_ASSERT_EQUALS("12345678.901234567890123456789012345678901234567890",
- a.quantity_string());
- }
-
- void testCreateCommodityAnnotatedWithEntry()
- {
- amount_t a("10 AAPL (entry 6)");
- TS_ASSERT_EQUALS("10", a.quantity_string());
- commodity_t c = a.commodity();
- TS_ASSERT_EQUALS("AAPL", c.symbol());
- TS_ASSERT_EQUALS("AAPL", c.base_symbol());
- TS_ASSERT(c.annotated);
- //TODO: check entry annotation
- }
-
- void testCreateCommodityAnnotatedWithEntry2()
- {
- amount_t *a = new amount_t("10 AAPL (entry 6)");
- TS_ASSERT_EQUALS("10", a->quantity_string());
- commodity_t c = a->commodity();
- TS_ASSERT_EQUALS("AAPL", c.symbol());
- TS_ASSERT_EQUALS("AAPL", c.base_symbol());
- TS_ASSERT(c.annotated);
- //TODO: check entry annotation
- }
-
- void testAddTwoAmountsWithoutCommodity()
- {
- amount_t a("6");
- amount_t b("9");
- TS_ASSERT_EQUALS(* new amount_t((long int)15), a+b);
- }
-
- void testAddTwoAmountsWithSameCommodity()
- {
- amount_t a("$ 6");
- amount_t b("$ 9");
- TS_ASSERT_EQUALS(* new amount_t("$ 15"), a+b);
- }
-
- void testCannotAddTwoAmountsWithDifferentCommodities()
- {
- amount_t a("$ 6");
- amount_t b("9 GLD");
- TS_ASSERT_THROWS(a+b, amount_error*);
- }
-
- void testCompareTwoAmountsWithSameCommodity()
- {
- amount_t a("6 SCOX");
- amount_t b("9 SCOX");
- TS_ASSERT(a < b);
- TS_ASSERT(a <= b);
- TS_ASSERT(!(a > b));
- TS_ASSERT(!(a >= b));
- TS_ASSERT(!(a == b));
- }
-
- void testCannotCompareTwoAmountsWithDifferentCommodities()
- {
- amount_t a("$ 6");
- amount_t b("9 GLD");
-
- TS_ASSERT_THROWS(a < b, amount_error*);
- TS_ASSERT_THROWS(a <= b, amount_error*);
- TS_ASSERT_THROWS(a > b, amount_error*);
- TS_ASSERT_THROWS(a >= b, amount_error*);
- TS_ASSERT(!(a == b));
- }
-};
-
-#endif // __TESTAMOUNT_H
diff --git a/tests/baseline/1001 b/tests/baseline/1001
deleted file mode 100644
index f570768a..00000000
--- a/tests/baseline/1001
+++ /dev/null
@@ -1 +0,0 @@
-Error: "tests/cases/1001.dat", line 10: Only one transaction with null amount allowed per entry
diff --git a/tests/baseline/1002 b/tests/baseline/1002
deleted file mode 100644
index 8ca4d8d4..00000000
--- a/tests/baseline/1002
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- xgain -20
- Liabilities:MasterCard
diff --git a/tests/baseline/1003 b/tests/baseline/1003
deleted file mode 100644
index 61e03c48..00000000
--- a/tests/baseline/1003
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- xtime -20
- Liabilities:MasterCard
diff --git a/tests/baseline/1004 b/tests/baseline/1004
deleted file mode 100644
index dbbfea17..00000000
--- a/tests/baseline/1004
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- xopti -20
- Liabilities:MasterCard
diff --git a/tests/baseline/1005 b/tests/baseline/1005
deleted file mode 100644
index 8541a38d..00000000
--- a/tests/baseline/1005
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- xgain 0
- Liabilities:MasterCard
diff --git a/tests/baseline/1006 b/tests/baseline/1006
deleted file mode 100644
index 0ea315f9..00000000
--- a/tests/baseline/1006
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- xtime 0
- Liabilities:MasterCard
diff --git a/tests/baseline/1007 b/tests/baseline/1007
deleted file mode 100644
index 40f2827a..00000000
--- a/tests/baseline/1007
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- xopti 0
- Liabilities:MasterCard
diff --git a/tests/baseline/1008 b/tests/baseline/1008
deleted file mode 100644
index 385151b2..00000000
--- a/tests/baseline/1008
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- xgain $-20.00
- Equity:Options
diff --git a/tests/baseline/1009 b/tests/baseline/1009
deleted file mode 100644
index 127ad02a..00000000
--- a/tests/baseline/1009
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- xtime $-20.00
- Equity:Options
diff --git a/tests/baseline/1010 b/tests/baseline/1010
deleted file mode 100644
index e408036b..00000000
--- a/tests/baseline/1010
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- xopti $-20.00
- Equity:Options
diff --git a/tests/baseline/1011 b/tests/baseline/1011
deleted file mode 100644
index 9c70db18..00000000
--- a/tests/baseline/1011
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- xgain 0
- Equity:Options
diff --git a/tests/baseline/1012 b/tests/baseline/1012
deleted file mode 100644
index 890005ab..00000000
--- a/tests/baseline/1012
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- xtime 0
- Equity:Options
diff --git a/tests/baseline/1013 b/tests/baseline/1013
deleted file mode 100644
index 1b7bb71e..00000000
--- a/tests/baseline/1013
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- xopti 0
- Equity:Options
diff --git a/tests/baseline/1014 b/tests/baseline/1014
deleted file mode 100644
index 384133b6..00000000
--- a/tests/baseline/1014
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- Capital Gains $-20.00
- Liabilities:MasterCard
diff --git a/tests/baseline/1015 b/tests/baseline/1015
deleted file mode 100644
index 46d5f454..00000000
--- a/tests/baseline/1015
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- time -20
- Liabilities:MasterCard
diff --git a/tests/baseline/1016 b/tests/baseline/1016
deleted file mode 100644
index 7fa39204..00000000
--- a/tests/baseline/1016
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- Brokerage:Options -20.00 COMP
- Liabilities:MasterCard
diff --git a/tests/baseline/1017 b/tests/baseline/1017
deleted file mode 100644
index 14b37f8b..00000000
--- a/tests/baseline/1017
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- Capital Gains 0
- Liabilities:MasterCard
diff --git a/tests/baseline/1018 b/tests/baseline/1018
deleted file mode 100644
index 9f3761f8..00000000
--- a/tests/baseline/1018
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- time 0
- Liabilities:MasterCard
diff --git a/tests/baseline/1019 b/tests/baseline/1019
deleted file mode 100644
index 10f14f4e..00000000
--- a/tests/baseline/1019
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- Brokerage:Options 0
- Liabilities:MasterCard
diff --git a/tests/baseline/1020 b/tests/baseline/1020
deleted file mode 100644
index f1547ca7..00000000
--- a/tests/baseline/1020
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- Expenses -20
- Liabilities:MasterCard
diff --git a/tests/baseline/1021 b/tests/baseline/1021
deleted file mode 100644
index 7e3fe64d..00000000
--- a/tests/baseline/1021
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 stock optionx
- Expenses 0
- Liabilities:MasterCard
diff --git a/tests/baseline/1022 b/tests/baseline/1022
deleted file mode 100644
index 953a3c02..00000000
--- a/tests/baseline/1022
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- Capital Gains $-20.00
- Equity:Options
diff --git a/tests/baseline/1023 b/tests/baseline/1023
deleted file mode 100644
index 62274097..00000000
--- a/tests/baseline/1023
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- time $-20.00
- Equity:Options
diff --git a/tests/baseline/1024 b/tests/baseline/1024
deleted file mode 100644
index 8272dfd9..00000000
--- a/tests/baseline/1024
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- Brokerage:Options -20.00 COMP
- Equity:Options
diff --git a/tests/baseline/1025 b/tests/baseline/1025
deleted file mode 100644
index 52dc8139..00000000
--- a/tests/baseline/1025
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- Capital Gains 0
- Equity:Options
diff --git a/tests/baseline/1026 b/tests/baseline/1026
deleted file mode 100644
index 5071338a..00000000
--- a/tests/baseline/1026
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- time 0
- Equity:Options
diff --git a/tests/baseline/1027 b/tests/baseline/1027
deleted file mode 100644
index 31d7c8ab..00000000
--- a/tests/baseline/1027
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- Brokerage:Options 45.60 COMP
- Equity:Options
diff --git a/tests/baseline/1028 b/tests/baseline/1028
deleted file mode 100644
index 8272dfd9..00000000
--- a/tests/baseline/1028
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- Brokerage:Options -20.00 COMP
- Equity:Options
diff --git a/tests/baseline/1029 b/tests/baseline/1029
deleted file mode 100644
index 31d7c8ab..00000000
--- a/tests/baseline/1029
+++ /dev/null
@@ -1,4 +0,0 @@
-
-2006/10/20 Stock option vesting schedule
- Brokerage:Options 45.60 COMP
- Equity:Options
diff --git a/tests/cases/1001.dat b/tests/cases/1001.dat
deleted file mode 100644
index c781fb4f..00000000
--- a/tests/cases/1001.dat
+++ /dev/null
@@ -1,10 +0,0 @@
-1.00s = 100c
-1.00G = 100s
-
-D 1.00G
-
-2006/03/14 Opening Balances
- Assets:Ebonheart 63869c
- Assets:Tajer 248720c
- Assets:Gruulmorg
- Equity:Gold
diff --git a/tests/cases/1002.dat b/tests/cases/1002.dat
deleted file mode 100644
index 8719b603..00000000
--- a/tests/cases/1002.dat
+++ /dev/null
@@ -1,25 +0,0 @@
-D $1,000.00
-
-A Liabilities:MasterCard
-
-@def foo(x)=x*1000
-
-2006/02/14 Buy
- Broker AAPL 10 @ $200.00 ; this is a little note [2006/10/10]
- Broker AAPL 10 @ $300.00
- Bank ; this is a comment
-
-2006/02/14 Sell
- Bank $2000.00 + $500.00
- Broker AAPL -5 [2005/10/20] {$200.0015} @ $500.00
- Capital Gains $-1500.00
-
-2005/10/20 Stock option vesting schedule
- Brokerage:Options (1.00 COMP {$10.00}*min((([2006/08/20]-[2005/10/20])/3600/24)*0.15, 5000))
- Equity:Options
-
-P 2006/02/27 00:00:00 COMP $15.00
-
-;2005/10/20 Stock option vesting schedule
-; Brokerage:Time (1+@now)
-; Equity:Time
diff --git a/tests/cases/1030.dat b/tests/cases/1030.dat
deleted file mode 100644
index 60fcac1c..00000000
--- a/tests/cases/1030.dat
+++ /dev/null
@@ -1,24 +0,0 @@
-2006/03/01 Basis
- Assets:Pool $1000
- (Shares) 1000 shares {$1.00}
- Equity
-
-P 2006/03/01 shares $1.00
-
-2006/03/10 Purchase
- (Payable:You) -500 shares {$1}
- (Shares) -500 shares
- Assets:Investment $500
- Income:Investments $-500
-
-2006/03/16 Growth
- Assets:Pool $500
- Income:Capital Gains
-
-P 2006/03/16 shares $1.50
-
-2006/03/16 Cash out
- (Payable:You) 250 shares {$1}
- (Shares) 250 shares
- Expenses:Investors
- Assets (- valueof({1 shares}) * 250)
diff --git a/tests/cases/1032.dat b/tests/cases/1032.dat
deleted file mode 100644
index 1b103f1d..00000000
--- a/tests/cases/1032.dat
+++ /dev/null
@@ -1,834 +0,0 @@
-i 2003/10/06 15:21:00 EDG fp_conv
-o 2003/10/06 15:47:53 retrieved documents from ACM and the Web
-i 2003/10/08 16:25:08 EDG fp_conv
-o 2003/10/08 17:10:50 read Steele document; need to print and implement
-i 2003/10/18 14:54:34 EDG fp_conv
-o 2003/10/18 16:56:59
-i 2003/10/18 23:50:34 EDG fp_conv
-o 2003/10/19 01:08:12
-i 2003/10/19 10:54:33 EDG fp_conv
-o 2003/10/19 12:41:56
-i 2003/10/19 16:21:07 EDG fp_conv
-o 2003/10/19 18:29:39
-i 2003/10/21 14:54:10 EDG fp_conv
-o 2003/10/21 18:07:07
-i 2003/10/21 22:44:17 EDG fp_conv
-o 2003/10/21 23:55:57
-i 2003/10/22 22:26:53 EDG fp_conv
-o 2003/10/22 23:22:49
-i 2003/10/23 02:44:00 EDG fp_conv
-o 2003/10/23 03:34:00
-i 2003/10/23 16:12:27 EDG fp_conv
-o 2003/10/23 17:39:08
-i 2003/10/26 10:32:16 ForumJobs Meeting
-o 2003/10/26 15:59:28
-i 2003/10/26 17:26:23 ForumJobs Meeting
-o 2003/10/26 17:57:52
-i 2003/10/27 16:02:10 EDG fp_conv
-o 2003/10/27 17:33:53
-i 2003/10/27 22:38:57 EDG fp_conv
-o 2003/10/28 23:37:51 Steele is working in C, with fgmp
-i 2003/10/28 12:13:46 EDG fp_conv
-o 2003/10/28 12:27:31 sought documents relating to MP math
-i 2003/10/28 16:14:41 EDG fp_conv
-o 2003/10/28 18:32:44 still need to implement mul/div/mod
-i 2003/10/29 22:18:23 EDG fp_conv
-o 2003/10/29 23:38:07
-i 2003/10/30 16:39:19 EDG fp_conv
-o 2003/10/30 17:18:07
-i 2003/10/30 23:33:14 EDG fp_conv
-o 2003/10/31 01:21:18
-i 2003/10/31 13:16:38 EDG fp_conv
-o 2003/10/31 15:01:32
-i 2003/10/31 16:49:51 EDG fp_conv
-o 2003/10/31 17:26:45
-i 2003/10/31 23:15:10 EDG fp_conv
-o 2003/11/01 00:48:11
-i 2003/11/01 12:50:49 EDG fp_conv
-o 2003/11/01 14:01:35
-i 2003/11/01 18:53:48 EDG fp_conv
-o 2003/11/01 19:16:41
-i 2003/11/02 21:50:36 EDG fp_conv
-o 2003/11/03 00:05:10
-i 2003/11/03 19:30:48 EDG fp_conv
-o 2003/11/03 21:44:02
-i 2003/11/04 19:55:44 EDG fp_conv
-o 2003/11/04 20:54:24
-i 2003/11/05 15:36:45 EDG fp_conv
-o 2003/11/05 18:35:36
-i 2003/11/06 16:23:41 EDG fp_conv
-o 2003/11/06 16:37:04
-i 2003/11/06 17:23:27 EDG fp_conv
-o 2003/11/06 17:37:22
-i 2003/11/07 00:02:44 EDG fp_conv
-o 2003/11/07 01:44:34
-i 2003/11/07 14:52:46 EDG fp_conv
-o 2003/11/07 15:07:55
-i 2003/11/07 17:16:59 EDG fp_conv
-o 2003/11/07 18:01:49
-i 2003/11/08 16:47:41 EDG fp_conv
-o 2003/11/08 17:25:38
-i 2003/11/09 15:25:01 EDG fp_conv
-o 2003/11/09 15:49:55
-i 2003/11/10 17:54:44 EDG fp_conv
-o 2003/11/10 18:43:49
-i 2003/11/11 15:02:51 EDG fp_conv
-o 2003/11/11 15:23:49
-i 2003/11/13 20:37:34 EDG fp_conv
-o 2003/11/13 21:24:47
-i 2003/11/14 01:23:47 EDG fp_conv
-o 2003/11/14 02:58:15
-i 2003/11/16 19:43:32 EDG fp_conv
-o 2003/11/16 20:13:59
-i 2003/11/16 21:37:30 EDG fp_conv
-o 2003/11/16 23:32:12
-i 2003/11/16 23:53:07 EDG fp_conv
-o 2003/11/17 00:10:06
-i 2003/11/19 01:14:33 EDG fp_conv
-o 2003/11/19 02:29:34
-i 2003/11/19 14:54:48 EDG fp_conv
-o 2003/11/19 18:27:36
-i 2003/11/19 22:49:09 EDG fp_conv
-o 2003/11/19 23:42:47
-i 2003/11/19 23:53:56 EDG fp_conv
-o 2003/11/20 00:22:53
-i 2003/11/23 00:27:28 EDG fp_conv
-o 2003/11/23 02:36:37
-i 2003/11/23 14:24:29 EDG fp_conv
-o 2003/11/23 14:46:21
-i 2003/11/23 15:23:12 EDG fp_conv
-o 2003/11/23 16:24:58
-i 2003/11/24 19:04:26 EDG fp_conv
-o 2003/11/24 20:40:45
-i 2003/11/24 21:13:32 EDG fp_conv
-o 2003/11/24 22:50:26
-i 2003/11/25 16:04:57 EDG fp_conv
-o 2003/11/25 19:16:07
-i 2003/11/25 20:01:32 EDG fp_conv
-o 2003/11/25 20:20:37
-i 2003/11/25 22:03:56 EDG fp_conv
-o 2003/11/25 22:32:45
-i 2003/11/30 18:45:02 EDG fp_conv
-o 2003/11/30 19:45:58
-i 2003/11/30 21:49:24 EDG fp_conv
-o 2003/11/30 22:36:42
-i 2003/12/03 15:48:03 EDG fp_conv
-o 2003/12/03 17:07:22
-i 2003/12/04 14:46:53 EDG fp_conv
-o 2003/12/04 16:05:17
-i 2003/12/07 16:00:38 ForumJobs Meeting
-o 2003/12/07 19:00:27
-i 2003/12/08 00:41:29 EDG fp_conv
-o 2003/12/08 01:18:18
-i 2003/12/08 14:12:19 EDG fp_conv
-o 2003/12/08 14:57:03
-i 2003/12/08 16:14:25 EDG fp_conv
-o 2003/12/08 17:37:53
-i 2003/12/08 22:11:16 EDG fp_conv
-o 2003/12/09 02:18:03
-i 2003/12/09 16:19:26 ForumJobs
-o 2003/12/09 18:41:55
-i 2003/12/09 19:46:55 ForumJobs
-o 2003/12/09 20:12:32
-i 2003/12/09 21:25:34 ForumJobs
-o 2003/12/09 23:23:50
-i 2003/12/09 23:48:46 EDG fp_conv
-o 2003/12/10 03:17:33
-i 2003/12/10 13:50:03 EDG fp_conv
-o 2003/12/10 14:30:31
-i 2003/12/10 16:50:40 EDG fp_conv
-o 2003/12/11 05:12:28
-i 2003/12/11 14:31:30 EDG fp_conv
-o 2003/12/11 18:17:08
-i 2003/12/12 00:56:19 EDG fp_conv
-o 2003/12/12 02:38:32
-i 2003/12/12 16:42:33 EDG fp_conv
-o 2003/12/12 20:13:46
-i 2003/12/12 22:09:46 EDG fp_conv
-o 2003/12/13 01:26:52
-i 2003/12/14 16:00:43 ForumJobs Meeting
-o 2003/12/14 18:44:38
-i 2003/12/14 20:12:59 EDG fp_conv
-o 2003/12/15 02:49:53
-i 2003/12/15 15:21:19 EDG fp_conv
-o 2003/12/15 17:59:45
-i 2003/12/15 19:06:10 EDG fp_conv
-o 2003/12/16 00:12:14
-i 2003/12/16 00:12:15 ForumJobs
-o 2003/12/16 01:16:51
-i 2003/12/16 16:43:32 EDG fp_conv
-o 2003/12/16 18:44:46
-i 2003/12/16 19:54:15 EDG fp_conv
-o 2003/12/17 01:08:19
-i 2003/12/17 01:34:12 ForumJobs
-o 2003/12/17 01:51:34
-i 2003/12/17 16:56:39 EDG fp_conv
-o 2003/12/17 19:15:58
-i 2003/12/17 22:49:59 EDG fp_conv
-o 2003/12/18 00:47:10
-i 2003/12/18 16:53:31 ForumJobs
-o 2003/12/18 18:35:43
-i 2003/12/19 18:12:39 ForumJobs
-o 2003/12/19 20:01:01
-i 2003/12/23 17:30:29 ForumJobs
-o 2003/12/23 18:10:41
-i 2004/01/13 12:32:55 ForumJobs
-o 2004/01/13 12:53:55
-i 2004/01/14 14:23:47 EDG fp_conv
-o 2004/01/14 19:25:42
-i 2004/01/15 17:20:01 EDG fp_conv
-o 2004/01/15 18:40:34
-i 2004/01/16 12:50:26 EDG fp_conv
-o 2004/01/16 18:39:45
-i 2004/01/16 19:23:09 EDG fp_conv
-o 2004/01/16 20:41:14
-i 2004/01/19 12:48:57 EDG fp_conv
-o 2004/01/19 15:18:38
-i 2004/01/20 16:07:50 ForumJobs
-o 2004/01/20 19:18:04
-i 2004/01/21 13:18:04 EDG fp_conv
-o 2004/01/21 18:18:04
-i 2004/01/22 13:39:27 EDG fp_conv
-o 2004/01/22 18:31:03
-i 2004/01/22 19:12:42 EDG fp_conv
-o 2004/01/22 20:48:41
-i 2004/01/23 12:58:41 EDG fp_conv
-o 2004/01/23 15:27:58
-i 2004/01/23 16:00:00 ForumJobs
-o 2004/01/23 19:15:00
-i 2004/01/23 21:30:26 EDG fp_conv
-o 2004/01/23 23:45:56
-i 2004/01/25 17:19:42 ForumJobs
-o 2004/01/25 18:22:37
-i 2004/01/26 22:34:31 ForumJobs
-o 2004/01/26 23:35:23
-i 2004/01/27 12:53:42 EDG fp_conv
-o 2004/01/27 13:23:15
-i 2004/01/27 19:42:50 EDG fp_conv
-o 2004/01/27 19:57:28
-i 2004/01/27 22:08:53 ForumJobs
-o 2004/01/27 22:44:40
-i 2004/01/27 23:14:41 EDG fp_conv
-o 2004/01/27 23:48:05
-i 2004/01/28 00:00:27 ForumJobs
-o 2004/01/28 00:39:21
-i 2004/01/28 17:04:22 ForumJobs
-o 2004/01/28 17:34:31
-i 2004/01/29 14:07:44 ForumJobs
-o 2004/01/29 16:07:04
-i 2004/01/29 17:15:58 ForumJobs
-o 2004/01/29 18:55:40
-i 2004/01/29 22:11:41 ForumJobs
-o 2004/01/29 23:31:41
-i 2004/01/30 15:56:02 ForumJobs
-o 2004/01/30 17:54:06
-i 2004/01/30 20:43:00 ForumJobs
-o 2004/01/30 21:56:30
-i 2004/01/30 22:16:09 ForumJobs
-o 2004/01/30 22:28:35
-i 2004/02/03 17:19:26 ForumJobs
-o 2004/02/03 19:24:08
-i 2004/02/08 14:42:44 ForumJobs
-o 2004/02/08 18:07:07
-i 2004/02/10 18:05:54 EDG fp_conv
-o 2004/02/10 19:10:24
-i 2004/02/10 19:10:25 ForumJobs
-o 2004/02/10 19:16:23
-i 2004/02/11 18:04:55 ForumJobs
-o 2004/02/11 19:25:12
-i 2004/02/11 21:42:52 ForumJobs
-o 2004/02/12 01:16:28
-i 2004/02/12 16:01:50 ForumJobs
-o 2004/02/12 16:45:17
-i 2004/02/17 15:36:11 ForumJobs
-o 2004/02/17 17:37:50
-i 2004/02/17 22:20:06 ForumJobs
-o 2004/02/17 23:20:18
-i 2004/02/20 18:00:44 EDG fp_conv
-o 2004/02/20 19:21:42
-i 2004/02/23 21:16:24 EDG fp_conv
-o 2004/02/24 00:01:01
-i 2004/02/24 21:29:21 EDG fp_conv
-o 2004/02/24 21:53:23
-i 2004/02/25 10:55:19 EDG fp_conv
-o 2004/02/25 13:30:24
-i 2004/02/27 13:57:39 EDG fp_conv
-o 2004/02/27 15:49:31
-i 2004/02/27 16:18:22 EDG fp_conv
-o 2004/02/27 17:47:34
-i 2004/03/02 11:45:36 ForumJobs
-o 2004/03/02 12:45:38
-i 2004/03/02 12:45:38 EDG fp_conv
-o 2004/03/02 18:01:03
-i 2004/03/02 21:03:44 ForumJobs
-o 2004/03/02 21:39:25
-i 2004/03/03 12:05:24 EDG fp_conv
-o 2004/03/03 17:47:43
-i 2004/03/03 20:34:26 EDG fp_conv
-o 2004/03/03 22:04:50
-i 2004/03/04 19:38:23 EDG fp_conv
-o 2004/03/04 21:08:57
-i 2004/03/07 16:53:41 ForumJobs
-o 2004/03/07 17:45:30
-i 2004/03/09 14:24:33 ForumJobs
-o 2004/03/09 15:07:53
-i 2004/03/09 15:07:55 EDG fp_conv
-o 2004/03/09 17:53:35
-i 2004/03/22 23:21:08 ForumJobs
-o 2004/03/23 00:09:32
-i 2004/03/23 15:45:28 ForumJobs
-o 2004/03/23 16:38:24
-i 2004/03/30 23:30:17 ForumJobs
-o 2004/03/31 01:31:43
-i 2004/04/02 17:49:46 ForumJobs
-o 2004/04/02 21:02:43
-i 2004/04/03 20:33:51 ForumJobs
-o 2004/04/03 22:49:54
-i 2004/04/04 18:46:00 ForumJobs
-o 2004/04/04 19:03:09
-i 2004/04/05 20:21:23 ForumJobs
-o 2004/04/05 20:51:24
-i 2004/04/06 15:32:37 ForumJobs
-o 2004/04/06 16:40:53
-i 2004/04/08 12:54:44 ForumJobs
-o 2004/04/08 13:27:18
-i 2004/04/08 14:50:39 ForumJobs
-o 2004/04/08 15:39:51
-i 2004/04/08 16:05:52 ForumJobs
-o 2004/04/08 17:16:33
-i 2004/04/08 23:46:34 ForumJobs
-o 2004/04/09 00:57:38
-i 2004/04/10 13:00:57 ForumJobs
-o 2004/04/10 14:53:01
-i 2004/04/12 16:18:02 ForumJobs
-o 2004/04/12 17:34:57
-i 2004/04/12 23:39:20 ForumJobs
-o 2004/04/13 00:32:45
-i 2004/04/14 12:23:48 ForumJobs
-o 2004/04/14 12:50:42
-i 2004/04/15 16:48:51 ForumJobs
-o 2004/04/15 17:55:10 invoice #1
-i 2004/04/26 12:34:11 ForumJobs
-o 2004/04/26 13:04:54
-i 2004/05/06 14:00:28 ForumJobs
-o 2004/05/06 14:38:53
-i 2004/07/27 14:11:31 Borland
-o 2004/07/27 14:35:56 discussed fake symbol consolidation schemes
-i 2004/07/29 17:54:42 Borland
-o 2004/07/29 18:32:50 discussed hashing issues in fake ns sym lookup
-i 2004/07/30 12:41:33 Borland
-o 2004/07/30 12:57:16 answered namespace lookup questions
-i 2005/04/04 13:03:18 3dex oss
-o 2005/04/04 14:33:18
-i 2005/04/18 11:03:18 3dex oss
-o 2005/04/18 12:36:18
-i 2005/04/18 14:03:18 3dex oss
-o 2005/04/18 16:57:56
-i 2005/05/10 13:45:16 EDG dr
-o 2005/05/10 15:30:52 10 issues checked
-i 2005/05/12 14:32:16 EDG dr
-o 2005/05/12 18:12:55
-i 2005/05/13 11:20:51 EDG dr
-o 2005/05/13 12:41:03
-i 2005/05/13 15:50:54 EDG dr
-o 2005/05/13 16:13:23 break
-i 2005/05/13 16:43:59 EDG dr
-o 2005/05/13 17:37:44
-i 2005/05/16 12:30:37 EDG dr
-o 2005/05/16 13:02:55
-i 2005/05/16 16:02:11 EDG dr
-o 2005/05/16 16:36:20
-i 2005/05/17 18:33:14 EDG dr
-o 2005/05/17 18:51:02
-i 2005/05/18 14:32:08 EDG dr
-o 2005/05/18 15:07:43
-i 2005/05/20 20:02:55 EDG dr
-o 2005/05/20 21:21:14
-i 2005/05/20 21:42:16 EDG dr
-o 2005/05/20 21:52:59
-i 2005/05/21 15:39:59 EDG dr
-o 2005/05/21 16:33:27
-i 2005/05/23 20:48:20 EDG dr
-o 2005/05/23 22:17:08
-i 2005/05/24 13:08:29 EDG dr
-o 2005/05/24 15:25:06
-i 2005/06/28 15:08:29 3dex security
-o 2005/06/28 18:44:13
-i 2005/06/28 22:38:57 3dex ledger
-o 2005/06/28 23:51:31
-i 2005/06/29 16:11:41 3dex ledger
-o 2005/06/29 19:46:04
-i 2005/07/01 15:03:35 3dex ledger
-o 2005/07/01 20:41:08
-i 2005/07/02 14:00:35 3dex ledger
-o 2005/07/02 18:00:35
-i 2005/07/03 13:30:35 3dex ledger
-o 2005/07/03 17:00:35
-i 2005/07/04 14:30:35 3dex ledger
-o 2005/07/04 19:00:35
-i 2005/07/04 20:44:35 3dex ledger
-o 2005/07/04 22:01:35
-i 2005/07/05 17:20:29 3dex ledger
-o 2005/07/05 22:58:35
-i 2005/07/06 23:43:14 3dex ledger
-o 2005/07/07 03:09:01
-i 2005/07/07 19:43:14 3dex ledger
-o 2005/07/07 21:09:01
-i 2005/07/09 13:55:22 3dex ledger
-o 2005/07/09 14:34:29
-i 2005/07/09 15:08:05 3dex ledger
-o 2005/07/09 17:46:15
-i 2005/07/12 21:24:03 3dex ledger
-o 2005/07/13 02:00:03
-i 2005/07/14 21:24:03 3dex ledger
-o 2005/07/14 21:59:40
-i 2005/07/16 22:03:57 3dex ledger
-o 2005/07/17 00:34:07
-i 2005/07/17 10:53:38 3dex ledger
-o 2005/07/17 11:56:49
-i 2005/08/10 15:45:54 3dex soap
-o 2005/08/10 16:12:02
-i 2005/08/11 15:04:23 3dex soap
-o 2005/08/11 16:07:35
-i 2005/08/15 15:07:18 3dex soap
-o 2005/08/15 15:35:18
-i 2005/08/15 15:36:14 3dex soap
-o 2005/08/15 16:18:41
-i 2005/08/17 16:21:10 3dex soap
-o 2005/08/17 16:53:37
-i 2005/08/18 14:01:46 3dex soap
-o 2005/08/18 18:33:12
-i 2005/08/25 13:30:44 3dex soap
-o 2005/08/25 15:38:09
-i 2005/08/25 16:52:18 3dex soap
-o 2005/08/25 17:54:52
-i 2005/08/25 20:19:34 3dex soap
-o 2005/08/25 21:32:07
-i 2005/08/28 18:06:40 3dex soap
-o 2005/08/28 18:36:55
-i 2005/08/28 19:34:56 3dex soap
-o 2005/08/28 19:55:25
-i 2005/10/22 00:55:10 3dex video
-o 2005/10/22 01:52:58
-i 2005/10/22 22:00:59 3dex video
-o 2005/10/22 22:58:30
-i 2005/10/25 13:00:00 MOW meeting
-o 2005/10/25 17:00:00
-i 2005/10/26 10:00:00 MOW meeting
-o 2005/10/26 14:00:00
-i 2005/10/26 18:51:52 MOW planning
-o 2005/10/26 18:57:43
-i 2005/10/27 20:34:28 MOW website
-o 2005/10/27 21:40:28
-i 2005/10/28 22:04:28 MOW website
-o 2005/10/28 22:50:28
-i 2005/10/29 23:04:28 MOW planning
-o 2005/10/29 23:40:28
-i 2005/10/30 22:18:28 MOW website
-o 2005/10/31 01:22:28
-i 2005/10/31 05:15:29 3dex video
-o 2005/10/31 05:55:34
-i 2005/10/31 15:51:55 3dex video
-o 2005/10/31 17:37:09
-i 2005/11/01 01:15:34 MOW website
-o 2005/11/01 02:34:55
-i 2005/11/01 18:04:47 MOW website
-o 2005/11/01 18:35:25
-i 2005/11/01 20:39:42 MOW website
-o 2005/11/01 23:00:08
-i 2005/11/02 00:26:09 MOW website
-o 2005/11/02 04:14:33
-i 2005/11/02 17:07:40 MOW website
-o 2005/11/02 18:13:49
-i 2005/11/02 22:09:53 MOW website
-o 2005/11/02 22:41:31
-i 2005/11/03 00:11:02 MOW website
-o 2005/11/03 04:52:25
-i 2005/11/03 04:55:38 3dex video
-o 2005/11/03 06:22:42
-i 2005/11/03 19:45:00 MOW website
-o 2005/11/03 22:53:12
-i 2005/11/04 00:16:33 MOW website
-o 2005/11/04 00:43:07
-i 2005/11/04 02:01:23 MOW website
-o 2005/11/04 04:31:25
-i 2005/11/04 14:11:55 MOW website
-o 2005/11/04 16:48:30
-i 2005/11/04 16:59:06 MOW website
-o 2005/11/04 19:54:46
-i 2005/11/05 22:29:03 MOW website
-o 2005/11/06 04:53:57
-i 2005/11/07 02:13:36 MOW website
-o 2005/11/07 04:38:12
-i 2005/11/07 20:29:07 MOW website
-o 2005/11/07 23:03:45
-i 2005/11/07 23:53:46 MOW website
-o 2005/11/08 04:35:27
-i 2005/11/08 16:02:23 MOW website
-o 2005/11/08 17:26:28
-i 2005/11/09 00:25:44 MOW website
-o 2005/11/09 03:35:00
-i 2005/11/09 14:28:03 MOW website
-o 2005/11/09 14:49:27
-i 2005/11/09 17:13:19 MOW website
-o 2005/11/09 17:29:51
-i 2005/11/10 00:13:33 MOW website
-o 2005/11/10 03:08:40
-i 2005/11/10 15:22:12 MOW website
-o 2005/11/10 16:33:16
-i 2005/11/10 19:10:44 MOW website
-o 2005/11/10 22:19:44
-i 2005/11/11 01:00:54 MOW website
-o 2005/11/11 01:31:39
-i 2005/11/11 01:50:07 MOW website
-o 2005/11/11 03:13:36
-i 2005/11/11 03:35:18 MOW website
-o 2005/11/11 05:32:27
-i 2005/11/13 20:07:14 MOW website
-o 2005/11/13 21:14:05
-i 2005/11/13 22:02:04 MOW website
-o 2005/11/14 01:34:44
-i 2005/11/14 23:35:33 MOW website
-o 2005/11/15 12:25:00
-i 2005/11/15 01:31:53 MOW website
-o 2005/11/15 01:54:55
-i 2005/11/15 16:58:26 MOW website
-o 2005/11/15 18:17:43
-i 2005/11/15 21:16:00 MOW website
-o 2005/11/15 22:57:24
-i 2005/11/15 23:52:00 MOW website
-o 2005/11/16 07:08:14
-i 2005/11/16 20:06:33 MOW website
-o 2005/11/16 23:00:45
-i 2005/11/17 00:15:29 MOW website
-o 2005/11/17 09:39:11
-i 2005/11/17 15:33:03 MOW website: Polishing MainIndex.aspx
-o 2005/11/17 17:12:52
-i 2005/11/17 20:40:53 MOW website: Fixing the display of StoryTable.ascx
-o 2005/11/18 00:14:58
-i 2005/11/18 00:15:19 MOW website: Improve Asset class
-o 2005/11/18 03:42:35
-i 2005/11/19 19:04:00 MOW website: Fix MainIndex page
-o 2005/11/19 22:30:14
-i 2005/11/19 22:56:38 MOW website: Finalize HTML for AuthorIndex and Profile
-o 2005/11/20 07:49:00
-i 2005/11/20 19:02:48 MOW website: Resolving final issues
-o 2005/11/20 23:58:01
-i 2005/11/22 00:04:47 MOW website: MainIndex - Change header display and fixed alignment
-o 2005/11/22 02:54:14
-i 2005/11/22 02:54:14 MOW website: AuthorProfile - Fix alignment issues created by MainIndex fixes
-o 2005/11/22 03:08:13
-i 2005/11/22 03:08:13 MOW website: Breadcrumb navigation
-o 2005/11/22 04:13:01
-i 2005/11/22 04:13:01 MOW website: CreateTheme - Fixing style
-o 2005/11/22 04:45:41
-i 2005/11/22 04:45:41 MOW website: Confirming deletion
-o 2005/11/22 05:14:26
-i 2005/11/22 05:14:29 MOW website: Yellow message boxes
-o 2005/11/22 06:17:15
-i 2005/11/22 06:47:00 MOW website: MainIndex - Testing and fixing HTML
-o 2005/11/22 08:20:19
-i 2005/11/22 17:41:19 MOW website: MainIndex - Use PostedDate in Themes for sorting
-o 2005/11/22 18:14:23
-i 2005/11/22 18:49:32 MOW website: MainIndex - Use PostedDate in Themes for sorting
-o 2005/11/22 20:01:52
-i 2005/11/23 00:04:07 MOW website: AssetList - Finishing uploads
-o 2005/11/23 01:13:28
-i 2005/11/24 16:49:19 MOW website: AssetList - Support ordering
-o 2005/11/24 20:20:23
-i 2005/11/24 20:50:28 MOW website: AssetList - Finishing uploads
-o 2005/11/24 22:13:13
-i 2005/11/27 19:33:05 MOW website: StoryDetails - Adding other tables
-o 2005/11/27 21:15:26
-i 2005/11/28 20:27:07 MOW website: StoryDetails - Adding other tables
-o 2005/11/28 21:17:52
-i 2005/11/28 21:58:54 MOW website: StoryDetails - Adding other tables
-o 2005/11/29 00:49:57
-i 2005/11/29 02:16:10 MOW website: Use field validators again
-o 2005/11/29 03:20:58
-i 2005/11/29 04:09:57 MOW website: Use field validators again
-o 2005/11/29 04:35:41
-i 2005/11/30 00:08:28 MOW website: AddedStories - The main added story list
-o 2005/11/30 01:00:32
-i 2005/11/30 20:58:15 MOW website: AddedStories - The main added story list
-o 2005/11/30 22:01:42
-i 2005/11/30 22:57:15 MOW website: AddedStories - The main added story list
-o 2005/12/01 03:02:46
-i 2005/12/01 20:32:47 MOW website: AddedStories - The main added story list
-o 2005/12/01 21:18:13
-i 2005/12/01 21:18:27 MOW website: MainIndex - Further corrections using latest HTML
-o 2005/12/01 21:55:20
-i 2005/12/01 22:55:49 MOW website: MainIndex - Further corrections using latest HTML
-o 2005/12/02 03:21:50
-i 2005/12/02 12:19:06 MOW website: BookDescription
-o 2005/12/02 13:27:17
-i 2005/12/02 13:27:22 MOW website: Cleanup
-o 2005/12/02 17:35:32
-i 2005/12/05 17:50:37 MOW website: Cleanup
-o 2005/12/05 17:52:33
-i 2005/12/05 18:44:48 MOW website: Cleanup
-o 2005/12/05 22:13:06
-i 2005/12/05 22:29:40 MOW website: Cleanup
-o 2005/12/06 01:00:03
-i 2005/12/06 01:38:51 MOW website: Cleanup
-o 2005/12/06 02:30:04
-i 2005/12/06 18:44:24 MOW website: Cleanup
-o 2005/12/06 20:00:21
-i 2005/12/06 21:00:03 MOW website: Cleanup
-o 2005/12/07 00:44:28
-i 2005/12/07 00:44:46 MOW website: AuthorProfile - Support for CommLanguage and Passwords
-o 2005/12/07 02:10:41
-i 2005/12/07 02:11:11 MOW website: ShowImage - Make Popup size based on Image size
-o 2005/12/07 02:35:37
-i 2005/12/07 02:41:14 MOW website: ShowImage - Make Popup size based on Image size
-o 2005/12/07 03:02:45
-i 2005/12/07 04:22:17 MOW website: ShowImage - Make Popup size based on Image size
-o 2005/12/07 06:00:31
-i 2005/12/07 06:00:45 MOW website: AuthorIndex - Fixes for the Cleanup
-o 2005/12/07 06:31:09
-i 2005/12/07 10:10:21 MOW website: AddedStories - Add support for searching by author
-o 2005/12/07 11:37:51
-i 2005/12/07 12:55:55 MOW planning
-o 2005/12/07 13:26:03
-i 2005/12/07 13:26:19 MOW website: Cleanup
-o 2005/12/07 14:20:32
-i 2005/12/07 15:13:21 MOW website: Cleanup
-o 2005/12/07 15:46:54
-i 2005/12/07 17:52:33 MOW website: Cleanup
-o 2005/12/07 18:36:52
-i 2005/12/08 10:31:32 MOW website: Cleanup
-o 2005/12/08 16:28:12
-i 2005/12/08 20:37:50 MOW website: Cleanup
-o 2005/12/08 22:38:42
-i 2005/12/09 13:44:36 MOW website: StoryDetails - Split into multiple pages
-o 2005/12/09 14:09:44
-i 2005/12/11 14:46:31 MOW website: StoryDetails - Split into multiple pages
-o 2005/12/11 16:09:49
-i 2005/12/12 11:27:15 MOW website: StoryDetails - Split into multiple pages
-o 2005/12/12 14:39:05
-i 2005/12/12 14:58:15 MOW website: StoryDetails - Split into multiple pages
-o 2005/12/12 17:06:12
-i 2005/12/12 17:06:19 MOW website: Advertisements
-o 2005/12/12 18:22:28
-i 2005/12/12 19:07:13 MOW website: Advertisements
-o 2005/12/12 21:47:22
-i 2005/12/13 17:05:38 MOW website: Events
-o 2005/12/13 17:54:49
-i 2005/12/13 19:08:43 MOW website: Events
-o 2005/12/13 21:43:52
-i 2005/12/14 15:35:08 MOW website: Events
-o 2005/12/14 16:30:54
-i 2005/12/15 13:01:22 MOW website: Events
-o 2005/12/15 14:41:30
-i 2005/12/15 17:25:51 MOW website: Events
-o 2005/12/15 18:07:43
-i 2005/12/16 08:19:13 MOW website: Events
-o 2005/12/16 10:51:19
-i 2005/12/19 14:32:21 MOW website: Bugs
-o 2005/12/19 18:17:29
-i 2005/12/19 19:52:21 MOW website: Events
-o 2005/12/19 22:01:36
-i 2005/12/21 18:52:02 MOW website: TakeAction
-o 2005/12/21 21:30:58
-i 2005/12/29 22:35:56 MOW website: TakeAction
-o 2005/12/29 23:26:52
-i 2005/12/30 22:51:30 MOW website: Flags
-o 2005/12/31 00:02:40
-i 2006/01/02 19:12:34 MOW website: Communication
-o 2006/01/02 19:58:53
-i 2006/01/05 17:53:58 MOW website: Schedule
-o 2006/01/05 18:30:02
-i 2006/01/06 00:08:19 MOW website: Cleanup
-o 2006/01/06 01:36:11
-i 2006/01/07 17:25:54 MOW website: Cleanup
-o 2006/01/07 19:30:29
-i 2006/01/07 22:05:33 MOW website: Cleanup
-o 2006/01/08 00:45:18
-i 2006/01/08 15:36:25 MOW website: Cleanup
-o 2006/01/08 16:41:19
-i 2006/01/08 18:04:57 MOW website: Cleanup
-o 2006/01/08 20:03:15
-i 2006/01/08 20:50:43 MOW website: Cleanup
-o 2006/01/08 22:12:04
-i 2006/01/08 22:12:09 sina
-o 2006/01/08 23:19:16
-i 2006/01/08 23:19:17 MOW website: Cleanup
-o 2006/01/09 01:01:51
-i 2006/01/09 14:46:53 MOW website: Cleanup
-o 2006/01/09 17:19:53
-i 2006/01/09 18:19:54 MOW website: Cleanup
-o 2006/01/09 19:35:47
-i 2006/01/12 14:50:26 MOW website: Cleanup
-o 2006/01/12 17:34:41
-i 2006/01/12 19:59:47 MOW website: Cleanup
-o 2006/01/12 23:33:20
-i 2006/01/13 15:32:37 MOW website: Cleanup
-o 2006/01/13 16:03:19
-i 2006/01/14 18:50:12 sina
-o 2006/01/14 19:22:54
-i 2006/01/15 01:14:21 sina
-o 2006/01/15 01:50:04
-i 2006/01/15 02:21:13 MOW website: PressReleases
-o 2006/01/15 03:11:35
-i 2006/01/16 02:25:45 sina
-o 2006/01/16 02:59:01
-i 2006/01/16 18:08:28 MOW planning
-o 2006/01/16 18:21:21
-i 2006/01/16 21:43:57 sina
-o 2006/01/16 22:57:09
-i 2006/01/17 00:23:05 sina
-o 2006/01/17 02:51:55
-i 2006/01/17 18:01:54 sina
-o 2006/01/17 18:22:33
-i 2006/01/17 19:00:48 sina
-o 2006/01/17 19:19:36
-i 2006/01/17 19:34:10 sina
-o 2006/01/17 19:50:48
-i 2006/01/17 19:53:38 MOW website: v11
-o 2006/01/17 21:09:27
-i 2006/01/17 21:33:29 sina
-o 2006/01/17 21:55:42
-i 2006/01/17 22:02:38 sina
-o 2006/01/17 22:28:36
-i 2006/01/17 22:28:59 MOW website: v11
-o 2006/01/17 22:48:06
-i 2006/01/18 01:57:59 MOW website: v11
-o 2006/01/18 03:27:34
-i 2006/01/18 03:50:52 sina
-o 2006/01/18 04:32:47
-i 2006/01/18 18:07:31 MOW website: TakeAction
-o 2006/01/18 21:01:43
-i 2006/01/18 23:53:49 MOW website: TakeAction
-o 2006/01/19 01:20:50
-i 2006/01/19 14:50:56 MOW website: TakeAction
-o 2006/01/19 15:40:04
-i 2006/01/19 17:53:04 MOW website: TakeAction
-o 2006/01/19 19:25:36
-i 2006/01/22 00:59:17 MOW website: Cleanup
-o 2006/01/22 01:23:58
-i 2006/01/22 07:43:01 MOW website: Cleanup
-o 2006/01/22 10:02:30
-i 2006/01/22 19:18:23 MOW website: Cleanup
-o 2006/01/22 19:48:01
-i 2006/01/22 20:31:36 MOW website: Cleanup
-o 2006/01/22 23:05:58
-i 2006/01/23 18:45:59 MOW website: SubTheme
-o 2006/01/23 19:50:39
-i 2006/01/23 21:05:19 MOW website: SubTheme
-o 2006/01/24 01:02:44
-i 2006/01/24 20:07:33 MOW website: Bugs
-o 2006/01/24 20:39:11
-i 2006/01/25 08:38:02 MOW website: Bugs
-o 2006/01/25 10:17:25
-i 2006/01/25 11:54:50 MOW website: Bugs
-o 2006/01/25 14:26:52
-i 2006/01/27 03:42:03 MOW website: Bugs
-o 2006/01/27 04:35:28
-i 2006/01/27 05:25:53 MOW website: Bugs
-o 2006/01/27 06:14:55
-i 2006/01/27 16:27:55 MOW website: Bugs
-o 2006/01/27 16:36:42
-i 2006/01/27 17:08:46 MOW website: Bugs
-o 2006/01/27 17:38:40
-i 2006/01/29 15:42:37 MOW website: Bugs
-o 2006/01/29 16:12:01
-i 2006/01/29 22:12:15 MOW website: Bugs
-o 2006/01/30 00:30:12
-i 2006/01/30 13:24:14 MOW website: Comments
-o 2006/01/30 14:54:21
-i 2006/01/30 15:38:38 MOW website: Events
-o 2006/01/30 18:30:18
-i 2006/01/30 18:50:55 MOW website: AboutTheBook
-o 2006/01/30 19:14:55
-i 2006/01/31 12:30:40 MOW website: AboutTheBook
-o 2006/01/31 14:20:46
-i 2006/01/31 16:11:25 MOW website: Events
-o 2006/01/31 18:35:55
-i 2006/02/01 03:44:57 MOW website: public
-o 2006/02/01 04:21:29
-i 2006/02/01 10:40:37 MOW website: public
-o 2006/02/01 15:19:13
-i 2006/02/01 16:49:14 MOW website: public
-o 2006/02/01 19:37:46
-i 2006/02/02 09:55:16 MOW website: public
-o 2006/02/02 10:34:26
-i 2006/02/02 15:47:38 MOW website: public
-o 2006/02/02 18:39:48
-i 2006/02/03 09:14:47 MOW website: public
-o 2006/02/03 10:21:18
-i 2006/02/03 16:21:20 MOW website: public
-o 2006/02/03 17:26:02
-i 2006/02/05 11:49:52 MOW website: public
-o 2006/02/05 22:49:53
-i 2006/02/06 16:49:23 MOW website: public
-o 2006/02/06 18:10:33
-i 2006/02/07 17:01:45 MOW website: public
-o 2006/02/07 18:39:24
-i 2006/02/07 21:00:15 sina
-o 2006/02/08 23:27:28
-i 2006/02/08 11:47:31 MOW website: public
-o 2006/02/08 12:09:20
-i 2006/02/08 12:21:06 MOW website: public
-o 2006/02/08 12:54:37
-i 2006/02/10 21:20:03 MOW website: public
-o 2006/02/10 23:36:40
-i 2006/02/16 21:26:56 3dex scheduler
-o 2006/02/16 22:52:50
-i 2006/02/16 23:35:24 3dex scheduler
-o 2006/02/17 01:11:31
-i 2006/02/17 17:57:19 MOW website: public
-o 2006/02/17 19:09:45
-i 2006/02/17 21:00:15 sina
-o 2006/02/17 21:30:15
-i 2006/02/18 22:00:57 3dex scheduler
-o 2006/02/18 22:59:26
-i 2006/02/19 01:14:16 3dex scheduler
-o 2006/02/19 02:32:59
-i 2006/02/19 17:05:01 3dex scheduler
-o 2006/02/19 17:26:51
-i 2006/02/20 22:28:05 3dex scheduler
-o 2006/02/20 22:48:37
-i 2006/02/21 14:50:37 MOW website: Events
-o 2006/02/21 16:55:24
-i 2006/02/21 17:14:06 MOW website: Events
-o 2006/02/21 18:20:14
-i 2006/02/21 19:34:15 MOW website: Events
-o 2006/02/21 20:00:09
-i 2006/02/23 18:02:57 sina
-o 2006/02/23 19:35:47
-i 2006/02/28 16:01:01 MOW website: Factoids
-o 2006/02/28 18:32:39
-i 2006/02/28 22:13:01 MOW website: testing
-o 2006/02/28 23:41:58
-i 2006/03/02 12:52:49 MOW website: Factoids
-o 2006/03/02 14:13:21
-i 2006/03/02 16:01:03 MOW website: bugs
-o 2006/03/02 17:45:58
-i 2006/03/02 18:10:59 MOW website: bugs
-o 2006/03/02 18:59:01
-i 2006/03/03 16:23:29 MOW website: bugs
-o 2006/03/03 17:23:29
-i 2006/03/10 16:45:13 MOW website: testing
-o 2006/03/10 17:25:14
-i 2006/03/11 16:54:29 MOW website: testing
-o 2006/03/11 18:03:01
-i 2006/03/11 21:06:39 MOW website: testing
-o 2006/03/11 21:36:29
-i 2006/03/11 23:59:37 sina
-o 2006/03/12 03:30:39
-i 2006/03/13 00:41:27 MOW website: testing
-o 2006/03/13 02:04:44
-i 2006/03/14 03:19:06 MOW website: testing
-o 2006/03/14 03:37:49
-i 2006/03/15 02:03:32 MOW website: bugs
-o 2006/03/15 04:04:44
-i 2006/03/15 21:02:35 College:Setup
-o 2006/03/15 22:10:44
-i 2006/03/16 01:08:22 College:Setup
-o 2006/03/16 02:28:17
-i 2006/03/16 03:02:34 College:Setup
-o 2006/03/16 03:34:47
-i 2006/03/16 08:08:17 College:Setup
-o 2006/03/16 08:59:51
-i 2006/03/22 10:31:58 sina
-o 2006/03/22 13:06:15
diff --git a/tests/confirm.py b/tests/confirm.py
deleted file mode 100755
index f4947224..00000000
--- a/tests/confirm.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/python
-
-# This script confirms what ledger tells you.
-
-import sys
-import os
-import re
-
-def clean(num):
- return float(re.sub("(\s+|\$|,)","", num))
-
-running_total = 0.0
-index = 1
-last_line = ""
-errors = 0
-
-report = sys.argv[1]
-for line in os.popen("./ledger -f utils/standard.dat -e 2004/4 %s reg %s" %
- (report, sys.argv[2])):
- match = re.match("\\s*([-$,0-9.]+)\\s+([-$,0-9.]+)", line[55:])
- if not match:
- continue
- value = clean(match.group(1))
- total = clean(match.group(2))
-
- running_total += value
- diff = abs(running_total - total)
- if report == "-V" or report == "-G" and diff < 0.015:
- diff = 0.0
- if diff > 0.001:
- print "! discrepancy of %.2f (%.2f - %.2f) at line %d:" % \
- (running_total - total, running_total, total, index)
- print line,
- running_total = total
- errors += 1
-
- index += 1
- last_line = line
-
-balance_total = 0.0
-
-for line in os.popen("./ledger -f utils/standard.dat -e 2004/4 %s bal %s" %
- (report, sys.argv[2])):
- if line[0] != '-':
- balance_total = clean(line[:20])
-
-diff = abs(balance_total - running_total)
-if report == "-V" or report == "-G" and diff < 0.015:
- diff = 0.0
-if diff > 0.001:
- print
- print "! discrepancy of %.2f (%.2f - %.2f) between register and balance" % \
- (balance_total - running_total, balance_total, running_total)
- print last_line,
- print line,
- errors += 1
-
-sys.exit(errors)
diff --git a/tests/parser.h b/tests/parser.h
deleted file mode 100644
index aa1a3a74..00000000
--- a/tests/parser.h
+++ /dev/null
@@ -1,65 +0,0 @@
-#ifndef __TESTFILEFORMAT_H
-#define __TESTFILEFORMAT_H
-
-#include <cxxtest/TestSuite.h>
-
-#include <ledger.h>
-
-using namespace std;
-using namespace ledger;
-
-class TestFileFormat : public CxxTest::TestSuite
-{
-public:
- void testEmptyFileIsTextualFile()
- {
- stringstream emptyStream(stringstream::in);
- textual_parser_t textualParser;
- TS_ASSERT(textualParser.test(emptyStream));
- TS_ASSERT(emptyStream.good());
- TS_ASSERT_EQUALS(0, emptyStream.tellg());
- }
-
- void testEmptyFileIsNotXMLFile()
- {
-#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
- stringstream emptyStream(stringstream::in);
- xml_parser_t xmlParser;
- TS_ASSERT(!xmlParser.test(emptyStream));
- TS_ASSERT(emptyStream.good());
- TS_ASSERT_EQUALS(0, emptyStream.tellg());
-#endif
- }
-
- void testEmptyFileIsNotGnuCashFile()
- {
-#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
- stringstream emptyStream(stringstream::in);
- gnucash_parser_t gnucashParser;
- TS_ASSERT(!gnucashParser.test(emptyStream));
- TS_ASSERT(emptyStream.good());
- TS_ASSERT_EQUALS(0, emptyStream.tellg());
-#endif
- }
-
- void testEmptyFileIsNotBinaryFile()
- {
- stringstream emptyStream(stringstream::in);
- binary_parser_t binaryParser;
- TS_ASSERT(!binaryParser.test(emptyStream));
- TS_ASSERT(emptyStream.good());
- TS_ASSERT_EQUALS(0, emptyStream.tellg());
- }
-
- void testEmptyFileIsNotQIFFile()
- {
- stringstream emptyStream(stringstream::in);
- qif_parser_t qifParser;
- TS_ASSERT(!qifParser.test(emptyStream));
- TS_ASSERT(emptyStream.good());
- TS_ASSERT_EQUALS(0, emptyStream.tellg());
- }
-
-};
-
-#endif // __TESTFILEFORMAT_H
diff --git a/tests/regress b/tests/regress
deleted file mode 100755
index 9a6c4412..00000000
--- a/tests/regress
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/bin/sh
-
-TMPDIR=/tmp
-TESTS=tests
-UTILS=utils
-CASES=$TESTS/cases
-
-result=0
-
-generate=false
-if [ "$1" = "--generate" ]; then
- generate=true
-fi
-
-runtest() {
- num=$1
- shift
- if [ $generate = true ]; then
- echo generating $num
- ./ledger "$@" > $TESTS/baseline/$num 2> $TMPDIR/errors-$$.out
- cat $TMPDIR/errors-$$.out >> $TESTS/baseline/$num
- rm -f $TMPDIR/*-$$.out
- elif [ -r $TESTS/baseline/$num ]; then
- ./ledger "$@" > $TMPDIR/test-$$.out 2> $TMPDIR/errors-$$.out
- cat $TMPDIR/errors-$$.out >> $TMPDIR/test-$$.out
-
- diff $TESTS/baseline/$num $TMPDIR/test-$$.out \
- > $TMPDIR/result-$$.out 2>&1
- if [ -s $TMPDIR/result-$$.out ]; then
- echo Error: Regression $num failed
- echo ":: regression $num: ./ledger $@" >> errors.out
- cat $TMPDIR/result-$$.out >> errors.out
- result=`expr $result + 1`
- fi
- rm -f $TMPDIR/*-$$.out
- fi
-}
-
-echo Running Ledger regression tests...
-
-runtest 1032 -f $CASES/1032.dat -S t bal
-
-runtest 1031 -f $CASES/1030.dat reg
-runtest 1030 -f $CASES/1030.dat bal
-
-runtest 1029 -f $CASES/1002.dat entry 2006/10/20 "stock option"
-runtest 1028 -f $CASES/1002.dat entry 2006/10/20 "stock option" -20
-runtest 1027 -f $CASES/1002.dat entry 2006/10/20 "stock option" opti
-runtest 1026 -f $CASES/1002.dat entry 2006/10/20 "stock option" time
-runtest 1025 -f $CASES/1002.dat entry 2006/10/20 "stock option" gain
-runtest 1024 -f $CASES/1002.dat entry 2006/10/20 "stock option" opti -20
-runtest 1023 -f $CASES/1002.dat entry 2006/10/20 "stock option" time -20
-runtest 1022 -f $CASES/1002.dat entry 2006/10/20 "stock option" gain -20
-
-runtest 1021 -f $CASES/1002.dat entry 2006/10/20 "stock optionx"
-runtest 1020 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" -20
-runtest 1019 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" opti
-runtest 1018 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" time
-runtest 1017 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" gain
-runtest 1016 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" opti -20
-runtest 1015 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" time -20
-runtest 1014 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" gain -20
-
-runtest 1013 -f $CASES/1002.dat entry 2006/10/20 "stock option" xopti
-runtest 1012 -f $CASES/1002.dat entry 2006/10/20 "stock option" xtime
-runtest 1011 -f $CASES/1002.dat entry 2006/10/20 "stock option" xgain
-runtest 1010 -f $CASES/1002.dat entry 2006/10/20 "stock option" xopti -20
-runtest 1009 -f $CASES/1002.dat entry 2006/10/20 "stock option" xtime -20
-runtest 1008 -f $CASES/1002.dat entry 2006/10/20 "stock option" xgain -20
-
-runtest 1007 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xopti
-runtest 1006 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xtime
-runtest 1005 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xgain
-runtest 1004 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xopti -20
-runtest 1003 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xtime -20
-runtest 1002 -f $CASES/1002.dat entry 2006/10/20 "stock optionx" xgain -20
-
-runtest 1001 -f $CASES/1001.dat bal
-
-if [ -f $UTILS/standard.dat ]; then
- runtest 10 -f $UTILS/standard.dat --truncate=trailing -M -r -s -n reg rent
- runtest 9 -f $UTILS/standard.dat --truncate=trailing -M -r -s reg rent
- runtest 8 -f $UTILS/standard.dat --truncate=trailing -M -r -n reg rent
- runtest 7 -f $UTILS/standard.dat --truncate=trailing -M -r reg rent
- runtest 6 -f $UTILS/standard.dat --truncate=trailing -M reg rent
- runtest 5 -f $UTILS/standard.dat --truncate=trailing -r -s -n reg rent
- runtest 4 -f $UTILS/standard.dat --truncate=trailing -r -s reg rent
- runtest 3 -f $UTILS/standard.dat --truncate=trailing -r -n reg rent
- runtest 2 -f $UTILS/standard.dat --truncate=trailing -r reg rent
- runtest 1 -f $UTILS/standard.dat --truncate=trailing reg rent
-fi
-
-echo Running Ledger regression tests...done
-
-exit $result
diff --git a/tests/regtest b/tests/regtest
deleted file mode 100755
index 57dec8e0..00000000
--- a/tests/regtest
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/sh
-
-errors=0
-
-if [ ! -f utils/standard.dat ]; then
- exit 0
-fi
-
-for test in \
- "-O nrl:checking" \
- "-O ^expenses" \
- "-B 401" \
- "-V 401" \
- "-G 401" \
- "-B ira" \
- "-V ira" \
- "-G ira" \
- "-B retire" \
- "-V retire" \
- "-G retire"
-do
- echo testing: $test
- python tests/confirm.py $test
- errors=`expr $errors + $?`
-done
-
-exit $errors
diff --git a/tests/runtests.py b/tests/runtests.py
deleted file mode 100755
index 7649f775..00000000
--- a/tests/runtests.py
+++ /dev/null
@@ -1,184 +0,0 @@
-#!/usr/bin/env python
-
-import random
-import string
-import signal
-import os
-import sys
-import time
-
-true, false = 1, 0
-
-options = [
- "--account=TempAccount",
- "--actual",
- "--add-budget",
- "--amount-data",
- "--amount=a",
- "--ansi",
- "--ansi-invert",
- "--average",
- #"--balance-format",
- "--basis",
- "--begin=2004/01",
- "--budget",
- "--by-payee",
- #"--cache=/tmp/cache",
- "--cleared",
- "--collapse",
- "--comm-as-payee",
- #"--csv-register-format",
- "--current",
- "--date-format=%Y",
- "--descend='$100'",
- "--descend-if='t=={$100}'",
- "--deviation",
- "--display='a>10'",
- "--dow",
- "--download",
- "--effective",
- "--empty",
- "--end=2005/01",
- #"--equity-format",
- #"--file=/tmp/file",
- "--forecast='d<[2006]'",
- "--format=%Y",
- #"--full-help",
- "--gain",
- "--head=10",
- #"--help",
- #"--help-calc",
- #"--help-comm",
- #"--help-disp",
- #"--init-file=/tmp/init",
- #"--input-date-format",
- "--limit='a>10'",
- "--lots",
- "--lot-prices",
- "--lot-dates",
- "--lot-tags",
- "--market",
- "--monthly",
- "--no-cache",
- #"--output=/tmp/output",
- #"--pager",
- #"--percentage",
- "--performance",
- "--period-sort=A\\(t\\)",
- "--period=oct",
- #"--plot-amount-format",
- #"--plot-total-format",
- "--price",
- "--price-exp=1000",
- #"--price-db=/tmp/foo",
- #"--prices-format",
- #"--print-format",
- "--quantity",
- "--real",
- #"--reconcile",
- #"--reconcile-date",
- #"--register-format",
- "--related",
- "--sort=A\\(t\\)",
- "--subtotal",
- "--tail=5",
- "--total-data",
- "--total=O",
- "--totals",
- "--unbudgeted",
- "--uncleared",
- #"--version",
- "--weekly",
- "--wide",
- #"--wide-register-format",
- #"--write-hdr-format",
- #"--write-xact-format",
- "--yearly",
-]
-
-commands = [
- "bal rent",
- "bal ira",
- "bal auto",
- "reg rent",
- "reg ira",
- "reg expenses:food",
- "print rent",
- "print irc",
- "xml rent",
- "xml irc",
- "equity rent",
- "equity ira",
- "prices AAPL",
-]
-
-random.seed ()
-
-loop = true
-count = 0
-errors = 0
-if len(sys.argv) > 1:
- errors = int(sys.argv[1])
-signals = 0
-
-while loop:
- try:
- n = random.randint (0, len (options))
- opts = random.sample (options, n)
- for cmd in commands:
- if os.path.exists ("/tmp/out"):
- os.unlink ("/tmp/out")
-
- cmd = "./ledger -f utils/standard.dat " + string.join(opts, " ") + " " + cmd + \
- " >> /tmp/out 2>&1"
-
- sys.stdout = open ("/tmp/out", "w")
- print "::", cmd
- sys.stdout.close ()
-
- ret = os.system (cmd)
-
- sys.stdout = open ("/tmp/out", "a")
-
- # Convert an odd UNIX return type into the appropriate
- # signal indication.
- if ret and ret % 256 == 0 and ret / 256 > 127:
- ret = 0x100 + (ret / 256 - 128)
-
- if ret and ret % 256 == 0:
- print "ERROR: Return value", ret / 256
- sys.stdout.close ()
- os.system ("cat /tmp/out >> errors.out")
- errors += 1
- elif ret:
- if ret % 256 == signal.SIGINT:
- loop = false
- break
- print "SIGNAL: Return value", ret % 256
- sys.stdout.close ()
- os.system ("cat /tmp/out >> signals.out")
- signals += 1
- else:
- sys.stdout.close ()
- os.system ("cat /tmp/out >> results.out")
-
- sys.stdout = sys.__stdout__
- count += 1
- if count < 10 or \
- (count < 100 and count % 10 == 0) or \
- (count < 1000 and count % 100 == 0) or \
- count % 1000 == 0:
- if signals > 0 and errors > 0:
- print "%d tests ... (%d signals, %d errors)" % \
- (count, signals, errors)
- elif signals > 0:
- print "%d tests ... (%d signals)" % \
- (count, signals)
- elif errors > 0:
- print "%d tests ... (%d errors)" % \
- (count, errors)
- else:
- print "%d tests ..." % count
-
- except KeyboardInterrupt:
- loop = false
diff --git a/tests/textual.h b/tests/textual.h
deleted file mode 100644
index adf24c77..00000000
--- a/tests/textual.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef __TESTTEXTUALJOURNAL_H
-#define __TESTTEXTUALJOURNAL_H
-
-#include <cxxtest/TestSuite.h>
-
-#include <textual.h>
-#include <config.h>
-
-using namespace std;
-using namespace ledger;
-
-class TestTextualJournal : public CxxTest::TestSuite
-{
-public:
- void testEmptyFileIsTextualFile()
- {
- stringstream j(stringstream::in);
-
- j << "2005/10/15 Something" << endl;
- j << " A $ 42" << endl;
- j << " B" << endl;
-
- textual_parser_t textualParser;
- TS_ASSERT(textualParser.test(j));
- TS_ASSERT(j.good());
- TS_ASSERT_EQUALS(0, j.tellg());
-
- config_t config;
- std::auto_ptr<journal_t> journal(new journal_t);
- textualParser.parse(j, config, journal.get());
- }
-};
-
-#endif // __TESTTEXTUALJOURNAL_H
diff --git a/textual.cc b/textual.cc
deleted file mode 100644
index 8daf4d6a..00000000
--- a/textual.cc
+++ /dev/null
@@ -1,1117 +0,0 @@
-#if defined(__GNUG__) && __GNUG__ < 3
-#define _XOPEN_SOURCE
-#endif
-
-#include "journal.h"
-#include "textual.h"
-#include "datetime.h"
-#include "valexpr.h"
-#include "error.h"
-#include "option.h"
-#include "config.h"
-#include "timing.h"
-#include "util.h"
-#include "acconf.h"
-
-#include <fstream>
-#include <sstream>
-#include <cstring>
-#include <cctype>
-#include <cstdio>
-#include <cstdlib>
-#include <climits>
-
-#ifdef HAVE_REALPATH
-extern "C" char *realpath(const char *, char resolved_path[]);
-#endif
-
-#define TIMELOG_SUPPORT 1
-
-namespace ledger {
-
-#define MAX_LINE 1024
-
-static std::string path;
-static unsigned int linenum;
-static unsigned int src_idx;
-static accounts_map account_aliases;
-
-static std::list<std::pair<std::string, int> > include_stack;
-
-#ifdef TIMELOG_SUPPORT
-struct time_entry_t {
- datetime_t checkin;
- account_t * account;
- std::string desc;
-
- time_entry_t() : account(NULL) {}
- time_entry_t(datetime_t _checkin,
- account_t * _account = NULL,
- std::string _desc = "")
- : checkin(_checkin), account(_account), desc(_desc) {}
-
- time_entry_t(const time_entry_t& entry)
- : checkin(entry.checkin), account(entry.account),
- desc(entry.desc) {}
-};
-#endif
-
-inline char * next_element(char * buf, bool variable = false)
-{
- for (char * p = buf; *p; p++) {
- if (! (*p == ' ' || *p == '\t'))
- continue;
-
- if (! variable) {
- *p = '\0';
- return skip_ws(p + 1);
- }
- else if (*p == '\t') {
- *p = '\0';
- return skip_ws(p + 1);
- }
- else if (*(p + 1) == ' ') {
- *p = '\0';
- return skip_ws(p + 2);
- }
- }
- return NULL;
-}
-
-static value_expr parse_amount_expr(std::istream& in, amount_t& amount,
- transaction_t * xact,
- unsigned short flags = 0)
-{
- value_expr expr(parse_value_expr(in, NULL, flags | PARSE_VALEXPR_RELAXED |
- PARSE_VALEXPR_PARTIAL)->acquire());
-
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Parsed an amount expression");
-
-#ifdef DEBUG_ENABLED
- DEBUG_IF("ledger.textual.parse") {
- if (_debug_stream) {
- ledger::dump_value_expr(*_debug_stream, expr);
- *_debug_stream << std::endl;
- }
- }
-#endif
-
- if (! compute_amount(expr, amount, xact))
- throw new parse_error("Amount expression failed to compute");
-
-#if 0
- if (expr->kind == value_expr_t::CONSTANT) {
- expr = NULL;
- } else {
- DEBUG_IF("ledger.textual.parse") {
- std::cout << "Value expression tree:" << std::endl;
- ledger::dump_value_expr(std::cout, expr.get());
- }
- }
-#else
- expr = NULL;
-#endif
-
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "The transaction amount is " << xact->amount);
- return expr;
-}
-
-transaction_t * parse_transaction(char * line, account_t * account,
- entry_t * entry = NULL)
-{
- std::istringstream in(line);
-
- std::string err_desc;
- try {
-
- // The account will be determined later...
- std::auto_ptr<transaction_t> xact(new transaction_t(NULL));
- if (entry)
- xact->entry = entry;
-
- // Parse the state flag
-
- char p = peek_next_nonws(in);
- switch (p) {
- case '*':
- xact->state = transaction_t::CLEARED;
- in.get(p);
- p = peek_next_nonws(in);
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Parsed the CLEARED flag");
- break;
- case '!':
- xact->state = transaction_t::PENDING;
- in.get(p);
- p = peek_next_nonws(in);
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Parsed the PENDING flag");
- break;
- }
-
- // Parse the account name
-
- unsigned long account_beg = in.tellg();
- unsigned long account_end = account_beg;
- while (! in.eof()) {
- in.get(p);
- if (in.eof() || (std::isspace(p) &&
- (p == '\t' || in.peek() == EOF ||
- std::isspace(in.peek()))))
- break;
- account_end++;
- }
-
- if (account_beg == account_end)
- throw new parse_error("No account was specified");
-
- char * b = &line[account_beg];
- char * e = &line[account_end];
- if ((*b == '[' && *(e - 1) == ']') ||
- (*b == '(' && *(e - 1) == ')')) {
- xact->flags |= TRANSACTION_VIRTUAL;
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Parsed a virtual account name");
- if (*b == '[') {
- xact->flags |= TRANSACTION_BALANCE;
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Parsed a balanced virtual account name");
- }
- b++; e--;
- }
-
- std::string name(b, e - b);
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Parsed account name " << name);
- if (account_aliases.size() > 0) {
- accounts_map::const_iterator i = account_aliases.find(name);
- if (i != account_aliases.end())
- xact->account = (*i).second;
- }
- if (! xact->account)
- xact->account = account->find_account(name);
-
- // Parse the optional amount
-
- if (in.good() && ! in.eof()) {
- p = peek_next_nonws(in);
- if (in.eof())
- goto finished;
- if (p == ';')
- goto parse_note;
- if (p == '=' && entry)
- goto parse_assign;
-
- try {
- unsigned long beg = (long)in.tellg();
-
- xact->amount_expr =
- parse_amount_expr(in, xact->amount, xact.get(),
- PARSE_VALEXPR_NO_REDUCE | PARSE_VALEXPR_NO_ASSIGN);
-
- unsigned long end = (long)in.tellg();
- xact->amount_expr.expr = std::string(line, beg, end - beg);
- }
- catch (error * err) {
- err_desc = "While parsing transaction amount:";
- throw err;
- }
- }
-
- // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST)
-
- if (in.good() && ! in.eof()) {
- p = peek_next_nonws(in);
- if (p == '@') {
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Found a price indicator");
- bool per_unit = true;
- in.get(p);
- if (in.peek() == '@') {
- in.get(p);
- per_unit = false;
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "And it's for a total price");
- }
-
- if (in.good() && ! in.eof()) {
- xact->cost = new amount_t;
-
- try {
- unsigned long beg = (long)in.tellg();
-
- if (parse_amount_expr(in, *xact->cost, xact.get(),
- PARSE_VALEXPR_NO_MIGRATE |
- PARSE_VALEXPR_NO_ASSIGN))
- throw new parse_error
- ("A transaction's cost must evaluate to a constant value");
-
- unsigned long end = (long)in.tellg();
-
- if (per_unit)
- xact->cost_expr = (std::string("@") +
- std::string(line, beg, end - beg));
- else
- xact->cost_expr = (std::string("@@") +
- std::string(line, beg, end - beg));
- }
- catch (error * err) {
- err_desc = "While parsing transaction cost:";
- throw err;
- }
-
- if (*xact->cost < 0)
- throw new parse_error("A transaction's cost may not be negative");
-
- amount_t per_unit_cost(*xact->cost);
- if (per_unit)
- *xact->cost *= xact->amount;
- else
- per_unit_cost /= xact->amount;
-
- if (xact->amount.commodity() &&
- ! xact->amount.commodity().annotated)
- xact->amount.annotate_commodity(per_unit_cost,
- xact->entry ? xact->entry->actual_date() : datetime_t(),
- xact->entry ? xact->entry->code : "");
-
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Total cost is " << *xact->cost);
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Per-unit cost is " << per_unit_cost);
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Annotated amount is " << xact->amount);
- }
- }
- }
-
- xact->amount.reduce();
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Reduced amount is " << xact->amount);
-
-parse_assign:
- if (entry != NULL) {
- // Parse the optional assigned (= AMOUNT)
-
- if (in.good() && ! in.eof()) {
- p = peek_next_nonws(in);
- if (p == '=') {
- in.get(p);
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Found a balance assignment indicator");
- if (in.good() && ! in.eof()) {
- amount_t amt;
-
- try {
- unsigned long beg = (long)in.tellg();
-
- if (parse_amount_expr(in, amt, xact.get(),
- PARSE_VALEXPR_NO_MIGRATE))
- throw new parse_error
- ("An assigned balance must evaluate to a constant value");
-
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "XACT assign: parsed amt = " << amt);
-
- unsigned long end = (long)in.tellg();
-
- account_xdata_t& xdata(account_xdata(*xact->account));
-
- DEBUG_PRINT("ledger.xact.assign",
- "account balance = " << xdata.value);
- DEBUG_PRINT("ledger.xact.assign",
- "xact amount = " << amt);
-
- amount_t diff;
- if (xdata.value.type == value_t::AMOUNT)
- diff = amt - *((amount_t *) xdata.value.data);
- else if (xdata.value.type == value_t::BALANCE)
- diff = amt - ((balance_t *) xdata.value.data)->amount(amt.commodity());
- else if (xdata.value.type == value_t::BALANCE_PAIR)
- diff = amt - ((balance_pair_t *) xdata.value.data)->quantity.amount(amt.commodity());
- else
- diff = amt;
-
- DEBUG_PRINT("ledger.xact.assign",
- "diff = " << diff);
-
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "XACT assign: diff = " << diff);
-
- if (! diff.realzero()) {
- if (xact->amount) {
- transaction_t * temp
- = new transaction_t(xact->account, diff, TRANSACTION_CALCULATED);
- entry->add_transaction(temp);
-
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Created balancing transaction");
- } else {
- xact->amount = diff;
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Overwrite null transaction");
- }
- xdata.value = amt;
- }
- }
- catch (error * err) {
- err_desc = "While parsing assigned balance:";
- throw err;
- }
- }
- }
- }
- }
-
- // Parse the optional note
-
- parse_note:
- if (in.good() && ! in.eof()) {
- p = peek_next_nonws(in);
- if (p == ';') {
- in.get(p);
- p = peek_next_nonws(in);
- xact->note = &line[in.tellg()];
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Parsed a note '" << xact->note << "'");
-
- if (char * b = std::strchr(xact->note.c_str(), '['))
- if (char * e = std::strchr(xact->note.c_str(), ']')) {
- char buf[256];
- std::strncpy(buf, b + 1, e - b - 1);
- buf[e - b - 1] = '\0';
-
- DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " <<
- "Parsed a transaction date " << buf);
-
- if (char * p = std::strchr(buf, '=')) {
- *p++ = '\0';
- xact->_date_eff = p;
- }
- if (buf[0])
- xact->_date = buf;
- }
- }
- }
-
- finished:
- return xact.release();
-
- }
- catch (error * err) {
- err->context.push_back
- (new line_context(line, (long)in.tellg() - 1,
- ! err_desc.empty() ?
- err_desc : "While parsing transaction:"));
- throw err;
- }
-}
-
-bool parse_transactions(std::istream& in,
- account_t * account,
- entry_base_t& entry,
- const std::string& kind,
- unsigned long beg_pos)
-{
- static char line[MAX_LINE + 1];
- bool added = false;
-
- while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
- in.getline(line, MAX_LINE);
- if (in.eof())
- break;
-
- int len = std::strlen(line);
- if (line[len - 1] == '\r')
- line[--len] = '\0';
-
- beg_pos += len + 1;
- linenum++;
-
- if (line[0] == ' ' || line[0] == '\t') {
- char * p = skip_ws(line);
- if (! *p)
- break;
- }
- if (transaction_t * xact = parse_transaction(line, account)) {
- entry.add_transaction(xact);
- added = true;
- }
- }
-
- return added;
-}
-
-namespace {
- TIMER_DEF(parsing_total, "total parsing time");
- TIMER_DEF(entry_xacts, "parsing transactions");
- TIMER_DEF(entry_details, "parsing entry details");
- TIMER_DEF(entry_date, "parsing entry date");
-}
-
-entry_t * parse_entry(std::istream& in, char * line, account_t * master,
- textual_parser_t& parser, unsigned long& pos)
-{
- std::auto_ptr<entry_t> curr(new entry_t);
-
- // Parse the date
-
- TIMER_START(entry_date);
-
- char * next = next_element(line);
-
- if (char * p = std::strchr(line, '=')) {
- *p++ = '\0';
- curr->_date_eff = p;
- }
- curr->_date = line;
-
- TIMER_STOP(entry_date);
-
- // Parse the optional cleared flag: *
-
- TIMER_START(entry_details);
-
- transaction_t::state_t state = transaction_t::UNCLEARED;
- if (next) {
- switch (*next) {
- case '*':
- state = transaction_t::CLEARED;
- next = skip_ws(++next);
- break;
- case '!':
- state = transaction_t::PENDING;
- next = skip_ws(++next);
- break;
- }
- }
-
- // Parse the optional code: (TEXT)
-
- if (next && *next == '(') {
- if (char * p = std::strchr(next++, ')')) {
- *p++ = '\0';
- curr->code = next;
- next = skip_ws(p);
- }
- }
-
- // Parse the description text
-
- curr->payee = next ? next : "<Unspecified payee>";
-
- TIMER_STOP(entry_details);
-
- // Parse all of the transactions associated with this entry
-
- TIMER_START(entry_xacts);
-
- unsigned long end_pos;
- unsigned long beg_line = linenum;
-
- while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
- unsigned long beg_pos = (unsigned long)in.tellg();
-
- line[0] = '\0';
- in.getline(line, MAX_LINE);
- if (in.eof() && line[0] == '\0')
- break;
-
- int len = std::strlen(line);
- if (line[len - 1] == '\r')
- line[--len] = '\0';
-
- end_pos = beg_pos + len + 1;
- linenum++;
-
- if (line[0] == ' ' || line[0] == '\t') {
- char * p = skip_ws(line);
- if (! *p)
- break;
- }
-
- if (transaction_t * xact = parse_transaction(line, master, curr.get())) {
- if (state != transaction_t::UNCLEARED &&
- xact->state == transaction_t::UNCLEARED)
- xact->state = state;
-
- xact->beg_pos = beg_pos;
- xact->beg_line = beg_line;
- xact->end_pos = end_pos;
- xact->end_line = linenum;
- pos = end_pos;
-
- curr->add_transaction(xact);
- }
-
- if (in.eof())
- break;
- }
-
- TIMER_STOP(entry_xacts);
-
- return curr.release();
-}
-
-template <typename T>
-struct push_var {
- T& var;
- T prev;
- push_var(T& _var) : var(_var), prev(var) {}
- ~push_var() { var = prev; }
-};
-
-static inline void parse_symbol(char *& p, std::string& symbol)
-{
- if (*p == '"') {
- char * q = std::strchr(p + 1, '"');
- if (! q)
- throw new parse_error("Quoted commodity symbol lacks closing quote");
- symbol = std::string(p + 1, 0, q - p - 1);
- p = q + 2;
- } else {
- char * q = next_element(p);
- symbol = p;
- if (q)
- p = q;
- else
- p += symbol.length();
- }
- if (symbol.empty())
- throw new parse_error("Failed to parse commodity");
-}
-
-bool textual_parser_t::test(std::istream& in) const
-{
- char buf[5];
-
- in.read(buf, 5);
- if (std::strncmp(buf, "<?xml", 5) == 0) {
-#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
- throw new parse_error("Ledger file contains XML data, but format was not recognized");
-#else
- throw new parse_error("Ledger file contains XML data, but no XML support present");
-#endif
- }
-
- in.clear();
- in.seekg(0, std::ios::beg);
- assert(in.good());
- return true;
-}
-
-static void clock_out_from_timelog(std::list<time_entry_t>& time_entries,
- const datetime_t& when,
- account_t * account,
- const char * desc,
- journal_t * journal)
-{
- time_entry_t event;
-
- if (time_entries.size() == 1) {
- event = time_entries.back();
- time_entries.clear();
- }
- else if (time_entries.empty()) {
- throw new parse_error("Timelog check-out event without a check-in");
- }
- else if (! account) {
- throw new parse_error
- ("When multiple check-ins are active, checking out requires an account");
- }
- else {
- bool found = false;
-
- for (std::list<time_entry_t>::iterator i = time_entries.begin();
- i != time_entries.end();
- i++)
- if (account == (*i).account) {
- event = *i;
- found = true;
- time_entries.erase(i);
- break;
- }
-
- if (! found)
- throw new parse_error
- ("Timelog check-out event does not match any current check-ins");
- }
-
- if (desc && event.desc.empty()) {
- event.desc = desc;
- desc = NULL;
- }
-
- std::auto_ptr<entry_t> curr(new entry_t);
- curr->_date = when;
- curr->code = desc ? desc : "";
- curr->payee = event.desc;
-
- if (curr->_date < event.checkin)
- throw new parse_error
- ("Timelog check-out date less than corresponding check-in");
-
- char buf[32];
- std::sprintf(buf, "%lds", curr->_date - event.checkin);
- amount_t amt;
- amt.parse(buf);
-
- transaction_t * xact
- = new transaction_t(event.account, amt, TRANSACTION_VIRTUAL);
- xact->state = transaction_t::CLEARED;
- curr->add_transaction(xact);
-
- if (! journal->add_entry(curr.get()))
- throw new parse_error("Failed to record 'out' timelog entry");
- else
- curr.release();
-}
-
-unsigned int textual_parser_t::parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master,
- const std::string * original_file)
-{
- static bool added_auto_entry_hook = false;
- static char line[MAX_LINE + 1];
- char c;
- unsigned int count = 0;
- unsigned int errors = 0;
-
- TIMER_START(parsing_total);
-
- std::list<account_t *> account_stack;
- auto_entry_finalizer_t auto_entry_finalizer(journal);
- std::list<time_entry_t> time_entries;
-
- if (! master)
- master = journal->master;
-
- account_stack.push_front(master);
-
- path = journal->sources.back();
- src_idx = journal->sources.size() - 1;
- linenum = 1;
-
- unsigned long beg_pos = in.tellg();
- unsigned long end_pos;
- unsigned long beg_line = linenum;
-
- while (in.good() && ! in.eof()) {
- try {
- in.getline(line, MAX_LINE);
- if (in.eof())
- break;
-
- int len = std::strlen(line);
- if (line[len - 1] == '\r')
- line[--len] = '\0';
-
- end_pos = beg_pos + len + 1;
- linenum++;
-
- switch (line[0]) {
- case '\0':
- break;
-
- case ' ':
- case '\t': {
- char * p = skip_ws(line);
- if (*p)
- throw new parse_error("Line begins with whitespace");
- break;
- }
-
-#ifdef TIMELOG_SUPPORT
- case 'i':
- case 'I': {
- std::string date(line, 2, 19);
-
- char * p = skip_ws(line + 22);
- char * n = next_element(p, true);
-
- time_entry_t event(date, account_stack.front()->find_account(p),
- n ? n : "");
-
- if (! time_entries.empty())
- for (std::list<time_entry_t>::iterator i = time_entries.begin();
- i != time_entries.end();
- i++)
- if (event.account == (*i).account)
- throw new parse_error
- ("Cannot double check-in to the same account");
-
- time_entries.push_back(event);
- break;
- }
-
- case 'o':
- case 'O':
- if (time_entries.empty()) {
- throw new parse_error("Timelog check-out event without a check-in");
- } else {
- std::string date(line, 2, 19);
-
- char * p = skip_ws(line + 22);
- char * n = next_element(p, true);
-
- clock_out_from_timelog
- (time_entries, date,
- p ? account_stack.front()->find_account(p) : NULL, n, journal);
- count++;
- }
- break;
-#endif // TIMELOG_SUPPORT
-
- case 'D': { // a default commodity for "entry"
- amount_t amt(skip_ws(line + 1));
- commodity_t::default_commodity = &amt.commodity();
- break;
- }
-
- case 'A': // a default account for unbalanced xacts
- journal->basket =
- account_stack.front()->find_account(skip_ws(line + 1));
- break;
-
- case 'C': // a set of conversions
- if (char * p = std::strchr(line + 1, '=')) {
- *p++ = '\0';
- parse_conversion(line + 1, p);
- }
- break;
-
- case 'P': { // a pricing entry
- char * date_field_ptr = skip_ws(line + 1);
- char * time_field_ptr = next_element(date_field_ptr);
- if (! time_field_ptr) break;
- std::string date_field = date_field_ptr;
-
- char * symbol_and_price;
- datetime_t datetime;
-
- if (std::isdigit(time_field_ptr[0])) {
- symbol_and_price = next_element(time_field_ptr);
- if (! symbol_and_price) break;
- datetime = date_field + " " + time_field_ptr;
- } else {
- symbol_and_price = time_field_ptr;
- datetime = date_t(date_field);
- }
-
- std::string symbol;
- parse_symbol(symbol_and_price, symbol);
- amount_t price(symbol_and_price);
-
- if (commodity_t * commodity = commodity_t::find_or_create(symbol))
- commodity->add_price(datetime, price);
- break;
- }
-
- case 'N': { // don't download prices
- char * p = skip_ws(line + 1);
- std::string symbol;
- parse_symbol(p, symbol);
-
- if (commodity_t * commodity = commodity_t::find_or_create(symbol))
- commodity->add_flags(COMMODITY_STYLE_NOMARKET);
- break;
- }
-
- case 'Y': // set the current year
- date_t::current_year = std::atoi(skip_ws(line + 1));
- break;
-
-#ifdef TIMELOG_SUPPORT
- case 'h':
- case 'b':
-#endif
- case '*': // comment line
- case ';': // comment line
- break;
-
- case '-': { // option setting
- char * p = next_element(line);
- if (! p) {
- p = std::strchr(line, '=');
- if (p)
- *p++ = '\0';
- }
- process_option(config_options, line + 2, p);
- break;
- }
-
- case '=': { // automated entry
- if (! added_auto_entry_hook) {
- journal->add_entry_finalizer(&auto_entry_finalizer);
- added_auto_entry_hook = true;
- }
-
- auto_entry_t * ae = new auto_entry_t(skip_ws(line + 1));
- if (parse_transactions(in, account_stack.front(), *ae,
- "automated", end_pos)) {
- journal->auto_entries.push_back(ae);
- ae->src_idx = src_idx;
- ae->beg_pos = beg_pos;
- ae->beg_line = beg_line;
- ae->end_pos = end_pos;
- ae->end_line = linenum;
- }
- break;
- }
-
- case '~': { // period entry
- period_entry_t * pe = new period_entry_t(skip_ws(line + 1));
- if (! pe->period)
- throw new parse_error(std::string("Parsing time period '") + line + "'");
-
- if (parse_transactions(in, account_stack.front(), *pe,
- "period", end_pos)) {
- if (pe->finalize()) {
- extend_entry_base(journal, *pe, true);
- journal->period_entries.push_back(pe);
- pe->src_idx = src_idx;
- pe->beg_pos = beg_pos;
- pe->beg_line = beg_line;
- pe->end_pos = end_pos;
- pe->end_line = linenum;
- } else {
- throw new parse_error("Period entry failed to balance");
- }
- }
- 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);
- push_var<unsigned long> save_beg_pos(beg_pos);
- push_var<unsigned long> save_end_pos(end_pos);
- push_var<unsigned int> save_linenum(linenum);
-
- path = p;
- if (path[0] != '/' && path[0] != '\\' && path[0] != '~') {
- std::string::size_type pos = save_path.prev.rfind('/');
- if (pos == std::string::npos)
- pos = save_path.prev.rfind('\\');
- if (pos != std::string::npos)
- path = std::string(save_path.prev, 0, pos + 1) + path;
- }
- path = resolve_path(path);
-
- DEBUG_PRINT("ledger.textual.include", "line " << linenum << ": " <<
- "Including path '" << path << "'");
-
- include_stack.push_back(std::pair<std::string, int>
- (journal->sources.back(), linenum - 1));
- count += parse_journal_file(path, config, journal,
- account_stack.front());
- include_stack.pop_back();
- }
- else if (word == "account") {
- account_t * acct;
- acct = account_stack.front()->find_account(p);
- account_stack.push_front(acct);
- }
- else if (word == "end") {
- account_stack.pop_front();
- }
- else if (word == "alias") {
- char * b = p;
- if (char * e = std::strchr(b, '=')) {
- char * z = e - 1;
- while (std::isspace(*z))
- *z-- = '\0';
- *e++ = '\0';
- e = skip_ws(e);
-
- // Once we have an alias name (b) and the target account
- // name (e), add a reference to the account in the
- // `account_aliases' map, which is used by the transaction
- // parser to resolve alias references.
- account_t * acct = account_stack.front()->find_account(e);
- std::pair<accounts_map::iterator, bool> result
- = account_aliases.insert(accounts_pair(b, acct));
- assert(result.second);
- }
- }
- else if (word == "def") {
- if (! global_scope.get())
- init_value_expr();
- parse_value_definition(p);
- }
- break;
- }
-
- default: {
- unsigned int first_line = linenum;
- unsigned long pos = beg_pos;
- if (entry_t * entry =
- parse_entry(in, line, account_stack.front(), *this, pos)) {
- if (journal->add_entry(entry)) {
- entry->src_idx = src_idx;
- entry->beg_pos = beg_pos;
- entry->beg_line = beg_line;
- entry->end_pos = pos;
- entry->end_line = linenum;
- count++;
- } else {
- delete entry;
- throw new parse_error("Entry does not balance");
- }
- } else {
- throw new parse_error("Failed to parse entry");
- }
- end_pos = pos;
- break;
- }
- }
- }
- catch (error * err) {
- for (std::list<std::pair<std::string, int> >::reverse_iterator i =
- include_stack.rbegin();
- i != include_stack.rend();
- i++)
- err->context.push_back(new include_context((*i).first, (*i).second,
- "In file included from"));
- err->context.push_front(new file_context(path, linenum - 1));
-
- std::cout.flush();
- if (errors > 0 && err->context.size() > 1)
- std::cerr << std::endl;
- err->reveal_context(std::cerr, "Error");
- std::cerr << err->what() << std::endl;
- delete err;
- errors++;
- }
- beg_pos = end_pos;
- }
-
- done:
- if (! time_entries.empty()) {
- std::list<account_t *> accounts;
-
- for (std::list<time_entry_t>::iterator i = time_entries.begin();
- i != time_entries.end();
- i++)
- accounts.push_back((*i).account);
-
- for (std::list<account_t *>::iterator i = accounts.begin();
- i != accounts.end();
- i++)
- clock_out_from_timelog(time_entries, datetime_t::now, *i, NULL, journal);
-
- assert(time_entries.empty());
- }
-
- if (added_auto_entry_hook)
- journal->remove_entry_finalizer(&auto_entry_finalizer);
-
- if (errors > 0)
- throw (int)errors;
-
- TIMER_STOP(parsing_total);
-
- return count;
-}
-
-void write_textual_journal(journal_t& journal, std::string path,
- item_handler<transaction_t>& formatter,
- const std::string& write_hdr_format,
- std::ostream& out)
-{
- unsigned long index = 0;
- std::string found;
-
- if (path.empty()) {
- if (! journal.sources.empty())
- found = *journal.sources.begin();
- } else {
-#ifdef HAVE_REALPATH
- char buf1[PATH_MAX];
- char buf2[PATH_MAX];
-
- ::realpath(path.c_str(), buf1);
-
- for (strings_list::iterator i = journal.sources.begin();
- i != journal.sources.end();
- i++) {
- ::realpath((*i).c_str(), buf2);
- if (std::strcmp(buf1, buf2) == 0) {
- found = *i;
- break;
- }
- index++;
- }
-#else
- for (strings_list::iterator i = journal.sources.begin();
- i != journal.sources.end();
- i++) {
- if (path == *i) {
- found = *i;
- break;
- }
- index++;
- }
-#endif
- }
-
- if (found.empty())
- throw new error(std::string("Journal does not refer to file '") +
- path + "'");
-
- entries_list::iterator el = journal.entries.begin();
- auto_entries_list::iterator al = journal.auto_entries.begin();
- period_entries_list::iterator pl = journal.period_entries.begin();
-
- unsigned long pos = 0;
-
- format_t hdr_fmt(write_hdr_format);
- std::ifstream in(found.c_str());
-
- while (! in.eof()) {
- entry_base_t * base = NULL;
- if (el != journal.entries.end() && pos == (*el)->beg_pos) {
- hdr_fmt.format(out, details_t(**el));
- base = *el++;
- }
- else if (al != journal.auto_entries.end() && pos == (*al)->beg_pos) {
- out << "= " << (*al)->predicate_string << '\n';
- base = *al++;
- }
- else if (pl != journal.period_entries.end() && pos == (*pl)->beg_pos) {
- out << "~ " << (*pl)->period_string << '\n';
- base = *pl++;
- }
-
- char c;
- if (base) {
- for (transactions_list::iterator x = base->transactions.begin();
- x != base->transactions.end();
- x++)
- if (! ((*x)->flags & TRANSACTION_AUTO)) {
- transaction_xdata(**x).dflags |= TRANSACTION_TO_DISPLAY;
- formatter(**x);
- }
- formatter.flush();
-
- while (pos < base->end_pos) {
- in.get(c);
- pos = in.tellg(); // pos++;
- }
- } else {
- in.get(c);
- pos = in.tellg(); // pos++;
- out.put(c);
- }
- }
-}
-
-} // namespace ledger
diff --git a/textual.h b/textual.h
deleted file mode 100644
index 8ad653c5..00000000
--- a/textual.h
+++ /dev/null
@@ -1,46 +0,0 @@
-#ifndef _TEXTUAL_H
-#define _TEXTUAL_H
-
-#include "parser.h"
-#include "format.h"
-#include "walk.h"
-
-namespace ledger {
-
-class textual_parser_t : public parser_t
-{
- public:
- virtual bool test(std::istream& in) const;
-
- virtual unsigned int parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master = NULL,
- const std::string * original_file = NULL);
-};
-
-transaction_t * parse_transaction_text(char * line, account_t * account);
-transaction_t * parse_transaction(std::istream& in, account_t * account);
-
-void write_textual_journal(journal_t& journal, std::string path,
- item_handler<transaction_t>& formatter,
- const std::string& write_hdr_format,
- std::ostream& out);
-
-class include_context : public file_context {
- public:
- include_context(const std::string& file, unsigned long line,
- const std::string& desc = "") throw()
- : file_context(file, line, desc) {}
- virtual ~include_context() throw() {}
-
- virtual void describe(std::ostream& out) const throw() {
- if (! desc.empty())
- out << desc << ": ";
- out << "\"" << file << "\", line " << line << ":" << std::endl;
- }
-};
-
-} // namespace ledger
-
-#endif // _TEXTUAL_H
diff --git a/timing.h b/timing.h
deleted file mode 100644
index 7e1029ea..00000000
--- a/timing.h
+++ /dev/null
@@ -1,62 +0,0 @@
-#ifndef _TIMING_H
-#define _TIMING_H
-
-#include "debug.h"
-
-#include <ctime>
-
-namespace ledger {
-
-class timing_t
-{
- public:
- std::clock_t begin;
- std::clock_t cumulative;
- std::string file;
- unsigned long line;
- std::string symbol;
- std::string category;
-
- timing_t(const std::string& _symbol, const std::string& _category)
- : begin(0), cumulative(0), symbol(_symbol), category(_category) {}
-
- timing_t(const std::string& _symbol)
- : begin(0), cumulative(0), symbol(_symbol) {}
-
- ~timing_t() {
- std::string cls = "timing.results.";
- cls += symbol;
- DEBUG_PRINT(cls.c_str(), file << ":" << line << ": "
- << category << " = "
- << (double(cumulative) / double(CLOCKS_PER_SEC)) << "s");
- }
-
- void start(const std::string& _file, unsigned long _line) {
- file = _file;
- line = _line;
- begin = std::clock();
- }
- void start() {
- begin = std::clock();
- }
-
- void stop() {
- cumulative += std::clock() - begin;
- }
-};
-
-#ifdef DEBUG_ENABLED
-#define TIMER_DEF(sym, cat) static timing_t sym(#sym, cat)
-#define TIMER_DEF_(sym) static timing_t sym(#sym, #sym)
-#define TIMER_START(sym) sym.start(__FILE__, __LINE__)
-#define TIMER_STOP(sym) sym.stop()
-#else
-#define TIMER_DEF(sym, cat)
-#define TIMER_DEF_(sym)
-#define TIMER_START(sym)
-#define TIMER_STOP(sym)
-#endif
-
-} // namespace ledger
-
-#endif // _TIMING_H
diff --git a/util.h b/util.h
deleted file mode 100644
index 21008a22..00000000
--- a/util.h
+++ /dev/null
@@ -1,62 +0,0 @@
-#ifndef _UTIL_H
-#define _UTIL_H
-
-#include <iostream>
-
-#if defined __FreeBSD__ && __FreeBSD__ <=4
-// FreeBSD has a broken isspace macro, so dont use it
-#undef isspace(c)
-#endif
-
-#if defined(__GNUG__) && __GNUG__ < 3
-namespace std {
- inline ostream & right (ostream & i) {
- i.setf(i.right, i.adjustfield);
- return i;
- }
- inline ostream & left (ostream & i) {
- i.setf(i.left, i.adjustfield);
- return i;
- }
-}
-typedef unsigned long istream_pos_type;
-typedef unsigned long ostream_pos_type;
-#else
-typedef std::istream::pos_type istream_pos_type;
-typedef std::ostream::pos_type ostream_pos_type;
-#endif
-
-inline char * skip_ws(char * ptr) {
- while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
- ptr++;
- return ptr;
-}
-
-inline char peek_next_nonws(std::istream& in) {
- char c = in.peek();
- while (! in.eof() && std::isspace(c)) {
- in.get(c);
- c = in.peek();
- }
- return c;
-}
-
-#define READ_INTO(str, targ, size, var, cond) { \
- char * _p = targ; \
- var = str.peek(); \
- while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \
- str.get(var); \
- if (str.eof()) \
- break; \
- if (var == '\\') { \
- str.get(var); \
- if (in.eof()) \
- break; \
- } \
- *_p++ = var; \
- var = str.peek(); \
- } \
- *_p = '\0'; \
-}
-
-#endif // _UTIL_H
diff --git a/valexpr.cc b/valexpr.cc
deleted file mode 100644
index 0a2f27ee..00000000
--- a/valexpr.cc
+++ /dev/null
@@ -1,1995 +0,0 @@
-#include "valexpr.h"
-#include "walk.h"
-#include "error.h"
-#include "datetime.h"
-#include "debug.h"
-#include "util.h"
-
-namespace ledger {
-
-value_expr amount_expr;
-value_expr total_expr;
-
-std::auto_ptr<scope_t> global_scope;
-datetime_t terminus;
-
-details_t::details_t(const transaction_t& _xact)
- : entry(_xact.entry), xact(&_xact), account(xact_account(_xact))
-{
- DEBUG_PRINT("ledger.memory.ctors", "ctor details_t");
-}
-
-bool compute_amount(value_expr_t * expr, amount_t& amt,
- const transaction_t * xact, value_expr_t * context)
-{
- value_t result;
- try {
- expr->compute(result, xact ? details_t(*xact) : details_t(), context);
- result.cast(value_t::AMOUNT);
- amt = *((amount_t *) result.data);
- }
- catch (error * err) {
- if (err->context.empty() ||
- ! dynamic_cast<valexpr_context *>(err->context.back()))
- err->context.push_back(new valexpr_context(expr));
- error_context * last = err->context.back();
- if (valexpr_context * ctxt = dynamic_cast<valexpr_context *>(last)) {
- ctxt->expr = expr->acquire();
- ctxt->desc = "While computing amount expression:";
- }
- throw err;
- }
- return true;
-}
-
-value_expr_t::~value_expr_t()
-{
- DEBUG_PRINT("ledger.memory.dtors", "dtor value_expr_t " << this);
-
- DEBUG_PRINT("ledger.valexpr.memory", "Destroying " << this);
- assert(refc == 0);
-
- if (left)
- left->release();
-
- switch (kind) {
- case F_CODE_MASK:
- case F_PAYEE_MASK:
- case F_NOTE_MASK:
- case F_ACCOUNT_MASK:
- case F_SHORT_ACCOUNT_MASK:
- case F_COMMODITY_MASK:
- assert(mask);
- delete mask;
- break;
-
- case CONSTANT:
- assert(value);
- delete value;
- break;
-
- default:
- if (kind > TERMINALS && right)
- right->release();
- break;
- }
-}
-
-namespace {
- int count_leaves(value_expr_t * expr)
- {
- int count = 0;
- if (expr->kind != value_expr_t::O_COM) {
- count = 1;
- } else {
- count += count_leaves(expr->left);
- count += count_leaves(expr->right);
- }
- return count;
- }
-
- value_expr_t * reduce_leaves(value_expr_t * expr, const details_t& details,
- value_expr_t * context)
- {
- if (! expr)
- return NULL;
-
- value_expr temp;
-
- if (expr->kind != value_expr_t::O_COM) {
- if (expr->kind < value_expr_t::TERMINALS) {
- temp.reset(expr);
- } else {
- temp.reset(new value_expr_t(value_expr_t::CONSTANT));
- temp->value = new value_t;
- expr->compute(*(temp->value), details, context);
- }
- } else {
- temp.reset(new value_expr_t(value_expr_t::O_COM));
- temp->set_left(reduce_leaves(expr->left, details, context));
- temp->set_right(reduce_leaves(expr->right, details, context));
- }
- return temp.release();
- }
-
- value_expr_t * find_leaf(value_expr_t * context, int goal, int& found)
- {
- if (! context)
- return NULL;
-
- if (context->kind != value_expr_t::O_COM) {
- if (goal == found++)
- return context;
- } else {
- value_expr_t * expr = find_leaf(context->left, goal, found);
- if (expr)
- return expr;
- expr = find_leaf(context->right, goal, found);
- if (expr)
- return expr;
- }
- return NULL;
- }
-}
-
-void value_expr_t::compute(value_t& result, const details_t& details,
- value_expr_t * context) const
-{
- try {
- switch (kind) {
- case ARG_INDEX:
- throw new compute_error("Cannot directly compute an arg_index");
-
- case CONSTANT:
- assert(value);
- result = *value;
- break;
-
- case F_NOW:
- result = terminus;
- break;
-
- case AMOUNT:
- if (details.xact) {
- if (transaction_has_xdata(*details.xact) &&
- transaction_xdata_(*details.xact).dflags & TRANSACTION_COMPOUND)
- result = transaction_xdata_(*details.xact).value;
- else
- result = details.xact->amount;
- }
- else if (details.account && account_has_xdata(*details.account)) {
- result = account_xdata(*details.account).value;
- }
- else {
- result = 0L;
- }
- break;
-
- case PRICE:
- if (details.xact) {
- bool set = false;
- if (transaction_has_xdata(*details.xact)) {
- transaction_xdata_t& xdata(transaction_xdata_(*details.xact));
- if (xdata.dflags & TRANSACTION_COMPOUND) {
- result = xdata.value.price();
- set = true;
- }
- }
- if (! set)
- result = details.xact->amount.price();
- }
- else if (details.account && account_has_xdata(*details.account)) {
- result = account_xdata(*details.account).value.price();
- }
- else {
- result = 0L;
- }
- break;
-
- case COST:
- if (details.xact) {
- bool set = false;
- if (transaction_has_xdata(*details.xact)) {
- transaction_xdata_t& xdata(transaction_xdata_(*details.xact));
- if (xdata.dflags & TRANSACTION_COMPOUND) {
- result = xdata.value.cost();
- set = true;
- }
- }
-
- if (! set) {
- if (details.xact->cost)
- result = *details.xact->cost;
- else
- result = details.xact->amount;
- }
- }
- else if (details.account && account_has_xdata(*details.account)) {
- result = account_xdata(*details.account).value.cost();
- }
- else {
- result = 0L;
- }
- break;
-
- case TOTAL:
- if (details.xact && transaction_has_xdata(*details.xact))
- result = transaction_xdata_(*details.xact).total;
- else if (details.account && account_has_xdata(*details.account))
- result = account_xdata(*details.account).total;
- else
- result = 0L;
- break;
- case PRICE_TOTAL:
- if (details.xact && transaction_has_xdata(*details.xact))
- result = transaction_xdata_(*details.xact).total.price();
- else if (details.account && account_has_xdata(*details.account))
- result = account_xdata(*details.account).total.price();
- else
- result = 0L;
- break;
- case COST_TOTAL:
- if (details.xact && transaction_has_xdata(*details.xact))
- result = transaction_xdata_(*details.xact).total.cost();
- else if (details.account && account_has_xdata(*details.account))
- result = account_xdata(*details.account).total.cost();
- else
- result = 0L;
- break;
-
- case VALUE_EXPR:
- if (amount_expr.get())
- amount_expr->compute(result, details, context);
- else
- result = 0L;
- break;
- case TOTAL_EXPR:
- if (total_expr.get())
- total_expr->compute(result, details, context);
- else
- result = 0L;
- break;
-
- case DATE:
- if (details.xact && transaction_has_xdata(*details.xact) &&
- transaction_xdata_(*details.xact).date)
- result = transaction_xdata_(*details.xact).date;
- else if (details.xact)
- result = details.xact->date();
- else if (details.entry)
- result = details.entry->date();
- else
- result = terminus;
- break;
-
- case ACT_DATE:
- if (details.xact && transaction_has_xdata(*details.xact) &&
- transaction_xdata_(*details.xact).date)
- result = transaction_xdata_(*details.xact).date;
- else if (details.xact)
- result = details.xact->actual_date();
- else if (details.entry)
- result = details.entry->actual_date();
- else
- result = terminus;
- break;
-
- case EFF_DATE:
- if (details.xact && transaction_has_xdata(*details.xact) &&
- transaction_xdata_(*details.xact).date)
- result = transaction_xdata_(*details.xact).date;
- else if (details.xact)
- result = details.xact->effective_date();
- else if (details.entry)
- result = details.entry->effective_date();
- else
- result = terminus;
- break;
-
- case CLEARED:
- if (details.xact)
- result = details.xact->state == transaction_t::CLEARED;
- else
- result = false;
- break;
- case PENDING:
- if (details.xact)
- result = details.xact->state == transaction_t::PENDING;
- else
- result = false;
- break;
-
- case REAL:
- if (details.xact)
- result = ! (details.xact->flags & TRANSACTION_VIRTUAL);
- else
- result = true;
- break;
-
- case ACTUAL:
- if (details.xact)
- result = ! (details.xact->flags & TRANSACTION_AUTO);
- else
- result = true;
- break;
-
- case INDEX:
- if (details.xact && transaction_has_xdata(*details.xact))
- result = long(transaction_xdata_(*details.xact).index + 1);
- else if (details.account && account_has_xdata(*details.account))
- result = long(account_xdata(*details.account).count);
- else
- result = 0L;
- break;
-
- case COUNT:
- if (details.xact && transaction_has_xdata(*details.xact))
- result = long(transaction_xdata_(*details.xact).index + 1);
- else if (details.account && account_has_xdata(*details.account))
- result = long(account_xdata(*details.account).total_count);
- else
- result = 0L;
- break;
-
- case DEPTH:
- if (details.account)
- result = long(details.account->depth);
- else
- result = 0L;
- break;
-
- case F_PRICE: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- expr->compute(result, details, context);
- result = result.price();
- break;
- }
-
- case F_DATE: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- expr->compute(result, details, context);
- result = result.date();
- break;
- }
-
- case F_DATECMP: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- expr->compute(result, details, context);
- result = result.date();
- if (! result)
- break;
-
- arg_index = 0;
- expr = find_leaf(context, 1, arg_index);
- value_t moment;
- expr->compute(moment, details, context);
- if (moment.type == value_t::DATETIME) {
- result.cast(value_t::INTEGER);
- moment.cast(value_t::INTEGER);
- result -= moment;
- } else {
- throw new compute_error("Invalid date passed to datecmp(value,date)",
- new valexpr_context(expr));
- }
- break;
- }
-
- case F_YEAR:
- case F_MONTH:
- case F_DAY: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- expr->compute(result, details, context);
-
- if (result.type != value_t::DATETIME)
- throw new compute_error("Invalid date passed to year|month|day(date)",
- new valexpr_context(expr));
-
- datetime_t& moment(*((datetime_t *)result.data));
- switch (kind) {
- case F_YEAR:
- result = (long)moment.year();
- break;
- case F_MONTH:
- result = (long)moment.month();
- break;
- case F_DAY:
- result = (long)moment.day();
- break;
- }
- break;
- }
-
- case F_ARITH_MEAN: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- if (details.xact && transaction_has_xdata(*details.xact)) {
- expr->compute(result, details, context);
- result /= amount_t(long(transaction_xdata_(*details.xact).index + 1));
- }
- else if (details.account && account_has_xdata(*details.account) &&
- account_xdata(*details.account).total_count) {
- expr->compute(result, details, context);
- result /= amount_t(long(account_xdata(*details.account).total_count));
- }
- else {
- result = 0L;
- }
- break;
- }
-
- case F_PARENT:
- if (details.account && details.account->parent)
- left->compute(result, details_t(*details.account->parent), context);
- break;
-
- case F_ABS: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- expr->compute(result, details, context);
- result.abs();
- break;
- }
-
- case F_ROUND: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- expr->compute(result, details, context);
- result.round();
- break;
- }
-
- case F_COMMODITY: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- expr->compute(result, details, context);
- if (result.type != value_t::AMOUNT)
- throw new compute_error("Argument to commodity() must be a commoditized amount",
- new valexpr_context(expr));
- amount_t temp("1");
- temp.set_commodity(((amount_t *) result.data)->commodity());
- result = temp;
- break;
- }
-
- case F_SET_COMMODITY: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- value_t temp;
- expr->compute(temp, details, context);
-
- arg_index = 0;
- expr = find_leaf(context, 1, arg_index);
- expr->compute(result, details, context);
- if (result.type != value_t::AMOUNT)
- throw new compute_error
- ("Second argument to set_commodity() must be a commoditized amount",
- new valexpr_context(expr));
- amount_t one("1");
- one.set_commodity(((amount_t *) result.data)->commodity());
- result = one;
-
- result *= temp;
- break;
- }
-
- case F_QUANTITY: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- expr->compute(result, details, context);
-
- balance_t * bal = NULL;
- switch (result.type) {
- case value_t::BALANCE_PAIR:
- bal = &((balance_pair_t *) result.data)->quantity;
- // fall through...
-
- case value_t::BALANCE:
- if (! bal)
- bal = (balance_t *) result.data;
-
- if (bal->amounts.size() < 2) {
- result.cast(value_t::AMOUNT);
- } else {
- value_t temp;
- for (amounts_map::const_iterator i = bal->amounts.begin();
- i != bal->amounts.end();
- i++) {
- amount_t x = (*i).second;
- x.clear_commodity();
- temp += x;
- }
- result = temp;
- assert(temp.type == value_t::AMOUNT);
- }
- // fall through...
-
- case value_t::AMOUNT:
- ((amount_t *) result.data)->clear_commodity();
- break;
-
- default:
- break;
- }
- break;
- }
-
- case F_CODE_MASK:
- assert(mask);
- if (details.entry)
- result = mask->match(details.entry->code);
- else
- result = false;
- break;
-
- case F_PAYEE_MASK:
- assert(mask);
- if (details.entry)
- result = mask->match(details.entry->payee);
- else
- result = false;
- break;
-
- case F_NOTE_MASK:
- assert(mask);
- if (details.xact)
- result = mask->match(details.xact->note);
- else
- result = false;
- break;
-
- case F_ACCOUNT_MASK:
- assert(mask);
- if (details.account)
- result = mask->match(details.account->fullname());
- else
- result = false;
- break;
-
- case F_SHORT_ACCOUNT_MASK:
- assert(mask);
- if (details.account)
- result = mask->match(details.account->name);
- else
- result = false;
- break;
-
- case F_COMMODITY_MASK:
- assert(mask);
- if (details.xact)
- result = mask->match(details.xact->amount.commodity().base_symbol());
- else
- result = false;
- break;
-
- case O_ARG: {
- int arg_index = 0;
- assert(left);
- assert(left->kind == ARG_INDEX);
- value_expr_t * expr = find_leaf(context, left->arg_index, arg_index);
- if (expr)
- expr->compute(result, details, context);
- else
- result = 0L;
- break;
- }
-
- case O_COM:
- if (! left)
- throw new compute_error("Comma operator missing left operand",
- new valexpr_context(this));
- if (! right)
- throw new compute_error("Comma operator missing right operand",
- new valexpr_context(this));
- left->compute(result, details, context);
- right->compute(result, details, context);
- break;
-
- case O_DEF:
- result = 0L;
- break;
-
- case O_REF: {
- assert(left);
- if (right) {
- value_expr args(reduce_leaves(right, details, context));
- left->compute(result, details, args.get());
- } else {
- left->compute(result, details, context);
- }
- break;
- }
-
- case F_VALUE: {
- int arg_index = 0;
- value_expr_t * expr = find_leaf(context, 0, arg_index);
- expr->compute(result, details, context);
-
- arg_index = 0;
- expr = find_leaf(context, 1, arg_index);
- value_t moment;
- expr->compute(moment, details, context);
- if (moment.type != value_t::DATETIME)
- throw new compute_error("Invalid date passed to P(value,date)",
- new valexpr_context(expr));
-
- result = result.value(*((datetime_t *)moment.data));
- break;
- }
-
- case O_NOT:
- left->compute(result, details, context);
- if (result.strip_annotations())
- result = false;
- else
- result = true;
- break;
-
- case O_QUES: {
- assert(left);
- assert(right);
- assert(right->kind == O_COL);
- left->compute(result, details, context);
- if (result.strip_annotations())
- right->left->compute(result, details, context);
- else
- right->right->compute(result, details, context);
- break;
- }
-
- case O_AND:
- assert(left);
- assert(right);
- left->compute(result, details, context);
- result = result.strip_annotations();
- if (result)
- right->compute(result, details, context);
- break;
-
- case O_OR:
- assert(left);
- assert(right);
- left->compute(result, details, context);
- if (! result.strip_annotations())
- right->compute(result, details, context);
- break;
-
- case O_NEQ:
- case O_EQ:
- case O_LT:
- case O_LTE:
- case O_GT:
- case O_GTE: {
- assert(left);
- assert(right);
- value_t temp;
- left->compute(temp, details, context);
- right->compute(result, details, context);
- switch (kind) {
- case O_NEQ: result = temp != result; break;
- case O_EQ: result = temp == result; break;
- case O_LT: result = temp < result; break;
- case O_LTE: result = temp <= result; break;
- case O_GT: result = temp > result; break;
- case O_GTE: result = temp >= result; break;
- default: assert(0); break;
- }
- break;
- }
-
- case O_NEG:
- assert(left);
- left->compute(result, details, context);
- result.negate();
- break;
-
- case O_ADD:
- case O_SUB:
- case O_MUL:
- case O_DIV: {
- assert(left);
- assert(right);
- value_t temp;
- right->compute(temp, details, context);
- left->compute(result, details, context);
- switch (kind) {
- case O_ADD: result += temp; break;
- case O_SUB: result -= temp; break;
- case O_MUL: result *= temp; break;
- case O_DIV: result /= temp; break;
- default: assert(0); break;
- }
- break;
- }
-
- case O_PERC: {
- assert(left);
- result = "100.0%";
- value_t temp;
- left->compute(temp, details, context);
- result *= temp;
- break;
- }
-
- case LAST:
- default:
- assert(0);
- break;
- }
- }
- catch (error * err) {
- if (err->context.empty() ||
- ! dynamic_cast<valexpr_context *>(err->context.back()))
- err->context.push_back(new valexpr_context(this));
- throw err;
- }
-}
-
-static inline void unexpected(char c, char wanted = '\0') {
- if ((unsigned char) c == 0xff) {
- if (wanted)
- throw new value_expr_error(std::string("Missing '") + wanted + "'");
- else
- throw new value_expr_error("Unexpected end");
- } else {
- if (wanted)
- throw new value_expr_error(std::string("Invalid char '") + c +
- "' (wanted '" + wanted + "')");
- else
- throw new value_expr_error(std::string("Invalid char '") + c + "'");
- }
-}
-
-value_expr_t * parse_value_term(std::istream& in, scope_t * scope,
- const short flags);
-
-inline value_expr_t * parse_value_term(const char * p, scope_t * scope,
- const short flags) {
- std::istringstream stream(p);
- return parse_value_term(stream, scope, flags);
-}
-
-value_expr_t * parse_value_term(std::istream& in, scope_t * scope,
- const short flags)
-{
- value_expr node;
-
- char buf[256];
- char c = peek_next_nonws(in);
-
- if (flags & PARSE_VALEXPR_RELAXED) {
- if (c == '@') {
- in.get(c);
- c = peek_next_nonws(in);
- }
- else if (! (c == '(' || c == '[' || c == '{' || c == '/')) {
- amount_t temp;
- char prev_c = c;
- unsigned long pos = 0;
- // When in relaxed parsing mode, we do want to migrate commodity
- // flags, so that any precision specified by the user updates
- // the current maximum precision displayed.
- pos = (long)in.tellg();
-
- unsigned char parse_flags = 0;
- if (flags & PARSE_VALEXPR_NO_MIGRATE)
- parse_flags |= AMOUNT_PARSE_NO_MIGRATE;
- if (flags & PARSE_VALEXPR_NO_REDUCE)
- parse_flags |= AMOUNT_PARSE_NO_REDUCE;
-
- if (! temp.parse(in, parse_flags | AMOUNT_PARSE_SOFT_FAIL)) {
- in.clear();
- in.seekg(pos, std::ios::beg);
- c = prev_c;
- goto parse_ident;
- }
-
- node.reset(new value_expr_t(value_expr_t::CONSTANT));
- node->value = new value_t(temp);
- goto parsed;
- }
- }
-
- parse_ident:
- if (std::isdigit(c) || c == '.') {
- READ_INTO(in, buf, 255, c, std::isdigit(c) || c == '.');
- amount_t temp;
- temp.parse(buf, AMOUNT_PARSE_NO_MIGRATE);
- node.reset(new value_expr_t(value_expr_t::CONSTANT));
- node->value = new value_t(temp);
- goto parsed;
- }
- else if (std::isalnum(c) || c == '_') {
- bool have_args = false;
- istream_pos_type beg;
-
- READ_INTO(in, buf, 255, c, std::isalnum(c) || c == '_');
- c = peek_next_nonws(in);
- if (c == '(') {
- in.get(c);
- beg = in.tellg();
-
- int paren_depth = 0;
- while (! in.eof()) {
- in.get(c);
- if (c == '(' || c == '{' || c == '[')
- paren_depth++;
- else if (c == ')' || c == '}' || c == ']') {
- if (paren_depth == 0)
- break;
- paren_depth--;
- }
- }
- if (c != ')')
- unexpected(c, ')');
-
- have_args = true;
- c = peek_next_nonws(in);
- }
-
- bool definition = false;
- if (c == '=') {
- in.get(c);
- if ((flags & PARSE_VALEXPR_NO_ASSIGN) ||
- peek_next_nonws(in) == '=') {
- in.unget();
- c = '\0';
- } else {
- definition = true;
- }
- }
-
- if (definition) {
- std::auto_ptr<scope_t> params(new scope_t(scope));
-
- int arg_index = 0;
- if (have_args) {
- bool done = false;
-
- in.clear();
- in.seekg(beg, std::ios::beg);
- while (! done && ! in.eof()) {
- char ident[32];
- READ_INTO(in, ident, 31, c, std::isalnum(c) || c == '_');
-
- c = peek_next_nonws(in);
- in.get(c);
- if (c != ',' && c != ')')
- unexpected(c, ')');
- else if (c == ')')
- done = true;
-
- // Define the parameter so that on lookup the parser will find
- // an O_ARG value.
- node.reset(new value_expr_t(value_expr_t::O_ARG));
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = arg_index++;
- params->define(ident, node.release());
- }
-
- if (peek_next_nonws(in) != '=') {
- in.get(c);
- unexpected(c, '=');
- }
- in.get(c);
- }
-
- // Define the value associated with the defined identifier
- value_expr def(parse_boolean_expr(in, params.get(), flags));
- if (! def.get())
- throw new value_expr_error(std::string("Definition failed for '") + buf + "'");
-
- node.reset(new value_expr_t(value_expr_t::O_DEF));
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = arg_index;
- node->set_right(def.release());
-
- scope->define(buf, node.get());
- } else {
- assert(scope);
- value_expr_t * def = scope->lookup(buf);
- if (! def) {
- if (buf[1] == '\0' &&
- (buf[0] == 'c' || buf[0] == 'C' || buf[0] == 'p' ||
- buf[0] == 'w' || buf[0] == 'W' || buf[0] == 'e')) {
- in.unget();
- goto find_term;
- }
- throw new value_expr_error(std::string("Unknown identifier '") +
- buf + "'");
- }
- else if (def->kind == value_expr_t::O_DEF) {
- node.reset(new value_expr_t(value_expr_t::O_REF));
- node->set_left(def->right);
-
- int count = 0;
- if (have_args) {
- in.clear();
- in.seekg(beg, std::ios::beg);
- value_expr args
- (parse_value_expr(in, scope, flags | PARSE_VALEXPR_PARTIAL));
-
- if (peek_next_nonws(in) != ')') {
- in.get(c);
- unexpected(c, ')');
- }
- in.get(c);
-
- if (args.get()) {
- count = count_leaves(args.get());
- node->set_right(args.release());
- }
- }
-
- if (count != def->left->arg_index) {
- std::ostringstream errmsg;
- errmsg << "Wrong number of arguments to '" << buf
- << "': saw " << count << ", wanted " << def->left->arg_index;
- throw new value_expr_error(errmsg.str());
- }
- }
- else {
- node.reset(def);
- }
- }
- goto parsed;
- }
-
- find_term:
- in.get(c);
- switch (c) {
- // Functions
- case '^':
- node.reset(new value_expr_t(value_expr_t::F_PARENT));
- node->set_left(parse_value_term(in, scope, flags));
- break;
-
- // Other
- case 'c':
- case 'C':
- case 'p':
- case 'w':
- case 'W':
- case 'e':
- case '/': {
- bool code_mask = c == 'c';
- bool commodity_mask = c == 'C';
- bool payee_mask = c == 'p';
- bool note_mask = c == 'e';
- bool short_account_mask = c == 'w';
-
- if (c == '/') {
- c = peek_next_nonws(in);
- if (c == '/') {
- in.get(c);
- c = in.peek();
- if (c == '/') {
- in.get(c);
- c = in.peek();
- short_account_mask = true;
- } else {
- payee_mask = true;
- }
- }
- } else {
- in.get(c);
- }
-
- // Read in the regexp
- READ_INTO(in, buf, 255, c, c != '/');
- if (c != '/')
- unexpected(c, '/');
-
- value_expr_t::kind_t kind;
-
- if (short_account_mask)
- kind = value_expr_t::F_SHORT_ACCOUNT_MASK;
- else if (code_mask)
- kind = value_expr_t::F_CODE_MASK;
- else if (commodity_mask)
- kind = value_expr_t::F_COMMODITY_MASK;
- else if (payee_mask)
- kind = value_expr_t::F_PAYEE_MASK;
- else if (note_mask)
- kind = value_expr_t::F_NOTE_MASK;
- else
- kind = value_expr_t::F_ACCOUNT_MASK;
-
- in.get(c);
- node.reset(new value_expr_t(kind));
- node->mask = new mask_t(buf);
- break;
- }
-
- case '{': {
- amount_t temp;
- temp.parse(in, AMOUNT_PARSE_NO_MIGRATE);
- in.get(c);
- if (c != '}')
- unexpected(c, '}');
-
- node.reset(new value_expr_t(value_expr_t::CONSTANT));
- node->value = new value_t(temp);
- break;
- }
-
- case '(': {
- std::auto_ptr<scope_t> locals(new scope_t(scope));
- node.reset(parse_value_expr(in, locals.get(),
- flags | PARSE_VALEXPR_PARTIAL));
- in.get(c);
- if (c != ')')
- unexpected(c, ')');
- break;
- }
-
- case '[': {
- READ_INTO(in, buf, 255, c, c != ']');
- if (c != ']')
- unexpected(c, ']');
- in.get(c);
-
- interval_t timespan(buf);
- node.reset(new value_expr_t(value_expr_t::CONSTANT));
- node->value = new value_t(timespan.first());
- break;
- }
-
- default:
- in.unget();
- break;
- }
-
- parsed:
- return node.release();
-}
-
-value_expr_t * parse_mul_expr(std::istream& in, scope_t * scope,
- const short flags)
-{
- value_expr node;
-
- if (peek_next_nonws(in) == '%') {
- char c;
- in.get(c);
- node.reset(new value_expr_t(value_expr_t::O_PERC));
- node->set_left(parse_value_term(in, scope, flags));
- return node.release();
- }
-
- node.reset(parse_value_term(in, scope, flags));
-
- if (node.get() && ! in.eof()) {
- char c = peek_next_nonws(in);
- while (c == '*' || c == '/') {
- in.get(c);
- switch (c) {
- case '*': {
- value_expr prev(node.release());
- node.reset(new value_expr_t(value_expr_t::O_MUL));
- node->set_left(prev.release());
- node->set_right(parse_value_term(in, scope, flags));
- break;
- }
-
- case '/': {
- value_expr prev(node.release());
- node.reset(new value_expr_t(value_expr_t::O_DIV));
- node->set_left(prev.release());
- node->set_right(parse_value_term(in, scope, flags));
- break;
- }
- }
- c = peek_next_nonws(in);
- }
- }
-
- return node.release();
-}
-
-value_expr_t * parse_add_expr(std::istream& in, scope_t * scope,
- const short flags)
-{
- value_expr node;
-
- if (peek_next_nonws(in) == '-') {
- char c;
- in.get(c);
- value_expr expr(parse_mul_expr(in, scope, flags));
- if (expr->kind == value_expr_t::CONSTANT) {
- expr->value->negate();
- return expr.release();
- }
- node.reset(new value_expr_t(value_expr_t::O_NEG));
- node->set_left(expr.release());
- return node.release();
- }
-
- node.reset(parse_mul_expr(in, scope, flags));
-
- if (node.get() && ! in.eof()) {
- char c = peek_next_nonws(in);
- while (c == '+' || c == '-') {
- in.get(c);
- switch (c) {
- case '+': {
- value_expr prev(node.release());
- node.reset(new value_expr_t(value_expr_t::O_ADD));
- node->set_left(prev.release());
- node->set_right(parse_mul_expr(in, scope, flags));
- break;
- }
-
- case '-': {
- value_expr prev(node.release());
- node.reset(new value_expr_t(value_expr_t::O_SUB));
- node->set_left(prev.release());
- node->set_right(parse_mul_expr(in, scope, flags));
- break;
- }
- }
- c = peek_next_nonws(in);
- }
- }
-
- return node.release();
-}
-
-value_expr_t * parse_logic_expr(std::istream& in, scope_t * scope,
- const short flags)
-{
- value_expr node;
-
- if (peek_next_nonws(in) == '!') {
- char c;
- in.get(c);
- node.reset(new value_expr_t(value_expr_t::O_NOT));
- node->set_left(parse_add_expr(in, scope, flags));
- return node.release();
- }
-
- node.reset(parse_add_expr(in, scope, flags));
-
- if (node.get() && ! in.eof()) {
- char c = peek_next_nonws(in);
- if (c == '!' || c == '=' || c == '<' || c == '>') {
- in.get(c);
- switch (c) {
- case '!':
- case '=': {
- bool negate = c == '!';
- if (! negate && (flags & PARSE_VALEXPR_NO_ASSIGN)) {
- in.unget();
- break;
- }
- else if ((c = peek_next_nonws(in)) == '=') {
- in.get(c);
- }
- else {
- unexpected(c, '=');
- }
- value_expr prev(node.release());
- node.reset(new value_expr_t(negate ? value_expr_t::O_NEQ :
- value_expr_t::O_EQ));
- node->set_left(prev.release());
- node->set_right(parse_add_expr(in, scope, flags));
- break;
- }
-
- case '<': {
- value_expr prev(node.release());
- node.reset(new value_expr_t(value_expr_t::O_LT));
- if (peek_next_nonws(in) == '=') {
- in.get(c);
- node->kind = value_expr_t::O_LTE;
- }
- node->set_left(prev.release());
- node->set_right(parse_add_expr(in, scope, flags));
- break;
- }
-
- case '>': {
- value_expr prev(node.release());
- node.reset(new value_expr_t(value_expr_t::O_GT));
- if (peek_next_nonws(in) == '=') {
- in.get(c);
- node->kind = value_expr_t::O_GTE;
- }
- node->set_left(prev.release());
- node->set_right(parse_add_expr(in, scope, flags));
- break;
- }
-
- default:
- if (! in.eof())
- unexpected(c);
- break;
- }
- }
- }
-
- return node.release();
-}
-
-value_expr_t * parse_boolean_expr(std::istream& in, scope_t * scope,
- const short flags)
-{
- value_expr node(parse_logic_expr(in, scope, flags));
-
- if (node.get() && ! in.eof()) {
- char c = peek_next_nonws(in);
- while (c == '&' || c == '|' || c == '?') {
- in.get(c);
- switch (c) {
- case '&': {
- value_expr prev(node.release());
- node.reset(new value_expr_t(value_expr_t::O_AND));
- node->set_left(prev.release());
- node->set_right(parse_logic_expr(in, scope, flags));
- break;
- }
-
- case '|': {
- value_expr prev(node.release());
- node.reset(new value_expr_t(value_expr_t::O_OR));
- node->set_left(prev.release());
- node->set_right(parse_logic_expr(in, scope, flags));
- break;
- }
-
- case '?': {
- value_expr prev(node.release());
- node.reset(new value_expr_t(value_expr_t::O_QUES));
- node->set_left(prev.release());
- node->set_right(new value_expr_t(value_expr_t::O_COL));
- node->right->set_left(parse_logic_expr(in, scope, flags));
- c = peek_next_nonws(in);
- if (c != ':')
- unexpected(c, ':');
- in.get(c);
- node->right->set_right(parse_logic_expr(in, scope, flags));
- break;
- }
-
- default:
- if (! in.eof())
- unexpected(c);
- break;
- }
- c = peek_next_nonws(in);
- }
- }
-
- return node.release();
-}
-
-void init_value_expr()
-{
- global_scope.reset(new scope_t());
- scope_t * globals = global_scope.get();
-
- value_expr_t * node;
-
- // Basic terms
- node = new value_expr_t(value_expr_t::F_NOW);
- globals->define("m", node);
- globals->define("now", node);
- globals->define("today", node);
-
- node = new value_expr_t(value_expr_t::AMOUNT);
- globals->define("a", node);
- globals->define("amount", node);
-
- node = new value_expr_t(value_expr_t::PRICE);
- globals->define("i", node);
- globals->define("price", node);
-
- node = new value_expr_t(value_expr_t::COST);
- globals->define("b", node);
- globals->define("cost", node);
-
- node = new value_expr_t(value_expr_t::DATE);
- globals->define("d", node);
- globals->define("date", node);
-
- node = new value_expr_t(value_expr_t::ACT_DATE);
- globals->define("act_date", node);
- globals->define("actual_date", node);
-
- node = new value_expr_t(value_expr_t::EFF_DATE);
- globals->define("eff_date", node);
- globals->define("effective_date", node);
-
- node = new value_expr_t(value_expr_t::CLEARED);
- globals->define("X", node);
- globals->define("cleared", node);
-
- node = new value_expr_t(value_expr_t::PENDING);
- globals->define("Y", node);
- globals->define("pending", node);
-
- node = new value_expr_t(value_expr_t::REAL);
- globals->define("R", node);
- globals->define("real", node);
-
- node = new value_expr_t(value_expr_t::ACTUAL);
- globals->define("L", node);
- globals->define("actual", node);
-
- node = new value_expr_t(value_expr_t::INDEX);
- globals->define("n", node);
- globals->define("index", node);
-
- node = new value_expr_t(value_expr_t::COUNT);
- globals->define("N", node);
- globals->define("count", node);
-
- node = new value_expr_t(value_expr_t::DEPTH);
- globals->define("l", node);
- globals->define("depth", node);
-
- node = new value_expr_t(value_expr_t::TOTAL);
- globals->define("O", node);
- globals->define("total", node);
-
- node = new value_expr_t(value_expr_t::PRICE_TOTAL);
- globals->define("I", node);
- globals->define("total_price", node);
-
- node = new value_expr_t(value_expr_t::COST_TOTAL);
- globals->define("B", node);
- globals->define("total_cost", node);
-
- // Relating to format_t
- globals->define("t", new value_expr_t(value_expr_t::VALUE_EXPR));
- globals->define("T", new value_expr_t(value_expr_t::TOTAL_EXPR));
-
- // Functions
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 1;
- node->set_right(new value_expr_t(value_expr_t::F_ABS));
- globals->define("U", node);
- globals->define("abs", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 1;
- node->set_right(new value_expr_t(value_expr_t::F_ROUND));
- globals->define("round", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 1;
- node->set_right(new value_expr_t(value_expr_t::F_QUANTITY));
- globals->define("S", node);
- globals->define("quant", node);
- globals->define("quantity", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 1;
- node->set_right(new value_expr_t(value_expr_t::F_COMMODITY));
- globals->define("comm", node);
- globals->define("commodity", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 2;
- node->set_right(new value_expr_t(value_expr_t::F_SET_COMMODITY));
- globals->define("setcomm", node);
- globals->define("set_commodity", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 1;
- node->set_right(new value_expr_t(value_expr_t::F_ARITH_MEAN));
- globals->define("A", node);
- globals->define("avg", node);
- globals->define("mean", node);
- globals->define("average", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 2;
- node->set_right(new value_expr_t(value_expr_t::F_VALUE));
- globals->define("P", node);
-
- parse_value_definition("@value=@P(@t,@m)", globals);
- parse_value_definition("@total_value=@P(@T,@m)", globals);
- parse_value_definition("@valueof(x)=@P(@x,@m)", globals);
- parse_value_definition("@datedvalueof(x,y)=@P(@x,@y)", globals);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 1;
- node->set_right(new value_expr_t(value_expr_t::F_PRICE));
- globals->define("priceof", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 1;
- node->set_right(new value_expr_t(value_expr_t::F_DATE));
- globals->define("dateof", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 2;
- node->set_right(new value_expr_t(value_expr_t::F_DATECMP));
- globals->define("datecmp", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 1;
- node->set_right(new value_expr_t(value_expr_t::F_YEAR));
- globals->define("yearof", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 1;
- node->set_right(new value_expr_t(value_expr_t::F_MONTH));
- globals->define("monthof", node);
-
- node = new value_expr_t(value_expr_t::O_DEF);
- node->set_left(new value_expr_t(value_expr_t::ARG_INDEX));
- node->left->arg_index = 1;
- node->set_right(new value_expr_t(value_expr_t::F_DAY));
- globals->define("dayof", node);
-
- parse_value_definition("@year=@yearof(@d)", globals);
- parse_value_definition("@month=@monthof(@d)", globals);
- parse_value_definition("@day=@dayof(@d)", globals);
-
- // Macros
- node = parse_value_expr("@P(@a,@d)");
- globals->define("v", node);
- globals->define("market", node);
-
- node = parse_value_expr("@P(@O,@d)");
- globals->define("V", node);
- globals->define("total_market", node);
-
- node = parse_value_expr("@v-@b");
- globals->define("g", node);
- globals->define("gain", node);
-
- node = parse_value_expr("@V-@B");
- globals->define("G", node);
- globals->define("total_gain", node);
-
- parse_value_definition("@min(x,y)=@x<@y?@x:@y", globals);
- parse_value_definition("@max(x,y)=@x>@y?@x:@y", globals);
-}
-
-value_expr_t * parse_value_expr(std::istream& in, scope_t * scope,
- const short flags)
-{
- if (! global_scope.get())
- init_value_expr();
-
- std::auto_ptr<scope_t> this_scope(new scope_t(scope ? scope :
- global_scope.get()));
- value_expr node;
- node.reset(parse_boolean_expr(in, this_scope.get(), flags));
-
- if (node.get() && ! in.eof()) {
- char c = peek_next_nonws(in);
- while (c == ',') {
- in.get(c);
- switch (c) {
- case ',': {
- value_expr prev(node.release());
- node.reset(new value_expr_t(value_expr_t::O_COM));
- node->set_left(prev.release());
- node->set_right(parse_logic_expr(in, this_scope.get(), flags));
- break;
- }
-
- default:
- if (! in.eof())
- unexpected(c);
- break;
- }
- c = peek_next_nonws(in);
- }
- }
-
- char c;
- if (! node.get()) {
- in.get(c);
- if (in.eof())
- throw new value_expr_error(std::string("Failed to parse value expression"));
- else
- unexpected(c);
- } else if (! (flags & PARSE_VALEXPR_PARTIAL)) {
- in.get(c);
- if (! in.eof())
- unexpected(c);
- else
- in.unget();
- }
-
- return node.release();
-}
-
-valexpr_context::valexpr_context(const ledger::value_expr_t * _expr,
- const std::string& desc) throw()
- : expr(_expr), error_node(_expr), error_context(desc)
-{
- error_node->acquire();
-}
-
-valexpr_context::~valexpr_context() throw()
-{
- if (expr) expr->release();
- if (error_node) error_node->release();
-}
-
-void valexpr_context::describe(std::ostream& out) const throw()
-{
- if (! expr) {
- out << "valexpr_context expr not set!" << std::endl;
- return;
- }
-
- if (! desc.empty())
- out << desc << std::endl;
-
- out << " ";
- unsigned long start = (long)out.tellp() - 1;
- unsigned long begin;
- unsigned long end;
- bool found = ledger::write_value_expr(out, expr, true,
- error_node, &begin, &end);
- out << std::endl;
- if (found) {
- out << " ";
- for (int i = 0; i < end - start; i++) {
- if (i >= begin - start)
- out << "^";
- else
- out << " ";
- }
- out << std::endl;
- }
-}
-
-bool write_value_expr(std::ostream& out,
- const value_expr_t * node,
- const bool relaxed,
- const value_expr_t * node_to_find,
- unsigned long * start_pos,
- unsigned long * end_pos)
-{
- int arg_index = 0;
- bool found = false;
- value_expr_t * expr;
-
- if (start_pos && node == node_to_find) {
- *start_pos = (long)out.tellp() - 1;
- found = true;
- }
-
- std::string symbol;
-
- switch (node->kind) {
- case value_expr_t::ARG_INDEX:
- out << node->arg_index;
- break;
-
- case value_expr_t::CONSTANT:
- switch (node->value->type) {
- case value_t::BOOLEAN:
- assert(0);
- break;
- case value_t::DATETIME:
- out << '[' << *(node->value) << ']';
- break;
- case value_t::INTEGER:
- case value_t::AMOUNT:
- if (! relaxed)
- out << '{';
- out << *(node->value);
- if (! relaxed)
- out << '}';
- break;
- case value_t::BALANCE:
- case value_t::BALANCE_PAIR:
- assert(0);
- break;
- }
- break;
-
- case value_expr_t::AMOUNT:
- symbol = "amount"; break;
- case value_expr_t::PRICE:
- symbol = "price"; break;
- case value_expr_t::COST:
- symbol = "cost"; break;
- case value_expr_t::DATE:
- symbol = "date"; break;
- case value_expr_t::ACT_DATE:
- symbol = "actual_date"; break;
- case value_expr_t::EFF_DATE:
- symbol = "effective_date"; break;
- case value_expr_t::CLEARED:
- symbol = "cleared"; break;
- case value_expr_t::PENDING:
- symbol = "pending"; break;
- case value_expr_t::REAL:
- symbol = "real"; break;
- case value_expr_t::ACTUAL:
- symbol = "actual"; break;
- case value_expr_t::INDEX:
- symbol = "index"; break;
- case value_expr_t::COUNT:
- symbol = "count"; break;
- case value_expr_t::DEPTH:
- symbol = "depth"; break;
- case value_expr_t::TOTAL:
- symbol = "total"; break;
- case value_expr_t::PRICE_TOTAL:
- symbol = "total_price"; break;
- case value_expr_t::COST_TOTAL:
- symbol = "total_cost"; break;
- case value_expr_t::F_NOW:
- symbol = "now"; break;
-
- case value_expr_t::VALUE_EXPR:
- if (write_value_expr(out, amount_expr.get(), relaxed,
- node_to_find, start_pos, end_pos))
- found = true;
- break;
- case value_expr_t::TOTAL_EXPR:
- if (write_value_expr(out, total_expr.get(), relaxed,
- node_to_find, start_pos, end_pos))
- found = true;
- break;
-
- case value_expr_t::F_ARITH_MEAN:
- symbol = "average"; break;
- case value_expr_t::F_ABS:
- symbol = "abs"; break;
- case value_expr_t::F_QUANTITY:
- symbol = "quantity"; break;
- case value_expr_t::F_COMMODITY:
- symbol = "commodity"; break;
- case value_expr_t::F_SET_COMMODITY:
- symbol = "set_commodity"; break;
- case value_expr_t::F_VALUE:
- symbol = "valueof"; break;
- case value_expr_t::F_PRICE:
- symbol = "priceof"; break;
- case value_expr_t::F_DATE:
- symbol = "dateof"; break;
- case value_expr_t::F_DATECMP:
- symbol = "datecmp"; break;
- case value_expr_t::F_YEAR:
- symbol = "yearof"; break;
- case value_expr_t::F_MONTH:
- symbol = "monthof"; break;
- case value_expr_t::F_DAY:
- symbol = "dayof"; break;
-
- case value_expr_t::F_CODE_MASK:
- out << "c/" << node->mask->pattern << "/";
- break;
- case value_expr_t::F_PAYEE_MASK:
- out << "p/" << node->mask->pattern << "/";
- break;
- case value_expr_t::F_NOTE_MASK:
- out << "e/" << node->mask->pattern << "/";
- break;
- case value_expr_t::F_ACCOUNT_MASK:
- out << "W/" << node->mask->pattern << "/";
- break;
- case value_expr_t::F_SHORT_ACCOUNT_MASK:
- out << "w/" << node->mask->pattern << "/";
- break;
- case value_expr_t::F_COMMODITY_MASK:
- out << "C/" << node->mask->pattern << "/";
- break;
-
- case value_expr_t::O_NOT:
- out << "!";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- break;
- case value_expr_t::O_NEG:
- out << "-";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- break;
- case value_expr_t::O_PERC:
- out << "%";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- break;
-
- case value_expr_t::O_ARG:
- out << "@arg" << node->arg_index;
- break;
- case value_expr_t::O_DEF:
- out << "<def args=\"";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << "\" value=\"";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << "\">";
- break;
-
- case value_expr_t::O_REF:
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- if (node->right) {
- out << "(";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- }
- break;
-
- case value_expr_t::O_COM:
- if (node->left &&
- write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ", ";
- if (node->right &&
- write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- break;
- case value_expr_t::O_QUES:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " ? ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
- case value_expr_t::O_COL:
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " : ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- break;
-
- case value_expr_t::O_AND:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " & ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
- case value_expr_t::O_OR:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " | ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
-
- case value_expr_t::O_NEQ:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " != ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
- case value_expr_t::O_EQ:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " == ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
- case value_expr_t::O_LT:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " < ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
- case value_expr_t::O_LTE:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " <= ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
- case value_expr_t::O_GT:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " > ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
- case value_expr_t::O_GTE:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " >= ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
-
- case value_expr_t::O_ADD:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " + ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
- case value_expr_t::O_SUB:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " - ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
- case value_expr_t::O_MUL:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " * ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
- case value_expr_t::O_DIV:
- out << "(";
- if (write_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << " / ";
- if (write_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
- found = true;
- out << ")";
- break;
-
- case value_expr_t::LAST:
- default:
- assert(0);
- break;
- }
-
- if (! symbol.empty()) {
- if (commodity_t::find(symbol))
- out << '@';
- out << symbol;
- }
-
- if (end_pos && node == node_to_find)
- *end_pos = (long)out.tellp() - 1;
-
- return found;
-}
-
-void dump_value_expr(std::ostream& out, const value_expr_t * node,
- const int depth)
-{
- out.setf(std::ios::left);
- out.width(10);
- out << node << " ";
-
- for (int i = 0; i < depth; i++)
- out << " ";
-
- switch (node->kind) {
- case value_expr_t::ARG_INDEX:
- out << "ARG_INDEX - " << node->arg_index;
- break;
- case value_expr_t::CONSTANT:
- out << "CONSTANT - " << *(node->value);
- break;
-
- case value_expr_t::AMOUNT: out << "AMOUNT"; break;
- case value_expr_t::PRICE: out << "PRICE"; break;
- case value_expr_t::COST: out << "COST"; break;
- case value_expr_t::DATE: out << "DATE"; break;
- case value_expr_t::ACT_DATE: out << "ACT_DATE"; break;
- case value_expr_t::EFF_DATE: out << "EFF_DATE"; break;
- case value_expr_t::CLEARED: out << "CLEARED"; break;
- case value_expr_t::PENDING: out << "PENDING"; break;
- case value_expr_t::REAL: out << "REAL"; break;
- case value_expr_t::ACTUAL: out << "ACTUAL"; break;
- case value_expr_t::INDEX: out << "INDEX"; break;
- case value_expr_t::COUNT: out << "COUNT"; break;
- case value_expr_t::DEPTH: out << "DEPTH"; break;
- case value_expr_t::TOTAL: out << "TOTAL"; break;
- case value_expr_t::PRICE_TOTAL: out << "PRICE_TOTAL"; break;
- case value_expr_t::COST_TOTAL: out << "COST_TOTAL"; break;
-
- case value_expr_t::VALUE_EXPR: out << "VALUE_EXPR"; break;
- case value_expr_t::TOTAL_EXPR: out << "TOTAL_EXPR"; break;
-
- case value_expr_t::F_NOW: out << "F_NOW"; break;
- case value_expr_t::F_ARITH_MEAN: out << "F_ARITH_MEAN"; break;
- case value_expr_t::F_ABS: out << "F_ABS"; break;
- case value_expr_t::F_QUANTITY: out << "F_QUANTITY"; break;
- case value_expr_t::F_COMMODITY: out << "F_COMMODITY"; break;
- case value_expr_t::F_SET_COMMODITY: out << "F_SET_COMMODITY"; break;
- case value_expr_t::F_CODE_MASK: out << "F_CODE_MASK"; break;
- case value_expr_t::F_PAYEE_MASK: out << "F_PAYEE_MASK"; break;
- case value_expr_t::F_NOTE_MASK: out << "F_NOTE_MASK"; break;
- case value_expr_t::F_ACCOUNT_MASK:
- out << "F_ACCOUNT_MASK"; break;
- case value_expr_t::F_SHORT_ACCOUNT_MASK:
- out << "F_SHORT_ACCOUNT_MASK"; break;
- case value_expr_t::F_COMMODITY_MASK:
- out << "F_COMMODITY_MASK"; break;
- case value_expr_t::F_VALUE: out << "F_VALUE"; break;
- case value_expr_t::F_PRICE: out << "F_PRICE"; break;
- case value_expr_t::F_DATE: out << "F_DATE"; break;
- case value_expr_t::F_DATECMP: out << "F_DATECMP"; break;
- case value_expr_t::F_YEAR: out << "F_YEAR"; break;
- case value_expr_t::F_MONTH: out << "F_MONTH"; break;
- case value_expr_t::F_DAY: out << "F_DAY"; break;
-
- case value_expr_t::O_NOT: out << "O_NOT"; break;
- case value_expr_t::O_ARG: out << "O_ARG"; break;
- case value_expr_t::O_DEF: out << "O_DEF"; break;
- case value_expr_t::O_REF: out << "O_REF"; break;
- case value_expr_t::O_COM: out << "O_COM"; break;
- case value_expr_t::O_QUES: out << "O_QUES"; break;
- case value_expr_t::O_COL: out << "O_COL"; break;
- case value_expr_t::O_AND: out << "O_AND"; break;
- case value_expr_t::O_OR: out << "O_OR"; break;
- case value_expr_t::O_NEQ: out << "O_NEQ"; break;
- case value_expr_t::O_EQ: out << "O_EQ"; break;
- case value_expr_t::O_LT: out << "O_LT"; break;
- case value_expr_t::O_LTE: out << "O_LTE"; break;
- case value_expr_t::O_GT: out << "O_GT"; break;
- case value_expr_t::O_GTE: out << "O_GTE"; break;
- case value_expr_t::O_NEG: out << "O_NEG"; break;
- case value_expr_t::O_ADD: out << "O_ADD"; break;
- case value_expr_t::O_SUB: out << "O_SUB"; break;
- case value_expr_t::O_MUL: out << "O_MUL"; break;
- case value_expr_t::O_DIV: out << "O_DIV"; break;
- case value_expr_t::O_PERC: out << "O_PERC"; break;
-
- case value_expr_t::LAST:
- default:
- assert(0);
- break;
- }
-
- out << " (" << node->refc << ')' << std::endl;
-
- if (node->kind > value_expr_t::TERMINALS) {
- if (node->left) {
- dump_value_expr(out, node->left, depth + 1);
- if (node->right)
- dump_value_expr(out, node->right, depth + 1);
- } else {
- assert(! node->right);
- }
- } else {
- assert(! node->left);
- }
-}
-
-} // namespace ledger
diff --git a/valexpr.h b/valexpr.h
deleted file mode 100644
index 0ea0682b..00000000
--- a/valexpr.h
+++ /dev/null
@@ -1,518 +0,0 @@
-#ifndef _VALEXPR_H
-#define _VALEXPR_H
-
-#include "value.h"
-#include "error.h"
-#include "mask.h"
-
-#include <memory>
-
-namespace ledger {
-
-class entry_t;
-class transaction_t;
-class account_t;
-
-struct details_t
-{
- const entry_t * entry;
- 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");
- }
- details_t(const transaction_t& _xact);
- details_t(const account_t& _account)
- : entry(NULL), xact(NULL), account(&_account) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor details_t");
- }
-#ifdef DEBUG_ENABLED
- ~details_t() {
- DEBUG_PRINT("ledger.memory.dtors", "dtor details_t");
- }
-#endif
-};
-
-struct value_expr_t
-{
- enum kind_t {
- // Constants
- CONSTANT,
- ARG_INDEX,
-
- CONSTANTS,
-
- // Item details
- AMOUNT,
- COST,
- PRICE,
- DATE,
- ACT_DATE,
- EFF_DATE,
- CLEARED,
- PENDING,
- REAL,
- ACTUAL,
- INDEX,
- DEPTH,
-
- // Item totals
- COUNT,
- TOTAL,
- COST_TOTAL,
- PRICE_TOTAL,
-
- // Relating to format_t
- VALUE_EXPR,
- TOTAL_EXPR,
-
- // Functions
- F_NOW,
- F_ARITH_MEAN,
- F_QUANTITY,
- F_COMMODITY,
- F_SET_COMMODITY,
- F_VALUE,
- F_ABS,
- F_ROUND,
- F_PRICE,
- F_DATE,
- F_DATECMP,
- F_YEAR,
- F_MONTH,
- F_DAY,
- F_CODE_MASK,
- F_PAYEE_MASK,
- F_NOTE_MASK,
- F_ACCOUNT_MASK,
- 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,
- O_GT,
- O_GTE,
- O_NOT,
- O_AND,
- 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;
-
- union {
- value_t * value;
- mask_t * mask;
- unsigned int arg_index; // used by ARG_INDEX and O_ARG
- value_expr_t * right;
- };
-
- value_expr_t(const kind_t _kind)
- : kind(_kind), refc(0), left(NULL), right(NULL) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr_t " << this);
- }
- ~value_expr_t();
-
- 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 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 = details_t(),
- value_expr_t * context = NULL) const;
-
- value_t compute(const details_t& details = details_t(),
- value_expr_t * context = NULL) const {
- value_t temp;
- compute(temp, details, context);
- return temp;
- }
-
- private:
- value_expr_t(const value_expr_t&) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr_t (copy) " << this);
- }
-};
-
-class valexpr_context : public error_context {
- public:
- const ledger::value_expr_t * expr;
- const ledger::value_expr_t * error_node;
-
- valexpr_context(const ledger::value_expr_t * _expr,
- const std::string& desc = "") throw();
- virtual ~valexpr_context() throw();
-
- virtual void describe(std::ostream& out) const throw();
-};
-
-class compute_error : public error {
- public:
- compute_error(const std::string& reason, error_context * ctxt = NULL) throw()
- : error(reason, ctxt) {}
- virtual ~compute_error() throw() {}
-};
-
-class value_expr_error : public error {
- public:
- value_expr_error(const std::string& reason,
- error_context * ctxt = NULL) throw()
- : error(reason, ctxt) {}
- virtual ~value_expr_error() throw() {}
-};
-
-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) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor scope_t");
- }
- ~scope_t() {
- DEBUG_PRINT("ledger.memory.dtors", "dtor 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));
- if (! result.second) {
- symbols.erase(name);
- std::pair<symbol_map::iterator, bool> result
- = symbols.insert(symbol_pair(name, def));
- if (! result.second) {
- def->release();
- throw new compute_error(std::string("Redefinition of '") +
- name + "' in same scope");
- }
- }
- def->acquire();
- }
- 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 datetime_t terminus;
-extern bool initialized;
-
-void init_value_expr();
-
-bool compute_amount(value_expr_t * expr, amount_t& amt,
- const transaction_t * xact,
- value_expr_t * context = NULL);
-
-#define PARSE_VALEXPR_NORMAL 0x00
-#define PARSE_VALEXPR_PARTIAL 0x01
-#define PARSE_VALEXPR_RELAXED 0x02
-#define PARSE_VALEXPR_NO_MIGRATE 0x04
-#define PARSE_VALEXPR_NO_REDUCE 0x08
-#define PARSE_VALEXPR_NO_ASSIGN 0x10
-
-value_expr_t * parse_value_expr(std::istream& in,
- scope_t * scope = NULL,
- const short flags = PARSE_VALEXPR_RELAXED);
-
-inline value_expr_t *
-parse_value_expr(const std::string& str,
- scope_t * scope = NULL,
- const short flags = PARSE_VALEXPR_RELAXED) {
- std::istringstream stream(str);
- try {
- return parse_value_expr(stream, scope, flags);
- }
- catch (error * err) {
- err->context.push_back
- (new line_context(str, (long)stream.tellg() - 1,
- "While parsing value expression:"));
- throw err;
- }
-}
-
-inline value_expr_t *
-parse_value_expr(const char * p,
- scope_t * scope = NULL,
- const short flags = PARSE_VALEXPR_RELAXED) {
- return parse_value_expr(std::string(p), scope, flags);
-}
-
-void dump_value_expr(std::ostream& out, const value_expr_t * node,
- const int depth = 0);
-
-bool write_value_expr(std::ostream& out,
- const value_expr_t * node,
- const bool relaxed = true,
- const value_expr_t * node_to_find = NULL,
- unsigned long * start_pos = NULL,
- unsigned long * end_pos = NULL);
-
-//////////////////////////////////////////////////////////////////////
-
-inline void guarded_compute(const value_expr_t * expr,
- value_t& result,
- const details_t& details = details_t(),
- value_expr_t * context = NULL) {
- try {
- expr->compute(result, details);
- }
- catch (error * err) {
- if (err->context.empty() ||
- ! dynamic_cast<valexpr_context *>(err->context.back()))
- err->context.push_back(new valexpr_context(expr));
- error_context * last = err->context.back();
- if (valexpr_context * ctxt = dynamic_cast<valexpr_context *>(last)) {
- ctxt->expr = expr->acquire();
- ctxt->desc = "While computing value expression:";
- }
- throw err;
- }
-}
-
-inline value_t guarded_compute(const value_expr_t * expr,
- const details_t& details = details_t(),
- value_expr_t * context = NULL) {
- value_t temp;
- guarded_compute(expr, temp, details, context);
- return temp;
-}
-
-//////////////////////////////////////////////////////////////////////
-
-class value_expr
-{
- value_expr_t * ptr;
-public:
- std::string expr;
-
- value_expr() : ptr(NULL) {}
-
- value_expr(const std::string& _expr) : expr(_expr) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr");
- if (! _expr.empty())
- ptr = parse_value_expr(expr)->acquire();
- else
- ptr = NULL;
- }
- value_expr(value_expr_t * _ptr)
- : ptr(_ptr ? _ptr->acquire(): NULL) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr");
- }
- value_expr(const value_expr& other)
- : ptr(other.ptr ? other.ptr->acquire() : NULL),
- expr(other.expr) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor value_expr");
- }
- virtual ~value_expr() {
- DEBUG_PRINT("ledger.memory.dtors", "dtor value_expr");
- if (ptr)
- ptr->release();
- }
-
- value_expr& operator=(const std::string& _expr) {
- expr = _expr;
- reset(parse_value_expr(expr));
- return *this;
- }
- value_expr& operator=(value_expr_t * _expr) {
- expr = "";
- reset(_expr);
- return *this;
- }
- value_expr& operator=(const value_expr& _expr) {
- expr = _expr.expr;
- reset(_expr.get());
- return *this;
- }
-
- operator bool() const throw() {
- return ptr != NULL;
- }
- operator std::string() const throw() {
- return expr;
- }
- operator value_expr_t *() const throw() {
- return 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->release();
- ptr = p ? p->acquire() : NULL;
- }
- }
-
- virtual void compute(value_t& result,
- const details_t& details = details_t(),
- value_expr_t * context = NULL) {
- guarded_compute(ptr, result, details, context);
- }
- virtual value_t compute(const details_t& details = details_t(),
- value_expr_t * context = NULL) {
- value_t temp;
- guarded_compute(ptr, temp, details, context);
- return temp;
- }
-
- friend bool write_value_expr(std::ostream& out,
- const value_expr_t * node,
- const value_expr_t * node_to_find,
- unsigned long * start_pos,
- unsigned long * end_pos);
-};
-
-extern value_expr amount_expr;
-extern value_expr total_expr;
-
-inline void compute_amount(value_t& result,
- const details_t& details = details_t()) {
- if (amount_expr)
- amount_expr->compute(result, details);
-}
-
-inline value_t compute_amount(const details_t& details = details_t()) {
- if (amount_expr)
- return amount_expr->compute(details);
-}
-
-inline void compute_total(value_t& result,
- const details_t& details = details_t()) {
- if (total_expr)
- total_expr->compute(result, details);
-}
-
-inline value_t compute_total(const details_t& details = details_t()) {
- if (total_expr)
- return total_expr->compute(details);
-}
-
-value_expr_t * parse_boolean_expr(std::istream& in, scope_t * scope,
- const short flags);
-
-inline void parse_value_definition(const std::string& str,
- scope_t * scope = NULL) {
- std::istringstream def(str);
- value_expr expr
- (parse_boolean_expr(def, scope ? scope : global_scope.get(),
- PARSE_VALEXPR_RELAXED));
-}
-
-//////////////////////////////////////////////////////////////////////
-
-template <typename T>
-class item_predicate
-{
- public:
- 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())
- predicate = parse_value_expr(_predicate)->acquire();
- }
- item_predicate(const value_expr_t * _predicate = NULL)
- : 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)
- predicate->release();
- }
-
- bool operator()(const T& item) const {
- return (! predicate ||
- predicate->compute(details_t(item)).strip_annotations());
- }
-};
-
-} // namespace ledger
-
-#endif // _VALEXPR_H
diff --git a/value.cc b/value.cc
deleted file mode 100644
index 4bb74650..00000000
--- a/value.cc
+++ /dev/null
@@ -1,1696 +0,0 @@
-#include "value.h"
-#include "debug.h"
-#include "error.h"
-
-namespace ledger {
-
-void value_t::destroy()
-{
- switch (type) {
- case AMOUNT:
- ((amount_t *)data)->~amount_t();
- break;
- case BALANCE:
- ((balance_t *)data)->~balance_t();
- break;
- case BALANCE_PAIR:
- ((balance_pair_t *)data)->~balance_pair_t();
- break;
- default:
- break;
- }
-}
-
-void value_t::simplify()
-{
- if (realzero()) {
- DEBUG_PRINT("amounts.values.simplify", "Zeroing type " << type);
- *this = 0L;
- return;
- }
-
- if (type == BALANCE_PAIR &&
- (! ((balance_pair_t *) data)->cost ||
- ((balance_pair_t *) data)->cost->realzero())) {
- DEBUG_PRINT("amounts.values.simplify", "Reducing balance pair to balance");
- cast(BALANCE);
- }
-
- if (type == BALANCE &&
- ((balance_t *) data)->amounts.size() == 1) {
- DEBUG_PRINT("amounts.values.simplify", "Reducing balance to amount");
- cast(AMOUNT);
- }
-
- if (type == AMOUNT &&
- ! ((amount_t *) data)->commodity()) {
- DEBUG_PRINT("amounts.values.simplify", "Reducing amount to integer");
- cast(INTEGER);
- }
-}
-
-value_t& value_t::operator=(const value_t& value)
-{
- if (this == &value)
- return *this;
-
- destroy();
-
- switch (value.type) {
- case BOOLEAN:
- *((bool *) data) = *((bool *) value.data);
- break;
-
- case INTEGER:
- *((long *) data) = *((long *) value.data);
- break;
-
- case DATETIME:
- *((datetime_t *) data) = *((datetime_t *) value.data);
- break;
-
- case AMOUNT:
- new((amount_t *)data) amount_t(*((amount_t *) value.data));
- break;
-
- case BALANCE:
- new((balance_t *)data) balance_t(*((balance_t *) value.data));
- break;
-
- case BALANCE_PAIR:
- new((balance_pair_t *)data) balance_pair_t(*((balance_pair_t *) value.data));
- break;
-
- default:
- assert(0);
- break;
- }
-
- type = value.type;
-
- return *this;
-}
-
-value_t& value_t::operator+=(const value_t& value)
-{
- if (value.type == BOOLEAN)
- throw new value_error("Cannot add a boolean to a value");
- else if (value.type == DATETIME)
- throw new value_error("Cannot add a date/time to a value");
-
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot add a value to a boolean");
-
- case INTEGER:
- switch (value.type) {
- case INTEGER:
- *((long *) data) += *((long *) value.data);
- break;
- case AMOUNT:
- cast(AMOUNT);
- *((amount_t *) data) += *((amount_t *) value.data);
- break;
- case BALANCE:
- cast(BALANCE);
- *((balance_t *) data) += *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) += *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case DATETIME:
- switch (value.type) {
- case INTEGER:
- *((datetime_t *) data) += *((long *) value.data);
- break;
- case AMOUNT:
- *((datetime_t *) data) += long(*((amount_t *) value.data));
- break;
- case BALANCE:
- *((datetime_t *) data) += long(*((balance_t *) value.data));
- break;
- case BALANCE_PAIR:
- *((datetime_t *) data) += long(*((balance_pair_t *) value.data));
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case AMOUNT:
- switch (value.type) {
- case INTEGER:
- if (*((long *) value.data) &&
- ((amount_t *) data)->commodity()) {
- cast(BALANCE);
- return *this += value;
- }
- *((amount_t *) data) += *((long *) value.data);
- break;
-
- case AMOUNT:
- if (((amount_t *) data)->commodity() !=
- ((amount_t *) value.data)->commodity()) {
- cast(BALANCE);
- return *this += value;
- }
- *((amount_t *) data) += *((amount_t *) value.data);
- break;
-
- case BALANCE:
- cast(BALANCE);
- *((balance_t *) data) += *((balance_t *) value.data);
- break;
-
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) += *((balance_pair_t *) value.data);
- break;
-
- default:
- assert(0);
- break;
- }
- break;
-
- case BALANCE:
- switch (value.type) {
- case INTEGER:
- *((balance_t *) data) += *((long *) value.data);
- break;
- case AMOUNT:
- *((balance_t *) data) += *((amount_t *) value.data);
- break;
- case BALANCE:
- *((balance_t *) data) += *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) += *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case BALANCE_PAIR:
- switch (value.type) {
- case INTEGER:
- *((balance_pair_t *) data) += *((long *) value.data);
- break;
- case AMOUNT:
- *((balance_pair_t *) data) += *((amount_t *) value.data);
- break;
- case BALANCE:
- *((balance_pair_t *) data) += *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- *((balance_pair_t *) data) += *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- default:
- assert(0);
- break;
- }
- return *this;
-}
-
-value_t& value_t::operator-=(const value_t& value)
-{
- if (value.type == BOOLEAN)
- throw new value_error("Cannot subtract a boolean from a value");
- else if (value.type == DATETIME && type != DATETIME)
- throw new value_error("Cannot subtract a date/time from a value");
-
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot subtract a value from a boolean");
-
- case INTEGER:
- switch (value.type) {
- case INTEGER:
- *((long *) data) -= *((long *) value.data);
- break;
- case AMOUNT:
- cast(AMOUNT);
- *((amount_t *) data) -= *((amount_t *) value.data);
- break;
- case BALANCE:
- cast(BALANCE);
- *((balance_t *) data) -= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) -= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case DATETIME:
- switch (value.type) {
- case INTEGER:
- *((datetime_t *) data) -= *((long *) value.data);
- break;
- case DATETIME: {
- long val = *((datetime_t *) data) - *((datetime_t *) value.data);
- cast(INTEGER);
- *((long *) data) = val;
- break;
- }
- case AMOUNT:
- *((datetime_t *) data) -= long(*((amount_t *) value.data));
- break;
- case BALANCE:
- *((datetime_t *) data) -= long(*((balance_t *) value.data));
- break;
- case BALANCE_PAIR:
- *((datetime_t *) data) -= long(*((balance_pair_t *) value.data));
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case AMOUNT:
- switch (value.type) {
- case INTEGER:
- if (*((long *) value.data) &&
- ((amount_t *) data)->commodity()) {
- cast(BALANCE);
- return *this -= value;
- }
- *((amount_t *) data) -= *((long *) value.data);
- break;
-
- case AMOUNT:
- if (((amount_t *) data)->commodity() !=
- ((amount_t *) value.data)->commodity()) {
- cast(BALANCE);
- return *this -= value;
- }
- *((amount_t *) data) -= *((amount_t *) value.data);
- break;
-
- case BALANCE:
- cast(BALANCE);
- *((balance_t *) data) -= *((balance_t *) value.data);
- break;
-
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) -= *((balance_pair_t *) value.data);
- break;
-
- default:
- assert(0);
- break;
- }
- break;
-
- case BALANCE:
- switch (value.type) {
- case INTEGER:
- *((balance_t *) data) -= *((long *) value.data);
- break;
- case AMOUNT:
- *((balance_t *) data) -= *((amount_t *) value.data);
- break;
- case BALANCE:
- *((balance_t *) data) -= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) -= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case BALANCE_PAIR:
- switch (value.type) {
- case INTEGER:
- *((balance_pair_t *) data) -= *((long *) value.data);
- break;
- case AMOUNT:
- *((balance_pair_t *) data) -= *((amount_t *) value.data);
- break;
- case BALANCE:
- *((balance_pair_t *) data) -= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- *((balance_pair_t *) data) -= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- default:
- assert(0);
- break;
- }
-
- simplify();
-
- return *this;
-}
-
-value_t& value_t::operator*=(const value_t& value)
-{
- if (value.type == BOOLEAN)
- throw new value_error("Cannot multiply a boolean by a value");
- else if (value.type == DATETIME)
- throw new value_error("Cannot multiply a date/time by a value");
-
- if (value.realzero()) {
- *this = 0L;
- return *this;
- }
-
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot multiply a value by a boolean");
-
- case INTEGER:
- switch (value.type) {
- case INTEGER:
- *((long *) data) *= *((long *) value.data);
- break;
- case AMOUNT:
- cast(AMOUNT);
- *((amount_t *) data) *= *((amount_t *) value.data);
- break;
- case BALANCE:
- cast(BALANCE);
- *((balance_t *) data) *= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) *= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case AMOUNT:
- switch (value.type) {
- case INTEGER:
- *((amount_t *) data) *= *((long *) value.data);
- break;
- case AMOUNT:
- *((amount_t *) data) *= *((amount_t *) value.data);
- break;
- case BALANCE:
- cast(BALANCE);
- *((balance_t *) data) *= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) *= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case BALANCE:
- switch (value.type) {
- case INTEGER:
- *((balance_t *) data) *= *((long *) value.data);
- break;
- case AMOUNT:
- *((balance_t *) data) *= *((amount_t *) value.data);
- break;
- case BALANCE:
- *((balance_t *) data) *= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) *= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case BALANCE_PAIR:
- switch (value.type) {
- case INTEGER:
- *((balance_pair_t *) data) *= *((long *) value.data);
- break;
- case AMOUNT:
- *((balance_pair_t *) data) *= *((amount_t *) value.data);
- break;
- case BALANCE:
- *((balance_pair_t *) data) *= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- *((balance_pair_t *) data) *= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- default:
- assert(0);
- break;
- }
- return *this;
-}
-
-value_t& value_t::operator/=(const value_t& value)
-{
- if (value.type == BOOLEAN)
- throw new value_error("Cannot divide a boolean by a value");
- else if (value.type == DATETIME)
- throw new value_error("Cannot divide a date/time by a value");
-
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot divide a value by a boolean");
-
- case INTEGER:
- switch (value.type) {
- case INTEGER:
- *((long *) data) /= *((long *) value.data);
- break;
- case AMOUNT:
- cast(AMOUNT);
- *((amount_t *) data) /= *((amount_t *) value.data);
- break;
- case BALANCE:
- cast(BALANCE);
- *((balance_t *) data) /= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) /= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case AMOUNT:
- switch (value.type) {
- case INTEGER:
- *((amount_t *) data) /= *((long *) value.data);
- break;
- case AMOUNT:
- *((amount_t *) data) /= *((amount_t *) value.data);
- break;
- case BALANCE:
- cast(BALANCE);
- *((balance_t *) data) /= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) /= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case BALANCE:
- switch (value.type) {
- case INTEGER:
- *((balance_t *) data) /= *((long *) value.data);
- break;
- case AMOUNT:
- *((balance_t *) data) /= *((amount_t *) value.data);
- break;
- case BALANCE:
- *((balance_t *) data) /= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- cast(BALANCE_PAIR);
- *((balance_pair_t *) data) /= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- case BALANCE_PAIR:
- switch (value.type) {
- case INTEGER:
- *((balance_pair_t *) data) /= *((long *) value.data);
- break;
- case AMOUNT:
- *((balance_pair_t *) data) /= *((amount_t *) value.data);
- break;
- case BALANCE:
- *((balance_pair_t *) data) /= *((balance_t *) value.data);
- break;
- case BALANCE_PAIR:
- *((balance_pair_t *) data) /= *((balance_pair_t *) value.data);
- break;
- default:
- assert(0);
- break;
- }
- break;
-
- default:
- assert(0);
- break;
- }
- return *this;
-}
-
-#define DEF_VALUE_CMP_OP(OP) \
-bool value_t::operator OP(const value_t& value) \
-{ \
- switch (type) { \
- case BOOLEAN: \
- switch (value.type) { \
- case BOOLEAN: \
- return *((bool *) data) OP *((bool *) value.data); \
- \
- case INTEGER: \
- return *((bool *) data) OP bool(*((long *) value.data)); \
- \
- case DATETIME: \
- return *((bool *) data) OP bool(*((datetime_t *) value.data)); \
- \
- case AMOUNT: \
- return *((bool *) data) OP bool(*((amount_t *) value.data)); \
- \
- case BALANCE: \
- return *((bool *) data) OP bool(*((balance_t *) value.data)); \
- \
- case BALANCE_PAIR: \
- return *((bool *) data) OP bool(*((balance_pair_t *) value.data)); \
- \
- default: \
- assert(0); \
- break; \
- } \
- break; \
- \
- case INTEGER: \
- switch (value.type) { \
- case BOOLEAN: \
- return (*((long *) data) OP \
- ((long) *((bool *) value.data))); \
- \
- case INTEGER: \
- return (*((long *) data) OP *((long *) value.data)); \
- \
- case DATETIME: \
- return (*((long *) data) OP \
- ((long) ((datetime_t *) value.data)->when)); \
- \
- case AMOUNT: \
- return (amount_t(*((long *) data)) OP \
- *((amount_t *) value.data)); \
- \
- case BALANCE: \
- return (balance_t(*((long *) data)) OP \
- *((balance_t *) value.data)); \
- \
- case BALANCE_PAIR: \
- return (balance_pair_t(*((long *) data)) OP \
- *((balance_pair_t *) value.data)); \
- \
- default: \
- assert(0); \
- break; \
- } \
- break; \
- \
- case DATETIME: \
- switch (value.type) { \
- case BOOLEAN: \
- throw new value_error("Cannot compare a date/time to a boolean"); \
- \
- case INTEGER: \
- return (*((datetime_t *) data) OP \
- datetime_t(std::time_t(*((long *) value.data)))); \
- \
- case DATETIME: \
- return (*((datetime_t *) data) OP \
- *((datetime_t *) value.data)); \
- \
- case AMOUNT: \
- throw new value_error("Cannot compare a date/time to an amount"); \
- \
- case BALANCE: \
- throw new value_error("Cannot compare a date/time to a balance"); \
- \
- case BALANCE_PAIR: \
- throw new value_error("Cannot compare a date/time to a balance pair"); \
- \
- default: \
- assert(0); \
- break; \
- } \
- break; \
- \
- case AMOUNT: \
- switch (value.type) { \
- case BOOLEAN: \
- throw new value_error("Cannot compare an amount to a boolean"); \
- \
- case INTEGER: \
- return (*((amount_t *) data) OP \
- amount_t(*((long *) value.data))); \
- \
- case DATETIME: \
- throw new value_error("Cannot compare an amount to a date/time"); \
- \
- case AMOUNT: \
- return *((amount_t *) data) OP *((amount_t *) value.data); \
- \
- case BALANCE: \
- return (balance_t(*((amount_t *) data)) OP \
- *((balance_t *) value.data)); \
- \
- case BALANCE_PAIR: \
- return (balance_t(*((amount_t *) data)) OP \
- *((balance_pair_t *) value.data)); \
- \
- default: \
- assert(0); \
- break; \
- } \
- break; \
- \
- case BALANCE: \
- switch (value.type) { \
- case BOOLEAN: \
- throw new value_error("Cannot compare a balance to a boolean"); \
- \
- case INTEGER: \
- return *((balance_t *) data) OP *((long *) value.data); \
- \
- case DATETIME: \
- throw new value_error("Cannot compare a balance to a date/time"); \
- \
- case AMOUNT: \
- return *((balance_t *) data) OP *((amount_t *) value.data); \
- \
- case BALANCE: \
- return *((balance_t *) data) OP *((balance_t *) value.data); \
- \
- case BALANCE_PAIR: \
- return (*((balance_t *) data) OP \
- ((balance_pair_t *) value.data)->quantity); \
- \
- default: \
- assert(0); \
- break; \
- } \
- break; \
- \
- case BALANCE_PAIR: \
- switch (value.type) { \
- case BOOLEAN: \
- throw new value_error("Cannot compare a balance pair to a boolean"); \
- \
- case INTEGER: \
- return (((balance_pair_t *) data)->quantity OP \
- *((long *) value.data)); \
- \
- case DATETIME: \
- throw new value_error("Cannot compare a balance pair to a date/time"); \
- \
- case AMOUNT: \
- return (((balance_pair_t *) data)->quantity OP \
- *((amount_t *) value.data)); \
- \
- case BALANCE: \
- return (((balance_pair_t *) data)->quantity OP \
- *((balance_t *) value.data)); \
- \
- case BALANCE_PAIR: \
- return (*((balance_pair_t *) data) OP \
- *((balance_pair_t *) value.data)); \
- \
- default: \
- assert(0); \
- break; \
- } \
- break; \
- \
- default: \
- assert(0); \
- break; \
- } \
- return *this; \
-}
-
-DEF_VALUE_CMP_OP(==)
-DEF_VALUE_CMP_OP(<)
-DEF_VALUE_CMP_OP(<=)
-DEF_VALUE_CMP_OP(>)
-DEF_VALUE_CMP_OP(>=)
-
-template <>
-value_t::operator long() const
-{
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot convert a boolean to an integer");
- case INTEGER:
- return *((long *) data);
- case DATETIME:
- return ((datetime_t *) data)->when;
- case AMOUNT:
- return *((amount_t *) data);
- case BALANCE:
- throw new value_error("Cannot convert a balance to an integer");
- case BALANCE_PAIR:
- throw new value_error("Cannot convert a balance pair to an integer");
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return 0;
-}
-
-template <>
-value_t::operator datetime_t() const
-{
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot convert a boolean to a date/time");
- case INTEGER:
- return *((long *) data);
- case DATETIME:
- return *((datetime_t *) data);
- case AMOUNT:
- throw new value_error("Cannot convert an amount to a date/time");
- case BALANCE:
- throw new value_error("Cannot convert a balance to a date/time");
- case BALANCE_PAIR:
- throw new value_error("Cannot convert a balance pair to a date/time");
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return 0;
-}
-
-template <>
-value_t::operator double() const
-{
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot convert a boolean to a double");
- case INTEGER:
- return *((long *) data);
- case DATETIME:
- throw new value_error("Cannot convert a date/time to a double");
- case AMOUNT:
- return *((amount_t *) data);
- case BALANCE:
- throw new value_error("Cannot convert a balance to a double");
- case BALANCE_PAIR:
- throw new value_error("Cannot convert a balance pair to a double");
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return 0;
-}
-
-void value_t::cast(type_t cast_type)
-{
- switch (type) {
- case BOOLEAN:
- switch (cast_type) {
- case BOOLEAN:
- break;
- case INTEGER:
- throw new value_error("Cannot convert a boolean to an integer");
- case DATETIME:
- throw new value_error("Cannot convert a boolean to a date/time");
- case AMOUNT:
- throw new value_error("Cannot convert a boolean to an amount");
- case BALANCE:
- throw new value_error("Cannot convert a boolean to a balance");
- case BALANCE_PAIR:
- throw new value_error("Cannot convert a boolean to a balance pair");
-
- default:
- assert(0);
- break;
- }
- break;
-
- case INTEGER:
- switch (cast_type) {
- case BOOLEAN:
- *((bool *) data) = *((long *) data);
- break;
- case INTEGER:
- break;
- case DATETIME:
- *((datetime_t *) data) = datetime_t(std::time_t(*((long *) data)));
- break;
- case AMOUNT:
- new((amount_t *)data) amount_t(*((long *) data));
- break;
- case BALANCE:
- new((balance_t *)data) balance_t(amount_t(*((long *) data)));
- break;
- case BALANCE_PAIR:
- new((balance_pair_t *)data) balance_pair_t(amount_t(*((long *) data)));
- break;
-
- default:
- assert(0);
- break;
- }
- break;
-
- case DATETIME:
- switch (cast_type) {
- case BOOLEAN:
- *((bool *) data) = *((datetime_t *) data);
- break;
- case INTEGER:
- *((long *) data) = ((datetime_t *) data)->when;
- break;
- case DATETIME:
- break;
- case AMOUNT:
- throw new value_error("Cannot convert a date/time to an amount");
- case BALANCE:
- throw new value_error("Cannot convert a date/time to a balance");
- case BALANCE_PAIR:
- throw new value_error("Cannot convert a date/time to a balance pair");
-
- default:
- assert(0);
- break;
- }
- break;
-
- case AMOUNT:
- switch (cast_type) {
- case BOOLEAN: {
- bool temp = *((amount_t *) data);
- destroy();
- *((bool *)data) = temp;
- break;
- }
- case INTEGER: {
- long temp = *((amount_t *) data);
- destroy();
- *((long *)data) = temp;
- break;
- }
- case DATETIME:
- throw new value_error("Cannot convert an amount to a date/time");
- case AMOUNT:
- break;
- case BALANCE: {
- amount_t temp = *((amount_t *) data);
- destroy();
- new((balance_t *)data) balance_t(temp);
- break;
- }
- case BALANCE_PAIR: {
- amount_t temp = *((amount_t *) data);
- destroy();
- new((balance_pair_t *)data) balance_pair_t(temp);
- break;
- }
-
- default:
- assert(0);
- break;
- }
- break;
-
- case BALANCE:
- switch (cast_type) {
- case BOOLEAN: {
- bool temp = *((balance_t *) data);
- destroy();
- *((bool *)data) = temp;
- break;
- }
- case INTEGER:
- throw new value_error("Cannot convert a balance to an integer");
- case DATETIME:
- throw new value_error("Cannot convert a balance to a date/time");
-
- case AMOUNT: {
- balance_t * temp = (balance_t *) data;
- if (temp->amounts.size() == 1) {
- amount_t amt = (*temp->amounts.begin()).second;
- destroy();
- new((amount_t *)data) amount_t(amt);
- }
- else if (temp->amounts.size() == 0) {
- new((amount_t *)data) amount_t();
- }
- else {
- throw new value_error("Cannot convert a balance with "
- "multiple commodities to an amount");
- }
- break;
- }
- case BALANCE:
- break;
- case BALANCE_PAIR: {
- balance_t temp = *((balance_t *) data);
- destroy();
- new((balance_pair_t *)data) balance_pair_t(temp);
- break;
- }
-
- default:
- assert(0);
- break;
- }
- break;
-
- case BALANCE_PAIR:
- switch (cast_type) {
- case BOOLEAN: {
- bool temp = *((balance_pair_t *) data);
- destroy();
- *((bool *)data) = temp;
- break;
- }
- case INTEGER:
- throw new value_error("Cannot convert a balance pair to an integer");
- case DATETIME:
- throw new value_error("Cannot convert a balance pair to a date/time");
-
- case AMOUNT: {
- balance_t * temp = &((balance_pair_t *) data)->quantity;
- if (temp->amounts.size() == 1) {
- amount_t amt = (*temp->amounts.begin()).second;
- destroy();
- new((amount_t *)data) amount_t(amt);
- }
- else if (temp->amounts.size() == 0) {
- new((amount_t *)data) amount_t();
- }
- else {
- throw new value_error("Cannot convert a balance pair with "
- "multiple commodities to an amount");
- }
- break;
- }
- case BALANCE: {
- balance_t temp = ((balance_pair_t *) data)->quantity;
- destroy();
- new((balance_t *)data) balance_t(temp);
- break;
- }
- case BALANCE_PAIR:
- break;
-
- default:
- assert(0);
- break;
- }
- break;
-
- default:
- assert(0);
- break;
- }
- type = cast_type;
-}
-
-void value_t::negate()
-{
- switch (type) {
- case BOOLEAN:
- *((bool *) data) = ! *((bool *) data);
- break;
- case INTEGER:
- *((long *) data) = - *((long *) data);
- break;
- case DATETIME:
- cast(INTEGER);
- negate();
- break;
- case AMOUNT:
- ((amount_t *) data)->negate();
- break;
- case BALANCE:
- ((balance_t *) data)->negate();
- break;
- case BALANCE_PAIR:
- ((balance_pair_t *) data)->negate();
- break;
-
- default:
- assert(0);
- break;
- }
-}
-
-void value_t::abs()
-{
- switch (type) {
- case BOOLEAN:
- break;
- case INTEGER:
- if (*((long *) data) < 0)
- *((long *) data) = - *((long *) data);
- break;
- case DATETIME:
- break;
- case AMOUNT:
- ((amount_t *) data)->abs();
- break;
- case BALANCE:
- ((balance_t *) data)->abs();
- break;
- case BALANCE_PAIR:
- ((balance_pair_t *) data)->abs();
- break;
-
- default:
- assert(0);
- break;
- }
-}
-
-value_t value_t::value(const datetime_t& moment) const
-{
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot find the value of a boolean");
- case DATETIME:
- throw new value_error("Cannot find the value of a date/time");
- case INTEGER:
- return *this;
- case AMOUNT:
- return ((amount_t *) data)->value(moment);
- case BALANCE:
- return ((balance_t *) data)->value(moment);
- case BALANCE_PAIR:
- return ((balance_pair_t *) data)->quantity.value(moment);
- }
-}
-
-void value_t::reduce()
-{
- switch (type) {
- case BOOLEAN:
- case DATETIME:
- case INTEGER:
- break;
- case AMOUNT:
- ((amount_t *) data)->reduce();
- break;
- case BALANCE:
- ((balance_t *) data)->reduce();
- break;
- case BALANCE_PAIR:
- ((balance_pair_t *) data)->reduce();
- break;
- }
-}
-
-void value_t::round()
-{
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot round a boolean");
- case DATETIME:
- throw new value_error("Cannot round a date/time");
- case INTEGER:
- break;
- case AMOUNT:
- *((amount_t *) data) = ((amount_t *) data)->round();
- break;
- case BALANCE:
- ((balance_t *) data)->round();
- break;
- case BALANCE_PAIR:
- ((balance_pair_t *) data)->round();
- break;
- }
-}
-
-value_t value_t::unround() const
-{
- value_t temp;
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot un-round a boolean");
- case DATETIME:
- throw new value_error("Cannot un-round a date/time");
- case INTEGER:
- break;
- case AMOUNT:
- temp = ((amount_t *) data)->unround();
- break;
- case BALANCE:
- temp = ((balance_t *) data)->unround();
- break;
- case BALANCE_PAIR:
- temp = ((balance_pair_t *) data)->unround();
- break;
- }
- return temp;
-}
-
-value_t value_t::price() const
-{
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot find the price of a boolean");
- case INTEGER:
- return *this;
- case DATETIME:
- throw new value_error("Cannot find the price of a date/time");
-
- case AMOUNT:
- return ((amount_t *) data)->price();
-
- case BALANCE:
- return ((balance_t *) data)->price();
-
- case BALANCE_PAIR:
- return ((balance_pair_t *) data)->quantity.price();
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return value_t();
-}
-
-value_t value_t::date() const
-{
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot find the date of a boolean");
- case INTEGER:
- return datetime_t();
- case DATETIME:
- return *this;
-
- case AMOUNT:
- return datetime_t(((amount_t *) data)->date());
-
- case BALANCE:
- return datetime_t(((balance_t *) data)->date());
-
- case BALANCE_PAIR:
- return datetime_t(((balance_pair_t *) data)->quantity.date());
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return value_t();
-}
-
-value_t value_t::strip_annotations(const bool keep_price,
- const bool keep_date,
- const bool keep_tag) const
-{
- switch (type) {
- case BOOLEAN:
- case INTEGER:
- case DATETIME:
- return *this;
-
- case AMOUNT:
- return ((amount_t *) data)->strip_annotations
- (keep_price, keep_date, keep_tag);
- case BALANCE:
- return ((balance_t *) data)->strip_annotations
- (keep_price, keep_date, keep_tag);
- case BALANCE_PAIR:
- return ((balance_pair_t *) data)->quantity.strip_annotations
- (keep_price, keep_date, keep_tag);
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return value_t();
-}
-
-value_t value_t::cost() const
-{
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot find the cost of a boolean");
- case INTEGER:
- case AMOUNT:
- case BALANCE:
- return *this;
- case DATETIME:
- throw new value_error("Cannot find the cost of a date/time");
-
- case BALANCE_PAIR:
- assert(((balance_pair_t *) data)->cost);
- if (((balance_pair_t *) data)->cost)
- return *(((balance_pair_t *) data)->cost);
- else
- return ((balance_pair_t *) data)->quantity;
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return value_t();
-}
-
-value_t& value_t::add(const amount_t& amount, const amount_t * cost)
-{
- switch (type) {
- case BOOLEAN:
- throw new value_error("Cannot add an amount to a boolean");
- case DATETIME:
- throw new value_error("Cannot add an amount to a date/time");
- case INTEGER:
- case AMOUNT:
- if (cost) {
- cast(BALANCE_PAIR);
- return add(amount, cost);
- }
- else if ((type == AMOUNT &&
- ((amount_t *) data)->commodity() != amount.commodity()) ||
- (type != AMOUNT && amount.commodity())) {
- cast(BALANCE);
- return add(amount, cost);
- }
- else if (type != AMOUNT) {
- cast(AMOUNT);
- }
- *((amount_t *) data) += amount;
- break;
-
- case BALANCE:
- if (cost) {
- cast(BALANCE_PAIR);
- return add(amount, cost);
- }
- *((balance_t *) data) += amount;
- break;
-
- case BALANCE_PAIR:
- ((balance_pair_t *) data)->add(amount, cost);
- break;
-
- default:
- assert(0);
- break;
- }
-
- return *this;
-}
-
-value_context::value_context(const value_t& _bal,
- const std::string& desc) throw()
- : bal(new value_t(_bal)), error_context(desc) {}
-
-value_context::~value_context() throw()
-{
- delete bal;
-}
-
-void value_context::describe(std::ostream& out) const throw()
-{
- if (! desc.empty())
- out << desc << std::endl;
-
- ledger::balance_t * ptr = NULL;
-
- out << std::right;
- out.width(20);
-
- switch (bal->type) {
- case ledger::value_t::BOOLEAN:
- out << (*((bool *) bal->data) ? "true" : "false");
- break;
- case ledger::value_t::INTEGER:
- out << *((long *) bal->data);
- break;
- case ledger::value_t::DATETIME:
- out << *((datetime_t *) bal->data);
- break;
- case ledger::value_t::AMOUNT:
- out << *((ledger::amount_t *) bal->data);
- break;
- case ledger::value_t::BALANCE:
- ptr = (ledger::balance_t *) bal->data;
- // fall through...
-
- case ledger::value_t::BALANCE_PAIR:
- if (! ptr)
- ptr = &((ledger::balance_pair_t *) bal->data)->quantity;
-
- ptr->write(out, 20);
- break;
- default:
- assert(0);
- break;
- }
- out << std::endl;
-}
-
-} // namespace ledger
-
-#ifdef USE_BOOST_PYTHON
-
-#include <boost/python.hpp>
-
-using namespace boost::python;
-using namespace ledger;
-
-long balance_len(balance_t& bal);
-amount_t balance_getitem(balance_t& bal, int i);
-long balance_pair_len(balance_pair_t& bal_pair);
-amount_t balance_pair_getitem(balance_pair_t& bal_pair, int i);
-
-long value_len(value_t& value)
-{
- switch (value.type) {
- case value_t::BOOLEAN:
- case value_t::INTEGER:
- case value_t::DATETIME:
- case value_t::AMOUNT:
- return 1;
-
- case value_t::BALANCE:
- return balance_len(*((balance_t *) value.data));
-
- case value_t::BALANCE_PAIR:
- return balance_pair_len(*((balance_pair_t *) value.data));
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return 0;
-}
-
-amount_t value_getitem(value_t& value, int i)
-{
- std::size_t len = value_len(value);
-
- if (abs(i) >= len) {
- PyErr_SetString(PyExc_IndexError, "Index out of range");
- throw_error_already_set();
- }
-
- switch (value.type) {
- case value_t::BOOLEAN:
- throw new value_error("Cannot cast a boolean to an amount");
-
- case value_t::INTEGER:
- return long(value);
-
- case value_t::DATETIME:
- throw new value_error("Cannot cast a date/time to an amount");
-
- case value_t::AMOUNT:
- return *((amount_t *) value.data);
-
- case value_t::BALANCE:
- return balance_getitem(*((balance_t *) value.data), i);
-
- case value_t::BALANCE_PAIR:
- return balance_pair_getitem(*((balance_pair_t *) value.data), i);
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return 0L;
-}
-
-double py_to_float(value_t& value)
-{
- return double(value);
-}
-
-void export_value()
-{
- scope in_value = class_< value_t > ("Value")
- .def(init<value_t>())
- .def(init<balance_pair_t>())
- .def(init<balance_t>())
- .def(init<amount_t>())
- .def(init<std::string>())
- .def(init<double>())
- .def(init<long>())
- .def(init<datetime_t>())
-
- .def(self + self)
- .def(self + other<balance_pair_t>())
- .def(self + other<balance_t>())
- .def(self + other<amount_t>())
- .def(self + long())
- .def(self + double())
-
- .def(other<balance_pair_t>() + self)
- .def(other<balance_t>() + self)
- .def(other<amount_t>() + self)
- .def(long() + self)
- .def(double() + self)
-
- .def(self - self)
- .def(self - other<balance_pair_t>())
- .def(self - other<balance_t>())
- .def(self - other<amount_t>())
- .def(self - long())
- .def(self - double())
-
- .def(other<balance_pair_t>() - self)
- .def(other<balance_t>() - self)
- .def(other<amount_t>() - self)
- .def(long() - self)
- .def(double() - self)
-
- .def(self * self)
- .def(self * other<balance_pair_t>())
- .def(self * other<balance_t>())
- .def(self * other<amount_t>())
- .def(self * long())
- .def(self * double())
-
- .def(other<balance_pair_t>() * self)
- .def(other<balance_t>() * self)
- .def(other<amount_t>() * self)
- .def(long() * self)
- .def(double() * self)
-
- .def(self / self)
- .def(self / other<balance_pair_t>())
- .def(self / other<balance_t>())
- .def(self / other<amount_t>())
- .def(self / long())
- .def(self / double())
-
- .def(other<balance_pair_t>() / self)
- .def(other<balance_t>() / self)
- .def(other<amount_t>() / self)
- .def(long() / self)
- .def(double() / self)
-
- .def(- self)
-
- .def(self += self)
- .def(self += other<balance_pair_t>())
- .def(self += other<balance_t>())
- .def(self += other<amount_t>())
- .def(self += long())
- .def(self += double())
-
- .def(self -= self)
- .def(self -= other<balance_pair_t>())
- .def(self -= other<balance_t>())
- .def(self -= other<amount_t>())
- .def(self -= long())
- .def(self -= double())
-
- .def(self *= self)
- .def(self *= other<balance_pair_t>())
- .def(self *= other<balance_t>())
- .def(self *= other<amount_t>())
- .def(self *= long())
- .def(self *= double())
-
- .def(self /= self)
- .def(self /= other<balance_pair_t>())
- .def(self /= other<balance_t>())
- .def(self /= other<amount_t>())
- .def(self /= long())
- .def(self /= double())
-
- .def(self < self)
- .def(self < other<balance_pair_t>())
- .def(self < other<balance_t>())
- .def(self < other<amount_t>())
- .def(self < long())
- .def(self < other<datetime_t>())
- .def(self < double())
-
- .def(other<balance_pair_t>() < self)
- .def(other<balance_t>() < self)
- .def(other<amount_t>() < self)
- .def(long() < self)
- .def(other<datetime_t>() < self)
- .def(double() < self)
-
- .def(self <= self)
- .def(self <= other<balance_pair_t>())
- .def(self <= other<balance_t>())
- .def(self <= other<amount_t>())
- .def(self <= long())
- .def(self <= other<datetime_t>())
- .def(self <= double())
-
- .def(other<balance_pair_t>() <= self)
- .def(other<balance_t>() <= self)
- .def(other<amount_t>() <= self)
- .def(long() <= self)
- .def(other<datetime_t>() <= self)
- .def(double() <= self)
-
- .def(self > self)
- .def(self > other<balance_pair_t>())
- .def(self > other<balance_t>())
- .def(self > other<amount_t>())
- .def(self > long())
- .def(self > other<datetime_t>())
- .def(self > double())
-
- .def(other<balance_pair_t>() > self)
- .def(other<balance_t>() > self)
- .def(other<amount_t>() > self)
- .def(long() > self)
- .def(other<datetime_t>() > self)
- .def(double() > self)
-
- .def(self >= self)
- .def(self >= other<balance_pair_t>())
- .def(self >= other<balance_t>())
- .def(self >= other<amount_t>())
- .def(self >= long())
- .def(self >= other<datetime_t>())
- .def(self >= double())
-
- .def(other<balance_pair_t>() >= self)
- .def(other<balance_t>() >= self)
- .def(other<amount_t>() >= self)
- .def(long() >= self)
- .def(other<datetime_t>() >= self)
- .def(double() >= self)
-
- .def(self == self)
- .def(self == other<balance_pair_t>())
- .def(self == other<balance_t>())
- .def(self == other<amount_t>())
- .def(self == long())
- .def(self == other<datetime_t>())
- .def(self == double())
-
- .def(other<balance_pair_t>() == self)
- .def(other<balance_t>() == self)
- .def(other<amount_t>() == self)
- .def(long() == self)
- .def(other<datetime_t>() == self)
- .def(double() == self)
-
- .def(self != self)
- .def(self != other<balance_pair_t>())
- .def(self != other<balance_t>())
- .def(self != other<amount_t>())
- .def(self != long())
- .def(self != other<datetime_t>())
- .def(self != double())
-
- .def(other<balance_pair_t>() != self)
- .def(other<balance_t>() != self)
- .def(other<amount_t>() != self)
- .def(long() != self)
- .def(other<datetime_t>() != self)
- .def(double() != self)
-
- .def(! self)
-
- .def(self_ns::int_(self))
- .def(self_ns::float_(self))
- .def(self_ns::str(self))
- .def(abs(self))
-
- .def_readonly("type", &value_t::type)
-
- .def("__len__", value_len)
- .def("__getitem__", value_getitem)
-
- .def("cast", &value_t::cast)
- .def("cost", &value_t::cost)
- .def("price", &value_t::price)
- .def("date", &value_t::date)
- .def("strip_annotations", &value_t::strip_annotations)
- .def("add", &value_t::add, return_internal_reference<>())
- .def("value", &value_t::value)
- .def("round", &value_t::round)
- .def("negate", &value_t::negate)
- .def("negated", &value_t::negated)
- ;
-
- enum_< value_t::type_t > ("ValueType")
- .value("BOOLEAN", value_t::BOOLEAN)
- .value("INTEGER", value_t::INTEGER)
- .value("DATETIME", value_t::DATETIME)
- .value("AMOUNT", value_t::AMOUNT)
- .value("BALANCE", value_t::BALANCE)
- .value("BALANCE_PAIR", value_t::BALANCE_PAIR)
- ;
-}
-
-#endif // USE_BOOST_PYTHON
diff --git a/value.h b/value.h
deleted file mode 100644
index fe01786b..00000000
--- a/value.h
+++ /dev/null
@@ -1,444 +0,0 @@
-#ifndef _VALUE_H
-#define _VALUE_H
-
-#include "amount.h"
-#include "balance.h"
-#include "error.h"
-
-#include <exception>
-
-namespace ledger {
-
-// The following type is a polymorphous value type used solely for
-// performance reasons. The alternative is to compute value
-// expressions (valexpr.cc) in terms of the largest data type,
-// balance_t. This was found to be prohibitively expensive, especially
-// when large logic chains were involved, since many temporary
-// allocations would occur for every operator. With value_t, and the
-// fact that logic chains only need boolean values to continue, no
-// memory allocations need to take place at all.
-
-class value_t
-{
- public:
- char data[sizeof(balance_pair_t)];
-
- enum type_t {
- BOOLEAN,
- INTEGER,
- DATETIME,
- AMOUNT,
- BALANCE,
- BALANCE_PAIR
- } type;
-
- value_t() {
- *((long *) data) = 0;
- type = INTEGER;
- }
-
- value_t(const value_t& value) : type(INTEGER) {
- *this = value;
- }
- value_t(const bool value) {
- *((bool *) data) = value;
- type = BOOLEAN;
- }
- value_t(const long value) {
- *((long *) data) = value;
- type = INTEGER;
- }
- value_t(const datetime_t value) {
- *((datetime_t *) data) = value;
- type = DATETIME;
- }
- value_t(const unsigned long value) {
- new((amount_t *) data) amount_t(value);
- type = AMOUNT;
- }
- value_t(const double value) {
- new((amount_t *) data) amount_t(value);
- type = AMOUNT;
- }
- value_t(const std::string& value) {
- new((amount_t *) data) amount_t(value);
- type = AMOUNT;
- }
- value_t(const char * value) {
- new((amount_t *) data) amount_t(value);
- type = AMOUNT;
- }
- value_t(const amount_t& value) {
- new((amount_t *)data) amount_t(value);
- type = AMOUNT;
- }
- value_t(const balance_t& value) : type(INTEGER) {
- *this = value;
- }
- value_t(const balance_pair_t& value) : type(INTEGER) {
- *this = value;
- }
-
- ~value_t() {
- destroy();
- }
-
- void destroy();
- void simplify();
-
- value_t& operator=(const value_t& value);
- value_t& operator=(const bool value) {
- if ((bool *) data != &value) {
- destroy();
- *((bool *) data) = value;
- type = BOOLEAN;
- }
- return *this;
- }
- value_t& operator=(const long value) {
- if ((long *) data != &value) {
- destroy();
- *((long *) data) = value;
- type = INTEGER;
- }
- return *this;
- }
- value_t& operator=(const datetime_t value) {
- if ((datetime_t *) data != &value) {
- destroy();
- *((datetime_t *) data) = value;
- type = DATETIME;
- }
- return *this;
- }
- value_t& operator=(const unsigned long value) {
- return *this = amount_t(value);
- }
- value_t& operator=(const double value) {
- return *this = amount_t(value);
- }
- value_t& operator=(const std::string& value) {
- return *this = amount_t(value);
- }
- value_t& operator=(const char * value) {
- return *this = amount_t(value);
- }
- value_t& operator=(const amount_t& value) {
- if (type == AMOUNT &&
- (amount_t *) data == &value)
- return *this;
-
- if (value.realzero()) {
- return *this = 0L;
- } else {
- destroy();
- new((amount_t *)data) amount_t(value);
- type = AMOUNT;
- }
- return *this;
- }
- value_t& operator=(const balance_t& value) {
- if (type == BALANCE &&
- (balance_t *) data == &value)
- return *this;
-
- if (value.realzero()) {
- return *this = 0L;
- }
- else if (value.amounts.size() == 1) {
- return *this = (*value.amounts.begin()).second;
- }
- else {
- destroy();
- new((balance_t *)data) balance_t(value);
- type = BALANCE;
- return *this;
- }
- }
- value_t& operator=(const balance_pair_t& value) {
- if (type == BALANCE_PAIR &&
- (balance_pair_t *) data == &value)
- return *this;
-
- if (value.realzero()) {
- return *this = 0L;
- }
- else if (! value.cost) {
- return *this = value.quantity;
- }
- else {
- destroy();
- new((balance_pair_t *)data) balance_pair_t(value);
- type = BALANCE_PAIR;
- return *this;
- }
- }
-
- value_t& operator+=(const value_t& value);
- value_t& operator-=(const value_t& value);
- value_t& operator*=(const value_t& value);
- value_t& operator/=(const value_t& value);
-
- template <typename T>
- value_t& operator+=(const T& value) {
- return *this += value_t(value);
- }
- template <typename T>
- value_t& operator-=(const T& value) {
- return *this -= value_t(value);
- }
- template <typename T>
- value_t& operator*=(const T& value) {
- return *this *= value_t(value);
- }
- template <typename T>
- value_t& operator/=(const T& value) {
- return *this /= value_t(value);
- }
-
- value_t operator+(const value_t& value) {
- value_t temp(*this);
- temp += value;
- return temp;
- }
- value_t operator-(const value_t& value) {
- value_t temp(*this);
- temp -= value;
- return temp;
- }
- value_t operator*(const value_t& value) {
- value_t temp(*this);
- temp *= value;
- return temp;
- }
- value_t operator/(const value_t& value) {
- value_t temp(*this);
- temp /= value;
- return temp;
- }
-
- template <typename T>
- value_t operator+(const T& value) {
- return *this + value_t(value);
- }
- template <typename T>
- value_t operator-(const T& value) {
- return *this - value_t(value);
- }
- template <typename T>
- value_t operator*(const T& value) {
- return *this * value_t(value);
- }
- template <typename T>
- value_t operator/(const T& value) {
- return *this / value_t(value);
- }
-
- bool operator<(const value_t& value);
- bool operator<=(const value_t& value);
- bool operator>(const value_t& value);
- bool operator>=(const value_t& value);
- bool operator==(const value_t& value);
- bool operator!=(const value_t& value) {
- return ! (*this == value);
- }
-
- template <typename T>
- bool operator<(const T& value) {
- return *this < value_t(value);
- }
- template <typename T>
- bool operator<=(const T& value) {
- return *this <= value_t(value);
- }
- template <typename T>
- bool operator>(const T& value) {
- return *this > value_t(value);
- }
- template <typename T>
- bool operator>=(const T& value) {
- return *this >= value_t(value);
- }
- template <typename T>
- bool operator==(const T& value) {
- return *this == value_t(value);
- }
- template <typename T>
- bool operator!=(const T& value) {
- return ! (*this == value);
- }
-
- template <typename T>
- operator T() const;
-
- void negate();
- value_t negated() const {
- value_t temp = *this;
- temp.negate();
- return temp;
- }
- value_t operator-() const {
- return negated();
- }
-
- bool realzero() const {
- switch (type) {
- case BOOLEAN:
- return ! *((bool *) data);
- case INTEGER:
- return *((long *) data) == 0;
- case DATETIME:
- return ! *((datetime_t *) data);
- case AMOUNT:
- return ((amount_t *) data)->realzero();
- case BALANCE:
- return ((balance_t *) data)->realzero();
- case BALANCE_PAIR:
- return ((balance_pair_t *) data)->realzero();
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return 0;
- }
-
- void abs();
- void cast(type_t cast_type);
- value_t cost() const;
- value_t price() const;
- value_t date() const;
-
- value_t strip_annotations(const bool keep_price = amount_t::keep_price,
- const bool keep_date = amount_t::keep_date,
- const bool keep_tag = amount_t::keep_tag) const;
-
- value_t& add(const amount_t& amount, const amount_t * cost = NULL);
- value_t value(const datetime_t& moment) const;
- void reduce();
-
- value_t reduced() const {
- value_t temp(*this);
- temp.reduce();
- return temp;
- }
-
- void round();
- value_t unround() const;
-};
-
-#define DEF_VALUE_AUX_OP(OP) \
- inline value_t operator OP(const balance_pair_t& value, \
- const value_t& obj) { \
- return value_t(value) OP obj; \
- } \
- inline value_t operator OP(const balance_t& value, \
- const value_t& obj) { \
- return value_t(value) OP obj; \
- } \
- inline value_t operator OP(const amount_t& value, \
- const value_t& obj) { \
- return value_t(value) OP obj; \
- } \
- template <typename T> \
- inline value_t operator OP(T value, const value_t& obj) { \
- return value_t(value) OP obj; \
- }
-
-DEF_VALUE_AUX_OP(+)
-DEF_VALUE_AUX_OP(-)
-DEF_VALUE_AUX_OP(*)
-DEF_VALUE_AUX_OP(/)
-
-DEF_VALUE_AUX_OP(<)
-DEF_VALUE_AUX_OP(<=)
-DEF_VALUE_AUX_OP(>)
-DEF_VALUE_AUX_OP(>=)
-DEF_VALUE_AUX_OP(==)
-DEF_VALUE_AUX_OP(!=)
-
-template <typename T>
-value_t::operator T() const
-{
- switch (type) {
- case BOOLEAN:
- return *((bool *) data);
- case INTEGER:
- return *((long *) data);
- case DATETIME:
- return *((datetime_t *) data);
- case AMOUNT:
- return *((amount_t *) data);
- case BALANCE:
- return *((balance_t *) data);
- case BALANCE_PAIR:
- return *((balance_pair_t *) data);
-
- default:
- assert(0);
- break;
- }
- assert(0);
- return 0;
-}
-
-template <> value_t::operator long() const;
-template <> value_t::operator datetime_t() const;
-template <> value_t::operator double() const;
-
-inline value_t abs(const value_t& value) {
- value_t temp(value);
- temp.abs();
- return temp;
-}
-
-inline std::ostream& operator<<(std::ostream& out, const value_t& value) {
- switch (value.type) {
- case value_t::BOOLEAN:
- out << (*((bool *) value.data) ? "true" : "false");
- break;
- case value_t::INTEGER:
- out << *((long *) value.data);
- break;
- case value_t::DATETIME:
- out << *((datetime_t *) value.data);
- break;
- case value_t::AMOUNT:
- out << *((amount_t *) value.data);
- break;
- case value_t::BALANCE:
- out << *((balance_t *) value.data);
- break;
- case value_t::BALANCE_PAIR:
- out << *((balance_pair_t *) value.data);
- break;
-
- default:
- assert(0);
- break;
- }
- return out;
-}
-
-class value_context : public error_context
-{
- value_t * bal;
- public:
- value_context(const value_t& _bal,
- const std::string& desc = "") throw();
- virtual ~value_context() throw();
-
- virtual void describe(std::ostream& out) const throw();
-};
-
-class value_error : public error {
- public:
- value_error(const std::string& reason, error_context * ctxt = NULL) throw()
- : error(reason, ctxt) {}
- virtual ~value_error() throw() {}
-};
-
-} // namespace ledger
-
-#endif // _VALUE_H
diff --git a/walk.cc b/walk.cc
deleted file mode 100644
index 88b5461a..00000000
--- a/walk.cc
+++ /dev/null
@@ -1,911 +0,0 @@
-#include "walk.h"
-#include "format.h"
-#include "textual.h"
-#include "util.h"
-
-#include <algorithm>
-
-namespace ledger {
-
-template <>
-bool compare_items<transaction_t>::operator()(const transaction_t * left,
- const transaction_t * right)
-{
- assert(left);
- assert(right);
-
- transaction_xdata_t& lxdata(transaction_xdata(*left));
- if (! (lxdata.dflags & TRANSACTION_SORT_CALC)) {
- guarded_compute(sort_order, lxdata.sort_value, details_t(*left));
- lxdata.sort_value.reduce();
- lxdata.dflags |= TRANSACTION_SORT_CALC;
- }
-
- transaction_xdata_t& rxdata(transaction_xdata(*right));
- if (! (rxdata.dflags & TRANSACTION_SORT_CALC)) {
- guarded_compute(sort_order, rxdata.sort_value, details_t(*right));
- rxdata.sort_value.reduce();
- rxdata.dflags |= TRANSACTION_SORT_CALC;
- }
-
- DEBUG_PRINT("ledger.walk.compare_items_xact",
- "lxdata.sort_value = " << lxdata.sort_value);
- DEBUG_PRINT("ledger.walk.compare_items_xact",
- "rxdata.sort_value = " << rxdata.sort_value);
-
- return lxdata.sort_value < rxdata.sort_value;
-}
-
-transaction_xdata_t& transaction_xdata(const transaction_t& xact)
-{
- if (! xact.data)
- xact.data = new transaction_xdata_t();
- return *((transaction_xdata_t *) xact.data);
-}
-
-void add_transaction_to(const transaction_t& xact, value_t& value)
-{
- if (transaction_has_xdata(xact) &&
- transaction_xdata_(xact).dflags & TRANSACTION_COMPOUND) {
- value += transaction_xdata_(xact).value;
- }
- else if (xact.cost || ! value.realzero()) {
- value.add(xact.amount, xact.cost);
- }
- else {
- value = xact.amount;
- }
-}
-
-void truncate_entries::flush()
-{
- if (! xacts.size())
- return;
-
- entry_t * last_entry = (*xacts.begin())->entry;
-
- int l = 0;
- for (transactions_list::iterator x = xacts.begin();
- x != xacts.end();
- x++)
- if (last_entry != (*x)->entry) {
- l++;
- last_entry = (*x)->entry;
- }
- l++;
-
- last_entry = (*xacts.begin())->entry;
-
- int i = 0;
- for (transactions_list::iterator x = xacts.begin();
- x != xacts.end();
- x++) {
- if (last_entry != (*x)->entry) {
- last_entry = (*x)->entry;
- i++;
- }
-
- bool print = false;
- if (head_count) {
- if (head_count > 0 && i < head_count)
- print = true;
- else if (head_count < 0 && i >= - head_count)
- print = true;
- }
-
- if (! print && tail_count) {
- if (tail_count > 0 && l - i <= tail_count)
- print = true;
- else if (tail_count < 0 && l - i > - tail_count)
- print = true;
- }
-
- if (print)
- item_handler<transaction_t>::operator()(**x);
- }
- xacts.clear();
-
- item_handler<transaction_t>::flush();
-}
-
-void set_account_value::operator()(transaction_t& xact)
-{
- account_t * acct = xact_account(xact);
- assert(acct);
-
- account_xdata_t& xdata = account_xdata(*acct);
- add_transaction_to(xact, xdata.value);
-
- xdata.count++;
- if (xact.flags & TRANSACTION_VIRTUAL)
- xdata.virtuals++;
-
- item_handler<transaction_t>::operator()(xact);
-}
-
-void sort_transactions::post_accumulated_xacts()
-{
- std::stable_sort(transactions.begin(), transactions.end(),
- compare_items<transaction_t>(sort_order));
-
- for (transactions_deque::iterator i = transactions.begin();
- i != transactions.end();
- i++) {
- transaction_xdata(**i).dflags &= ~TRANSACTION_SORT_CALC;
- item_handler<transaction_t>::operator()(**i);
- }
-
- transactions.clear();
-}
-
-void calc_transactions::operator()(transaction_t& xact)
-{
- try {
-
- transaction_xdata_t& xdata(transaction_xdata(xact));
-
- if (last_xact && transaction_has_xdata(*last_xact)) {
- xdata.total += transaction_xdata_(*last_xact).total;
- xdata.index = transaction_xdata_(*last_xact).index + 1;
- } else {
- xdata.index = 0;
- }
-
- if (! (xdata.dflags & TRANSACTION_NO_TOTAL))
- add_transaction_to(xact, xdata.total);
-
- item_handler<transaction_t>::operator()(xact);
-
- last_xact = &xact;
-
- }
- catch (error * err) {
- err->context.push_front
- (new xact_context(xact, "Calculating transaction at"));
- throw err;
- }
-}
-
-void invert_transactions::operator()(transaction_t& xact)
-{
- if (transaction_has_xdata(xact) &&
- transaction_xdata_(xact).dflags & TRANSACTION_COMPOUND) {
- transaction_xdata_(xact).value.negate();
- } else {
- xact.amount.negate();
- if (xact.cost)
- xact.cost->negate();
- }
-
- item_handler<transaction_t>::operator()(xact);
-}
-
-
-static inline
-void handle_value(const value_t& value,
- account_t * account,
- entry_t * entry,
- unsigned int flags,
- std::list<transaction_t>& temps,
- item_handler<transaction_t>& handler,
- const datetime_t& date = datetime_t(),
- transactions_list * component_xacts = NULL)
-{
- temps.push_back(transaction_t(account));
- transaction_t& xact(temps.back());
- xact.entry = entry;
- xact.flags |= TRANSACTION_BULK_ALLOC;
- entry->add_transaction(&xact);
-
- // If there are component transactions to associate with this
- // temporary, do so now.
-
- if (component_xacts)
- transaction_xdata(xact).copy_component_xacts(*component_xacts);
-
- // If the account for this transaction is all virtual, then report
- // the transaction as such. This allows subtotal reports to show
- // "(Account)" for accounts that contain only virtual transactions.
-
- if (account && account_has_xdata(*account))
- if (! (account_xdata_(*account).dflags & ACCOUNT_HAS_NON_VIRTUALS)) {
- xact.flags |= TRANSACTION_VIRTUAL;
- if (! (account_xdata_(*account).dflags & ACCOUNT_HAS_UNB_VIRTUALS))
- xact.flags |= TRANSACTION_BALANCE;
- }
-
- transaction_xdata_t& xdata(transaction_xdata(xact));
-
- if (date)
- xdata.date = date;
-
- value_t temp(value);
-
- switch (value.type) {
- case value_t::BOOLEAN:
- case value_t::DATETIME:
- case value_t::INTEGER:
- temp.cast(value_t::AMOUNT);
- // fall through...
-
- case value_t::AMOUNT:
- xact.amount = *((amount_t *) temp.data);
- break;
-
- case value_t::BALANCE:
- case value_t::BALANCE_PAIR:
- xdata.value = temp;
- flags |= TRANSACTION_COMPOUND;
- break;
- }
-
- if (flags)
- xdata.dflags |= flags;
-
- handler(xact);
-}
-
-void collapse_transactions::report_subtotal()
-{
- assert(count >= 1);
-
- if (count == 1) {
- item_handler<transaction_t>::operator()(*last_xact);
- } else {
- entry_temps.push_back(entry_t());
- entry_t& entry = entry_temps.back();
- entry.payee = last_entry->payee;
- entry._date = last_entry->_date;
-
- handle_value(subtotal, &totals_account, last_entry, 0, xact_temps,
- *handler);
- }
-
- last_entry = NULL;
- last_xact = NULL;
- subtotal = 0L;
- count = 0;
-}
-
-void collapse_transactions::operator()(transaction_t& xact)
-{
- // If we've reached a new entry, report on the subtotal
- // accumulated thus far.
-
- if (last_entry && last_entry != xact.entry && count > 0)
- report_subtotal();
-
- add_transaction_to(xact, subtotal);
- count++;
-
- last_entry = xact.entry;
- last_xact = &xact;
-}
-
-void related_transactions::flush()
-{
- if (transactions.size() > 0) {
- for (transactions_list::iterator i = transactions.begin();
- i != transactions.end();
- i++) {
- if ((*i)->entry) {
- for (transactions_list::iterator j = (*i)->entry->transactions.begin();
- j != (*i)->entry->transactions.end();
- j++) {
- transaction_xdata_t& xdata = transaction_xdata(**j);
- if (! (xdata.dflags & TRANSACTION_HANDLED) &&
- (! (xdata.dflags & TRANSACTION_RECEIVED) ?
- ! ((*j)->flags & (TRANSACTION_AUTO | TRANSACTION_VIRTUAL)) :
- also_matching)) {
- xdata.dflags |= TRANSACTION_HANDLED;
- item_handler<transaction_t>::operator()(**j);
- }
- }
- } else {
- // This code should only be reachable from the "output"
- // command, since that is the only command which attempts to
- // output auto or period entries.
- transaction_xdata_t& xdata = transaction_xdata(**i);
- if (! (xdata.dflags & TRANSACTION_HANDLED) &&
- ! ((*i)->flags & TRANSACTION_AUTO)) {
- xdata.dflags |= TRANSACTION_HANDLED;
- item_handler<transaction_t>::operator()(**i);
- }
- }
- }
- }
-
- item_handler<transaction_t>::flush();
-}
-
-void changed_value_transactions::output_diff(const datetime_t& current)
-{
- value_t cur_bal;
-
- transaction_xdata(*last_xact).date = current;
- compute_total(cur_bal, details_t(*last_xact));
- cur_bal.round();
- transaction_xdata(*last_xact).date = 0;
-
- if (value_t diff = cur_bal - last_balance) {
- entry_temps.push_back(entry_t());
- entry_t& entry = entry_temps.back();
- entry.payee = "Commodities revalued";
- entry._date = current;
-
- handle_value(diff, NULL, &entry, TRANSACTION_NO_TOTAL, xact_temps,
- *handler);
- }
-}
-
-void changed_value_transactions::operator()(transaction_t& xact)
-{
- if (last_xact) {
- datetime_t moment;
- if (transaction_has_xdata(*last_xact))
- moment = transaction_xdata_(*last_xact).date;
- else
- moment = xact.date();
- output_diff(moment);
- }
-
- if (changed_values_only)
- transaction_xdata(xact).dflags |= TRANSACTION_DISPLAYED;
-
- item_handler<transaction_t>::operator()(xact);
-
- compute_total(last_balance, details_t(xact));
- last_balance.round();
-
- last_xact = &xact;
-}
-
-void component_transactions::operator()(transaction_t& xact)
-{
- if (handler && pred(xact)) {
- if (transaction_has_xdata(xact) &&
- transaction_xdata_(xact).have_component_xacts())
- transaction_xdata_(xact).walk_component_xacts(*handler);
- else
- (*handler)(xact);
- }
-}
-
-void subtotal_transactions::report_subtotal(const char * spec_fmt)
-{
- std::ostringstream out_date;
- if (! spec_fmt) {
- std::string fmt = "- ";
- fmt += date_t::output_format;
- finish.write(out_date, fmt);
- } else {
- finish.write(out_date, spec_fmt);
- }
-
- entry_temps.push_back(entry_t());
- entry_t& entry = entry_temps.back();
- entry.payee = out_date.str();
- entry._date = start;
-
- for (values_map::iterator i = values.begin();
- i != values.end();
- i++)
- handle_value((*i).second.value, (*i).second.account, &entry, 0,
- xact_temps, *handler, finish, &(*i).second.components);
-
- values.clear();
-}
-
-void subtotal_transactions::operator()(transaction_t& xact)
-{
- if (! start || xact.date() < start)
- start = xact.date();
- if (! finish || xact.date() > finish)
- finish = xact.date();
-
- account_t * acct = xact_account(xact);
- assert(acct);
-
- values_map::iterator i = values.find(acct->fullname());
- if (i == values.end()) {
- value_t temp;
- add_transaction_to(xact, temp);
- std::pair<values_map::iterator, bool> result
- = values.insert(values_pair(acct->fullname(),
- acct_value_t(acct, temp)));
- assert(result.second);
-
- if (remember_components)
- (*result.first).second.components.push_back(&xact);
- } else {
- add_transaction_to(xact, (*i).second.value);
-
- if (remember_components)
- (*i).second.components.push_back(&xact);
- }
-
- // If the account for this transaction is all virtual, mark it as
- // such, so that `handle_value' can show "(Account)" for accounts
- // that contain only virtual transactions.
-
- if (! (xact.flags & TRANSACTION_VIRTUAL))
- account_xdata(*xact_account(xact)).dflags |= ACCOUNT_HAS_NON_VIRTUALS;
- else if (! (xact.flags & TRANSACTION_BALANCE))
- account_xdata(*xact_account(xact)).dflags |= ACCOUNT_HAS_UNB_VIRTUALS;
-}
-
-void interval_transactions::report_subtotal(const datetime_t& moment)
-{
- assert(last_xact);
-
- start = interval.begin;
- if (moment)
- finish = moment - 86400L;
- else
- finish = last_xact->date();
-
- subtotal_transactions::report_subtotal();
-
- last_xact = NULL;
-}
-
-void interval_transactions::operator()(transaction_t& xact)
-{
- const datetime_t date = xact.date();
-
- if ((interval.begin && date < interval.begin) ||
- (interval.end && date >= interval.end))
- return;
-
- if (interval) {
- if (! started) {
- if (! interval.begin)
- interval.start(date);
- start = interval.begin;
- started = true;
- }
-
- datetime_t quant = interval.increment(interval.begin);
- if (date >= quant) {
- if (last_xact)
- report_subtotal(quant);
-
- datetime_t temp;
- while (date >= (temp = interval.increment(quant))) {
- if (quant == temp)
- break;
- quant = temp;
- }
- start = interval.begin = quant;
- }
-
- subtotal_transactions::operator()(xact);
- } else {
- item_handler<transaction_t>::operator()(xact);
- }
-
- last_xact = &xact;
-}
-
-by_payee_transactions::~by_payee_transactions()
-{
- for (payee_subtotals_map::iterator i = payee_subtotals.begin();
- i != payee_subtotals.end();
- i++)
- delete (*i).second;
-}
-
-void by_payee_transactions::flush()
-{
- for (payee_subtotals_map::iterator i = payee_subtotals.begin();
- i != payee_subtotals.end();
- i++)
- (*i).second->report_subtotal((*i).first.c_str());
-
- item_handler<transaction_t>::flush();
-
- payee_subtotals.clear();
-}
-
-void by_payee_transactions::operator()(transaction_t& xact)
-{
- payee_subtotals_map::iterator i = payee_subtotals.find(xact.entry->payee);
- if (i == payee_subtotals.end()) {
- payee_subtotals_pair
- temp(xact.entry->payee,
- new subtotal_transactions(handler, remember_components));
- std::pair<payee_subtotals_map::iterator, bool> result
- = payee_subtotals.insert(temp);
-
- assert(result.second);
- if (! result.second)
- return;
- i = result.first;
- }
-
- if (xact.date() > (*i).second->start)
- (*i).second->start = xact.date();
-
- (*(*i).second)(xact);
-}
-
-void set_comm_as_payee::operator()(transaction_t& xact)
-{
- entry_temps.push_back(*xact.entry);
- entry_t& entry = entry_temps.back();
- entry._date = xact.date();
- entry.code = xact.entry->code;
-
- if (xact.amount.commodity())
- entry.payee = xact.amount.commodity().symbol();
- else
- entry.payee = "<none>";
-
- xact_temps.push_back(xact);
- transaction_t& temp = xact_temps.back();
- temp.entry = &entry;
- temp.state = xact.state;
- temp.flags |= TRANSACTION_BULK_ALLOC;
-
- entry.add_transaction(&temp);
-
- item_handler<transaction_t>::operator()(temp);
-}
-
-void set_code_as_payee::operator()(transaction_t& xact)
-{
- entry_temps.push_back(*xact.entry);
- entry_t& entry = entry_temps.back();
- entry._date = xact.date();
-
- if (! xact.entry->code.empty())
- entry.payee = xact.entry->code;
- else
- entry.payee = "<none>";
-
- xact_temps.push_back(xact);
- transaction_t& temp = xact_temps.back();
- temp.entry = &entry;
- temp.state = xact.state;
- temp.flags |= TRANSACTION_BULK_ALLOC;
-
- entry.add_transaction(&temp);
-
- item_handler<transaction_t>::operator()(temp);
-}
-
-void dow_transactions::flush()
-{
- for (int i = 0; i < 7; i++) {
- start = finish = 0;
- for (transactions_list::iterator d = days_of_the_week[i].begin();
- d != days_of_the_week[i].end();
- d++)
- subtotal_transactions::operator()(**d);
- subtotal_transactions::report_subtotal("%As");
- days_of_the_week[i].clear();
- }
-
- subtotal_transactions::flush();
-}
-
-void generate_transactions::add_period_entries
- (period_entries_list& period_entries)
-{
- for (period_entries_list::iterator i = period_entries.begin();
- i != period_entries.end();
- i++)
- for (transactions_list::iterator j = (*i)->transactions.begin();
- j != (*i)->transactions.end();
- j++)
- add_transaction((*i)->period, **j);
-}
-
-void generate_transactions::add_transaction(const interval_t& period,
- transaction_t& xact)
-{
- pending_xacts.push_back(pending_xacts_pair(period, &xact));
-}
-
-void budget_transactions::report_budget_items(const datetime_t& moment)
-{
- if (pending_xacts.size() == 0)
- return;
-
- bool reported;
- do {
- reported = false;
- for (pending_xacts_list::iterator i = pending_xacts.begin();
- i != pending_xacts.end();
- i++) {
- datetime_t& begin = (*i).first.begin;
- if (! begin) {
- (*i).first.start(moment);
- begin = (*i).first.begin;
- }
-
- if (begin < moment &&
- (! (*i).first.end || begin < (*i).first.end)) {
- transaction_t& xact = *(*i).second;
-
- DEBUG_PRINT("ledger.walk.budget", "Reporting budget for "
- << xact_account(xact)->fullname());
- DEBUG_PRINT_TIME("ledger.walk.budget", begin);
- DEBUG_PRINT_TIME("ledger.walk.budget", moment);
-
- entry_temps.push_back(entry_t());
- entry_t& entry = entry_temps.back();
- entry.payee = "Budget entry";
- entry._date = begin;
-
- xact_temps.push_back(xact);
- transaction_t& temp = xact_temps.back();
- temp.entry = &entry;
- temp.flags |= TRANSACTION_AUTO | TRANSACTION_BULK_ALLOC;
- temp.amount.negate();
- entry.add_transaction(&temp);
-
- begin = (*i).first.increment(begin);
-
- item_handler<transaction_t>::operator()(temp);
-
- reported = true;
- }
- }
- } while (reported);
-}
-
-void budget_transactions::operator()(transaction_t& xact)
-{
- bool xact_in_budget = false;
-
- for (pending_xacts_list::iterator i = pending_xacts.begin();
- i != pending_xacts.end();
- i++)
- for (account_t * acct = xact_account(xact);
- acct;
- acct = acct->parent) {
- if (acct == xact_account(*(*i).second)) {
- xact_in_budget = true;
- // Report the transaction as if it had occurred in the parent
- // account.
- if (xact_account(xact) != acct)
- transaction_xdata(xact).account = acct;
- goto handle;
- }
- }
-
- handle:
- if (xact_in_budget && flags & BUDGET_BUDGETED) {
- report_budget_items(xact.date());
- item_handler<transaction_t>::operator()(xact);
- }
- else if (! xact_in_budget && flags & BUDGET_UNBUDGETED) {
- item_handler<transaction_t>::operator()(xact);
- }
-}
-
-void forecast_transactions::add_transaction(const interval_t& period,
- transaction_t& xact)
-{
- generate_transactions::add_transaction(period, xact);
-
- interval_t& i = pending_xacts.back().first;
- if (! i.begin) {
- i.start(datetime_t::now);
- i.begin = i.increment(i.begin);
- } else {
- while (i.begin < datetime_t::now)
- i.begin = i.increment(i.begin);
- }
-}
-
-void forecast_transactions::flush()
-{
- transactions_list passed;
- datetime_t last;
-
- while (pending_xacts.size() > 0) {
- pending_xacts_list::iterator least = pending_xacts.begin();
- for (pending_xacts_list::iterator i = ++pending_xacts.begin();
- i != pending_xacts.end();
- i++)
- if ((*i).first.begin < (*least).first.begin)
- least = i;
-
- datetime_t& begin = (*least).first.begin;
-
- if ((*least).first.end && begin >= (*least).first.end) {
- pending_xacts.erase(least);
- passed.remove((*least).second);
- continue;
- }
-
- transaction_t& xact = *(*least).second;
-
- entry_temps.push_back(entry_t());
- entry_t& entry = entry_temps.back();
- entry.payee = "Forecast entry";
- entry._date = begin;
-
- xact_temps.push_back(xact);
- transaction_t& temp = xact_temps.back();
- temp.entry = &entry;
- temp.flags |= TRANSACTION_AUTO;
- temp.flags |= TRANSACTION_BULK_ALLOC;
- entry.add_transaction(&temp);
-
- datetime_t next = (*least).first.increment(begin);
- if (next < begin || // wraparound
- (last && (next - last) > 365 * 5 * 24 * 3600))
- break;
- begin = next;
-
- item_handler<transaction_t>::operator()(temp);
-
- if (transaction_has_xdata(temp) &&
- transaction_xdata_(temp).dflags & TRANSACTION_MATCHES) {
- if (! pred(temp))
- break;
- last = temp.date();
- passed.clear();
- } else {
- bool found = false;
- for (transactions_list::iterator i = passed.begin();
- i != passed.end();
- i++)
- if (*i == &xact) {
- found = true;
- break;
- }
-
- if (! found) {
- passed.push_back(&xact);
- if (passed.size() >= pending_xacts.size())
- break;
- }
- }
- }
-
- item_handler<transaction_t>::flush();
-}
-
-template <>
-bool compare_items<account_t>::operator()(const account_t * left,
- const account_t * right)
-{
- assert(left);
- assert(right);
-
- account_xdata_t& lxdata(account_xdata(*left));
- if (! (lxdata.dflags & ACCOUNT_SORT_CALC)) {
- guarded_compute(sort_order, lxdata.sort_value, details_t(*left));
- lxdata.dflags |= ACCOUNT_SORT_CALC;
- }
-
- account_xdata_t& rxdata(account_xdata(*right));
- if (! (rxdata.dflags & ACCOUNT_SORT_CALC)) {
- guarded_compute(sort_order, rxdata.sort_value, details_t(*right));
- rxdata.dflags |= ACCOUNT_SORT_CALC;
- }
-
- return lxdata.sort_value < rxdata.sort_value;
-}
-
-account_xdata_t& account_xdata(const account_t& account)
-{
- if (! account.data)
- account.data = new account_xdata_t();
-
- return *((account_xdata_t *) account.data);
-}
-
-void sum_accounts(account_t& account)
-{
- account_xdata_t& xdata(account_xdata(account));
-
- for (accounts_map::iterator i = account.accounts.begin();
- i != account.accounts.end();
- i++) {
- sum_accounts(*(*i).second);
-
- xdata.total += account_xdata_(*(*i).second).total;
- xdata.total_count += (account_xdata_(*(*i).second).total_count +
- account_xdata_(*(*i).second).count);
- }
-
- value_t result;
- compute_amount(result, details_t(account));
- if (! result.realzero())
- xdata.total += result;
- xdata.total_count += xdata.count;
-}
-
-void sort_accounts(account_t& account,
- const value_expr_t * sort_order,
- accounts_deque& accounts)
-{
- for (accounts_map::iterator i = account.accounts.begin();
- i != account.accounts.end();
- i++)
- accounts.push_back((*i).second);
-
- std::stable_sort(accounts.begin(), accounts.end(),
- compare_items<account_t>(sort_order));
-}
-
-void walk_accounts(account_t& account,
- item_handler<account_t>& handler,
- const value_expr_t * sort_order)
-{
- handler(account);
-
- if (sort_order) {
- accounts_deque accounts;
- sort_accounts(account, sort_order, accounts);
- for (accounts_deque::const_iterator i = accounts.begin();
- i != accounts.end();
- i++) {
- account_xdata(**i).dflags &= ~ACCOUNT_SORT_CALC;
- walk_accounts(**i, handler, sort_order);
- }
- } else {
- for (accounts_map::const_iterator i = account.accounts.begin();
- i != account.accounts.end();
- i++)
- walk_accounts(*(*i).second, handler, NULL);
- }
-}
-
-void walk_accounts(account_t& account,
- item_handler<account_t>& handler,
- const std::string& sort_string)
-{
- if (! sort_string.empty()) {
- value_expr sort_order;
- sort_order.reset(parse_value_expr(sort_string));
- walk_accounts(account, handler, sort_order.get());
- } else {
- walk_accounts(account, handler);
- }
-}
-
-void walk_commodities(commodities_map& commodities,
- item_handler<transaction_t>& handler)
-{
- std::list<transaction_t> xact_temps;
- std::list<entry_t> entry_temps;
- std::list<account_t> acct_temps;
-
- for (commodities_map::iterator i = commodities.begin();
- i != commodities.end();
- i++) {
- 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();
- j++) {
- entry_temps.back()._date = (*j).first;
-
- xact_temps.push_back(transaction_t(&acct_temps.back()));
- transaction_t& temp = xact_temps.back();
- temp.entry = &entry_temps.back();
- temp.amount = (*j).second;
- temp.flags |= TRANSACTION_BULK_ALLOC;
- entry_temps.back().add_transaction(&temp);
-
- handler(xact_temps.back());
- }
- }
-
- handler.flush();
-
- clear_entries_transactions(entry_temps);
-}
-
-} // namespace ledger
diff --git a/walk.h b/walk.h
deleted file mode 100644
index bd6bc2c2..00000000
--- a/walk.h
+++ /dev/null
@@ -1,751 +0,0 @@
-#ifndef _WALK_H
-#define _WALK_H
-
-#include "journal.h"
-#include "balance.h"
-#include "valexpr.h"
-#include "datetime.h"
-
-#include <iostream>
-#include <fstream>
-#include <deque>
-
-namespace ledger {
-
-template <typename T>
-struct item_handler {
- item_handler * handler;
-
- public:
- item_handler() : handler(NULL) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor item_handler<T>");
- }
- item_handler(item_handler * _handler) : handler(_handler) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor item_handler<T>");
- }
- virtual ~item_handler() {
- DEBUG_PRINT("ledger.memory.dtors", "dtor item_handler<T>");
- }
-
- virtual void flush() {
- if (handler)
- handler->flush();
- }
- virtual void operator()(T& item) {
- if (handler)
- (*handler)(item);
- }
-};
-
-template <typename T>
-class compare_items {
- const value_expr_t * sort_order;
- public:
- compare_items(const value_expr_t * _sort_order)
- : sort_order(_sort_order) {
- assert(sort_order);
- }
- bool operator()(const T * left, const T * right);
-};
-
-template <typename T>
-bool compare_items<T>::operator()(const T * left, const T * right)
-{
- assert(left);
- assert(right);
-
- value_t left_result;
- value_t right_result;
- guarded_compute(sort_order, left_result, details_t(*left));
- guarded_compute(sort_order, right_result, details_t(*right));
-
- return left_result < right_result;
-}
-
-template <>
-bool compare_items<transaction_t>::operator()(const transaction_t * left,
- const transaction_t * right);
-template <>
-bool compare_items<account_t>::operator()(const account_t * left,
- const account_t * right);
-
-//////////////////////////////////////////////////////////////////////
-//
-// Transaction handlers
-//
-
-#define TRANSACTION_RECEIVED 0x0001
-#define TRANSACTION_HANDLED 0x0002
-#define TRANSACTION_TO_DISPLAY 0x0004
-#define TRANSACTION_DISPLAYED 0x0008
-#define TRANSACTION_NO_TOTAL 0x0010
-#define TRANSACTION_SORT_CALC 0x0020
-#define TRANSACTION_COMPOUND 0x0040
-#define TRANSACTION_MATCHES 0x0080
-
-struct transaction_xdata_t
-{
- value_t total;
- value_t sort_value;
- value_t value;
- unsigned int index;
- unsigned short dflags;
- datetime_t date;
- account_t * account;
- void * ptr;
-
- transactions_list * component_xacts;
-
- transaction_xdata_t()
- : index(0), dflags(0),
- account(NULL), ptr(NULL), component_xacts(NULL) {
- DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_xdata_t " << this);
- }
-
- ~transaction_xdata_t() {
- DEBUG_PRINT("ledger.memory.dtors", "dtor transaction_xdata_t " << this);
- if (component_xacts)
- delete component_xacts;
- }
-
- void remember_xact(transaction_t& xact) {
- if (! component_xacts)
- component_xacts = new transactions_list;
- component_xacts->push_back(&xact);
- }
-
- bool have_component_xacts() const {
- return component_xacts != NULL && ! component_xacts->empty();
- }
-
- void copy_component_xacts(transactions_list& xacts) {
- for (transactions_list::const_iterator i = xacts.begin();
- i != xacts.end();
- i++)
- remember_xact(**i);
- }
-
- void walk_component_xacts(item_handler<transaction_t>& handler) const {
- for (transactions_list::const_iterator i = component_xacts->begin();
- i != component_xacts->end();
- i++)
- handler(**i);
- }
-};
-
-inline bool transaction_has_xdata(const transaction_t& xact) {
- return xact.data != NULL;
-}
-
-inline transaction_xdata_t& transaction_xdata_(const transaction_t& xact) {
- return *((transaction_xdata_t *) xact.data);
-}
-
-transaction_xdata_t& transaction_xdata(const transaction_t& xact);
-void add_transaction_to(const transaction_t& xact, value_t& value);
-
-inline account_t * xact_account(transaction_t& xact) {
- if (xact.data) {
- account_t * account = transaction_xdata(xact).account;
- if (account)
- return account;
- }
- return xact.account;
-}
-
-inline const account_t * xact_account(const transaction_t& xact) {
- return xact_account(const_cast<transaction_t&>(xact));
-}
-
-//////////////////////////////////////////////////////////////////////
-
-inline void walk_transactions(transactions_list::iterator begin,
- transactions_list::iterator end,
- item_handler<transaction_t>& handler) {
- for (transactions_list::iterator i = begin; i != end; i++)
- handler(**i);
-}
-
-inline void walk_transactions(transactions_list& list,
- item_handler<transaction_t>& handler) {
- walk_transactions(list.begin(), list.end(), handler);
-}
-
-inline void walk_entries(entries_list::iterator begin,
- entries_list::iterator end,
- item_handler<transaction_t>& handler) {
- for (entries_list::iterator i = begin; i != end; i++)
- walk_transactions((*i)->transactions, handler);
-}
-
-inline void walk_entries(entries_list& list,
- item_handler<transaction_t>& handler) {
- walk_entries(list.begin(), list.end(), handler);
-}
-
-//////////////////////////////////////////////////////////////////////
-
-class ignore_transactions : public item_handler<transaction_t>
-{
- public:
- virtual void operator()(transaction_t& xact) {}
-};
-
-class clear_transaction_xdata : public item_handler<transaction_t>
-{
- public:
- virtual void operator()(transaction_t& xact) {
- if (xact.data) {
- delete (transaction_xdata_t *) xact.data;
- xact.data = NULL;
- }
- }
-};
-
-class truncate_entries : public item_handler<transaction_t>
-{
- int head_count;
- int tail_count;
-
- transactions_list xacts;
-
- public:
- truncate_entries(item_handler<transaction_t> * handler,
- int _head_count, int _tail_count)
- : item_handler<transaction_t>(handler),
- head_count(_head_count), tail_count(_tail_count) {}
-
- virtual void flush();
- virtual void operator()(transaction_t& xact) {
- xacts.push_back(&xact);
- }
-};
-
-class set_account_value : public item_handler<transaction_t>
-{
- public:
- set_account_value(item_handler<transaction_t> * handler = NULL)
- : item_handler<transaction_t>(handler) {}
-
- virtual void operator()(transaction_t& xact);
-};
-
-class push_to_transactions_list : public item_handler<transaction_t>
-{
- public:
- transactions_list& xact_list;
-
- push_to_transactions_list(transactions_list& _xact_list)
- : xact_list(_xact_list) {}
-
- virtual void operator()(transaction_t& xact) {
- xact_list.push_back(&xact);
- }
-};
-
-class sort_transactions : public item_handler<transaction_t>
-{
- typedef std::deque<transaction_t *> transactions_deque;
-
- transactions_deque transactions;
- const value_expr_t * sort_order;
-
- public:
- sort_transactions(item_handler<transaction_t> * handler,
- const value_expr_t * _sort_order)
- : item_handler<transaction_t>(handler),
- sort_order(_sort_order->acquire()) {}
-
- sort_transactions(item_handler<transaction_t> * handler,
- const std::string& _sort_order)
- : item_handler<transaction_t>(handler) {
- assert(! _sort_order.empty());
- sort_order = parse_value_expr(_sort_order)->acquire();
- }
-
- virtual ~sort_transactions() {
- assert(sort_order);
- sort_order->release();
- }
-
- virtual void post_accumulated_xacts();
-
- virtual void flush() {
- post_accumulated_xacts();
- item_handler<transaction_t>::flush();
- }
-
- virtual void operator()(transaction_t& xact) {
- transactions.push_back(&xact);
- }
-};
-
-class sort_entries : public item_handler<transaction_t>
-{
- sort_transactions sorter;
- entry_t * last_entry;
-
- public:
- sort_entries(item_handler<transaction_t> * handler,
- const value_expr_t * _sort_order)
- : sorter(handler, _sort_order) {}
-
- sort_entries(item_handler<transaction_t> * handler,
- const std::string& _sort_order)
- : sorter(handler, _sort_order) {}
-
- virtual void flush() {
- sorter.flush();
- item_handler<transaction_t>::flush();
- }
-
- virtual void operator()(transaction_t& xact) {
- if (last_entry && xact.entry != last_entry)
- sorter.post_accumulated_xacts();
-
- sorter(xact);
-
- last_entry = xact.entry;
- }
-};
-
-class filter_transactions : public item_handler<transaction_t>
-{
- item_predicate<transaction_t> pred;
-
- public:
- filter_transactions(item_handler<transaction_t> * handler,
- const value_expr_t * predicate)
- : item_handler<transaction_t>(handler), pred(predicate) {}
-
- filter_transactions(item_handler<transaction_t> * handler,
- const std::string& predicate)
- : item_handler<transaction_t>(handler), pred(predicate) {}
-
- virtual void operator()(transaction_t& xact) {
- if (pred(xact)) {
- transaction_xdata(xact).dflags |= TRANSACTION_MATCHES;
- (*handler)(xact);
- }
- }
-};
-
-class calc_transactions : public item_handler<transaction_t>
-{
- transaction_t * last_xact;
-
- public:
- calc_transactions(item_handler<transaction_t> * handler)
- : item_handler<transaction_t>(handler), last_xact(NULL) {}
-
- virtual void operator()(transaction_t& xact);
-};
-
-class invert_transactions : public item_handler<transaction_t>
-{
- public:
- invert_transactions(item_handler<transaction_t> * handler)
- : item_handler<transaction_t>(handler) {}
-
- virtual void operator()(transaction_t& xact);
-};
-
-inline void clear_entries_transactions(std::list<entry_t>& entries_list) {
- for (std::list<entry_t>::iterator i = entries_list.begin();
- i != entries_list.end();
- i++)
- (*i).transactions.clear();
-}
-
-class collapse_transactions : public item_handler<transaction_t>
-{
- value_t subtotal;
- unsigned int count;
- entry_t * last_entry;
- transaction_t * last_xact;
- account_t totals_account;
-
- std::list<entry_t> entry_temps;
- std::list<transaction_t> xact_temps;
-
- public:
- collapse_transactions(item_handler<transaction_t> * handler)
- : item_handler<transaction_t>(handler), count(0),
- last_entry(NULL), last_xact(NULL),
- totals_account(NULL, "<Total>") {}
-
- ~collapse_transactions() {
- clear_entries_transactions(entry_temps);
- }
-
- virtual void flush() {
- if (subtotal)
- report_subtotal();
- item_handler<transaction_t>::flush();
- }
-
- void report_subtotal();
-
- virtual void operator()(transaction_t& xact);
-};
-
-class component_transactions : public item_handler<transaction_t>
-{
- item_predicate<transaction_t> pred;
-
- public:
- component_transactions(item_handler<transaction_t> * handler,
- const value_expr_t * predicate)
- : item_handler<transaction_t>(handler), pred(predicate) {}
-
- component_transactions(item_handler<transaction_t> * handler,
- const std::string& predicate)
- : item_handler<transaction_t>(handler), pred(predicate) {}
-
- virtual void operator()(transaction_t& xact);
-};
-
-class related_transactions : public item_handler<transaction_t>
-{
- transactions_list transactions;
- bool also_matching;
-
- public:
- related_transactions(item_handler<transaction_t> * handler,
- const bool _also_matching = false)
- : item_handler<transaction_t>(handler),
- also_matching(_also_matching) {}
-
- virtual void flush();
- virtual void operator()(transaction_t& xact) {
- transaction_xdata(xact).dflags |= TRANSACTION_RECEIVED;
- transactions.push_back(&xact);
- }
-};
-
-class changed_value_transactions : public item_handler<transaction_t>
-{
- // This filter requires that calc_transactions be used at some point
- // later in the chain.
-
- bool changed_values_only;
- transaction_t * last_xact;
- value_t last_balance;
-
- std::list<entry_t> entry_temps;
- std::list<transaction_t> xact_temps;
-
- public:
- changed_value_transactions(item_handler<transaction_t> * handler,
- bool _changed_values_only)
- : item_handler<transaction_t>(handler),
- changed_values_only(_changed_values_only), last_xact(NULL) {}
-
- ~changed_value_transactions() {
- clear_entries_transactions(entry_temps);
- }
-
- virtual void flush() {
- if (last_xact) {
- output_diff(datetime_t::now);
- last_xact = NULL;
- }
- item_handler<transaction_t>::flush();
- }
-
- void output_diff(const datetime_t& current);
-
- virtual void operator()(transaction_t& xact);
-};
-
-class subtotal_transactions : public item_handler<transaction_t>
-{
- struct acct_value_t {
- account_t * account;
- value_t value;
-
- transactions_list components;
-
- acct_value_t(account_t * a) : account(a) {}
- acct_value_t(account_t * a, value_t& v) : account(a), value(v) {}
- acct_value_t(const acct_value_t& av)
- : account(av.account), value(av.value) {}
- };
-
- typedef std::map<std::string, acct_value_t> values_map;
- typedef std::pair<std::string, acct_value_t> values_pair;
-
- protected:
- values_map values;
- bool remember_components;
-
- std::list<entry_t> entry_temps;
- std::list<transaction_t> xact_temps;
-
- public:
- datetime_t start;
- datetime_t finish;
-
- subtotal_transactions(item_handler<transaction_t> * handler,
- bool _remember_components = false)
- : item_handler<transaction_t>(handler),
- remember_components(_remember_components) {}
-#ifdef DEBUG_ENABLED
- subtotal_transactions(const subtotal_transactions&) {
- assert(0);
- }
-#endif
- virtual ~subtotal_transactions() {
- clear_entries_transactions(entry_temps);
- }
-
- void report_subtotal(const char * spec_fmt = NULL);
-
- virtual void flush() {
- if (values.size() > 0)
- report_subtotal();
- item_handler<transaction_t>::flush();
- }
- virtual void operator()(transaction_t& xact);
-};
-
-class interval_expr_error : public error {
- public:
- interval_expr_error(const std::string& reason,
- error_context * ctxt = NULL) throw()
- : error(reason, ctxt) {}
- virtual ~interval_expr_error() throw() {}
-};
-
-class interval_transactions : public subtotal_transactions
-{
- interval_t interval;
- transaction_t * last_xact;
- bool started;
-
- public:
- interval_transactions(item_handler<transaction_t> * _handler,
- const interval_t& _interval,
- bool remember_components = false)
- : subtotal_transactions(_handler, remember_components),
- interval(_interval), last_xact(NULL), started(false) {}
-
- interval_transactions(item_handler<transaction_t> * _handler,
- const std::string& _interval,
- bool remember_components = false)
- : subtotal_transactions(_handler, remember_components),
- interval(_interval), last_xact(NULL), started(false) {}
-
- void report_subtotal(const datetime_t& moment = datetime_t());
-
- virtual void flush() {
- if (last_xact)
- report_subtotal();
- subtotal_transactions::flush();
- }
- virtual void operator()(transaction_t& xact);
-};
-
-class by_payee_transactions : public item_handler<transaction_t>
-{
- typedef std::map<std::string, subtotal_transactions *> payee_subtotals_map;
- typedef std::pair<std::string, subtotal_transactions *> payee_subtotals_pair;
-
- payee_subtotals_map payee_subtotals;
- bool remember_components;
-
- public:
- by_payee_transactions(item_handler<transaction_t> * handler,
- bool _remember_components = false)
- : item_handler<transaction_t>(handler),
- remember_components(_remember_components) {}
- virtual ~by_payee_transactions();
-
- virtual void flush();
- virtual void operator()(transaction_t& xact);
-};
-
-class set_comm_as_payee : public item_handler<transaction_t>
-{
- std::list<entry_t> entry_temps;
- std::list<transaction_t> xact_temps;
-
- public:
- set_comm_as_payee(item_handler<transaction_t> * handler)
- : item_handler<transaction_t>(handler) {}
-
- ~set_comm_as_payee() {
- clear_entries_transactions(entry_temps);
- }
-
- virtual void operator()(transaction_t& xact);
-};
-
-class set_code_as_payee : public item_handler<transaction_t>
-{
- std::list<entry_t> entry_temps;
- std::list<transaction_t> xact_temps;
-
- public:
- set_code_as_payee(item_handler<transaction_t> * handler)
- : item_handler<transaction_t>(handler) {}
-
- ~set_code_as_payee() {
- clear_entries_transactions(entry_temps);
- }
-
- virtual void operator()(transaction_t& xact);
-};
-
-class dow_transactions : public subtotal_transactions
-{
- transactions_list days_of_the_week[7];
-
- public:
- dow_transactions(item_handler<transaction_t> * handler,
- bool remember_components = false)
- : subtotal_transactions(handler, remember_components) {}
-
- virtual void flush();
- virtual void operator()(transaction_t& xact) {
- days_of_the_week[xact.date().wday()].push_back(&xact);
- }
-};
-
-class generate_transactions : public item_handler<transaction_t>
-{
- protected:
- typedef std::pair<interval_t, transaction_t *> pending_xacts_pair;
- typedef std::list<pending_xacts_pair> pending_xacts_list;
-
- pending_xacts_list pending_xacts;
- std::list<entry_t> entry_temps;
- std::list<transaction_t> xact_temps;
-
- public:
- generate_transactions(item_handler<transaction_t> * handler)
- : item_handler<transaction_t>(handler) {}
-
- ~generate_transactions() {
- clear_entries_transactions(entry_temps);
- }
-
- void add_period_entries(period_entries_list& period_entries);
-
- virtual void add_transaction(const interval_t& period, transaction_t& xact);
-};
-
-#define BUDGET_NO_BUDGET 0x00
-#define BUDGET_BUDGETED 0x01
-#define BUDGET_UNBUDGETED 0x02
-
-class budget_transactions : public generate_transactions
-{
- unsigned short flags;
-
- public:
- budget_transactions(item_handler<transaction_t> * handler,
- unsigned long _flags = BUDGET_BUDGETED)
- : generate_transactions(handler), flags(_flags) {}
-
- void report_budget_items(const datetime_t& moment);
-
- virtual void operator()(transaction_t& xact);
-};
-
-class forecast_transactions : public generate_transactions
-{
- item_predicate<transaction_t> pred;
-
- public:
- forecast_transactions(item_handler<transaction_t> * handler,
- const value_expr_t * predicate)
- : generate_transactions(handler), pred(predicate) {}
-
- forecast_transactions(item_handler<transaction_t> * handler,
- const std::string& predicate)
- : generate_transactions(handler), pred(predicate) {}
-
- virtual void add_transaction(const interval_t& period,
- transaction_t& xact);
- virtual void flush();
-};
-
-
-//////////////////////////////////////////////////////////////////////
-//
-// Account walking functions
-//
-
-#define ACCOUNT_TO_DISPLAY 0x0001
-#define ACCOUNT_DISPLAYED 0x0002
-#define ACCOUNT_SORT_CALC 0x0004
-#define ACCOUNT_HAS_NON_VIRTUALS 0x0008
-#define ACCOUNT_HAS_UNB_VIRTUALS 0x0010
-
-struct account_xdata_t
-{
- value_t value;
- value_t total;
- value_t sort_value;
- unsigned int count; // transactions counted toward amount
- unsigned int total_count; // transactions counted toward total
- unsigned int virtuals;
- unsigned short dflags;
-
- account_xdata_t() : count(0), total_count(0), virtuals(0), dflags(0) {}
-};
-
-inline bool account_has_xdata(const account_t& account) {
- return account.data != NULL;
-}
-
-inline account_xdata_t& account_xdata_(const account_t& account) {
- return *((account_xdata_t *) account.data);
-}
-
-account_xdata_t& account_xdata(const account_t& account);
-
-//////////////////////////////////////////////////////////////////////
-
-class clear_account_xdata : public item_handler<account_t>
-{
- public:
- virtual void operator()(account_t& acct) {
- if (acct.data) {
- delete (account_xdata_t *) acct.data;
- acct.data = NULL;
- }
- }
-};
-
-void sum_accounts(account_t& account);
-
-typedef std::deque<account_t *> accounts_deque;
-
-void sort_accounts(account_t& account,
- const value_expr_t * sort_order,
- accounts_deque& accounts);
-void walk_accounts(account_t& account,
- item_handler<account_t>& handler,
- const value_expr_t * sort_order = NULL);
-void walk_accounts(account_t& account,
- item_handler<account_t>& handler,
- const std::string& sort_string);
-
-//////////////////////////////////////////////////////////////////////
-
-void walk_commodities(commodities_map& commodities,
- item_handler<transaction_t>& handler);
-
-inline void clear_journal_xdata(journal_t * journal) {
- clear_transaction_xdata xact_cleaner;
- walk_entries(journal->entries, xact_cleaner);
-
- clear_account_xdata acct_cleaner;
- walk_accounts(*journal->master, acct_cleaner);
-}
-
-} // namespace ledger
-
-#endif // _WALK_H
diff --git a/xml.h b/xml.h
deleted file mode 100644
index 13bf317c..00000000
--- a/xml.h
+++ /dev/null
@@ -1,46 +0,0 @@
-#ifndef _XML_H
-#define _XML_H
-
-#include "parser.h"
-#include "format.h"
-
-namespace ledger {
-
-#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
-
-class xml_parser_t : public parser_t
-{
- public:
- virtual bool test(std::istream& in) const;
-
- virtual unsigned int parse(std::istream& in,
- config_t& config,
- journal_t * journal,
- account_t * master = NULL,
- const std::string * original_file = NULL);
-};
-
-#endif
-
-class format_xml_entries : public format_entries
-{
- bool show_totals;
- public:
- format_xml_entries(std::ostream& output_stream,
- const bool _show_totals = false)
- : format_entries(output_stream, ""), show_totals(_show_totals) {
- output_stream << "<?xml version=\"1.0\"?>\n"
- << "<ledger version=\"2.5\">\n";
- }
-
- virtual void flush() {
- format_entries::flush();
- output_stream << "</ledger>" << std::endl;
- }
-
- virtual void format_last_entry();
-};
-
-} // namespace ledger
-
-#endif // _XML_H